feat: 归档L03与L09审批发布能力
- 新增统一审批中心与审批管理页面,支持流程配置、审批详情与角色/用户审批对象 - 接入聊天助手、知识库、工作流的发布与删除审批,并补齐发布态校验与快照展示
This commit is contained in:
@@ -12,6 +12,10 @@
|
|||||||
<artifactId>easyflow-api-admin</artifactId>
|
<artifactId>easyflow-api-admin</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-module-approval</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>tech.easyflow</groupId>
|
<groupId>tech.easyflow</groupId>
|
||||||
<artifactId>easyflow-module-ai</artifactId>
|
<artifactId>easyflow-module-ai</artifactId>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
import tech.easyflow.ai.easyagents.listener.PromptChoreChatStreamListener;
|
import tech.easyflow.ai.easyagents.listener.PromptChoreChatStreamListener;
|
||||||
import tech.easyflow.ai.entity.*;
|
import tech.easyflow.ai.entity.*;
|
||||||
|
import tech.easyflow.ai.publish.BotPublishAppService;
|
||||||
import tech.easyflow.ai.service.*;
|
import tech.easyflow.ai.service.*;
|
||||||
import tech.easyflow.ai.service.impl.BotServiceImpl;
|
import tech.easyflow.ai.service.impl.BotServiceImpl;
|
||||||
import tech.easyflow.common.audio.core.AudioServiceManager;
|
import tech.easyflow.common.audio.core.AudioServiceManager;
|
||||||
@@ -67,6 +68,8 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
|||||||
private AudioServiceManager audioServiceManager;
|
private AudioServiceManager audioServiceManager;
|
||||||
@Resource
|
@Resource
|
||||||
private CategoryPermissionService categoryPermissionService;
|
private CategoryPermissionService categoryPermissionService;
|
||||||
|
@Resource
|
||||||
|
private BotPublishAppService botPublishAppService;
|
||||||
|
|
||||||
public BotController(BotService service, ModelService modelService, BotWorkflowService botWorkflowService,
|
public BotController(BotService service, ModelService modelService, BotWorkflowService botWorkflowService,
|
||||||
BotDocumentCollectionService botDocumentCollectionService, BotMessageService botMessageService) {
|
BotDocumentCollectionService botDocumentCollectionService, BotMessageService botMessageService) {
|
||||||
@@ -184,17 +187,23 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
|||||||
@GetMapping("getDetail")
|
@GetMapping("getDetail")
|
||||||
@SaIgnore
|
@SaIgnore
|
||||||
public Result<Bot> getDetail(String id) {
|
public Result<Bot> getDetail(String id) {
|
||||||
Bot bot = botService.getDetail(id);
|
Bot bot = StpUtil.isLogin() ? botService.getDetail(id) : botService.getPublishedDetail(id);
|
||||||
if (bot != null && StpUtil.isLogin()) {
|
if (bot != null && StpUtil.isLogin()) {
|
||||||
categoryPermissionService.assertCategoryResourceVisible("BOT", bot.getCreatedBy(), bot.getCategoryId(), "无权限访问聊天助手");
|
categoryPermissionService.assertCategoryResourceVisible("BOT", bot.getCreatedBy(), bot.getCategoryId(), "无权限访问聊天助手");
|
||||||
}
|
}
|
||||||
|
if (bot == null) {
|
||||||
|
return Result.ok(null);
|
||||||
|
}
|
||||||
|
if (!StpUtil.isLogin() && !tech.easyflow.ai.enums.PublishStatus.from(bot.getPublishStatus()).isExternallyVisible()) {
|
||||||
|
throw new BusinessException("聊天助手尚未发布");
|
||||||
|
}
|
||||||
return Result.ok(bot);
|
return Result.ok(bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SaIgnore
|
@SaIgnore
|
||||||
public Result<Bot> detail(String id) {
|
public Result<Bot> detail(String id) {
|
||||||
Bot data = botService.getDetail(id);
|
Bot data = StpUtil.isLogin() ? botService.getDetail(id) : botService.getPublishedDetail(id);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return Result.ok(data);
|
return Result.ok(data);
|
||||||
}
|
}
|
||||||
@@ -202,6 +211,10 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
|||||||
categoryPermissionService.assertCategoryResourceVisible("BOT", data.getCreatedBy(), data.getCategoryId(), "无权限访问聊天助手");
|
categoryPermissionService.assertCategoryResourceVisible("BOT", data.getCreatedBy(), data.getCategoryId(), "无权限访问聊天助手");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!StpUtil.isLogin() && !tech.easyflow.ai.enums.PublishStatus.from(data.getPublishStatus()).isExternallyVisible()) {
|
||||||
|
throw new BusinessException("聊天助手尚未发布");
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> llmOptions = data.getModelOptions();
|
Map<String, Object> llmOptions = data.getModelOptions();
|
||||||
if (llmOptions == null) {
|
if (llmOptions == null) {
|
||||||
llmOptions = new HashMap<>();
|
llmOptions = new HashMap<>();
|
||||||
@@ -232,6 +245,18 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
|||||||
return Result.ok(data);
|
return Result.ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/submitPublishApproval")
|
||||||
|
@SaCheckPermission("/api/v1/bot/save")
|
||||||
|
public Result<BigInteger> submitPublishApproval(@JsonBody("id") BigInteger id) {
|
||||||
|
return Result.ok(botPublishAppService.submitPublishApproval(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/submitDeleteApproval")
|
||||||
|
@SaCheckPermission("/api/v1/bot/remove")
|
||||||
|
public Result<BigInteger> submitDeleteApproval(@JsonBody("id") BigInteger id) {
|
||||||
|
return Result.ok(botPublishAppService.submitDeleteApproval(id));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<List<Bot>> list(Bot entity, Boolean asTree, String sortKey, String sortType) {
|
public Result<List<Bot>> list(Bot entity, Boolean asTree, String sortKey, String sortType) {
|
||||||
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
@@ -360,6 +385,12 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
|||||||
return super.onRemoveBefore(ids);
|
return super.onRemoveBefore(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PostMapping("remove")
|
||||||
|
public Result<?> remove(@JsonBody(value = "id", required = true) Serializable id) {
|
||||||
|
return Result.fail(1, "请提交删除审批");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统提示词优化
|
* 系统提示词优化
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||||||
import tech.easyflow.ai.dto.BotKnowledgeBindingRequest;
|
import tech.easyflow.ai.dto.BotKnowledgeBindingRequest;
|
||||||
import tech.easyflow.ai.entity.BotDocumentCollection;
|
import tech.easyflow.ai.entity.BotDocumentCollection;
|
||||||
import tech.easyflow.ai.entity.DocumentCollection;
|
import tech.easyflow.ai.entity.DocumentCollection;
|
||||||
|
import tech.easyflow.ai.enums.PublishStatus;
|
||||||
import tech.easyflow.ai.permission.KnowledgeReadAccessSnapshot;
|
import tech.easyflow.ai.permission.KnowledgeReadAccessSnapshot;
|
||||||
import tech.easyflow.ai.permission.KnowledgeVisibilityQueryHelper;
|
import tech.easyflow.ai.permission.KnowledgeVisibilityQueryHelper;
|
||||||
import tech.easyflow.ai.service.BotDocumentCollectionService;
|
import tech.easyflow.ai.service.BotDocumentCollectionService;
|
||||||
@@ -76,6 +77,9 @@ public class BotDocumentCollectionController extends BaseCurdController<BotDocum
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
resourceAccessService.assertAccess(CategoryResourceType.KNOWLEDGE, collection, ResourceAction.READ, "无权限绑定知识库");
|
resourceAccessService.assertAccess(CategoryResourceType.KNOWLEDGE, collection, ResourceAction.READ, "无权限绑定知识库");
|
||||||
|
if (PublishStatus.from(collection.getPublishStatus()) != PublishStatus.PUBLISHED) {
|
||||||
|
throw new tech.easyflow.common.web.exceptions.BusinessException("仅已发布知识库可被聊天助手引用");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
service.saveBotAndKnowledge(botId, knowledgeBindings);
|
service.saveBotAndKnowledge(botId, knowledgeBindings);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package tech.easyflow.admin.controller.ai;
|
|||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import tech.easyflow.ai.entity.BotWorkflow;
|
import tech.easyflow.ai.entity.BotWorkflow;
|
||||||
import tech.easyflow.ai.entity.Workflow;
|
import tech.easyflow.ai.entity.Workflow;
|
||||||
|
import tech.easyflow.ai.enums.PublishStatus;
|
||||||
import tech.easyflow.ai.permission.WorkflowReadAccessSnapshot;
|
import tech.easyflow.ai.permission.WorkflowReadAccessSnapshot;
|
||||||
import tech.easyflow.ai.permission.WorkflowVisibilityQueryHelper;
|
import tech.easyflow.ai.permission.WorkflowVisibilityQueryHelper;
|
||||||
import tech.easyflow.ai.service.BotWorkflowService;
|
import tech.easyflow.ai.service.BotWorkflowService;
|
||||||
@@ -77,6 +78,9 @@ public class BotWorkflowController extends BaseCurdController<BotWorkflowService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
resourceAccessService.assertAccess(CategoryResourceType.WORKFLOW, workflow, ResourceAction.READ, "无权限绑定工作流");
|
resourceAccessService.assertAccess(CategoryResourceType.WORKFLOW, workflow, ResourceAction.READ, "无权限绑定工作流");
|
||||||
|
if (PublishStatus.from(workflow.getPublishStatus()) != PublishStatus.PUBLISHED) {
|
||||||
|
throw new tech.easyflow.common.web.exceptions.BusinessException("仅已发布工作流可被聊天助手引用");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
service.saveBotAndWorkflowTool(botId, workflowIds);
|
service.saveBotAndWorkflowTool(botId, workflowIds);
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import com.easyagents.rag.retrieval.RagRetrievalMetadataKeys;
|
|||||||
import com.mybatisflex.core.paginate.Page;
|
import com.mybatisflex.core.paginate.Page;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -17,6 +19,8 @@ import tech.easyflow.ai.dto.KnowledgeSearchResultItem;
|
|||||||
import tech.easyflow.ai.entity.BotDocumentCollection;
|
import tech.easyflow.ai.entity.BotDocumentCollection;
|
||||||
import tech.easyflow.ai.entity.DocumentCollection;
|
import tech.easyflow.ai.entity.DocumentCollection;
|
||||||
import tech.easyflow.ai.entity.Model;
|
import tech.easyflow.ai.entity.Model;
|
||||||
|
import tech.easyflow.ai.enums.PublishStatus;
|
||||||
|
import tech.easyflow.ai.publish.KnowledgePublishAppService;
|
||||||
import tech.easyflow.ai.rag.KnowledgeRetrievalRequest;
|
import tech.easyflow.ai.rag.KnowledgeRetrievalRequest;
|
||||||
import tech.easyflow.ai.rag.KnowledgeRetrievalModes;
|
import tech.easyflow.ai.rag.KnowledgeRetrievalModes;
|
||||||
import tech.easyflow.ai.service.BotDocumentCollectionService;
|
import tech.easyflow.ai.service.BotDocumentCollectionService;
|
||||||
@@ -65,6 +69,8 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
|
|||||||
private ResourceAccessService resourceAccessService;
|
private ResourceAccessService resourceAccessService;
|
||||||
@Resource
|
@Resource
|
||||||
private KnowledgeVisibilityQueryHelper knowledgeVisibilityQueryHelper;
|
private KnowledgeVisibilityQueryHelper knowledgeVisibilityQueryHelper;
|
||||||
|
@Resource
|
||||||
|
private KnowledgePublishAppService knowledgePublishAppService;
|
||||||
|
|
||||||
public DocumentCollectionController(DocumentCollectionService service, DocumentChunkService chunkService, ModelService llmService) {
|
public DocumentCollectionController(DocumentCollectionService service, DocumentChunkService chunkService, ModelService llmService) {
|
||||||
super(service);
|
super(service);
|
||||||
@@ -180,6 +186,30 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
|
|||||||
return Result.ok(llmService.listSelectableModels(entity, asTree, sortKey, sortType));
|
return Result.ok(llmService.listSelectableModels(entity, asTree, sortKey, sortType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交发布审批。
|
||||||
|
*
|
||||||
|
* @param id 知识库 ID
|
||||||
|
* @return 审批实例 ID
|
||||||
|
*/
|
||||||
|
@PostMapping("/submitPublishApproval")
|
||||||
|
@SaCheckPermission("/api/v1/documentCollection/save")
|
||||||
|
public Result<BigInteger> submitPublishApproval(@JsonBody("id") BigInteger id) {
|
||||||
|
return Result.ok(knowledgePublishAppService.submitPublishApproval(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交删除审批。
|
||||||
|
*
|
||||||
|
* @param id 知识库 ID
|
||||||
|
* @return 审批实例 ID
|
||||||
|
*/
|
||||||
|
@PostMapping("/submitDeleteApproval")
|
||||||
|
@SaCheckPermission("/api/v1/documentCollection/remove")
|
||||||
|
public Result<BigInteger> submitDeleteApproval(@JsonBody("id") BigInteger id) {
|
||||||
|
return Result.ok(knowledgePublishAppService.submitDeleteApproval(id));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("splitterProfile/save")
|
@PostMapping("splitterProfile/save")
|
||||||
@SaCheckPermission("/api/v1/documentCollection/save")
|
@SaCheckPermission("/api/v1/documentCollection/save")
|
||||||
@RequireResourceAccess(
|
@RequireResourceAccess(
|
||||||
@@ -218,6 +248,7 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
|
|||||||
public Result<List<DocumentCollection>> list(DocumentCollection entity, Boolean asTree, String sortKey, String sortType) {
|
public Result<List<DocumentCollection>> list(DocumentCollection entity, Boolean asTree, String sortKey, String sortType) {
|
||||||
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
knowledgeVisibilityQueryHelper.applyReadableAccess(queryWrapper);
|
knowledgeVisibilityQueryHelper.applyReadableAccess(queryWrapper);
|
||||||
|
applyPublishedOnlyFilter(queryWrapper);
|
||||||
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
return Result.ok(service.list(queryWrapper));
|
return Result.ok(service.list(queryWrapper));
|
||||||
}
|
}
|
||||||
@@ -225,9 +256,16 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
|
|||||||
@Override
|
@Override
|
||||||
protected Page<DocumentCollection> queryPage(Page<DocumentCollection> page, QueryWrapper queryWrapper) {
|
protected Page<DocumentCollection> queryPage(Page<DocumentCollection> page, QueryWrapper queryWrapper) {
|
||||||
knowledgeVisibilityQueryHelper.applyReadableAccess(queryWrapper);
|
knowledgeVisibilityQueryHelper.applyReadableAccess(queryWrapper);
|
||||||
|
applyPublishedOnlyFilter(queryWrapper);
|
||||||
return super.queryPage(page, queryWrapper);
|
return super.queryPage(page, queryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PostMapping("remove")
|
||||||
|
public Result<?> remove(@JsonBody(value = "id", required = true) Serializable id) {
|
||||||
|
return Result.fail(1, "请提交删除审批");
|
||||||
|
}
|
||||||
|
|
||||||
private void normalizeVisibilityScope(DocumentCollection entity, boolean isSave) {
|
private void normalizeVisibilityScope(DocumentCollection entity, boolean isSave) {
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
return;
|
return;
|
||||||
@@ -275,6 +313,17 @@ public class DocumentCollectionController extends BaseCurdController<DocumentCol
|
|||||||
return "kb_" + UUID.randomUUID().toString().replace("-", "").substring(0, 28);
|
return "kb_" + UUID.randomUUID().toString().replace("-", "").substring(0, 28);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyPublishedOnlyFilter(QueryWrapper queryWrapper) {
|
||||||
|
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||||
|
if (attributes == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String publishedOnly = attributes.getRequest().getParameter("publishedOnly");
|
||||||
|
if ("true".equalsIgnoreCase(publishedOnly)) {
|
||||||
|
queryWrapper.eq("publish_status", PublishStatus.PUBLISHED.getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<KnowledgeSearchResultItem> toKnowledgeSearchResult(List<Document> documents) {
|
private List<KnowledgeSearchResultItem> toKnowledgeSearchResult(List<Document> documents) {
|
||||||
List<KnowledgeSearchResultItem> results = new ArrayList<>();
|
List<KnowledgeSearchResultItem> results = new ArrayList<>();
|
||||||
if (documents == null) {
|
if (documents == null) {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import com.easyagents.flow.core.chain.runtime.ChainExecutor;
|
|||||||
import com.easyagents.flow.core.parser.ChainParser;
|
import com.easyagents.flow.core.parser.ChainParser;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import tech.easyflow.ai.permission.WorkflowVisibilityQueryHelper;
|
import tech.easyflow.ai.permission.WorkflowVisibilityQueryHelper;
|
||||||
@@ -23,6 +25,8 @@ import tech.easyflow.ai.easyagentsflow.service.TinyFlowService;
|
|||||||
import tech.easyflow.ai.easyagentsflow.service.WorkflowCheckService;
|
import tech.easyflow.ai.easyagentsflow.service.WorkflowCheckService;
|
||||||
import tech.easyflow.ai.easyagentsflow.service.WorkflowDatacenterContentService;
|
import tech.easyflow.ai.easyagentsflow.service.WorkflowDatacenterContentService;
|
||||||
import tech.easyflow.ai.entity.Workflow;
|
import tech.easyflow.ai.entity.Workflow;
|
||||||
|
import tech.easyflow.ai.enums.PublishStatus;
|
||||||
|
import tech.easyflow.ai.publish.WorkflowPublishAppService;
|
||||||
import tech.easyflow.ai.service.BotWorkflowService;
|
import tech.easyflow.ai.service.BotWorkflowService;
|
||||||
import tech.easyflow.ai.service.ModelService;
|
import tech.easyflow.ai.service.ModelService;
|
||||||
import tech.easyflow.ai.service.WorkflowService;
|
import tech.easyflow.ai.service.WorkflowService;
|
||||||
@@ -82,6 +86,8 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
|||||||
private ResourceAccessService resourceAccessService;
|
private ResourceAccessService resourceAccessService;
|
||||||
@Resource
|
@Resource
|
||||||
private WorkflowVisibilityQueryHelper workflowVisibilityQueryHelper;
|
private WorkflowVisibilityQueryHelper workflowVisibilityQueryHelper;
|
||||||
|
@Resource
|
||||||
|
private WorkflowPublishAppService workflowPublishAppService;
|
||||||
|
|
||||||
public WorkflowController(WorkflowService service, ModelService modelService) {
|
public WorkflowController(WorkflowService service, ModelService modelService) {
|
||||||
super(service);
|
super(service);
|
||||||
@@ -239,6 +245,30 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
|||||||
return Result.ok(res);
|
return Result.ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交发布审批。
|
||||||
|
*
|
||||||
|
* @param id 工作流 ID
|
||||||
|
* @return 审批实例 ID
|
||||||
|
*/
|
||||||
|
@PostMapping("/submitPublishApproval")
|
||||||
|
@SaCheckPermission("/api/v1/workflow/save")
|
||||||
|
public Result<BigInteger> submitPublishApproval(@JsonBody("id") BigInteger id) {
|
||||||
|
return Result.ok(workflowPublishAppService.submitPublishApproval(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交删除审批。
|
||||||
|
*
|
||||||
|
* @param id 工作流 ID
|
||||||
|
* @return 审批实例 ID
|
||||||
|
*/
|
||||||
|
@PostMapping("/submitDeleteApproval")
|
||||||
|
@SaCheckPermission("/api/v1/workflow/remove")
|
||||||
|
public Result<BigInteger> submitDeleteApproval(@JsonBody("id") BigInteger id) {
|
||||||
|
return Result.ok(workflowPublishAppService.submitDeleteApproval(id));
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/supportedCodeEngines")
|
@GetMapping("/supportedCodeEngines")
|
||||||
@SaCheckPermission("/api/v1/workflow/query")
|
@SaCheckPermission("/api/v1/workflow/query")
|
||||||
public Result<?> supportedCodeEngines() {
|
public Result<?> supportedCodeEngines() {
|
||||||
@@ -336,6 +366,7 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
|||||||
public Result<List<Workflow>> list(Workflow entity, Boolean asTree, String sortKey, String sortType) {
|
public Result<List<Workflow>> list(Workflow entity, Boolean asTree, String sortKey, String sortType) {
|
||||||
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||||
workflowVisibilityQueryHelper.applyReadableAccess(queryWrapper);
|
workflowVisibilityQueryHelper.applyReadableAccess(queryWrapper);
|
||||||
|
applyPublishedOnlyFilter(queryWrapper);
|
||||||
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||||
return Result.ok(service.list(queryWrapper));
|
return Result.ok(service.list(queryWrapper));
|
||||||
}
|
}
|
||||||
@@ -343,9 +374,16 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
|||||||
@Override
|
@Override
|
||||||
protected Page<Workflow> queryPage(Page<Workflow> page, QueryWrapper queryWrapper) {
|
protected Page<Workflow> queryPage(Page<Workflow> page, QueryWrapper queryWrapper) {
|
||||||
workflowVisibilityQueryHelper.applyReadableAccess(queryWrapper);
|
workflowVisibilityQueryHelper.applyReadableAccess(queryWrapper);
|
||||||
|
applyPublishedOnlyFilter(queryWrapper);
|
||||||
return super.queryPage(page, queryWrapper);
|
return super.queryPage(page, queryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PostMapping("remove")
|
||||||
|
public Result<?> remove(@JsonBody(value = "id", required = true) Serializable id) {
|
||||||
|
return Result.fail(1, "请提交删除审批");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Result onRemoveBefore(Collection<Serializable> ids) {
|
protected Result onRemoveBefore(Collection<Serializable> ids) {
|
||||||
for (Serializable id : ids) {
|
for (Serializable id : ids) {
|
||||||
@@ -381,4 +419,15 @@ public class WorkflowController extends BaseCurdController<WorkflowService, Work
|
|||||||
}
|
}
|
||||||
return workflow;
|
return workflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyPublishedOnlyFilter(QueryWrapper queryWrapper) {
|
||||||
|
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||||
|
if (attributes == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String publishedOnly = attributes.getRequest().getParameter("publishedOnly");
|
||||||
|
if ("true".equalsIgnoreCase(publishedOnly)) {
|
||||||
|
queryWrapper.eq("publish_status", PublishStatus.PUBLISHED.getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package tech.easyflow.admin.controller.system;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import tech.easyflow.common.domain.Result;
|
||||||
|
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||||
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
|
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalAssigneeOptionVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowPageVo;
|
||||||
|
import tech.easyflow.approval.service.ApprovalAssigneeService;
|
||||||
|
import tech.easyflow.approval.service.ApprovalFlowService;
|
||||||
|
import tech.easyflow.system.service.CategoryPermissionService;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程管理控制器。
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/approvalFlow")
|
||||||
|
public class ApprovalFlowController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalFlowService approvalFlowService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CategoryPermissionService categoryPermissionService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalAssigneeService approvalAssigneeService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询审批流程。
|
||||||
|
*
|
||||||
|
* @param name 流程名称
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param actionType 动作类型
|
||||||
|
* @param status 流程状态
|
||||||
|
* @param pageNumber 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
@GetMapping("/page")
|
||||||
|
@SaCheckPermission("/api/v1/approvalFlow/query")
|
||||||
|
public Result<Page<ApprovalFlowPageVo>> page(String name, String resourceType, String actionType, String status,
|
||||||
|
Long pageNumber, Long pageSize) {
|
||||||
|
return Result.ok(approvalFlowService.pageFlows(name, resourceType, actionType, status, pageNumber, pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询审批流程详情。
|
||||||
|
*
|
||||||
|
* @param id 流程ID
|
||||||
|
* @return 流程详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail")
|
||||||
|
@SaCheckPermission("/api/v1/approvalFlow/query")
|
||||||
|
public Result<ApprovalFlowDetailVo> detail(BigInteger id) {
|
||||||
|
return Result.ok(approvalFlowService.getFlowDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询审批角色选项。
|
||||||
|
*
|
||||||
|
* @return 角色选项
|
||||||
|
*/
|
||||||
|
@GetMapping("/assigneeRoleOptions")
|
||||||
|
@SaCheckPermission("/api/v1/approvalFlow/query")
|
||||||
|
public Result<List<ApprovalAssigneeOptionVo>> assigneeRoleOptions() {
|
||||||
|
return Result.ok(approvalAssigneeService.listRoleOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询审批用户选项。
|
||||||
|
*
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @param pageNumber 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 用户选项分页
|
||||||
|
*/
|
||||||
|
@GetMapping("/assigneeAccountPage")
|
||||||
|
@SaCheckPermission("/api/v1/approvalFlow/query")
|
||||||
|
public Result<Page<ApprovalAssigneeOptionVo>> assigneeAccountPage(String keyword, Long pageNumber, Long pageSize) {
|
||||||
|
return Result.ok(approvalAssigneeService.pageAccountOptions(keyword, pageNumber, pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增审批流程。
|
||||||
|
*
|
||||||
|
* @param request 流程请求体
|
||||||
|
* @return 新增流程ID
|
||||||
|
*/
|
||||||
|
@PostMapping("/save")
|
||||||
|
@SaCheckPermission("/api/v1/approvalFlow/save")
|
||||||
|
public Result<BigInteger> save(@JsonBody ApprovalFlowDetailVo request) {
|
||||||
|
assertSuperAdmin();
|
||||||
|
BigInteger operatorId = SaTokenUtil.getLoginAccount().getId();
|
||||||
|
return Result.ok(approvalFlowService.saveFlow(request, operatorId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新审批流程。
|
||||||
|
*
|
||||||
|
* @param request 流程请求体
|
||||||
|
* @return 处理结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/update")
|
||||||
|
@SaCheckPermission("/api/v1/approvalFlow/save")
|
||||||
|
public Result<Void> update(@JsonBody ApprovalFlowDetailVo request) {
|
||||||
|
assertSuperAdmin();
|
||||||
|
BigInteger operatorId = SaTokenUtil.getLoginAccount().getId();
|
||||||
|
approvalFlowService.updateFlow(request, operatorId);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用审批流程。
|
||||||
|
*
|
||||||
|
* @param id 流程ID
|
||||||
|
* @return 处理结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/enable")
|
||||||
|
@SaCheckPermission("/api/v1/approvalFlow/enable")
|
||||||
|
public Result<Void> enable(@JsonBody(value = "id", required = true) BigInteger id) {
|
||||||
|
assertSuperAdmin();
|
||||||
|
approvalFlowService.enableFlow(id, SaTokenUtil.getLoginAccount().getId());
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停用审批流程。
|
||||||
|
*
|
||||||
|
* @param id 流程ID
|
||||||
|
* @return 处理结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/disable")
|
||||||
|
@SaCheckPermission("/api/v1/approvalFlow/disable")
|
||||||
|
public Result<Void> disable(@JsonBody(value = "id", required = true) BigInteger id) {
|
||||||
|
assertSuperAdmin();
|
||||||
|
approvalFlowService.disableFlow(id, SaTokenUtil.getLoginAccount().getId());
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除审批流程。
|
||||||
|
*
|
||||||
|
* @param id 流程ID
|
||||||
|
* @return 处理结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/remove")
|
||||||
|
@SaCheckPermission("/api/v1/approvalFlow/remove")
|
||||||
|
public Result<Void> remove(@JsonBody(value = "id", required = true) BigInteger id) {
|
||||||
|
assertSuperAdmin();
|
||||||
|
approvalFlowService.removeFlow(id);
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSuperAdmin() {
|
||||||
|
if (!categoryPermissionService.isCurrentSuperAdmin()) {
|
||||||
|
throw new BusinessException("仅超级管理员可管理审批流程");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package tech.easyflow.admin.controller.system;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import tech.easyflow.common.domain.Result;
|
||||||
|
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||||
|
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalActionRequest;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalInstanceDetailVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalInstancePageVo;
|
||||||
|
import tech.easyflow.approval.service.ApprovalInstanceService;
|
||||||
|
import tech.easyflow.approval.service.ApprovalQueryService;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批实例控制器。
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/approvalInstance")
|
||||||
|
public class ApprovalInstanceController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalQueryService approvalQueryService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalInstanceService approvalInstanceService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询待审批列表。
|
||||||
|
*
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param actionType 动作类型
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @param pageNumber 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
@GetMapping("/pendingPage")
|
||||||
|
@SaCheckPermission("/api/v1/approvalInstance/query")
|
||||||
|
public Result<Page<ApprovalInstancePageVo>> pendingPage(String resourceType, String actionType, String keyword,
|
||||||
|
Long pageNumber, Long pageSize) {
|
||||||
|
return Result.ok(approvalQueryService.pendingPage(resourceType, actionType, keyword, pageNumber, pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询已审批列表。
|
||||||
|
*
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param actionType 动作类型
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @param pageNumber 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
@GetMapping("/processedPage")
|
||||||
|
@SaCheckPermission("/api/v1/approvalInstance/query")
|
||||||
|
public Result<Page<ApprovalInstancePageVo>> processedPage(String resourceType, String actionType, String keyword,
|
||||||
|
Long pageNumber, Long pageSize) {
|
||||||
|
return Result.ok(approvalQueryService.processedPage(resourceType, actionType, keyword, pageNumber, pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询我发起的审批列表。
|
||||||
|
*
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param actionType 动作类型
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @param pageNumber 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
@GetMapping("/initiatedPage")
|
||||||
|
@SaCheckPermission("/api/v1/approvalInstance/query")
|
||||||
|
public Result<Page<ApprovalInstancePageVo>> initiatedPage(String resourceType, String actionType, String keyword,
|
||||||
|
Long pageNumber, Long pageSize) {
|
||||||
|
return Result.ok(approvalQueryService.initiatedPage(resourceType, actionType, keyword, pageNumber, pageSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询审批实例详情。
|
||||||
|
*
|
||||||
|
* @param id 审批实例ID
|
||||||
|
* @return 详情结果
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail")
|
||||||
|
@SaCheckPermission("/api/v1/approvalInstance/query")
|
||||||
|
public Result<ApprovalInstanceDetailVo> detail(BigInteger id) {
|
||||||
|
return Result.ok(approvalQueryService.detail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过审批。
|
||||||
|
*
|
||||||
|
* @param request 审批动作请求
|
||||||
|
* @return 处理结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/approve")
|
||||||
|
@SaCheckPermission("/api/v1/approvalInstance/approve")
|
||||||
|
public Result<Void> approve(@JsonBody ApprovalActionRequest request) {
|
||||||
|
approvalInstanceService.approve(request.getInstanceId(), request.getComment(), currentOperatorId());
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驳回审批。
|
||||||
|
*
|
||||||
|
* @param request 审批动作请求
|
||||||
|
* @return 处理结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/reject")
|
||||||
|
@SaCheckPermission("/api/v1/approvalInstance/reject")
|
||||||
|
public Result<Void> reject(@JsonBody ApprovalActionRequest request) {
|
||||||
|
approvalInstanceService.reject(request.getInstanceId(), request.getComment(), currentOperatorId());
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回审批。
|
||||||
|
*
|
||||||
|
* @param request 审批动作请求
|
||||||
|
* @return 处理结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/revoke")
|
||||||
|
@SaCheckPermission("/api/v1/approvalInstance/revoke")
|
||||||
|
public Result<Void> revoke(@JsonBody ApprovalActionRequest request) {
|
||||||
|
approvalInstanceService.revoke(request.getInstanceId(), request.getComment(), currentOperatorId());
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigInteger currentOperatorId() {
|
||||||
|
return SaTokenUtil.getLoginAccount().getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import jakarta.validation.constraints.NotBlank;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
import tech.easyflow.approval.annotation.RequirePublishedAccess;
|
||||||
import tech.easyflow.ai.entity.Bot;
|
import tech.easyflow.ai.entity.Bot;
|
||||||
import tech.easyflow.ai.entity.ChatRequestParams;
|
import tech.easyflow.ai.entity.ChatRequestParams;
|
||||||
import tech.easyflow.ai.service.BotService;
|
import tech.easyflow.ai.service.BotService;
|
||||||
@@ -37,8 +38,9 @@ public class PublicBotController {
|
|||||||
* 根据id或别名获取bot详情
|
* 根据id或别名获取bot详情
|
||||||
*/
|
*/
|
||||||
@GetMapping("/getByIdOrAlias")
|
@GetMapping("/getByIdOrAlias")
|
||||||
|
@RequirePublishedAccess(resourceType = "BOT", idExpr = "#key", denyMessage = "聊天助手尚未发布")
|
||||||
public Result<Bot> getByIdOrAlias(@NotBlank(message = "key不能为空") String key) {
|
public Result<Bot> getByIdOrAlias(@NotBlank(message = "key不能为空") String key) {
|
||||||
return Result.ok(botService.getDetail(key));
|
return Result.ok(botService.getPublishedDetail(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,6 +49,7 @@ public class PublicBotController {
|
|||||||
* @return 返回SseEmitter对象,用于服务器向客户端推送聊天响应数据
|
* @return 返回SseEmitter对象,用于服务器向客户端推送聊天响应数据
|
||||||
*/
|
*/
|
||||||
@PostMapping("chat")
|
@PostMapping("chat")
|
||||||
|
@RequirePublishedAccess(resourceType = "BOT", idExpr = "#chatRequestParams.botId", denyMessage = "聊天助手尚未发布")
|
||||||
public SseEmitter chat(@RequestBody ChatRequestParams chatRequestParams, HttpServletRequest request) {
|
public SseEmitter chat(@RequestBody ChatRequestParams chatRequestParams, HttpServletRequest request) {
|
||||||
String apikey = request.getHeader(SysApiKey.KEY_Apikey);
|
String apikey = request.getHeader(SysApiKey.KEY_Apikey);
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import com.easyagents.flow.core.parser.ChainParser;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import tech.easyflow.approval.annotation.RequirePublishedAccess;
|
||||||
|
import tech.easyflow.ai.easyagentsflow.support.PublishedWorkflowDefinitionIds;
|
||||||
import tech.easyflow.ai.easyagentsflow.entity.ChainInfo;
|
import tech.easyflow.ai.easyagentsflow.entity.ChainInfo;
|
||||||
import tech.easyflow.ai.easyagentsflow.entity.NodeInfo;
|
import tech.easyflow.ai.easyagentsflow.entity.NodeInfo;
|
||||||
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage;
|
import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage;
|
||||||
@@ -51,10 +53,11 @@ public class PublicWorkflowController {
|
|||||||
* @return 工作流详情
|
* @return 工作流详情
|
||||||
*/
|
*/
|
||||||
@GetMapping(value = "/getByIdOrAlias")
|
@GetMapping(value = "/getByIdOrAlias")
|
||||||
|
@RequirePublishedAccess(resourceType = "WORKFLOW", idExpr = "#key", denyMessage = "工作流尚未发布")
|
||||||
public Result<Workflow> getByIdOrAlias(
|
public Result<Workflow> getByIdOrAlias(
|
||||||
@RequestParam
|
@RequestParam
|
||||||
@NotBlank(message = "key不能为空") String key) {
|
@NotBlank(message = "key不能为空") String key) {
|
||||||
Workflow workflow = workflowService.getDetail(key);
|
Workflow workflow = workflowService.getPublishedDetail(key);
|
||||||
return Result.ok(workflow);
|
return Result.ok(workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,17 +84,18 @@ public class PublicWorkflowController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/runAsync")
|
@PostMapping("/runAsync")
|
||||||
@SaCheckPermission("/api/v1/workflow/save")
|
@SaCheckPermission("/api/v1/workflow/save")
|
||||||
|
@RequirePublishedAccess(resourceType = "WORKFLOW", idExpr = "#id", denyMessage = "工作流尚未发布")
|
||||||
public Result<String> runAsync(@JsonBody(value = "id", required = true) BigInteger id,
|
public Result<String> runAsync(@JsonBody(value = "id", required = true) BigInteger id,
|
||||||
@JsonBody("variables") Map<String, Object> variables) {
|
@JsonBody("variables") Map<String, Object> variables) {
|
||||||
if (variables == null) {
|
if (variables == null) {
|
||||||
variables = new HashMap<>();
|
variables = new HashMap<>();
|
||||||
}
|
}
|
||||||
Workflow workflow = workflowService.getById(id);
|
Workflow workflow = workflowService.getPublishedById(id);
|
||||||
if (workflow == null) {
|
if (workflow == null) {
|
||||||
throw new RuntimeException("工作流不存在");
|
throw new RuntimeException("工作流不存在");
|
||||||
}
|
}
|
||||||
workflowCheckService.checkOrThrow(workflow.getContent(), WorkflowCheckStage.PRE_EXECUTE, workflow.getId());
|
workflowCheckService.checkOrThrow(workflow.getContent(), WorkflowCheckStage.PRE_EXECUTE, workflow.getId());
|
||||||
String executeId = chainExecutor.executeAsync(id.toString(), variables);
|
String executeId = chainExecutor.executeAsync(PublishedWorkflowDefinitionIds.published(id.toString()), variables);
|
||||||
return Result.ok(executeId);
|
return Result.ok(executeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,8 +122,9 @@ public class PublicWorkflowController {
|
|||||||
|
|
||||||
@GetMapping("getRunningParameters")
|
@GetMapping("getRunningParameters")
|
||||||
@SaCheckPermission("/api/v1/workflow/query")
|
@SaCheckPermission("/api/v1/workflow/query")
|
||||||
|
@RequirePublishedAccess(resourceType = "WORKFLOW", idExpr = "#id", denyMessage = "工作流尚未发布")
|
||||||
public Result<?> getRunningParameters(@RequestParam BigInteger id) {
|
public Result<?> getRunningParameters(@RequestParam BigInteger id) {
|
||||||
Workflow workflow = workflowService.getById(id);
|
Workflow workflow = workflowService.getPublishedById(id);
|
||||||
|
|
||||||
if (workflow == null) {
|
if (workflow == null) {
|
||||||
return Result.fail(1, "can not find the workflow by id: " + id);
|
return Result.fail(1, "can not find the workflow by id: " + id);
|
||||||
|
|||||||
@@ -80,6 +80,10 @@
|
|||||||
<groupId>tech.easyflow</groupId>
|
<groupId>tech.easyflow</groupId>
|
||||||
<artifactId>easyflow-module-system</artifactId>
|
<artifactId>easyflow-module-system</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-module-approval</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.java-websocket</groupId>
|
<groupId>org.java-websocket</groupId>
|
||||||
|
|||||||
@@ -14,25 +14,31 @@ import java.util.*;
|
|||||||
public class WorkflowTool extends BaseTool {
|
public class WorkflowTool extends BaseTool {
|
||||||
|
|
||||||
private BigInteger workflowId;
|
private BigInteger workflowId;
|
||||||
|
private String definitionId;
|
||||||
|
|
||||||
public WorkflowTool() {
|
public WorkflowTool() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorkflowTool(Workflow workflow, boolean needEnglishName) {
|
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.workflowId = workflow.getId();
|
||||||
|
this.definitionId = definitionId;
|
||||||
if (needEnglishName) {
|
if (needEnglishName) {
|
||||||
this.name = workflow.getEnglishName();
|
this.name = workflow.getEnglishName();
|
||||||
} else {
|
} else {
|
||||||
this.name = workflow.getTitle();
|
this.name = workflow.getTitle();
|
||||||
}
|
}
|
||||||
this.description = workflow.getDescription();
|
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);
|
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();
|
List<com.easyagents.flow.core.chain.Parameter> parameterDefs = definition.getStartParameters();
|
||||||
if (parameterDefs == null || parameterDefs.isEmpty()) {
|
if (parameterDefs == null || parameterDefs.isEmpty()) {
|
||||||
return new Parameter[0];
|
return new Parameter[0];
|
||||||
@@ -131,16 +137,25 @@ public class WorkflowTool extends BaseTool {
|
|||||||
this.workflowId = workflowId;
|
this.workflowId = workflowId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDefinitionId() {
|
||||||
|
return definitionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefinitionId(String definitionId) {
|
||||||
|
this.definitionId = definitionId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object invoke(Map<String, Object> argsMap) {
|
public Object invoke(Map<String, Object> argsMap) {
|
||||||
ChainExecutor executor = SpringContextUtil.getBean(ChainExecutor.class);
|
ChainExecutor executor = SpringContextUtil.getBean(ChainExecutor.class);
|
||||||
return executor.execute(workflowId.toString(), argsMap);
|
return executor.execute(definitionId, argsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "AiWorkflowFunction{" +
|
return "AiWorkflowFunction{" +
|
||||||
"workflowId=" + workflowId +
|
"workflowId=" + workflowId +
|
||||||
|
", definitionId='" + definitionId + '\'' +
|
||||||
", name='" + name + '\'' +
|
", name='" + name + '\'' +
|
||||||
", description='" + description + '\'' +
|
", description='" + description + '\'' +
|
||||||
", parameters=" + Arrays.toString(parameters) +
|
", 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.chain.repository.ChainDefinitionRepository;
|
||||||
import com.easyagents.flow.core.parser.ChainParser;
|
import com.easyagents.flow.core.parser.ChainParser;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import tech.easyflow.ai.easyagentsflow.support.PublishedWorkflowDefinitionIds;
|
||||||
import tech.easyflow.ai.easyagentsflow.service.WorkflowDatacenterContentService;
|
import tech.easyflow.ai.easyagentsflow.service.WorkflowDatacenterContentService;
|
||||||
import tech.easyflow.ai.entity.Workflow;
|
import tech.easyflow.ai.entity.Workflow;
|
||||||
import tech.easyflow.ai.service.WorkflowService;
|
import tech.easyflow.ai.service.WorkflowService;
|
||||||
@@ -22,10 +23,14 @@ public class ChainDefinitionRepositoryImpl implements ChainDefinitionRepository
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChainDefinition getChainDefinitionById(String id) {
|
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());
|
String json = workflowDatacenterContentService.prepareContent(workflow.getContent());
|
||||||
ChainDefinition chainDefinition = chainParser.parse(json);
|
ChainDefinition chainDefinition = chainParser.parse(json);
|
||||||
chainDefinition.setId(workflow.getId().toString());
|
chainDefinition.setId(id);
|
||||||
chainDefinition.setName(workflow.getEnglishName());
|
chainDefinition.setName(workflow.getEnglishName());
|
||||||
chainDefinition.setDescription(workflow.getDescription());
|
chainDefinition.setDescription(workflow.getDescription());
|
||||||
return chainDefinition;
|
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) {
|
public Tool toFunction(boolean needEnglishName) {
|
||||||
return new WorkflowTool(this, 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")
|
@Column(comment = "修改者ID")
|
||||||
private BigInteger modifiedBy;
|
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() {
|
public BigInteger getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -239,4 +269,44 @@ public class BotBase extends DateEntity implements Serializable {
|
|||||||
this.modifiedBy = modifiedBy;
|
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 = "可见范围")
|
@Column(comment = "可见范围")
|
||||||
private String visibilityScope;
|
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() {
|
public BigInteger getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -366,4 +396,44 @@ public class DocumentCollectionBase extends DateEntity implements Serializable {
|
|||||||
this.visibilityScope = visibilityScope;
|
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.Column;
|
||||||
import com.mybatisflex.annotation.Id;
|
import com.mybatisflex.annotation.Id;
|
||||||
import com.mybatisflex.annotation.KeyType;
|
import com.mybatisflex.annotation.KeyType;
|
||||||
|
import com.mybatisflex.core.handler.FastjsonTypeHandler;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
import tech.easyflow.common.entity.DateEntity;
|
import tech.easyflow.common.entity.DateEntity;
|
||||||
|
|
||||||
|
|
||||||
@@ -109,6 +111,36 @@ public class WorkflowBase extends DateEntity implements Serializable {
|
|||||||
@Column(comment = "可见范围")
|
@Column(comment = "可见范围")
|
||||||
private String visibilityScope;
|
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() {
|
public BigInteger getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -237,4 +269,44 @@ public class WorkflowBase extends DateEntity implements Serializable {
|
|||||||
this.visibilityScope = visibilityScope;
|
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);
|
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 checkChatBeforeStart(BigInteger botId, String prompt, String conversationId, BotServiceImpl.ChatCheckResult chatCheckResult);
|
||||||
|
|
||||||
SseEmitter startChat(BigInteger botId, String prompt, BigInteger conversationId, List<Map<String, String>> messages,
|
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 getDetail(String idOrAlias);
|
||||||
|
|
||||||
DocumentCollection getByAlias(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 tech.easyflow.ai.entity.Workflow;
|
||||||
import com.mybatisflex.core.service.IService;
|
import com.mybatisflex.core.service.IService;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务层。
|
* 服务层。
|
||||||
*
|
*
|
||||||
@@ -18,4 +20,28 @@ public interface WorkflowService extends IService<Workflow> {
|
|||||||
|
|
||||||
|
|
||||||
Workflow getByAlias(String alias);
|
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;
|
package tech.easyflow.ai.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import com.easyagents.core.file2text.File2TextService;
|
import com.easyagents.core.file2text.File2TextService;
|
||||||
import com.easyagents.core.file2text.source.HttpDocumentSource;
|
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.DefaultBotMessageMemory;
|
||||||
import tech.easyflow.ai.easyagents.memory.PublicBotMessageMemory;
|
import tech.easyflow.ai.easyagents.memory.PublicBotMessageMemory;
|
||||||
import tech.easyflow.ai.easyagents.memory.RuntimeChatMemory;
|
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.entity.*;
|
||||||
|
import tech.easyflow.ai.enums.PublishStatus;
|
||||||
import tech.easyflow.ai.mapper.BotMapper;
|
import tech.easyflow.ai.mapper.BotMapper;
|
||||||
import tech.easyflow.ai.service.*;
|
import tech.easyflow.ai.service.*;
|
||||||
import tech.easyflow.ai.utils.CustomBeanUtils;
|
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 Map<String, Object> modelOptions;
|
||||||
private ChatModel chatModel;
|
private ChatModel chatModel;
|
||||||
private String conversationIdStr;
|
private String conversationIdStr;
|
||||||
|
private boolean publishedAccess;
|
||||||
|
|
||||||
public Bot getAiBot() {return aiBot;}
|
public Bot getAiBot() {return aiBot;}
|
||||||
|
|
||||||
@@ -91,6 +96,10 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
|||||||
public String getConversationIdStr() {return conversationIdStr;}
|
public String getConversationIdStr() {return conversationIdStr;}
|
||||||
|
|
||||||
public void setConversationIdStr(String conversationIdStr) {this.conversationIdStr = 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")
|
@Resource(name = "sseThreadPool")
|
||||||
@@ -100,8 +109,12 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
|||||||
@Resource
|
@Resource
|
||||||
private BotWorkflowService botWorkflowService;
|
private BotWorkflowService botWorkflowService;
|
||||||
@Resource
|
@Resource
|
||||||
|
private WorkflowService workflowService;
|
||||||
|
@Resource
|
||||||
private BotDocumentCollectionService botDocumentCollectionService;
|
private BotDocumentCollectionService botDocumentCollectionService;
|
||||||
@Resource
|
@Resource
|
||||||
|
private DocumentCollectionService documentCollectionService;
|
||||||
|
@Resource
|
||||||
private BotPluginService botPluginService;
|
private BotPluginService botPluginService;
|
||||||
@Resource
|
@Resource
|
||||||
private PluginItemService pluginItemService;
|
private PluginItemService pluginItemService;
|
||||||
@@ -141,6 +154,57 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
|||||||
return getOne(queryWrapper);
|
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) {
|
public SseEmitter checkChatBeforeStart(BigInteger botId, String prompt, String conversationId, ChatCheckResult chatCheckResult) {
|
||||||
if (!StringUtils.hasLength(prompt)) {
|
if (!StringUtils.hasLength(prompt)) {
|
||||||
return ChatSseUtil.sendSystemError(conversationId, "提示词不能为空");
|
return ChatSseUtil.sendSystemError(conversationId, "提示词不能为空");
|
||||||
@@ -175,6 +239,16 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
|||||||
if ((!login || anonymousAccount) && !aiBot.isAnonymousEnabled()) {
|
if ((!login || anonymousAccount) && !aiBot.isAnonymousEnabled()) {
|
||||||
return ChatSseUtil.sendSystemError(conversationId, "此聊天助手不支持匿名访问");
|
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();
|
Map<String, Object> modelOptions = aiBot.getModelOptions();
|
||||||
Model model = modelService.getModelInstance(aiBot.getModelId());
|
Model model = modelService.getModelInstance(aiBot.getModelId());
|
||||||
if (model == null) {
|
if (model == null) {
|
||||||
@@ -213,7 +287,10 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
|||||||
prompt = "【用户问题】:\n" + prompt + "\n\n请基于用户上传的附件内容回答用户问题: \n" + "【用户上传的附件内容】:\n" + attachmentsToString ;
|
prompt = "【用户问题】:\n" + prompt + "\n\n请基于用户上传的附件内容回答用户问题: \n" + "【用户上传的附件内容】:\n" + attachmentsToString ;
|
||||||
}
|
}
|
||||||
UserMessage userMessage = new UserMessage(prompt);
|
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);
|
ChatOptions chatOptions = getChatOptions(modelOptions);
|
||||||
Boolean enableDeepThinking = MapUtil.getBoolean(modelOptions, Bot.KEY_ENABLE_DEEP_THINKING, false);
|
Boolean enableDeepThinking = MapUtil.getBoolean(modelOptions, Bot.KEY_ENABLE_DEEP_THINKING, false);
|
||||||
chatOptions.setThinkingEnabled(enableDeepThinking);
|
chatOptions.setThinkingEnabled(enableDeepThinking);
|
||||||
@@ -270,6 +347,8 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
|||||||
userMessage.addTools(buildFunctionList(Maps.of("botId", botId)
|
userMessage.addTools(buildFunctionList(Maps.of("botId", botId)
|
||||||
.set("needEnglishName", false)
|
.set("needEnglishName", false)
|
||||||
.set("needAccountId", false)
|
.set("needAccountId", false)
|
||||||
|
.set("bot", chatCheckResult.getAiBot())
|
||||||
|
.set("publishedOnly", chatCheckResult.isPublishedAccess())
|
||||||
));
|
));
|
||||||
ChatSseEmitter chatSseEmitter = new ChatSseEmitter();
|
ChatSseEmitter chatSseEmitter = new ChatSseEmitter();
|
||||||
SseEmitter emitter = chatSseEmitter.getEmitter();
|
SseEmitter emitter = chatSseEmitter.getEmitter();
|
||||||
@@ -380,9 +459,17 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
|||||||
if (needEnglishName == null) {
|
if (needEnglishName == null) {
|
||||||
needEnglishName = false;
|
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();
|
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||||
|
if (usePublishedSnapshot) {
|
||||||
|
appendPublishedWorkflowTools(functionList, runtimeBot, needEnglishName);
|
||||||
|
appendPublishedKnowledgeTools(functionList, runtimeBot, needEnglishName);
|
||||||
|
} else {
|
||||||
// 工作流 function 集合
|
// 工作流 function 集合
|
||||||
queryWrapper.eq(BotWorkflow::getBotId, botId);
|
queryWrapper.eq(BotWorkflow::getBotId, botId);
|
||||||
List<BotWorkflow> botWorkflows = botWorkflowService.getMapper()
|
List<BotWorkflow> botWorkflows = botWorkflowService.getMapper()
|
||||||
@@ -406,6 +493,7 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
|||||||
functionList.add(function);
|
functionList.add(function);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 插件 function 集合
|
// 插件 function 集合
|
||||||
queryWrapper = QueryWrapper.create();
|
queryWrapper = QueryWrapper.create();
|
||||||
@@ -437,6 +525,59 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
|||||||
return functionList;
|
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) {
|
public String attachmentsToString(List<String> fileList) {
|
||||||
StringBuilder messageBuilder = new StringBuilder();
|
StringBuilder messageBuilder = new StringBuilder();
|
||||||
if (fileList != null && !fileList.isEmpty()) {
|
if (fileList != null && !fileList.isEmpty()) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package tech.easyflow.ai.service.impl;
|
package tech.easyflow.ai.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.easyagents.core.document.Document;
|
import com.easyagents.core.document.Document;
|
||||||
import com.easyagents.core.model.rerank.RerankException;
|
import com.easyagents.core.model.rerank.RerankException;
|
||||||
import com.easyagents.core.model.rerank.RerankModel;
|
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.DocumentCollection;
|
||||||
import tech.easyflow.ai.entity.FaqItem;
|
import tech.easyflow.ai.entity.FaqItem;
|
||||||
import tech.easyflow.ai.entity.Model;
|
import tech.easyflow.ai.entity.Model;
|
||||||
|
import tech.easyflow.ai.enums.PublishStatus;
|
||||||
import tech.easyflow.ai.mapper.DocumentChunkMapper;
|
import tech.easyflow.ai.mapper.DocumentChunkMapper;
|
||||||
import tech.easyflow.ai.mapper.DocumentCollectionMapper;
|
import tech.easyflow.ai.mapper.DocumentCollectionMapper;
|
||||||
import tech.easyflow.ai.mapper.FaqItemMapper;
|
import tech.easyflow.ai.mapper.FaqItemMapper;
|
||||||
@@ -148,6 +150,57 @@ public class DocumentCollectionServiceImpl extends ServiceImpl<DocumentCollectio
|
|||||||
return formatDocuments(searchDocuments, shouldApplyMinSimilarityFilter(retrievalMode, reranked), minSimilarity, docRecallMaxNum);
|
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,
|
private VectorRetriever buildVectorRetriever(DocumentCollection documentCollection,
|
||||||
int docRecallMaxNum,
|
int docRecallMaxNum,
|
||||||
Float minSimilarity) {
|
Float minSimilarity) {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
package tech.easyflow.ai.service.impl;
|
package tech.easyflow.ai.service.impl;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
import tech.easyflow.ai.entity.Workflow;
|
import tech.easyflow.ai.entity.Workflow;
|
||||||
|
import tech.easyflow.ai.enums.PublishStatus;
|
||||||
import tech.easyflow.ai.mapper.WorkflowMapper;
|
import tech.easyflow.ai.mapper.WorkflowMapper;
|
||||||
import tech.easyflow.ai.service.WorkflowService;
|
import tech.easyflow.ai.service.WorkflowService;
|
||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
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.common.web.exceptions.BusinessException;
|
||||||
import tech.easyflow.ai.utils.CustomBeanUtils;
|
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
|
@Override
|
||||||
public boolean updateById(Workflow entity, boolean ignoreNulls) {
|
public boolean updateById(Workflow entity, boolean ignoreNulls) {
|
||||||
|
|||||||
37
easyflow-modules/easyflow-module-approval/pom.xml
Normal file
37
easyflow-modules/easyflow-module-approval/pom.xml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-modules</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<name>easyflow-module-approval</name>
|
||||||
|
<artifactId>easyflow-module-approval</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mybatis-flex</groupId>
|
||||||
|
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-base</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-satoken</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-module-system</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package tech.easyflow.approval.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交审批动作注解。
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ApprovalAction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源类型。
|
||||||
|
*
|
||||||
|
* @return 资源类型编码
|
||||||
|
*/
|
||||||
|
String resourceType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动作类型。
|
||||||
|
*
|
||||||
|
* @return 动作类型编码
|
||||||
|
*/
|
||||||
|
String actionType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源 ID 的 SpEL 表达式。
|
||||||
|
*
|
||||||
|
* @return SpEL 表达式
|
||||||
|
*/
|
||||||
|
String idExpr();
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package tech.easyflow.approval.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已发布访问门禁注解。
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface RequirePublishedAccess {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源类型。
|
||||||
|
*
|
||||||
|
* @return 资源类型编码
|
||||||
|
*/
|
||||||
|
String resourceType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源标识的 SpEL 表达式。
|
||||||
|
*
|
||||||
|
* @return 标识表达式
|
||||||
|
*/
|
||||||
|
String idExpr();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拒绝访问时的提示文案。
|
||||||
|
*
|
||||||
|
* @return 错误提示
|
||||||
|
*/
|
||||||
|
String denyMessage() default "资源尚未发布";
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package tech.easyflow.approval.aspect;
|
||||||
|
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.springframework.context.expression.MethodBasedEvaluationContext;
|
||||||
|
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||||
|
import org.springframework.core.ParameterNameDiscoverer;
|
||||||
|
import org.springframework.expression.ExpressionParser;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import tech.easyflow.approval.annotation.ApprovalAction;
|
||||||
|
import tech.easyflow.approval.service.ApprovalActionFacade;
|
||||||
|
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提审动作切面。
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
public class ApprovalActionAspect {
|
||||||
|
|
||||||
|
private final ApprovalActionFacade approvalActionFacade;
|
||||||
|
private final ExpressionParser expressionParser = new SpelExpressionParser();
|
||||||
|
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||||
|
|
||||||
|
public ApprovalActionAspect(ApprovalActionFacade approvalActionFacade) {
|
||||||
|
this.approvalActionFacade = approvalActionFacade;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截提审方法,先执行资源校验,再统一发起审批。
|
||||||
|
*
|
||||||
|
* @param joinPoint 切点
|
||||||
|
* @param approvalAction 注解
|
||||||
|
* @return 审批实例 ID
|
||||||
|
* @throws Throwable 执行异常
|
||||||
|
*/
|
||||||
|
@Around("@annotation(approvalAction)")
|
||||||
|
public Object doAround(ProceedingJoinPoint joinPoint, ApprovalAction approvalAction) throws Throwable {
|
||||||
|
Object identifier = resolveIdentifier(joinPoint, approvalAction.idExpr());
|
||||||
|
BigInteger resourceId = identifier == null ? null : new BigInteger(String.valueOf(identifier));
|
||||||
|
joinPoint.proceed();
|
||||||
|
return approvalActionFacade.submit(
|
||||||
|
approvalAction.resourceType(),
|
||||||
|
resourceId,
|
||||||
|
approvalAction.actionType(),
|
||||||
|
SaTokenUtil.getLoginAccount().getId()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object resolveIdentifier(ProceedingJoinPoint joinPoint, String idExpr) {
|
||||||
|
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
|
||||||
|
MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(
|
||||||
|
joinPoint.getTarget(),
|
||||||
|
method,
|
||||||
|
joinPoint.getArgs(),
|
||||||
|
parameterNameDiscoverer
|
||||||
|
);
|
||||||
|
return expressionParser.parseExpression(idExpr).getValue(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package tech.easyflow.approval.aspect;
|
||||||
|
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.springframework.context.expression.MethodBasedEvaluationContext;
|
||||||
|
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||||
|
import org.springframework.core.ParameterNameDiscoverer;
|
||||||
|
import org.springframework.expression.ExpressionParser;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import tech.easyflow.approval.annotation.RequirePublishedAccess;
|
||||||
|
import tech.easyflow.approval.service.ApprovalActionFacade;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已发布访问门禁切面。
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
public class RequirePublishedAccessAspect {
|
||||||
|
|
||||||
|
private final ApprovalActionFacade approvalActionFacade;
|
||||||
|
private final ExpressionParser expressionParser = new SpelExpressionParser();
|
||||||
|
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||||
|
|
||||||
|
public RequirePublishedAccessAspect(ApprovalActionFacade approvalActionFacade) {
|
||||||
|
this.approvalActionFacade = approvalActionFacade;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行发布态门禁校验。
|
||||||
|
*
|
||||||
|
* @param joinPoint 切点
|
||||||
|
* @param requirePublishedAccess 注解
|
||||||
|
* @return 原方法返回值
|
||||||
|
* @throws Throwable 执行异常
|
||||||
|
*/
|
||||||
|
@Around("@annotation(requirePublishedAccess)")
|
||||||
|
public Object doAround(ProceedingJoinPoint joinPoint,
|
||||||
|
RequirePublishedAccess requirePublishedAccess) throws Throwable {
|
||||||
|
Object identifier = resolveIdentifier(joinPoint, requirePublishedAccess.idExpr());
|
||||||
|
approvalActionFacade.assertPublishedAccess(
|
||||||
|
requirePublishedAccess.resourceType(),
|
||||||
|
identifier,
|
||||||
|
requirePublishedAccess.denyMessage()
|
||||||
|
);
|
||||||
|
return joinPoint.proceed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object resolveIdentifier(ProceedingJoinPoint joinPoint, String idExpr) {
|
||||||
|
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
|
||||||
|
MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(
|
||||||
|
joinPoint.getTarget(),
|
||||||
|
method,
|
||||||
|
joinPoint.getArgs(),
|
||||||
|
parameterNameDiscoverer
|
||||||
|
);
|
||||||
|
return expressionParser.parseExpression(idExpr).getValue(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package tech.easyflow.approval.config;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批模块配置。
|
||||||
|
*/
|
||||||
|
@MapperScan("tech.easyflow.approval.mapper")
|
||||||
|
@AutoConfiguration
|
||||||
|
public class ApprovalModuleConfig {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package tech.easyflow.approval.entity;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Table;
|
||||||
|
import tech.easyflow.approval.entity.base.ApprovalFlowBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程实体。
|
||||||
|
*/
|
||||||
|
@Table("tb_approval_flow")
|
||||||
|
public class ApprovalFlow extends ApprovalFlowBase {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package tech.easyflow.approval.entity;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Table;
|
||||||
|
import tech.easyflow.approval.entity.base.ApprovalFlowScopeBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程范围实体。
|
||||||
|
*/
|
||||||
|
@Table("tb_approval_flow_scope")
|
||||||
|
public class ApprovalFlowScope extends ApprovalFlowScopeBase {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package tech.easyflow.approval.entity;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Table;
|
||||||
|
import tech.easyflow.approval.entity.base.ApprovalFlowStepBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程步骤实体。
|
||||||
|
*/
|
||||||
|
@Table("tb_approval_flow_step")
|
||||||
|
public class ApprovalFlowStep extends ApprovalFlowStepBase {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package tech.easyflow.approval.entity;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Table;
|
||||||
|
import tech.easyflow.approval.entity.base.ApprovalInstanceBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批实例实体。
|
||||||
|
*/
|
||||||
|
@Table("tb_approval_instance")
|
||||||
|
public class ApprovalInstance extends ApprovalInstanceBase {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package tech.easyflow.approval.entity;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Table;
|
||||||
|
import tech.easyflow.approval.entity.base.ApprovalLogBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批日志实体。
|
||||||
|
*/
|
||||||
|
@Table("tb_approval_log")
|
||||||
|
public class ApprovalLog extends ApprovalLogBase {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package tech.easyflow.approval.entity;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Table;
|
||||||
|
import tech.easyflow.approval.entity.base.ApprovalTaskBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批任务实体。
|
||||||
|
*/
|
||||||
|
@Table("tb_approval_task")
|
||||||
|
public class ApprovalTask extends ApprovalTaskBase {
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package tech.easyflow.approval.entity.base;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Column;
|
||||||
|
import com.mybatisflex.annotation.Id;
|
||||||
|
import com.mybatisflex.annotation.KeyType;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程主表基础字段。
|
||||||
|
*/
|
||||||
|
public class ApprovalFlowBase implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
@Column(comment = "流程名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(comment = "资源类型")
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
@Column(comment = "动作类型")
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
@Column(comment = "优先级")
|
||||||
|
private Integer priority;
|
||||||
|
|
||||||
|
@Column(comment = "流程状态")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(comment = "流程版本")
|
||||||
|
private Integer version;
|
||||||
|
|
||||||
|
@Column(comment = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Column(comment = "创建时间")
|
||||||
|
private Date created;
|
||||||
|
|
||||||
|
@Column(comment = "创建者")
|
||||||
|
private BigInteger createdBy;
|
||||||
|
|
||||||
|
@Column(comment = "修改时间")
|
||||||
|
private Date modified;
|
||||||
|
|
||||||
|
@Column(comment = "修改者")
|
||||||
|
private BigInteger modifiedBy;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceType(String resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActionType() {
|
||||||
|
return actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionType(String actionType) {
|
||||||
|
this.actionType = actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(Integer priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(Integer version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemark() {
|
||||||
|
return remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemark(String remark) {
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getCreatedBy() {
|
||||||
|
return createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedBy(BigInteger createdBy) {
|
||||||
|
this.createdBy = createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModified() {
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModified(Date modified) {
|
||||||
|
this.modified = modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getModifiedBy() {
|
||||||
|
return modifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedBy(BigInteger modifiedBy) {
|
||||||
|
this.modifiedBy = modifiedBy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package tech.easyflow.approval.entity.base;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Column;
|
||||||
|
import com.mybatisflex.annotation.Id;
|
||||||
|
import com.mybatisflex.annotation.KeyType;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程范围基础字段。
|
||||||
|
*/
|
||||||
|
public class ApprovalFlowScopeBase implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
@Column(comment = "流程ID")
|
||||||
|
private BigInteger flowId;
|
||||||
|
|
||||||
|
@Column(comment = "范围类型")
|
||||||
|
private String scopeType;
|
||||||
|
|
||||||
|
@Column(comment = "范围值")
|
||||||
|
private BigInteger scopeValue;
|
||||||
|
|
||||||
|
@Column(comment = "是否包含子节点")
|
||||||
|
private Integer includeChildren;
|
||||||
|
|
||||||
|
@Column(comment = "创建时间")
|
||||||
|
private Date created;
|
||||||
|
|
||||||
|
@Column(comment = "创建者")
|
||||||
|
private BigInteger createdBy;
|
||||||
|
|
||||||
|
@Column(comment = "修改时间")
|
||||||
|
private Date modified;
|
||||||
|
|
||||||
|
@Column(comment = "修改者")
|
||||||
|
private BigInteger modifiedBy;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getFlowId() {
|
||||||
|
return flowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlowId(BigInteger flowId) {
|
||||||
|
this.flowId = flowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScopeType() {
|
||||||
|
return scopeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopeType(String scopeType) {
|
||||||
|
this.scopeType = scopeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getScopeValue() {
|
||||||
|
return scopeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopeValue(BigInteger scopeValue) {
|
||||||
|
this.scopeValue = scopeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getIncludeChildren() {
|
||||||
|
return includeChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIncludeChildren(Integer includeChildren) {
|
||||||
|
this.includeChildren = includeChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getCreatedBy() {
|
||||||
|
return createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedBy(BigInteger createdBy) {
|
||||||
|
this.createdBy = createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModified() {
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModified(Date modified) {
|
||||||
|
this.modified = modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getModifiedBy() {
|
||||||
|
return modifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedBy(BigInteger modifiedBy) {
|
||||||
|
this.modifiedBy = modifiedBy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package tech.easyflow.approval.entity.base;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Column;
|
||||||
|
import com.mybatisflex.annotation.Id;
|
||||||
|
import com.mybatisflex.annotation.KeyType;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程步骤基础字段。
|
||||||
|
*/
|
||||||
|
public class ApprovalFlowStepBase implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
@Column(comment = "流程ID")
|
||||||
|
private BigInteger flowId;
|
||||||
|
|
||||||
|
@Column(comment = "步骤顺序")
|
||||||
|
private Integer stepNo;
|
||||||
|
|
||||||
|
@Column(comment = "步骤名称")
|
||||||
|
private String stepName;
|
||||||
|
|
||||||
|
@Column(comment = "审批对象类型")
|
||||||
|
private String assigneeType;
|
||||||
|
|
||||||
|
@Column(comment = "审批对象ID")
|
||||||
|
private BigInteger assigneeTargetId;
|
||||||
|
|
||||||
|
@Column(comment = "审批对象编码")
|
||||||
|
private String assigneeTargetCode;
|
||||||
|
|
||||||
|
@Column(comment = "审批对象名称")
|
||||||
|
private String assigneeTargetName;
|
||||||
|
|
||||||
|
@Column(comment = "创建时间")
|
||||||
|
private Date created;
|
||||||
|
|
||||||
|
@Column(comment = "创建者")
|
||||||
|
private BigInteger createdBy;
|
||||||
|
|
||||||
|
@Column(comment = "修改时间")
|
||||||
|
private Date modified;
|
||||||
|
|
||||||
|
@Column(comment = "修改者")
|
||||||
|
private BigInteger modifiedBy;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getFlowId() {
|
||||||
|
return flowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlowId(BigInteger flowId) {
|
||||||
|
this.flowId = flowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStepNo() {
|
||||||
|
return stepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStepNo(Integer stepNo) {
|
||||||
|
this.stepNo = stepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStepName() {
|
||||||
|
return stepName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStepName(String stepName) {
|
||||||
|
this.stepName = stepName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeType() {
|
||||||
|
return assigneeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeType(String assigneeType) {
|
||||||
|
this.assigneeType = assigneeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getAssigneeTargetId() {
|
||||||
|
return assigneeTargetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetId(BigInteger assigneeTargetId) {
|
||||||
|
this.assigneeTargetId = assigneeTargetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeTargetCode() {
|
||||||
|
return assigneeTargetCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetCode(String assigneeTargetCode) {
|
||||||
|
this.assigneeTargetCode = assigneeTargetCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeTargetName() {
|
||||||
|
return assigneeTargetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetName(String assigneeTargetName) {
|
||||||
|
this.assigneeTargetName = assigneeTargetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getCreatedBy() {
|
||||||
|
return createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedBy(BigInteger createdBy) {
|
||||||
|
this.createdBy = createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModified() {
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModified(Date modified) {
|
||||||
|
this.modified = modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getModifiedBy() {
|
||||||
|
return modifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedBy(BigInteger modifiedBy) {
|
||||||
|
this.modifiedBy = modifiedBy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
package tech.easyflow.approval.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批实例基础字段。
|
||||||
|
*/
|
||||||
|
public class ApprovalInstanceBase implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
@Column(comment = "流程ID")
|
||||||
|
private BigInteger flowId;
|
||||||
|
|
||||||
|
@Column(comment = "流程版本")
|
||||||
|
private Integer flowVersion;
|
||||||
|
|
||||||
|
@Column(comment = "资源类型")
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
@Column(comment = "资源ID")
|
||||||
|
private BigInteger resourceId;
|
||||||
|
|
||||||
|
@Column(comment = "动作类型")
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
@Column(comment = "实例状态")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(comment = "当前步骤序号")
|
||||||
|
private Integer currentStepNo;
|
||||||
|
|
||||||
|
@Column(typeHandler = FastjsonTypeHandler.class, comment = "审批快照")
|
||||||
|
private Map<String, Object> snapshotJson;
|
||||||
|
|
||||||
|
@Column(comment = "审批摘要")
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
@Column(comment = "申请人ID")
|
||||||
|
private BigInteger applicantId;
|
||||||
|
|
||||||
|
@Column(comment = "提交时间")
|
||||||
|
private Date submittedAt;
|
||||||
|
|
||||||
|
@Column(comment = "完成时间")
|
||||||
|
private Date finishedAt;
|
||||||
|
|
||||||
|
@Column(comment = "创建时间")
|
||||||
|
private Date created;
|
||||||
|
|
||||||
|
@Column(comment = "创建者")
|
||||||
|
private BigInteger createdBy;
|
||||||
|
|
||||||
|
@Column(comment = "修改时间")
|
||||||
|
private Date modified;
|
||||||
|
|
||||||
|
@Column(comment = "修改者")
|
||||||
|
private BigInteger modifiedBy;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getFlowId() {
|
||||||
|
return flowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlowId(BigInteger flowId) {
|
||||||
|
this.flowId = flowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getFlowVersion() {
|
||||||
|
return flowVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlowVersion(Integer flowVersion) {
|
||||||
|
this.flowVersion = flowVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceType(String resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getResourceId() {
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceId(BigInteger resourceId) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActionType() {
|
||||||
|
return actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionType(String actionType) {
|
||||||
|
this.actionType = actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCurrentStepNo() {
|
||||||
|
return currentStepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentStepNo(Integer currentStepNo) {
|
||||||
|
this.currentStepNo = currentStepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getSnapshotJson() {
|
||||||
|
return snapshotJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnapshotJson(Map<String, Object> snapshotJson) {
|
||||||
|
this.snapshotJson = snapshotJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSummary(String summary) {
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getApplicantId() {
|
||||||
|
return applicantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApplicantId(BigInteger applicantId) {
|
||||||
|
this.applicantId = applicantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getSubmittedAt() {
|
||||||
|
return submittedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubmittedAt(Date submittedAt) {
|
||||||
|
this.submittedAt = submittedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getFinishedAt() {
|
||||||
|
return finishedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFinishedAt(Date finishedAt) {
|
||||||
|
this.finishedAt = finishedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getCreatedBy() {
|
||||||
|
return createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedBy(BigInteger createdBy) {
|
||||||
|
this.createdBy = createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModified() {
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModified(Date modified) {
|
||||||
|
this.modified = modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getModifiedBy() {
|
||||||
|
return modifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedBy(BigInteger modifiedBy) {
|
||||||
|
this.modifiedBy = modifiedBy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package tech.easyflow.approval.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批日志基础字段。
|
||||||
|
*/
|
||||||
|
public class ApprovalLogBase implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
@Column(comment = "审批实例ID")
|
||||||
|
private BigInteger instanceId;
|
||||||
|
|
||||||
|
@Column(comment = "事件类型")
|
||||||
|
private String eventType;
|
||||||
|
|
||||||
|
@Column(comment = "操作人ID")
|
||||||
|
private BigInteger operatorId;
|
||||||
|
|
||||||
|
@Column(typeHandler = FastjsonTypeHandler.class, comment = "事件载荷")
|
||||||
|
private Map<String, Object> payloadJson;
|
||||||
|
|
||||||
|
@Column(comment = "创建时间")
|
||||||
|
private Date created;
|
||||||
|
|
||||||
|
@Column(comment = "创建者")
|
||||||
|
private BigInteger createdBy;
|
||||||
|
|
||||||
|
@Column(comment = "修改时间")
|
||||||
|
private Date modified;
|
||||||
|
|
||||||
|
@Column(comment = "修改者")
|
||||||
|
private BigInteger modifiedBy;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getInstanceId() {
|
||||||
|
return instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstanceId(BigInteger instanceId) {
|
||||||
|
this.instanceId = instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEventType() {
|
||||||
|
return eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEventType(String eventType) {
|
||||||
|
this.eventType = eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getOperatorId() {
|
||||||
|
return operatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperatorId(BigInteger operatorId) {
|
||||||
|
this.operatorId = operatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getPayloadJson() {
|
||||||
|
return payloadJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPayloadJson(Map<String, Object> payloadJson) {
|
||||||
|
this.payloadJson = payloadJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getCreatedBy() {
|
||||||
|
return createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedBy(BigInteger createdBy) {
|
||||||
|
this.createdBy = createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModified() {
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModified(Date modified) {
|
||||||
|
this.modified = modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getModifiedBy() {
|
||||||
|
return modifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedBy(BigInteger modifiedBy) {
|
||||||
|
this.modifiedBy = modifiedBy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
package tech.easyflow.approval.entity.base;
|
||||||
|
|
||||||
|
import com.mybatisflex.annotation.Column;
|
||||||
|
import com.mybatisflex.annotation.Id;
|
||||||
|
import com.mybatisflex.annotation.KeyType;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批任务基础字段。
|
||||||
|
*/
|
||||||
|
public class ApprovalTaskBase implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
@Column(comment = "审批实例ID")
|
||||||
|
private BigInteger instanceId;
|
||||||
|
|
||||||
|
@Column(comment = "步骤序号")
|
||||||
|
private Integer stepNo;
|
||||||
|
|
||||||
|
@Column(comment = "任务状态")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(comment = "指派角色编码")
|
||||||
|
private String assigneeRoleCode;
|
||||||
|
|
||||||
|
@Column(comment = "审批对象类型")
|
||||||
|
private String assigneeType;
|
||||||
|
|
||||||
|
@Column(comment = "审批对象ID")
|
||||||
|
private BigInteger assigneeTargetId;
|
||||||
|
|
||||||
|
@Column(comment = "审批对象编码")
|
||||||
|
private String assigneeTargetCode;
|
||||||
|
|
||||||
|
@Column(comment = "审批对象名称")
|
||||||
|
private String assigneeTargetName;
|
||||||
|
|
||||||
|
@Column(comment = "处理人ID")
|
||||||
|
private BigInteger actedBy;
|
||||||
|
|
||||||
|
@Column(comment = "处理时间")
|
||||||
|
private Date actedAt;
|
||||||
|
|
||||||
|
@Column(comment = "处理意见")
|
||||||
|
private String comment;
|
||||||
|
|
||||||
|
@Column(comment = "创建时间")
|
||||||
|
private Date created;
|
||||||
|
|
||||||
|
@Column(comment = "创建者")
|
||||||
|
private BigInteger createdBy;
|
||||||
|
|
||||||
|
@Column(comment = "修改时间")
|
||||||
|
private Date modified;
|
||||||
|
|
||||||
|
@Column(comment = "修改者")
|
||||||
|
private BigInteger modifiedBy;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getInstanceId() {
|
||||||
|
return instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstanceId(BigInteger instanceId) {
|
||||||
|
this.instanceId = instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStepNo() {
|
||||||
|
return stepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStepNo(Integer stepNo) {
|
||||||
|
this.stepNo = stepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeRoleCode() {
|
||||||
|
return assigneeRoleCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeRoleCode(String assigneeRoleCode) {
|
||||||
|
this.assigneeRoleCode = assigneeRoleCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeType() {
|
||||||
|
return assigneeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeType(String assigneeType) {
|
||||||
|
this.assigneeType = assigneeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getAssigneeTargetId() {
|
||||||
|
return assigneeTargetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetId(BigInteger assigneeTargetId) {
|
||||||
|
this.assigneeTargetId = assigneeTargetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeTargetCode() {
|
||||||
|
return assigneeTargetCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetCode(String assigneeTargetCode) {
|
||||||
|
this.assigneeTargetCode = assigneeTargetCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeTargetName() {
|
||||||
|
return assigneeTargetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetName(String assigneeTargetName) {
|
||||||
|
this.assigneeTargetName = assigneeTargetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getActedBy() {
|
||||||
|
return actedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActedBy(BigInteger actedBy) {
|
||||||
|
this.actedBy = actedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getActedAt() {
|
||||||
|
return actedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActedAt(Date actedAt) {
|
||||||
|
this.actedAt = actedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComment(String comment) {
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getCreatedBy() {
|
||||||
|
return createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedBy(BigInteger createdBy) {
|
||||||
|
this.createdBy = createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModified() {
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModified(Date modified) {
|
||||||
|
this.modified = modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getModifiedBy() {
|
||||||
|
return modifiedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiedBy(BigInteger modifiedBy) {
|
||||||
|
this.modifiedBy = modifiedBy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批动作请求。
|
||||||
|
*/
|
||||||
|
public class ApprovalActionRequest {
|
||||||
|
|
||||||
|
private BigInteger instanceId;
|
||||||
|
|
||||||
|
private String comment;
|
||||||
|
|
||||||
|
public BigInteger getInstanceId() {
|
||||||
|
return instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstanceId(BigInteger instanceId) {
|
||||||
|
this.instanceId = instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComment(String comment) {
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批对象选项。
|
||||||
|
*/
|
||||||
|
public class ApprovalAssigneeOptionVo {
|
||||||
|
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCode(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批回调上下文。
|
||||||
|
*/
|
||||||
|
public class ApprovalCallbackContext {
|
||||||
|
|
||||||
|
private ApprovalInstance instance;
|
||||||
|
|
||||||
|
private BigInteger operatorId;
|
||||||
|
|
||||||
|
private String comment;
|
||||||
|
|
||||||
|
public ApprovalInstance getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstance(ApprovalInstance instance) {
|
||||||
|
this.instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getOperatorId() {
|
||||||
|
return operatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperatorId(BigInteger operatorId) {
|
||||||
|
this.operatorId = operatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComment(String comment) {
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程详情。
|
||||||
|
*/
|
||||||
|
public class ApprovalFlowDetailVo {
|
||||||
|
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
private Integer priority;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private Integer version;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
private Date created;
|
||||||
|
|
||||||
|
private Date modified;
|
||||||
|
|
||||||
|
private boolean deletable;
|
||||||
|
|
||||||
|
private long pendingInstanceCount;
|
||||||
|
|
||||||
|
private List<ApprovalFlowScopeVo> scopes = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<ApprovalFlowStepVo> steps = new ArrayList<>();
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceType(String resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActionType() {
|
||||||
|
return actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionType(String actionType) {
|
||||||
|
this.actionType = actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(Integer priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(Integer version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemark() {
|
||||||
|
return remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemark(String remark) {
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModified() {
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModified(Date modified) {
|
||||||
|
this.modified = modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeletable() {
|
||||||
|
return deletable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeletable(boolean deletable) {
|
||||||
|
this.deletable = deletable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPendingInstanceCount() {
|
||||||
|
return pendingInstanceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPendingInstanceCount(long pendingInstanceCount) {
|
||||||
|
this.pendingInstanceCount = pendingInstanceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ApprovalFlowScopeVo> getScopes() {
|
||||||
|
return scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopes(List<ApprovalFlowScopeVo> scopes) {
|
||||||
|
this.scopes = scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ApprovalFlowStepVo> getSteps() {
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSteps(List<ApprovalFlowStepVo> steps) {
|
||||||
|
this.steps = steps;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程分页项。
|
||||||
|
*/
|
||||||
|
public class ApprovalFlowPageVo {
|
||||||
|
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
private Integer priority;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private Integer version;
|
||||||
|
|
||||||
|
private String scopeSummary;
|
||||||
|
|
||||||
|
private Integer stepCount;
|
||||||
|
|
||||||
|
private long pendingInstanceCount;
|
||||||
|
|
||||||
|
private Date modified;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceType(String resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActionType() {
|
||||||
|
return actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionType(String actionType) {
|
||||||
|
this.actionType = actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(Integer priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(Integer version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScopeSummary() {
|
||||||
|
return scopeSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopeSummary(String scopeSummary) {
|
||||||
|
this.scopeSummary = scopeSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStepCount() {
|
||||||
|
return stepCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStepCount(Integer stepCount) {
|
||||||
|
this.stepCount = stepCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPendingInstanceCount() {
|
||||||
|
return pendingInstanceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPendingInstanceCount(long pendingInstanceCount) {
|
||||||
|
this.pendingInstanceCount = pendingInstanceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getModified() {
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModified(Date modified) {
|
||||||
|
this.modified = modified;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程范围项。
|
||||||
|
*/
|
||||||
|
public class ApprovalFlowScopeVo {
|
||||||
|
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
private String scopeType;
|
||||||
|
|
||||||
|
private BigInteger scopeValue;
|
||||||
|
|
||||||
|
private Integer includeChildren;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScopeType() {
|
||||||
|
return scopeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopeType(String scopeType) {
|
||||||
|
this.scopeType = scopeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getScopeValue() {
|
||||||
|
return scopeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopeValue(BigInteger scopeValue) {
|
||||||
|
this.scopeValue = scopeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getIncludeChildren() {
|
||||||
|
return includeChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIncludeChildren(Integer includeChildren) {
|
||||||
|
this.includeChildren = includeChildren;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程步骤项。
|
||||||
|
*/
|
||||||
|
public class ApprovalFlowStepVo {
|
||||||
|
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
private Integer stepNo;
|
||||||
|
|
||||||
|
private String stepName;
|
||||||
|
|
||||||
|
private String assigneeType;
|
||||||
|
|
||||||
|
private BigInteger assigneeTargetId;
|
||||||
|
|
||||||
|
private String assigneeTargetCode;
|
||||||
|
|
||||||
|
private String assigneeTargetName;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStepNo() {
|
||||||
|
return stepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStepNo(Integer stepNo) {
|
||||||
|
this.stepNo = stepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStepName() {
|
||||||
|
return stepName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStepName(String stepName) {
|
||||||
|
this.stepName = stepName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeType() {
|
||||||
|
return assigneeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeType(String assigneeType) {
|
||||||
|
this.assigneeType = assigneeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getAssigneeTargetId() {
|
||||||
|
return assigneeTargetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetId(BigInteger assigneeTargetId) {
|
||||||
|
this.assigneeTargetId = assigneeTargetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeTargetCode() {
|
||||||
|
return assigneeTargetCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetCode(String assigneeTargetCode) {
|
||||||
|
this.assigneeTargetCode = assigneeTargetCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeTargetName() {
|
||||||
|
return assigneeTargetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetName(String assigneeTargetName) {
|
||||||
|
this.assigneeTargetName = assigneeTargetName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批实例详情。
|
||||||
|
*/
|
||||||
|
public class ApprovalInstanceDetailVo {
|
||||||
|
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
private BigInteger flowId;
|
||||||
|
|
||||||
|
private Integer flowVersion;
|
||||||
|
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
private BigInteger resourceId;
|
||||||
|
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private Integer currentStepNo;
|
||||||
|
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
private BigInteger applicantId;
|
||||||
|
|
||||||
|
private String applicantName;
|
||||||
|
|
||||||
|
private Date submittedAt;
|
||||||
|
|
||||||
|
private Date finishedAt;
|
||||||
|
|
||||||
|
private Map<String, Object> snapshotJson;
|
||||||
|
|
||||||
|
private boolean canApprove;
|
||||||
|
|
||||||
|
private boolean canReject;
|
||||||
|
|
||||||
|
private boolean canRevoke;
|
||||||
|
|
||||||
|
private List<ApprovalTaskVo> tasks = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<ApprovalLogVo> logs = new ArrayList<>();
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getFlowId() {
|
||||||
|
return flowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlowId(BigInteger flowId) {
|
||||||
|
this.flowId = flowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getFlowVersion() {
|
||||||
|
return flowVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlowVersion(Integer flowVersion) {
|
||||||
|
this.flowVersion = flowVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceType(String resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getResourceId() {
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceId(BigInteger resourceId) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActionType() {
|
||||||
|
return actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionType(String actionType) {
|
||||||
|
this.actionType = actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCurrentStepNo() {
|
||||||
|
return currentStepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentStepNo(Integer currentStepNo) {
|
||||||
|
this.currentStepNo = currentStepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSummary(String summary) {
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getApplicantId() {
|
||||||
|
return applicantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApplicantId(BigInteger applicantId) {
|
||||||
|
this.applicantId = applicantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApplicantName() {
|
||||||
|
return applicantName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApplicantName(String applicantName) {
|
||||||
|
this.applicantName = applicantName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getSubmittedAt() {
|
||||||
|
return submittedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubmittedAt(Date submittedAt) {
|
||||||
|
this.submittedAt = submittedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getFinishedAt() {
|
||||||
|
return finishedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFinishedAt(Date finishedAt) {
|
||||||
|
this.finishedAt = finishedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getSnapshotJson() {
|
||||||
|
return snapshotJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnapshotJson(Map<String, Object> snapshotJson) {
|
||||||
|
this.snapshotJson = snapshotJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCanApprove() {
|
||||||
|
return canApprove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanApprove(boolean canApprove) {
|
||||||
|
this.canApprove = canApprove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCanReject() {
|
||||||
|
return canReject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanReject(boolean canReject) {
|
||||||
|
this.canReject = canReject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCanRevoke() {
|
||||||
|
return canRevoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanRevoke(boolean canRevoke) {
|
||||||
|
this.canRevoke = canRevoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ApprovalTaskVo> getTasks() {
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTasks(List<ApprovalTaskVo> tasks) {
|
||||||
|
this.tasks = tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ApprovalLogVo> getLogs() {
|
||||||
|
return logs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogs(List<ApprovalLogVo> logs) {
|
||||||
|
this.logs = logs;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批实例分页项。
|
||||||
|
*/
|
||||||
|
public class ApprovalInstancePageVo {
|
||||||
|
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
private BigInteger resourceId;
|
||||||
|
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private Integer currentStepNo;
|
||||||
|
|
||||||
|
private String currentStepName;
|
||||||
|
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
private BigInteger applicantId;
|
||||||
|
|
||||||
|
private Date submittedAt;
|
||||||
|
|
||||||
|
private Date finishedAt;
|
||||||
|
|
||||||
|
private boolean canApprove;
|
||||||
|
|
||||||
|
private boolean canReject;
|
||||||
|
|
||||||
|
private boolean canRevoke;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceType(String resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getResourceId() {
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceId(BigInteger resourceId) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActionType() {
|
||||||
|
return actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionType(String actionType) {
|
||||||
|
this.actionType = actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCurrentStepNo() {
|
||||||
|
return currentStepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentStepNo(Integer currentStepNo) {
|
||||||
|
this.currentStepNo = currentStepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentStepName() {
|
||||||
|
return currentStepName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentStepName(String currentStepName) {
|
||||||
|
this.currentStepName = currentStepName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSummary(String summary) {
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getApplicantId() {
|
||||||
|
return applicantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApplicantId(BigInteger applicantId) {
|
||||||
|
this.applicantId = applicantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getSubmittedAt() {
|
||||||
|
return submittedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubmittedAt(Date submittedAt) {
|
||||||
|
this.submittedAt = submittedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getFinishedAt() {
|
||||||
|
return finishedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFinishedAt(Date finishedAt) {
|
||||||
|
this.finishedAt = finishedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCanApprove() {
|
||||||
|
return canApprove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanApprove(boolean canApprove) {
|
||||||
|
this.canApprove = canApprove;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCanReject() {
|
||||||
|
return canReject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanReject(boolean canReject) {
|
||||||
|
this.canReject = canReject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCanRevoke() {
|
||||||
|
return canRevoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanRevoke(boolean canRevoke) {
|
||||||
|
this.canRevoke = canRevoke;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批日志视图。
|
||||||
|
*/
|
||||||
|
public class ApprovalLogVo {
|
||||||
|
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
private String eventType;
|
||||||
|
|
||||||
|
private BigInteger operatorId;
|
||||||
|
|
||||||
|
private String operatorName;
|
||||||
|
|
||||||
|
private Date created;
|
||||||
|
|
||||||
|
private Map<String, Object> payloadJson;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEventType() {
|
||||||
|
return eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEventType(String eventType) {
|
||||||
|
this.eventType = eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getOperatorId() {
|
||||||
|
return operatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperatorId(BigInteger operatorId) {
|
||||||
|
this.operatorId = operatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOperatorName() {
|
||||||
|
return operatorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperatorName(String operatorName) {
|
||||||
|
this.operatorName = operatorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Date created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getPayloadJson() {
|
||||||
|
return payloadJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPayloadJson(Map<String, Object> payloadJson) {
|
||||||
|
this.payloadJson = payloadJson;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批提交后回调上下文。
|
||||||
|
*/
|
||||||
|
public class ApprovalSubmitCallbackContext {
|
||||||
|
|
||||||
|
private BigInteger instanceId;
|
||||||
|
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
private BigInteger resourceId;
|
||||||
|
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
private BigInteger operatorId;
|
||||||
|
|
||||||
|
public BigInteger getInstanceId() {
|
||||||
|
return instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstanceId(BigInteger instanceId) {
|
||||||
|
this.instanceId = instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceType(String resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getResourceId() {
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceId(BigInteger resourceId) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActionType() {
|
||||||
|
return actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionType(String actionType) {
|
||||||
|
this.actionType = actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getOperatorId() {
|
||||||
|
return operatorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOperatorId(BigInteger operatorId) {
|
||||||
|
this.operatorId = operatorId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起审批请求。
|
||||||
|
*/
|
||||||
|
public class ApprovalSubmitRequest {
|
||||||
|
|
||||||
|
private String resourceType;
|
||||||
|
|
||||||
|
private BigInteger resourceId;
|
||||||
|
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
private BigInteger categoryId;
|
||||||
|
|
||||||
|
private BigInteger deptId;
|
||||||
|
|
||||||
|
private BigInteger applicantId;
|
||||||
|
|
||||||
|
private String summary;
|
||||||
|
|
||||||
|
private Map<String, Object> snapshotJson;
|
||||||
|
|
||||||
|
public String getResourceType() {
|
||||||
|
return resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceType(String resourceType) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getResourceId() {
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceId(BigInteger resourceId) {
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActionType() {
|
||||||
|
return actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionType(String actionType) {
|
||||||
|
this.actionType = actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getCategoryId() {
|
||||||
|
return categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryId(BigInteger categoryId) {
|
||||||
|
this.categoryId = categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getDeptId() {
|
||||||
|
return deptId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeptId(BigInteger deptId) {
|
||||||
|
this.deptId = deptId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getApplicantId() {
|
||||||
|
return applicantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApplicantId(BigInteger applicantId) {
|
||||||
|
this.applicantId = applicantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSummary(String summary) {
|
||||||
|
this.summary = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getSnapshotJson() {
|
||||||
|
return snapshotJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnapshotJson(Map<String, Object> snapshotJson) {
|
||||||
|
this.snapshotJson = snapshotJson;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package tech.easyflow.approval.entity.vo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批任务视图。
|
||||||
|
*/
|
||||||
|
public class ApprovalTaskVo {
|
||||||
|
|
||||||
|
private BigInteger id;
|
||||||
|
|
||||||
|
private Integer stepNo;
|
||||||
|
|
||||||
|
private String stepName;
|
||||||
|
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
private String assigneeRoleCode;
|
||||||
|
|
||||||
|
private String assigneeType;
|
||||||
|
|
||||||
|
private BigInteger assigneeTargetId;
|
||||||
|
|
||||||
|
private String assigneeTargetCode;
|
||||||
|
|
||||||
|
private String assigneeTargetName;
|
||||||
|
|
||||||
|
private BigInteger actedBy;
|
||||||
|
|
||||||
|
private String actedByName;
|
||||||
|
|
||||||
|
private Date actedAt;
|
||||||
|
|
||||||
|
private String comment;
|
||||||
|
|
||||||
|
public BigInteger getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(BigInteger id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStepNo() {
|
||||||
|
return stepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStepNo(Integer stepNo) {
|
||||||
|
this.stepNo = stepNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStepName() {
|
||||||
|
return stepName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStepName(String stepName) {
|
||||||
|
this.stepName = stepName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeRoleCode() {
|
||||||
|
return assigneeRoleCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeRoleCode(String assigneeRoleCode) {
|
||||||
|
this.assigneeRoleCode = assigneeRoleCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeType() {
|
||||||
|
return assigneeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeType(String assigneeType) {
|
||||||
|
this.assigneeType = assigneeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getAssigneeTargetId() {
|
||||||
|
return assigneeTargetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetId(BigInteger assigneeTargetId) {
|
||||||
|
this.assigneeTargetId = assigneeTargetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeTargetCode() {
|
||||||
|
return assigneeTargetCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetCode(String assigneeTargetCode) {
|
||||||
|
this.assigneeTargetCode = assigneeTargetCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssigneeTargetName() {
|
||||||
|
return assigneeTargetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAssigneeTargetName(String assigneeTargetName) {
|
||||||
|
this.assigneeTargetName = assigneeTargetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getActedBy() {
|
||||||
|
return actedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActedBy(BigInteger actedBy) {
|
||||||
|
this.actedBy = actedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActedByName() {
|
||||||
|
return actedByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActedByName(String actedByName) {
|
||||||
|
this.actedByName = actedByName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getActedAt() {
|
||||||
|
return actedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActedAt(Date actedAt) {
|
||||||
|
this.actedAt = actedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComment(String comment) {
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package tech.easyflow.approval.enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批动作类型枚举。
|
||||||
|
*/
|
||||||
|
public enum ApprovalActionType {
|
||||||
|
|
||||||
|
PUBLISH("PUBLISH"),
|
||||||
|
DELETE("DELETE");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
ApprovalActionType(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取动作类型编码。
|
||||||
|
*
|
||||||
|
* @return 动作类型编码
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析动作类型。
|
||||||
|
*
|
||||||
|
* @param code 动作类型编码
|
||||||
|
* @return 动作类型
|
||||||
|
*/
|
||||||
|
public static ApprovalActionType from(String code) {
|
||||||
|
if (code == null) {
|
||||||
|
throw new IllegalArgumentException("actionType不能为空");
|
||||||
|
}
|
||||||
|
String normalized = code.trim().toUpperCase(Locale.ROOT);
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(item -> item.code.equals(normalized))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("不支持的actionType: " + code));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package tech.easyflow.approval.enums;
|
||||||
|
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批对象类型。
|
||||||
|
*/
|
||||||
|
public enum ApprovalAssigneeType {
|
||||||
|
|
||||||
|
ROLE("ROLE"),
|
||||||
|
USER("USER");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
ApprovalAssigneeType(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析审批对象类型。
|
||||||
|
*
|
||||||
|
* @param code 类型编码
|
||||||
|
* @return 审批对象类型
|
||||||
|
*/
|
||||||
|
public static ApprovalAssigneeType from(String code) {
|
||||||
|
if (!StringUtils.hasText(code)) {
|
||||||
|
throw new BusinessException("审批对象类型不能为空");
|
||||||
|
}
|
||||||
|
for (ApprovalAssigneeType value : values()) {
|
||||||
|
if (value.code.equalsIgnoreCase(code.trim())) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new BusinessException("审批对象类型非法");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package tech.easyflow.approval.enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批实例日志事件枚举。
|
||||||
|
*/
|
||||||
|
public enum ApprovalEventType {
|
||||||
|
|
||||||
|
SUBMITTED("SUBMITTED"),
|
||||||
|
STEP_CREATED("STEP_CREATED"),
|
||||||
|
APPROVED("APPROVED"),
|
||||||
|
REJECTED("REJECTED"),
|
||||||
|
REVOKED("REVOKED");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
ApprovalEventType(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取事件编码。
|
||||||
|
*
|
||||||
|
* @return 事件编码
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析事件类型。
|
||||||
|
*
|
||||||
|
* @param code 事件编码
|
||||||
|
* @return 事件类型
|
||||||
|
*/
|
||||||
|
public static ApprovalEventType from(String code) {
|
||||||
|
if (code == null) {
|
||||||
|
throw new IllegalArgumentException("eventType不能为空");
|
||||||
|
}
|
||||||
|
String normalized = code.trim().toUpperCase(Locale.ROOT);
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(item -> item.code.equals(normalized))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("不支持的eventType: " + code));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package tech.easyflow.approval.enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程状态枚举。
|
||||||
|
*/
|
||||||
|
public enum ApprovalFlowStatus {
|
||||||
|
|
||||||
|
ENABLED("ENABLED"),
|
||||||
|
DISABLED("DISABLED");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
ApprovalFlowStatus(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取状态编码。
|
||||||
|
*
|
||||||
|
* @return 状态编码
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析流程状态。
|
||||||
|
*
|
||||||
|
* @param code 状态编码
|
||||||
|
* @return 流程状态
|
||||||
|
*/
|
||||||
|
public static ApprovalFlowStatus from(String code) {
|
||||||
|
if (code == null) {
|
||||||
|
throw new IllegalArgumentException("status不能为空");
|
||||||
|
}
|
||||||
|
String normalized = code.trim().toUpperCase(Locale.ROOT);
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(item -> item.code.equals(normalized))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("不支持的status: " + code));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package tech.easyflow.approval.enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批实例状态枚举。
|
||||||
|
*/
|
||||||
|
public enum ApprovalInstanceStatus {
|
||||||
|
|
||||||
|
PENDING("PENDING"),
|
||||||
|
PROCESSING("PROCESSING"),
|
||||||
|
APPROVED("APPROVED"),
|
||||||
|
REJECTED("REJECTED"),
|
||||||
|
REVOKED("REVOKED");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
ApprovalInstanceStatus(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取状态编码。
|
||||||
|
*
|
||||||
|
* @return 状态编码
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否已结束。
|
||||||
|
*
|
||||||
|
* @return 是否结束
|
||||||
|
*/
|
||||||
|
public boolean isFinished() {
|
||||||
|
return this == APPROVED || this == REJECTED || this == REVOKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析实例状态。
|
||||||
|
*
|
||||||
|
* @param code 状态编码
|
||||||
|
* @return 实例状态
|
||||||
|
*/
|
||||||
|
public static ApprovalInstanceStatus from(String code) {
|
||||||
|
if (code == null) {
|
||||||
|
throw new IllegalArgumentException("status不能为空");
|
||||||
|
}
|
||||||
|
String normalized = code.trim().toUpperCase(Locale.ROOT);
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(item -> item.code.equals(normalized))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("不支持的status: " + code));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package tech.easyflow.approval.enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批资源类型枚举。
|
||||||
|
*/
|
||||||
|
public enum ApprovalResourceType {
|
||||||
|
|
||||||
|
BOT("BOT"),
|
||||||
|
WORKFLOW("WORKFLOW"),
|
||||||
|
KNOWLEDGE("KNOWLEDGE");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
ApprovalResourceType(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取资源类型编码。
|
||||||
|
*
|
||||||
|
* @return 资源类型编码
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析资源类型。
|
||||||
|
*
|
||||||
|
* @param code 资源类型编码
|
||||||
|
* @return 资源类型
|
||||||
|
*/
|
||||||
|
public static ApprovalResourceType from(String code) {
|
||||||
|
if (code == null) {
|
||||||
|
throw new IllegalArgumentException("resourceType不能为空");
|
||||||
|
}
|
||||||
|
String normalized = code.trim().toUpperCase(Locale.ROOT);
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(item -> item.code.equals(normalized))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("不支持的resourceType: " + code));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回全部资源类型编码。
|
||||||
|
*
|
||||||
|
* @return 资源类型编码列表
|
||||||
|
*/
|
||||||
|
public static List<String> allCodes() {
|
||||||
|
return Arrays.stream(values()).map(ApprovalResourceType::getCode).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package tech.easyflow.approval.enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程范围类型枚举。
|
||||||
|
*/
|
||||||
|
public enum ApprovalScopeType {
|
||||||
|
|
||||||
|
CATEGORY("CATEGORY"),
|
||||||
|
DEPT("DEPT");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
ApprovalScopeType(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取范围类型编码。
|
||||||
|
*
|
||||||
|
* @return 范围类型编码
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析范围类型。
|
||||||
|
*
|
||||||
|
* @param code 范围类型编码
|
||||||
|
* @return 范围类型
|
||||||
|
*/
|
||||||
|
public static ApprovalScopeType from(String code) {
|
||||||
|
if (code == null) {
|
||||||
|
throw new IllegalArgumentException("scopeType不能为空");
|
||||||
|
}
|
||||||
|
String normalized = code.trim().toUpperCase(Locale.ROOT);
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(item -> item.code.equals(normalized))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("不支持的scopeType: " + code));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package tech.easyflow.approval.enums;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批任务状态枚举。
|
||||||
|
*/
|
||||||
|
public enum ApprovalTaskStatus {
|
||||||
|
|
||||||
|
PENDING("PENDING"),
|
||||||
|
APPROVED("APPROVED"),
|
||||||
|
REJECTED("REJECTED"),
|
||||||
|
REVOKED("REVOKED");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
ApprovalTaskStatus(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取任务状态编码。
|
||||||
|
*
|
||||||
|
* @return 任务状态编码
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析任务状态。
|
||||||
|
*
|
||||||
|
* @param code 任务状态编码
|
||||||
|
* @return 任务状态
|
||||||
|
*/
|
||||||
|
public static ApprovalTaskStatus from(String code) {
|
||||||
|
if (code == null) {
|
||||||
|
throw new IllegalArgumentException("taskStatus不能为空");
|
||||||
|
}
|
||||||
|
String normalized = code.trim().toUpperCase(Locale.ROOT);
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(item -> item.code.equals(normalized))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("不支持的taskStatus: " + code));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package tech.easyflow.approval.mapper;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.BaseMapper;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程 Mapper。
|
||||||
|
*/
|
||||||
|
public interface ApprovalFlowMapper extends BaseMapper<ApprovalFlow> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package tech.easyflow.approval.mapper;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.BaseMapper;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlowScope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程范围 Mapper。
|
||||||
|
*/
|
||||||
|
public interface ApprovalFlowScopeMapper extends BaseMapper<ApprovalFlowScope> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package tech.easyflow.approval.mapper;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.BaseMapper;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlowStep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程步骤 Mapper。
|
||||||
|
*/
|
||||||
|
public interface ApprovalFlowStepMapper extends BaseMapper<ApprovalFlowStep> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package tech.easyflow.approval.mapper;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.BaseMapper;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批实例 Mapper。
|
||||||
|
*/
|
||||||
|
public interface ApprovalInstanceMapper extends BaseMapper<ApprovalInstance> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package tech.easyflow.approval.mapper;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.BaseMapper;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批日志 Mapper。
|
||||||
|
*/
|
||||||
|
public interface ApprovalLogMapper extends BaseMapper<ApprovalLog> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package tech.easyflow.approval.mapper;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.BaseMapper;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批任务 Mapper。
|
||||||
|
*/
|
||||||
|
public interface ApprovalTaskMapper extends BaseMapper<ApprovalTask> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package tech.easyflow.approval.service;
|
||||||
|
|
||||||
|
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批动作门面。
|
||||||
|
*/
|
||||||
|
public interface ApprovalActionFacade {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交审批。
|
||||||
|
*
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param resourceId 资源 ID
|
||||||
|
* @param actionType 动作类型
|
||||||
|
* @param operatorId 操作人 ID
|
||||||
|
* @return 审批实例 ID
|
||||||
|
*/
|
||||||
|
BigInteger submit(String resourceType, BigInteger resourceId, String actionType, BigInteger operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理审批通过后的业务回调。
|
||||||
|
*
|
||||||
|
* @param instance 审批实例
|
||||||
|
* @param operatorId 操作人 ID
|
||||||
|
* @param comment 审批意见
|
||||||
|
*/
|
||||||
|
void handleApproved(ApprovalInstance instance, BigInteger operatorId, String comment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理驳回后的业务回调。
|
||||||
|
*
|
||||||
|
* @param instance 审批实例
|
||||||
|
* @param operatorId 操作人 ID
|
||||||
|
* @param comment 审批意见
|
||||||
|
*/
|
||||||
|
void handleRejected(ApprovalInstance instance, BigInteger operatorId, String comment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理撤回后的业务回调。
|
||||||
|
*
|
||||||
|
* @param instance 审批实例
|
||||||
|
* @param operatorId 操作人 ID
|
||||||
|
* @param comment 审批意见
|
||||||
|
*/
|
||||||
|
void handleRevoked(ApprovalInstance instance, BigInteger operatorId, String comment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验已发布访问权限。
|
||||||
|
*
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param identifier 资源标识
|
||||||
|
* @param denyMessage 拒绝提示
|
||||||
|
*/
|
||||||
|
void assertPublishedAccess(String resourceType, Object identifier, String denyMessage);
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package tech.easyflow.approval.service;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalTask;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalAssigneeOptionVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批对象服务。
|
||||||
|
*/
|
||||||
|
public interface ApprovalAssigneeService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规范化并校验审批步骤中的审批对象配置。
|
||||||
|
*
|
||||||
|
* @param step 步骤配置
|
||||||
|
* @return 规范化后的步骤配置
|
||||||
|
*/
|
||||||
|
ApprovalFlowStepVo normalizeStepAssignee(ApprovalFlowStepVo step);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询可用角色选项。
|
||||||
|
*
|
||||||
|
* @return 角色选项
|
||||||
|
*/
|
||||||
|
List<ApprovalAssigneeOptionVo> listRoleOptions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询可用用户选项。
|
||||||
|
*
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @param pageNumber 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 用户选项分页
|
||||||
|
*/
|
||||||
|
Page<ApprovalAssigneeOptionVo> pageAccountOptions(String keyword, Long pageNumber, Long pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定账号当前仍有效的角色 ID 集合。
|
||||||
|
*
|
||||||
|
* @param accountId 账号 ID
|
||||||
|
* @return 角色 ID 集合
|
||||||
|
*/
|
||||||
|
Set<BigInteger> getAvailableRoleIds(BigInteger accountId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前账号是否命中审批任务。
|
||||||
|
*
|
||||||
|
* @param task 审批任务
|
||||||
|
* @param operatorId 当前操作人
|
||||||
|
* @param roleIds 当前操作人的有效角色集合
|
||||||
|
* @return 是否命中
|
||||||
|
*/
|
||||||
|
boolean canHandleTask(ApprovalTask task, BigInteger operatorId, Set<BigInteger> roleIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前账号命中的待审批实例 ID 集合。
|
||||||
|
*
|
||||||
|
* @param operatorId 当前操作人
|
||||||
|
* @param roleIds 当前操作人的有效角色集合
|
||||||
|
* @param instanceIds 可选的实例 ID 过滤条件
|
||||||
|
* @return 命中的实例 ID 集合
|
||||||
|
*/
|
||||||
|
Set<BigInteger> listPendingInstanceIds(BigInteger operatorId, Set<BigInteger> roleIds, List<BigInteger> instanceIds);
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package tech.easyflow.approval.service;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import com.mybatisflex.core.service.IService;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlow;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowPageVo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程服务。
|
||||||
|
*/
|
||||||
|
public interface ApprovalFlowService extends IService<ApprovalFlow> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询审批流程。
|
||||||
|
*
|
||||||
|
* @param name 流程名称
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param actionType 动作类型
|
||||||
|
* @param status 流程状态
|
||||||
|
* @param pageNumber 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 流程分页结果
|
||||||
|
*/
|
||||||
|
Page<ApprovalFlowPageVo> pageFlows(String name, String resourceType, String actionType, String status,
|
||||||
|
Long pageNumber, Long pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询审批流程详情。
|
||||||
|
*
|
||||||
|
* @param id 流程ID
|
||||||
|
* @return 流程详情
|
||||||
|
*/
|
||||||
|
ApprovalFlowDetailVo getFlowDetail(BigInteger id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增审批流程。
|
||||||
|
*
|
||||||
|
* @param request 流程详情请求
|
||||||
|
* @param operatorId 操作人ID
|
||||||
|
* @return 新增后的流程ID
|
||||||
|
*/
|
||||||
|
BigInteger saveFlow(ApprovalFlowDetailVo request, BigInteger operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新审批流程。
|
||||||
|
*
|
||||||
|
* @param request 流程详情请求
|
||||||
|
* @param operatorId 操作人ID
|
||||||
|
*/
|
||||||
|
void updateFlow(ApprovalFlowDetailVo request, BigInteger operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用审批流程。
|
||||||
|
*
|
||||||
|
* @param id 流程ID
|
||||||
|
* @param operatorId 操作人ID
|
||||||
|
*/
|
||||||
|
void enableFlow(BigInteger id, BigInteger operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停用审批流程。
|
||||||
|
*
|
||||||
|
* @param id 流程ID
|
||||||
|
* @param operatorId 操作人ID
|
||||||
|
*/
|
||||||
|
void disableFlow(BigInteger id, BigInteger operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除审批流程。
|
||||||
|
*
|
||||||
|
* @param id 流程ID
|
||||||
|
*/
|
||||||
|
void removeFlow(BigInteger id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package tech.easyflow.approval.service;
|
||||||
|
|
||||||
|
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批实例服务。
|
||||||
|
*/
|
||||||
|
public interface ApprovalInstanceService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起审批。
|
||||||
|
*
|
||||||
|
* @param request 发起审批请求
|
||||||
|
* @return 审批实例ID
|
||||||
|
*/
|
||||||
|
BigInteger submitApproval(ApprovalSubmitRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过审批。
|
||||||
|
*
|
||||||
|
* @param instanceId 审批实例ID
|
||||||
|
* @param comment 审批意见
|
||||||
|
* @param operatorId 操作人ID
|
||||||
|
*/
|
||||||
|
void approve(BigInteger instanceId, String comment, BigInteger operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驳回审批。
|
||||||
|
*
|
||||||
|
* @param instanceId 审批实例ID
|
||||||
|
* @param comment 审批意见
|
||||||
|
* @param operatorId 操作人ID
|
||||||
|
*/
|
||||||
|
void reject(BigInteger instanceId, String comment, BigInteger operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回审批。
|
||||||
|
*
|
||||||
|
* @param instanceId 审批实例ID
|
||||||
|
* @param comment 撤回说明
|
||||||
|
* @param operatorId 操作人ID
|
||||||
|
*/
|
||||||
|
void revoke(BigInteger instanceId, String comment, BigInteger operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断资源是否存在未结束审批实例。
|
||||||
|
*
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param resourceId 资源 ID
|
||||||
|
* @return 存在未结束实例时返回 true
|
||||||
|
*/
|
||||||
|
boolean existsActiveInstance(String resourceType, BigInteger resourceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据实例 ID 查询实例。
|
||||||
|
*
|
||||||
|
* @param instanceId 审批实例 ID
|
||||||
|
* @return 审批实例
|
||||||
|
*/
|
||||||
|
ApprovalInstance getById(BigInteger instanceId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package tech.easyflow.approval.service;
|
||||||
|
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程匹配服务。
|
||||||
|
*/
|
||||||
|
public interface ApprovalMatchService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据资源上下文匹配审批流程。
|
||||||
|
*
|
||||||
|
* @param request 审批提交请求
|
||||||
|
* @return 命中的流程详情
|
||||||
|
*/
|
||||||
|
ApprovalFlowDetailVo matchFlow(ApprovalSubmitRequest request);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package tech.easyflow.approval.service;
|
||||||
|
|
||||||
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalInstanceDetailVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalInstancePageVo;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批查询服务。
|
||||||
|
*/
|
||||||
|
public interface ApprovalQueryService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询待审批列表。
|
||||||
|
*
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param actionType 动作类型
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @param pageNumber 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 待审批分页
|
||||||
|
*/
|
||||||
|
Page<ApprovalInstancePageVo> pendingPage(String resourceType, String actionType, String keyword,
|
||||||
|
Long pageNumber, Long pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询已审批列表。
|
||||||
|
*
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param actionType 动作类型
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @param pageNumber 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 已审批分页
|
||||||
|
*/
|
||||||
|
Page<ApprovalInstancePageVo> processedPage(String resourceType, String actionType, String keyword,
|
||||||
|
Long pageNumber, Long pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询我发起的审批列表。
|
||||||
|
*
|
||||||
|
* @param resourceType 资源类型
|
||||||
|
* @param actionType 动作类型
|
||||||
|
* @param keyword 关键词
|
||||||
|
* @param pageNumber 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 我发起的审批分页
|
||||||
|
*/
|
||||||
|
Page<ApprovalInstancePageVo> initiatedPage(String resourceType, String actionType, String keyword,
|
||||||
|
Long pageNumber, Long pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询审批实例详情。
|
||||||
|
*
|
||||||
|
* @param instanceId 审批实例ID
|
||||||
|
* @return 审批实例详情
|
||||||
|
*/
|
||||||
|
ApprovalInstanceDetailVo detail(BigInteger instanceId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package tech.easyflow.approval.service;
|
||||||
|
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalCallbackContext;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalSubmitCallbackContext;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批资源处理器。
|
||||||
|
*/
|
||||||
|
public interface ApprovalSubjectHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前处理器支持的资源类型。
|
||||||
|
*
|
||||||
|
* @return 资源类型编码
|
||||||
|
*/
|
||||||
|
String resourceType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建审批提交请求。
|
||||||
|
*
|
||||||
|
* @param resourceId 资源 ID
|
||||||
|
* @param actionType 动作类型
|
||||||
|
* @param operatorId 操作人 ID
|
||||||
|
* @return 审批请求
|
||||||
|
*/
|
||||||
|
ApprovalSubmitRequest buildSubmitRequest(BigInteger resourceId, String actionType, BigInteger operatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批提交完成后的回调。
|
||||||
|
*
|
||||||
|
* @param context 回调上下文
|
||||||
|
*/
|
||||||
|
void onSubmitted(ApprovalSubmitCallbackContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批通过后的回调。
|
||||||
|
*
|
||||||
|
* @param context 回调上下文
|
||||||
|
*/
|
||||||
|
void onApproved(ApprovalCallbackContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批驳回后的回调。
|
||||||
|
*
|
||||||
|
* @param context 回调上下文
|
||||||
|
*/
|
||||||
|
void onRejected(ApprovalCallbackContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批撤回后的回调。
|
||||||
|
*
|
||||||
|
* @param context 回调上下文
|
||||||
|
*/
|
||||||
|
void onRevoked(ApprovalCallbackContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验资源是否已发布。
|
||||||
|
*
|
||||||
|
* @param identifier 资源标识(ID 或别名)
|
||||||
|
* @param denyMessage 拒绝提示
|
||||||
|
*/
|
||||||
|
void assertPublishedAccess(Object identifier, String denyMessage);
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package tech.easyflow.approval.service.impl;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
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.ApprovalResourceType;
|
||||||
|
import tech.easyflow.approval.service.ApprovalActionFacade;
|
||||||
|
import tech.easyflow.approval.service.ApprovalInstanceService;
|
||||||
|
import tech.easyflow.approval.service.ApprovalSubjectHandler;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批动作门面实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ApprovalActionFacadeImpl implements ApprovalActionFacade {
|
||||||
|
|
||||||
|
private final List<ApprovalSubjectHandler> handlers;
|
||||||
|
private final ApprovalInstanceService approvalInstanceService;
|
||||||
|
|
||||||
|
public ApprovalActionFacadeImpl(List<ApprovalSubjectHandler> handlers,
|
||||||
|
ApprovalInstanceService approvalInstanceService) {
|
||||||
|
this.handlers = handlers;
|
||||||
|
this.approvalInstanceService = approvalInstanceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public BigInteger submit(String resourceType, BigInteger resourceId, String actionType, BigInteger operatorId) {
|
||||||
|
ApprovalSubjectHandler handler = getHandler(resourceType);
|
||||||
|
ApprovalSubmitRequest request = handler.buildSubmitRequest(resourceId, actionType, operatorId);
|
||||||
|
BigInteger instanceId = approvalInstanceService.submitApproval(request);
|
||||||
|
|
||||||
|
ApprovalSubmitCallbackContext context = new ApprovalSubmitCallbackContext();
|
||||||
|
context.setInstanceId(instanceId);
|
||||||
|
context.setResourceType(request.getResourceType());
|
||||||
|
context.setResourceId(request.getResourceId());
|
||||||
|
context.setActionType(request.getActionType());
|
||||||
|
context.setOperatorId(operatorId);
|
||||||
|
handler.onSubmitted(context);
|
||||||
|
return instanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void handleApproved(ApprovalInstance instance, BigInteger operatorId, String comment) {
|
||||||
|
ApprovalSubjectHandler handler = getHandler(instance.getResourceType());
|
||||||
|
handler.onApproved(buildCallbackContext(instance, operatorId, comment));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void handleRejected(ApprovalInstance instance, BigInteger operatorId, String comment) {
|
||||||
|
ApprovalSubjectHandler handler = getHandler(instance.getResourceType());
|
||||||
|
handler.onRejected(buildCallbackContext(instance, operatorId, comment));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void handleRevoked(ApprovalInstance instance, BigInteger operatorId, String comment) {
|
||||||
|
ApprovalSubjectHandler handler = getHandler(instance.getResourceType());
|
||||||
|
handler.onRevoked(buildCallbackContext(instance, operatorId, comment));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void assertPublishedAccess(String resourceType, Object identifier, String denyMessage) {
|
||||||
|
ApprovalSubjectHandler handler = getHandler(resourceType);
|
||||||
|
handler.assertPublishedAccess(identifier, denyMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalCallbackContext buildCallbackContext(ApprovalInstance instance, BigInteger operatorId, String comment) {
|
||||||
|
ApprovalCallbackContext context = new ApprovalCallbackContext();
|
||||||
|
context.setInstance(instance);
|
||||||
|
context.setOperatorId(operatorId);
|
||||||
|
context.setComment(comment);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalSubjectHandler getHandler(String resourceType) {
|
||||||
|
String normalized = ApprovalResourceType.from(resourceType).getCode();
|
||||||
|
return handlers.stream()
|
||||||
|
.filter(item -> normalized.equals(item.resourceType()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalStateException("未找到审批处理器: " + resourceType));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
package tech.easyflow.approval.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalTask;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalAssigneeOptionVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalAssigneeType;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalTaskStatus;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
|
||||||
|
import tech.easyflow.approval.service.ApprovalAssigneeService;
|
||||||
|
import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||||
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
|
import tech.easyflow.system.entity.SysAccount;
|
||||||
|
import tech.easyflow.system.entity.SysRole;
|
||||||
|
import tech.easyflow.system.service.SysAccountService;
|
||||||
|
import tech.easyflow.system.service.SysRoleService;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批对象服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ApprovalAssigneeServiceImpl implements ApprovalAssigneeService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SysRoleService sysRoleService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SysAccountService sysAccountService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalTaskMapper approvalTaskMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ApprovalFlowStepVo normalizeStepAssignee(ApprovalFlowStepVo step) {
|
||||||
|
if (step == null) {
|
||||||
|
throw new BusinessException("审批步骤不能为空");
|
||||||
|
}
|
||||||
|
ApprovalAssigneeType assigneeType = ApprovalAssigneeType.from(step.getAssigneeType());
|
||||||
|
BigInteger targetId = step.getAssigneeTargetId();
|
||||||
|
if (targetId == null) {
|
||||||
|
throw new BusinessException("审批对象不能为空");
|
||||||
|
}
|
||||||
|
ApprovalAssigneeOptionVo option = resolveAssigneeOption(assigneeType, targetId);
|
||||||
|
step.setAssigneeType(assigneeType.getCode());
|
||||||
|
step.setAssigneeTargetId(option.getId());
|
||||||
|
step.setAssigneeTargetCode(option.getCode());
|
||||||
|
step.setAssigneeTargetName(option.getName());
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<ApprovalAssigneeOptionVo> listRoleOptions() {
|
||||||
|
return sysRoleService.list(QueryWrapper.create()
|
||||||
|
.eq(SysRole::getStatus, EnumDataStatus.AVAILABLE.getCode())
|
||||||
|
.orderBy("role_name asc, id asc"))
|
||||||
|
.stream()
|
||||||
|
.map(this::toRoleOption)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Page<ApprovalAssigneeOptionVo> pageAccountOptions(String keyword, Long pageNumber, Long pageSize) {
|
||||||
|
long actualPageNumber = pageNumber == null || pageNumber < 1 ? 1L : pageNumber;
|
||||||
|
long actualPageSize = pageSize == null || pageSize < 1 ? 20L : pageSize;
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.eq(SysAccount::getStatus, EnumDataStatus.AVAILABLE.getCode())
|
||||||
|
.orderBy("nickname asc, login_name asc, id asc");
|
||||||
|
if (StringUtils.hasText(keyword)) {
|
||||||
|
String likeKeyword = "%" + keyword.trim() + "%";
|
||||||
|
queryWrapper.and("(`login_name` like ? or `nickname` like ?)", likeKeyword, likeKeyword);
|
||||||
|
}
|
||||||
|
Page<SysAccount> page = sysAccountService.page(new Page<>(actualPageNumber, actualPageSize), queryWrapper);
|
||||||
|
Page<ApprovalAssigneeOptionVo> result = new Page<>(actualPageNumber, actualPageSize, page.getTotalRow());
|
||||||
|
result.setRecords(page.getRecords().stream().map(this::toAccountOption).collect(Collectors.toList()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Set<BigInteger> getAvailableRoleIds(BigInteger accountId) {
|
||||||
|
if (accountId == null) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
return sysRoleService.getRolesByAccountId(accountId).stream()
|
||||||
|
.filter(role -> role != null && EnumDataStatus.AVAILABLE.getCode().equals(role.getStatus()))
|
||||||
|
.map(SysRole::getId)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean canHandleTask(ApprovalTask task, BigInteger operatorId, Set<BigInteger> roleIds) {
|
||||||
|
if (task == null || operatorId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ApprovalAssigneeType assigneeType = ApprovalAssigneeType.from(task.getAssigneeType());
|
||||||
|
if (ApprovalAssigneeType.USER == assigneeType) {
|
||||||
|
return operatorId.equals(task.getAssigneeTargetId());
|
||||||
|
}
|
||||||
|
return roleIds != null && roleIds.contains(task.getAssigneeTargetId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Set<BigInteger> listPendingInstanceIds(BigInteger operatorId, Set<BigInteger> roleIds, List<BigInteger> instanceIds) {
|
||||||
|
if (operatorId == null) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
Set<BigInteger> result = new LinkedHashSet<>();
|
||||||
|
QueryWrapper userQuery = QueryWrapper.create()
|
||||||
|
.eq(ApprovalTask::getStatus, ApprovalTaskStatus.PENDING.getCode());
|
||||||
|
if (CollectionUtil.isNotEmpty(instanceIds)) {
|
||||||
|
userQuery.in(ApprovalTask::getInstanceId, instanceIds);
|
||||||
|
}
|
||||||
|
userQuery.eq(ApprovalTask::getAssigneeType, ApprovalAssigneeType.USER.getCode())
|
||||||
|
.eq(ApprovalTask::getAssigneeTargetId, operatorId);
|
||||||
|
result.addAll(approvalTaskMapper.selectListByQuery(userQuery).stream()
|
||||||
|
.map(ApprovalTask::getInstanceId)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new)));
|
||||||
|
if (CollectionUtil.isNotEmpty(roleIds)) {
|
||||||
|
QueryWrapper roleQuery = QueryWrapper.create()
|
||||||
|
.eq(ApprovalTask::getStatus, ApprovalTaskStatus.PENDING.getCode())
|
||||||
|
.eq(ApprovalTask::getAssigneeType, ApprovalAssigneeType.ROLE.getCode())
|
||||||
|
.in(ApprovalTask::getAssigneeTargetId, roleIds);
|
||||||
|
if (CollectionUtil.isNotEmpty(instanceIds)) {
|
||||||
|
roleQuery.in(ApprovalTask::getInstanceId, instanceIds);
|
||||||
|
}
|
||||||
|
result.addAll(approvalTaskMapper.selectListByQuery(roleQuery).stream()
|
||||||
|
.map(ApprovalTask::getInstanceId)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalAssigneeOptionVo resolveAssigneeOption(ApprovalAssigneeType assigneeType, BigInteger targetId) {
|
||||||
|
if (ApprovalAssigneeType.ROLE == assigneeType) {
|
||||||
|
SysRole role = sysRoleService.getById(targetId);
|
||||||
|
if (role == null || !EnumDataStatus.AVAILABLE.getCode().equals(role.getStatus())) {
|
||||||
|
throw new BusinessException("审批角色不存在或未启用");
|
||||||
|
}
|
||||||
|
return toRoleOption(role);
|
||||||
|
}
|
||||||
|
SysAccount account = sysAccountService.getById(targetId);
|
||||||
|
if (account == null || !EnumDataStatus.AVAILABLE.getCode().equals(account.getStatus())) {
|
||||||
|
throw new BusinessException("审批用户不存在或未启用");
|
||||||
|
}
|
||||||
|
return toAccountOption(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalAssigneeOptionVo toRoleOption(SysRole role) {
|
||||||
|
ApprovalAssigneeOptionVo option = new ApprovalAssigneeOptionVo();
|
||||||
|
option.setId(role.getId());
|
||||||
|
option.setCode(role.getRoleKey());
|
||||||
|
option.setName(role.getRoleName());
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalAssigneeOptionVo toAccountOption(SysAccount account) {
|
||||||
|
ApprovalAssigneeOptionVo option = new ApprovalAssigneeOptionVo();
|
||||||
|
option.setId(account.getId());
|
||||||
|
option.setCode(account.getLoginName());
|
||||||
|
option.setName(resolveAccountDisplayName(account));
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveAccountDisplayName(SysAccount account) {
|
||||||
|
if (StringUtils.hasText(account.getNickname())) {
|
||||||
|
return account.getNickname().trim();
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(account.getLoginName())) {
|
||||||
|
return account.getLoginName().trim();
|
||||||
|
}
|
||||||
|
return String.valueOf(account.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,464 @@
|
|||||||
|
package tech.easyflow.approval.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlow;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlowScope;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlowStep;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowPageVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowScopeVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalActionType;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalFlowStatus;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalResourceType;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalScopeType;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalFlowMapper;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalFlowScopeMapper;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalFlowStepMapper;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
|
||||||
|
import tech.easyflow.approval.service.ApprovalAssigneeService;
|
||||||
|
import tech.easyflow.approval.service.ApprovalFlowService;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ApprovalFlowServiceImpl extends ServiceImpl<ApprovalFlowMapper, ApprovalFlow> implements ApprovalFlowService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalFlowMapper approvalFlowMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalFlowScopeMapper approvalFlowScopeMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalFlowStepMapper approvalFlowStepMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalInstanceMapper approvalInstanceMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalAssigneeService approvalAssigneeService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Page<ApprovalFlowPageVo> pageFlows(String name, String resourceType, String actionType, String status,
|
||||||
|
Long pageNumber, Long pageSize) {
|
||||||
|
long actualPageNumber = pageNumber == null || pageNumber < 1 ? 1L : pageNumber;
|
||||||
|
long actualPageSize = pageSize == null || pageSize < 1 ? 10L : pageSize;
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||||
|
if (StringUtils.hasText(name)) {
|
||||||
|
queryWrapper.like(ApprovalFlow::getName, name.trim());
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(resourceType)) {
|
||||||
|
queryWrapper.eq(ApprovalFlow::getResourceType, ApprovalResourceType.from(resourceType).getCode());
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(actionType)) {
|
||||||
|
queryWrapper.eq(ApprovalFlow::getActionType, ApprovalActionType.from(actionType).getCode());
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(status)) {
|
||||||
|
queryWrapper.eq(ApprovalFlow::getStatus, ApprovalFlowStatus.from(status).getCode());
|
||||||
|
}
|
||||||
|
queryWrapper.orderBy("priority desc, modified desc, created asc");
|
||||||
|
|
||||||
|
Page<ApprovalFlow> entityPage = page(new Page<>(actualPageNumber, actualPageSize), queryWrapper);
|
||||||
|
List<ApprovalFlow> records = entityPage.getRecords();
|
||||||
|
List<BigInteger> flowIds = records.stream().map(ApprovalFlow::getId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
Map<BigInteger, List<ApprovalFlowScope>> scopeMap = loadScopeMap(flowIds);
|
||||||
|
Map<BigInteger, List<ApprovalFlowStep>> stepMap = loadStepMap(flowIds);
|
||||||
|
Map<BigInteger, Long> pendingCountMap = loadPendingInstanceCountMap(flowIds);
|
||||||
|
|
||||||
|
List<ApprovalFlowPageVo> result = new ArrayList<>();
|
||||||
|
for (ApprovalFlow record : records) {
|
||||||
|
ApprovalFlowPageVo item = new ApprovalFlowPageVo();
|
||||||
|
item.setId(record.getId());
|
||||||
|
item.setName(record.getName());
|
||||||
|
item.setResourceType(record.getResourceType());
|
||||||
|
item.setActionType(record.getActionType());
|
||||||
|
item.setPriority(record.getPriority());
|
||||||
|
item.setStatus(record.getStatus());
|
||||||
|
item.setVersion(record.getVersion());
|
||||||
|
item.setModified(record.getModified());
|
||||||
|
item.setPendingInstanceCount(pendingCountMap.getOrDefault(record.getId(), 0L));
|
||||||
|
item.setStepCount(stepMap.getOrDefault(record.getId(), List.of()).size());
|
||||||
|
item.setScopeSummary(buildScopeSummary(scopeMap.get(record.getId())));
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page<ApprovalFlowPageVo> voPage = new Page<>(actualPageNumber, actualPageSize, entityPage.getTotalRow());
|
||||||
|
voPage.setRecords(result);
|
||||||
|
return voPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ApprovalFlowDetailVo getFlowDetail(BigInteger id) {
|
||||||
|
ApprovalFlow flow = requireFlow(id);
|
||||||
|
ApprovalFlowDetailVo detail = toDetailVo(flow);
|
||||||
|
detail.setPendingInstanceCount(countPendingInstances(id));
|
||||||
|
detail.setDeletable(detail.getPendingInstanceCount() == 0);
|
||||||
|
detail.setScopes(loadScopeVos(id));
|
||||||
|
detail.setSteps(loadStepVos(id));
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public BigInteger saveFlow(ApprovalFlowDetailVo request, BigInteger operatorId) {
|
||||||
|
ApprovalFlowDetailVo normalized = normalizeRequest(request, true);
|
||||||
|
Date now = new Date();
|
||||||
|
ApprovalFlow flow = new ApprovalFlow();
|
||||||
|
flow.setName(normalized.getName());
|
||||||
|
flow.setResourceType(normalized.getResourceType());
|
||||||
|
flow.setActionType(normalized.getActionType());
|
||||||
|
flow.setPriority(normalized.getPriority());
|
||||||
|
flow.setStatus(normalized.getStatus());
|
||||||
|
flow.setVersion(1);
|
||||||
|
flow.setRemark(normalized.getRemark());
|
||||||
|
flow.setCreated(now);
|
||||||
|
flow.setCreatedBy(operatorId);
|
||||||
|
flow.setModified(now);
|
||||||
|
flow.setModifiedBy(operatorId);
|
||||||
|
approvalFlowMapper.insert(flow);
|
||||||
|
replaceChildren(flow.getId(), normalized, operatorId, now);
|
||||||
|
return flow.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateFlow(ApprovalFlowDetailVo request, BigInteger operatorId) {
|
||||||
|
ApprovalFlow existing = requireFlow(request == null ? null : request.getId());
|
||||||
|
ApprovalFlowDetailVo normalized = normalizeRequest(request, false);
|
||||||
|
Date now = new Date();
|
||||||
|
existing.setName(normalized.getName());
|
||||||
|
existing.setResourceType(normalized.getResourceType());
|
||||||
|
existing.setActionType(normalized.getActionType());
|
||||||
|
existing.setPriority(normalized.getPriority());
|
||||||
|
existing.setStatus(normalized.getStatus());
|
||||||
|
existing.setVersion((existing.getVersion() == null ? 1 : existing.getVersion()) + 1);
|
||||||
|
existing.setRemark(normalized.getRemark());
|
||||||
|
existing.setModified(now);
|
||||||
|
existing.setModifiedBy(operatorId);
|
||||||
|
approvalFlowMapper.update(existing);
|
||||||
|
clearChildren(existing.getId());
|
||||||
|
replaceChildren(existing.getId(), normalized, operatorId, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void enableFlow(BigInteger id, BigInteger operatorId) {
|
||||||
|
updateStatus(id, ApprovalFlowStatus.ENABLED.getCode(), operatorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void disableFlow(BigInteger id, BigInteger operatorId) {
|
||||||
|
updateStatus(id, ApprovalFlowStatus.DISABLED.getCode(), operatorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void removeFlow(BigInteger id) {
|
||||||
|
requireFlow(id);
|
||||||
|
if (countPendingInstances(id) > 0) {
|
||||||
|
throw new BusinessException("存在未完成审批实例,当前流程不允许删除");
|
||||||
|
}
|
||||||
|
clearChildren(id);
|
||||||
|
approvalFlowMapper.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatus(BigInteger id, String status, BigInteger operatorId) {
|
||||||
|
ApprovalFlow existing = requireFlow(id);
|
||||||
|
if (ApprovalFlowStatus.ENABLED.getCode().equals(status)) {
|
||||||
|
validateStoredStepAssignees(id);
|
||||||
|
}
|
||||||
|
existing.setStatus(status);
|
||||||
|
existing.setModified(new Date());
|
||||||
|
existing.setModifiedBy(operatorId);
|
||||||
|
approvalFlowMapper.update(existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalFlow requireFlow(BigInteger id) {
|
||||||
|
if (id == null) {
|
||||||
|
throw new BusinessException("流程ID不能为空");
|
||||||
|
}
|
||||||
|
ApprovalFlow flow = approvalFlowMapper.selectOneById(id);
|
||||||
|
if (flow == null) {
|
||||||
|
throw new BusinessException("审批流程不存在");
|
||||||
|
}
|
||||||
|
return flow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalFlowDetailVo normalizeRequest(ApprovalFlowDetailVo request, boolean create) {
|
||||||
|
if (request == null) {
|
||||||
|
throw new BusinessException("审批流程不能为空");
|
||||||
|
}
|
||||||
|
if (!create && request.getId() == null) {
|
||||||
|
throw new BusinessException("流程ID不能为空");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(request.getName())) {
|
||||||
|
throw new BusinessException("流程名称不能为空");
|
||||||
|
}
|
||||||
|
if (request.getPriority() == null) {
|
||||||
|
throw new BusinessException("优先级不能为空");
|
||||||
|
}
|
||||||
|
ApprovalFlowDetailVo normalized = new ApprovalFlowDetailVo();
|
||||||
|
normalized.setId(request.getId());
|
||||||
|
normalized.setName(request.getName().trim());
|
||||||
|
normalized.setResourceType(ApprovalResourceType.from(request.getResourceType()).getCode());
|
||||||
|
normalized.setActionType(ApprovalActionType.from(request.getActionType()).getCode());
|
||||||
|
normalized.setPriority(request.getPriority());
|
||||||
|
normalized.setStatus(StringUtils.hasText(request.getStatus())
|
||||||
|
? ApprovalFlowStatus.from(request.getStatus()).getCode()
|
||||||
|
: ApprovalFlowStatus.ENABLED.getCode());
|
||||||
|
normalized.setRemark(request.getRemark());
|
||||||
|
normalized.setScopes(normalizeScopes(request.getScopes()));
|
||||||
|
normalized.setSteps(normalizeSteps(request.getSteps()));
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ApprovalFlowScopeVo> normalizeScopes(List<ApprovalFlowScopeVo> scopes) {
|
||||||
|
if (CollectionUtil.isEmpty(scopes)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
Set<String> keys = new LinkedHashSet<>();
|
||||||
|
List<ApprovalFlowScopeVo> result = new ArrayList<>();
|
||||||
|
for (ApprovalFlowScopeVo item : scopes) {
|
||||||
|
if (item == null || item.getScopeValue() == null || !StringUtils.hasText(item.getScopeType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ApprovalFlowScopeVo normalized = new ApprovalFlowScopeVo();
|
||||||
|
normalized.setScopeType(ApprovalScopeType.from(item.getScopeType()).getCode());
|
||||||
|
normalized.setScopeValue(item.getScopeValue());
|
||||||
|
normalized.setIncludeChildren(item.getIncludeChildren() != null && item.getIncludeChildren() == 1 ? 1 : 0);
|
||||||
|
String uniqueKey = normalized.getScopeType() + ":" + normalized.getScopeValue();
|
||||||
|
if (keys.add(uniqueKey)) {
|
||||||
|
result.add(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ApprovalFlowStepVo> normalizeSteps(List<ApprovalFlowStepVo> steps) {
|
||||||
|
if (CollectionUtil.isEmpty(steps)) {
|
||||||
|
throw new BusinessException("审批步骤不能为空");
|
||||||
|
}
|
||||||
|
List<ApprovalFlowStepVo> result = new ArrayList<>();
|
||||||
|
int index = 1;
|
||||||
|
for (ApprovalFlowStepVo item : steps) {
|
||||||
|
if (item == null || !StringUtils.hasText(item.getStepName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ApprovalFlowStepVo normalized = new ApprovalFlowStepVo();
|
||||||
|
normalized.setStepNo(index++);
|
||||||
|
normalized.setStepName(item.getStepName().trim());
|
||||||
|
normalized.setAssigneeType(item.getAssigneeType());
|
||||||
|
normalized.setAssigneeTargetId(item.getAssigneeTargetId());
|
||||||
|
approvalAssigneeService.normalizeStepAssignee(normalized);
|
||||||
|
result.add(normalized);
|
||||||
|
}
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
throw new BusinessException("审批步骤不能为空");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearChildren(BigInteger flowId) {
|
||||||
|
approvalFlowScopeMapper.deleteByQuery(QueryWrapper.create().eq(ApprovalFlowScope::getFlowId, flowId));
|
||||||
|
approvalFlowStepMapper.deleteByQuery(QueryWrapper.create().eq(ApprovalFlowStep::getFlowId, flowId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void replaceChildren(BigInteger flowId, ApprovalFlowDetailVo request, BigInteger operatorId, Date now) {
|
||||||
|
for (ApprovalFlowScopeVo scopeVo : request.getScopes()) {
|
||||||
|
ApprovalFlowScope scope = new ApprovalFlowScope();
|
||||||
|
scope.setFlowId(flowId);
|
||||||
|
scope.setScopeType(scopeVo.getScopeType());
|
||||||
|
scope.setScopeValue(scopeVo.getScopeValue());
|
||||||
|
scope.setIncludeChildren(scopeVo.getIncludeChildren());
|
||||||
|
scope.setCreated(now);
|
||||||
|
scope.setCreatedBy(operatorId);
|
||||||
|
scope.setModified(now);
|
||||||
|
scope.setModifiedBy(operatorId);
|
||||||
|
approvalFlowScopeMapper.insert(scope);
|
||||||
|
}
|
||||||
|
for (ApprovalFlowStepVo stepVo : request.getSteps()) {
|
||||||
|
ApprovalFlowStep step = new ApprovalFlowStep();
|
||||||
|
step.setFlowId(flowId);
|
||||||
|
step.setStepNo(stepVo.getStepNo());
|
||||||
|
step.setStepName(stepVo.getStepName());
|
||||||
|
step.setAssigneeType(stepVo.getAssigneeType());
|
||||||
|
step.setAssigneeTargetId(stepVo.getAssigneeTargetId());
|
||||||
|
step.setAssigneeTargetCode(stepVo.getAssigneeTargetCode());
|
||||||
|
step.setAssigneeTargetName(stepVo.getAssigneeTargetName());
|
||||||
|
step.setCreated(now);
|
||||||
|
step.setCreatedBy(operatorId);
|
||||||
|
step.setModified(now);
|
||||||
|
step.setModifiedBy(operatorId);
|
||||||
|
approvalFlowStepMapper.insert(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalFlowDetailVo toDetailVo(ApprovalFlow flow) {
|
||||||
|
ApprovalFlowDetailVo detail = new ApprovalFlowDetailVo();
|
||||||
|
detail.setId(flow.getId());
|
||||||
|
detail.setName(flow.getName());
|
||||||
|
detail.setResourceType(flow.getResourceType());
|
||||||
|
detail.setActionType(flow.getActionType());
|
||||||
|
detail.setPriority(flow.getPriority());
|
||||||
|
detail.setStatus(flow.getStatus());
|
||||||
|
detail.setVersion(flow.getVersion());
|
||||||
|
detail.setRemark(flow.getRemark());
|
||||||
|
detail.setCreated(flow.getCreated());
|
||||||
|
detail.setModified(flow.getModified());
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ApprovalFlowScopeVo> loadScopeVos(BigInteger flowId) {
|
||||||
|
return approvalFlowScopeMapper.selectListByQuery(QueryWrapper.create().eq(ApprovalFlowScope::getFlowId, flowId))
|
||||||
|
.stream()
|
||||||
|
.sorted(Comparator.comparing(ApprovalFlowScope::getScopeType).thenComparing(ApprovalFlowScope::getScopeValue))
|
||||||
|
.map(item -> {
|
||||||
|
ApprovalFlowScopeVo scopeVo = new ApprovalFlowScopeVo();
|
||||||
|
scopeVo.setId(item.getId());
|
||||||
|
scopeVo.setScopeType(item.getScopeType());
|
||||||
|
scopeVo.setScopeValue(item.getScopeValue());
|
||||||
|
scopeVo.setIncludeChildren(item.getIncludeChildren());
|
||||||
|
return scopeVo;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ApprovalFlowStepVo> loadStepVos(BigInteger flowId) {
|
||||||
|
return approvalFlowStepMapper.selectListByQuery(QueryWrapper.create().eq(ApprovalFlowStep::getFlowId, flowId))
|
||||||
|
.stream()
|
||||||
|
.sorted(Comparator.comparing(ApprovalFlowStep::getStepNo))
|
||||||
|
.map(item -> {
|
||||||
|
ApprovalFlowStepVo stepVo = new ApprovalFlowStepVo();
|
||||||
|
stepVo.setId(item.getId());
|
||||||
|
stepVo.setStepNo(item.getStepNo());
|
||||||
|
stepVo.setStepName(item.getStepName());
|
||||||
|
stepVo.setAssigneeType(item.getAssigneeType());
|
||||||
|
stepVo.setAssigneeTargetId(item.getAssigneeTargetId());
|
||||||
|
stepVo.setAssigneeTargetCode(item.getAssigneeTargetCode());
|
||||||
|
stepVo.setAssigneeTargetName(item.getAssigneeTargetName());
|
||||||
|
return stepVo;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验数据库中已保存步骤的审批对象仍然有效。
|
||||||
|
*
|
||||||
|
* @param flowId 流程ID
|
||||||
|
*/
|
||||||
|
private void validateStoredStepAssignees(BigInteger flowId) {
|
||||||
|
List<ApprovalFlowStepVo> steps = loadStepVos(flowId);
|
||||||
|
if (CollectionUtil.isEmpty(steps)) {
|
||||||
|
throw new BusinessException("审批步骤不能为空");
|
||||||
|
}
|
||||||
|
for (ApprovalFlowStepVo step : steps) {
|
||||||
|
approvalAssigneeService.normalizeStepAssignee(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long countPendingInstances(BigInteger flowId) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.eq(ApprovalInstance::getFlowId, flowId)
|
||||||
|
.in(ApprovalInstance::getStatus, List.of(
|
||||||
|
ApprovalInstanceStatus.PENDING.getCode(),
|
||||||
|
ApprovalInstanceStatus.PROCESSING.getCode()));
|
||||||
|
return approvalInstanceMapper.selectCountByQuery(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<BigInteger, Long> loadPendingInstanceCountMap(List<BigInteger> flowIds) {
|
||||||
|
if (CollectionUtil.isEmpty(flowIds)) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.in(ApprovalInstance::getFlowId, flowIds)
|
||||||
|
.in(ApprovalInstance::getStatus, List.of(
|
||||||
|
ApprovalInstanceStatus.PENDING.getCode(),
|
||||||
|
ApprovalInstanceStatus.PROCESSING.getCode()));
|
||||||
|
List<ApprovalInstance> instances = approvalInstanceMapper.selectListByQuery(queryWrapper);
|
||||||
|
return instances.stream().collect(Collectors.groupingBy(ApprovalInstance::getFlowId, Collectors.counting()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<BigInteger, List<ApprovalFlowScope>> loadScopeMap(List<BigInteger> flowIds) {
|
||||||
|
if (CollectionUtil.isEmpty(flowIds)) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
List<ApprovalFlowScope> scopes = approvalFlowScopeMapper.selectListByQuery(
|
||||||
|
QueryWrapper.create().in(ApprovalFlowScope::getFlowId, flowIds));
|
||||||
|
return scopes.stream().collect(Collectors.groupingBy(
|
||||||
|
ApprovalFlowScope::getFlowId, LinkedHashMap::new, Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<BigInteger, List<ApprovalFlowStep>> loadStepMap(List<BigInteger> flowIds) {
|
||||||
|
if (CollectionUtil.isEmpty(flowIds)) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
List<ApprovalFlowStep> steps = approvalFlowStepMapper.selectListByQuery(
|
||||||
|
QueryWrapper.create().in(ApprovalFlowStep::getFlowId, flowIds));
|
||||||
|
return steps.stream().collect(Collectors.groupingBy(
|
||||||
|
ApprovalFlowStep::getFlowId, LinkedHashMap::new, Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildScopeSummary(List<ApprovalFlowScope> scopes) {
|
||||||
|
if (CollectionUtil.isEmpty(scopes)) {
|
||||||
|
return "全部";
|
||||||
|
}
|
||||||
|
long categoryCount = scopes.stream()
|
||||||
|
.filter(item -> ApprovalScopeType.CATEGORY.getCode().equals(item.getScopeType()))
|
||||||
|
.count();
|
||||||
|
long deptCount = scopes.stream()
|
||||||
|
.filter(item -> ApprovalScopeType.DEPT.getCode().equals(item.getScopeType()))
|
||||||
|
.count();
|
||||||
|
List<String> parts = new ArrayList<>();
|
||||||
|
if (categoryCount > 0) {
|
||||||
|
parts.add("分类 " + categoryCount);
|
||||||
|
}
|
||||||
|
if (deptCount > 0) {
|
||||||
|
parts.add("部门 " + deptCount);
|
||||||
|
}
|
||||||
|
return String.join(" / ", parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,431 @@
|
|||||||
|
package tech.easyflow.approval.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlowStep;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalLog;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalTask;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
|
||||||
|
import tech.easyflow.approval.service.ApprovalActionFacade;
|
||||||
|
import tech.easyflow.approval.service.ApprovalAssigneeService;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalAssigneeType;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalEventType;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalTaskStatus;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalFlowStepMapper;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalLogMapper;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
|
||||||
|
import tech.easyflow.approval.service.ApprovalInstanceService;
|
||||||
|
import tech.easyflow.approval.service.ApprovalMatchService;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批实例服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalMatchService approvalMatchService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalInstanceMapper approvalInstanceMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalTaskMapper approvalTaskMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalLogMapper approvalLogMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalFlowStepMapper approvalFlowStepMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalAssigneeService approvalAssigneeService;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Resource
|
||||||
|
private ApprovalActionFacade approvalActionFacade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public BigInteger submitApproval(ApprovalSubmitRequest request) {
|
||||||
|
ApprovalFlowDetailVo flow = approvalMatchService.matchFlow(request);
|
||||||
|
if (CollectionUtil.isEmpty(flow.getSteps())) {
|
||||||
|
throw new BusinessException("审批流程未配置步骤");
|
||||||
|
}
|
||||||
|
if (request.getApplicantId() == null) {
|
||||||
|
throw new BusinessException("申请人不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ApprovalFlowStepVo> steps = new ArrayList<>(flow.getSteps());
|
||||||
|
steps.sort(Comparator.comparing(ApprovalFlowStepVo::getStepNo));
|
||||||
|
ApprovalFlowStepVo firstStep = steps.get(0);
|
||||||
|
Date now = new Date();
|
||||||
|
|
||||||
|
ApprovalInstance instance = new ApprovalInstance();
|
||||||
|
instance.setFlowId(flow.getId());
|
||||||
|
instance.setFlowVersion(flow.getVersion());
|
||||||
|
instance.setResourceType(flow.getResourceType());
|
||||||
|
instance.setResourceId(request.getResourceId());
|
||||||
|
instance.setActionType(flow.getActionType());
|
||||||
|
instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
|
||||||
|
instance.setCurrentStepNo(firstStep.getStepNo());
|
||||||
|
instance.setSnapshotJson(buildInstanceSnapshot(request, flow, steps));
|
||||||
|
instance.setSummary(request.getSummary());
|
||||||
|
instance.setApplicantId(request.getApplicantId());
|
||||||
|
instance.setSubmittedAt(now);
|
||||||
|
instance.setCreated(now);
|
||||||
|
instance.setCreatedBy(request.getApplicantId());
|
||||||
|
instance.setModified(now);
|
||||||
|
instance.setModifiedBy(request.getApplicantId());
|
||||||
|
approvalInstanceMapper.insert(instance);
|
||||||
|
|
||||||
|
createTask(instance.getId(), firstStep, request.getApplicantId(), now);
|
||||||
|
appendLog(instance.getId(), ApprovalEventType.SUBMITTED.getCode(), request.getApplicantId(), Map.of(
|
||||||
|
"flowId", flow.getId(),
|
||||||
|
"flowVersion", flow.getVersion(),
|
||||||
|
"summary", request.getSummary()
|
||||||
|
), now);
|
||||||
|
appendLog(instance.getId(), ApprovalEventType.STEP_CREATED.getCode(), request.getApplicantId(),
|
||||||
|
buildStepCreatedPayload(firstStep), now);
|
||||||
|
return instance.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void approve(BigInteger instanceId, String comment, BigInteger operatorId) {
|
||||||
|
ApprovalInstance instance = requireActiveInstance(instanceId);
|
||||||
|
ApprovalTask currentTask = requireCurrentTask(instanceId, instance.getCurrentStepNo());
|
||||||
|
assertTaskOperable(currentTask, operatorId);
|
||||||
|
List<ApprovalFlowStepVo> steps = resolveFrozenSteps(instance);
|
||||||
|
|
||||||
|
Date now = new Date();
|
||||||
|
finishTask(currentTask, ApprovalTaskStatus.APPROVED.getCode(), comment, operatorId, now);
|
||||||
|
int currentIndex = findCurrentStepIndex(steps, instance.getCurrentStepNo());
|
||||||
|
if (currentIndex == steps.size() - 1) {
|
||||||
|
instance.setStatus(ApprovalInstanceStatus.APPROVED.getCode());
|
||||||
|
instance.setFinishedAt(now);
|
||||||
|
} else {
|
||||||
|
ApprovalFlowStepVo nextStep = steps.get(currentIndex + 1);
|
||||||
|
instance.setStatus(ApprovalInstanceStatus.PROCESSING.getCode());
|
||||||
|
instance.setCurrentStepNo(nextStep.getStepNo());
|
||||||
|
createTask(instance.getId(), nextStep, operatorId, now);
|
||||||
|
appendLog(instance.getId(), ApprovalEventType.STEP_CREATED.getCode(), operatorId,
|
||||||
|
buildStepCreatedPayload(nextStep), now);
|
||||||
|
}
|
||||||
|
instance.setModified(now);
|
||||||
|
instance.setModifiedBy(operatorId);
|
||||||
|
approvalInstanceMapper.update(instance);
|
||||||
|
appendLog(instance.getId(), ApprovalEventType.APPROVED.getCode(), operatorId, Map.of(
|
||||||
|
"stepNo", currentTask.getStepNo(),
|
||||||
|
"comment", comment == null ? "" : comment
|
||||||
|
), now);
|
||||||
|
if (ApprovalInstanceStatus.from(instance.getStatus()).isFinished()) {
|
||||||
|
approvalActionFacade.handleApproved(instance, operatorId, comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void reject(BigInteger instanceId, String comment, BigInteger operatorId) {
|
||||||
|
ApprovalInstance instance = requireActiveInstance(instanceId);
|
||||||
|
ApprovalTask currentTask = requireCurrentTask(instanceId, instance.getCurrentStepNo());
|
||||||
|
assertTaskOperable(currentTask, operatorId);
|
||||||
|
Date now = new Date();
|
||||||
|
finishTask(currentTask, ApprovalTaskStatus.REJECTED.getCode(), comment, operatorId, now);
|
||||||
|
instance.setStatus(ApprovalInstanceStatus.REJECTED.getCode());
|
||||||
|
instance.setFinishedAt(now);
|
||||||
|
instance.setModified(now);
|
||||||
|
instance.setModifiedBy(operatorId);
|
||||||
|
approvalInstanceMapper.update(instance);
|
||||||
|
appendLog(instance.getId(), ApprovalEventType.REJECTED.getCode(), operatorId, Map.of(
|
||||||
|
"stepNo", currentTask.getStepNo(),
|
||||||
|
"comment", comment == null ? "" : comment
|
||||||
|
), now);
|
||||||
|
approvalActionFacade.handleRejected(instance, operatorId, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void revoke(BigInteger instanceId, String comment, BigInteger operatorId) {
|
||||||
|
ApprovalInstance instance = requireActiveInstance(instanceId);
|
||||||
|
ApprovalTask currentTask = requireCurrentTask(instanceId, instance.getCurrentStepNo());
|
||||||
|
assertTaskOperable(currentTask, operatorId);
|
||||||
|
Date now = new Date();
|
||||||
|
finishTask(currentTask, ApprovalTaskStatus.REVOKED.getCode(), comment, operatorId, now);
|
||||||
|
instance.setStatus(ApprovalInstanceStatus.REVOKED.getCode());
|
||||||
|
instance.setFinishedAt(now);
|
||||||
|
instance.setModified(now);
|
||||||
|
instance.setModifiedBy(operatorId);
|
||||||
|
approvalInstanceMapper.update(instance);
|
||||||
|
appendLog(instance.getId(), ApprovalEventType.REVOKED.getCode(), operatorId, Map.of(
|
||||||
|
"stepNo", currentTask.getStepNo(),
|
||||||
|
"comment", comment == null ? "" : comment
|
||||||
|
), now);
|
||||||
|
approvalActionFacade.handleRevoked(instance, operatorId, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean existsActiveInstance(String resourceType, BigInteger resourceId) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.eq(ApprovalInstance::getResourceType, resourceType)
|
||||||
|
.eq(ApprovalInstance::getResourceId, resourceId)
|
||||||
|
.notIn(ApprovalInstance::getStatus,
|
||||||
|
ApprovalInstanceStatus.APPROVED.getCode(),
|
||||||
|
ApprovalInstanceStatus.REJECTED.getCode(),
|
||||||
|
ApprovalInstanceStatus.REVOKED.getCode());
|
||||||
|
return approvalInstanceMapper.selectCountByQuery(queryWrapper) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ApprovalInstance getById(BigInteger instanceId) {
|
||||||
|
return approvalInstanceMapper.selectOneById(instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildInstanceSnapshot(ApprovalSubmitRequest request, ApprovalFlowDetailVo flow,
|
||||||
|
List<ApprovalFlowStepVo> steps) {
|
||||||
|
Map<String, Object> snapshot = new LinkedHashMap<>();
|
||||||
|
if (request.getSnapshotJson() != null) {
|
||||||
|
snapshot.putAll(request.getSnapshotJson());
|
||||||
|
}
|
||||||
|
snapshot.put("categoryId", request.getCategoryId());
|
||||||
|
snapshot.put("deptId", request.getDeptId());
|
||||||
|
snapshot.put("flowId", flow.getId());
|
||||||
|
snapshot.put("flowVersion", flow.getVersion());
|
||||||
|
snapshot.put("steps", steps.stream().map(item -> {
|
||||||
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
|
map.put("stepNo", item.getStepNo());
|
||||||
|
map.put("stepName", item.getStepName());
|
||||||
|
map.put("assigneeType", item.getAssigneeType());
|
||||||
|
map.put("assigneeTargetId", item.getAssigneeTargetId());
|
||||||
|
map.put("assigneeTargetCode", item.getAssigneeTargetCode());
|
||||||
|
map.put("assigneeTargetName", item.getAssigneeTargetName());
|
||||||
|
return map;
|
||||||
|
}).collect(Collectors.toList()));
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ApprovalFlowStepVo> resolveFrozenSteps(ApprovalInstance instance) {
|
||||||
|
if (instance.getSnapshotJson() == null) {
|
||||||
|
throw new BusinessException("审批实例缺少流程快照");
|
||||||
|
}
|
||||||
|
Object value = instance.getSnapshotJson().get("steps");
|
||||||
|
if (!(value instanceof List<?> steps)) {
|
||||||
|
throw new BusinessException("审批实例缺少冻结步骤数据");
|
||||||
|
}
|
||||||
|
List<ApprovalFlowStepVo> result = new ArrayList<>();
|
||||||
|
for (Object step : steps) {
|
||||||
|
if (!(step instanceof Map<?, ?> stepMap)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Object stepNo = stepMap.get("stepNo");
|
||||||
|
Object stepName = stepMap.get("stepName");
|
||||||
|
if (!(stepNo instanceof Number) || stepName == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ApprovalFlowStepVo stepVo = new ApprovalFlowStepVo();
|
||||||
|
stepVo.setStepNo(((Number) stepNo).intValue());
|
||||||
|
stepVo.setStepName(String.valueOf(stepName));
|
||||||
|
Object assigneeType = stepMap.get("assigneeType");
|
||||||
|
Object assigneeTargetId = stepMap.get("assigneeTargetId");
|
||||||
|
Object assigneeTargetCode = stepMap.get("assigneeTargetCode");
|
||||||
|
Object assigneeTargetName = stepMap.get("assigneeTargetName");
|
||||||
|
if (assigneeType instanceof String type) {
|
||||||
|
stepVo.setAssigneeType(type);
|
||||||
|
}
|
||||||
|
if (assigneeTargetId instanceof Number number) {
|
||||||
|
stepVo.setAssigneeTargetId(BigInteger.valueOf(number.longValue()));
|
||||||
|
} else if (assigneeTargetId instanceof String string && !string.isBlank()) {
|
||||||
|
stepVo.setAssigneeTargetId(new BigInteger(string));
|
||||||
|
}
|
||||||
|
if (assigneeTargetCode != null) {
|
||||||
|
stepVo.setAssigneeTargetCode(String.valueOf(assigneeTargetCode));
|
||||||
|
}
|
||||||
|
if (assigneeTargetName != null) {
|
||||||
|
stepVo.setAssigneeTargetName(String.valueOf(assigneeTargetName));
|
||||||
|
}
|
||||||
|
result.add(stepVo);
|
||||||
|
}
|
||||||
|
mergeStepAssigneeFromFlow(instance.getFlowId(), result);
|
||||||
|
result.sort(Comparator.comparing(ApprovalFlowStepVo::getStepNo));
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
throw new BusinessException("审批实例缺少冻结步骤数据");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTask(BigInteger instanceId, ApprovalFlowStepVo step, BigInteger operatorId, Date now) {
|
||||||
|
ApprovalTask task = new ApprovalTask();
|
||||||
|
task.setInstanceId(instanceId);
|
||||||
|
task.setStepNo(step.getStepNo());
|
||||||
|
task.setStatus(ApprovalTaskStatus.PENDING.getCode());
|
||||||
|
task.setAssigneeRoleCode(ApprovalAssigneeType.ROLE.getCode().equals(step.getAssigneeType())
|
||||||
|
? step.getAssigneeTargetCode()
|
||||||
|
: null);
|
||||||
|
task.setAssigneeType(step.getAssigneeType());
|
||||||
|
task.setAssigneeTargetId(step.getAssigneeTargetId());
|
||||||
|
task.setAssigneeTargetCode(step.getAssigneeTargetCode());
|
||||||
|
task.setAssigneeTargetName(step.getAssigneeTargetName());
|
||||||
|
task.setCreated(now);
|
||||||
|
task.setCreatedBy(operatorId);
|
||||||
|
task.setModified(now);
|
||||||
|
task.setModifiedBy(operatorId);
|
||||||
|
approvalTaskMapper.insert(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishTask(ApprovalTask task, String status, String comment, BigInteger operatorId, Date now) {
|
||||||
|
task.setStatus(status);
|
||||||
|
task.setComment(comment);
|
||||||
|
task.setActedBy(operatorId);
|
||||||
|
task.setActedAt(now);
|
||||||
|
task.setModified(now);
|
||||||
|
task.setModifiedBy(operatorId);
|
||||||
|
approvalTaskMapper.update(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendLog(BigInteger instanceId, String eventType, BigInteger operatorId, Map<String, Object> payload, Date now) {
|
||||||
|
ApprovalLog log = new ApprovalLog();
|
||||||
|
log.setInstanceId(instanceId);
|
||||||
|
log.setEventType(eventType);
|
||||||
|
log.setOperatorId(operatorId);
|
||||||
|
log.setPayloadJson(new LinkedHashMap<>(payload));
|
||||||
|
log.setCreated(now);
|
||||||
|
log.setCreatedBy(operatorId);
|
||||||
|
log.setModified(now);
|
||||||
|
log.setModifiedBy(operatorId);
|
||||||
|
approvalLogMapper.insert(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalInstance requireActiveInstance(BigInteger instanceId) {
|
||||||
|
if (instanceId == null) {
|
||||||
|
throw new BusinessException("审批实例ID不能为空");
|
||||||
|
}
|
||||||
|
ApprovalInstance instance = approvalInstanceMapper.selectOneById(instanceId);
|
||||||
|
if (instance == null) {
|
||||||
|
throw new BusinessException("审批实例不存在");
|
||||||
|
}
|
||||||
|
if (ApprovalInstanceStatus.from(instance.getStatus()).isFinished()) {
|
||||||
|
throw new BusinessException("审批实例已结束,无法继续处理");
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalTask requireCurrentTask(BigInteger instanceId, Integer stepNo) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.eq(ApprovalTask::getInstanceId, instanceId)
|
||||||
|
.eq(ApprovalTask::getStepNo, stepNo)
|
||||||
|
.eq(ApprovalTask::getStatus, ApprovalTaskStatus.PENDING.getCode());
|
||||||
|
ApprovalTask task = approvalTaskMapper.selectOneByQuery(queryWrapper);
|
||||||
|
if (task == null) {
|
||||||
|
throw new BusinessException("当前审批任务不存在");
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findCurrentStepIndex(List<ApprovalFlowStepVo> steps, Integer currentStepNo) {
|
||||||
|
for (int i = 0; i < steps.size(); i++) {
|
||||||
|
if (steps.get(i).getStepNo().equals(currentStepNo)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new BusinessException("审批流程步骤不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验当前操作人是否命中审批任务。
|
||||||
|
*
|
||||||
|
* @param task 审批任务
|
||||||
|
* @param operatorId 当前操作人
|
||||||
|
*/
|
||||||
|
private void assertTaskOperable(ApprovalTask task, BigInteger operatorId) {
|
||||||
|
if (!approvalAssigneeService.canHandleTask(task, operatorId, approvalAssigneeService.getAvailableRoleIds(operatorId))) {
|
||||||
|
throw new BusinessException("当前用户无权处理该审批任务");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为历史审批实例补齐流程步骤里的审批对象信息。
|
||||||
|
*
|
||||||
|
* @param flowId 流程ID
|
||||||
|
* @param steps 冻结步骤
|
||||||
|
*/
|
||||||
|
private void mergeStepAssigneeFromFlow(BigInteger flowId, List<ApprovalFlowStepVo> steps) {
|
||||||
|
List<ApprovalFlowStep> storedSteps = approvalFlowStepMapper.selectListByQuery(
|
||||||
|
QueryWrapper.create().eq(ApprovalFlowStep::getFlowId, flowId));
|
||||||
|
if (CollectionUtil.isEmpty(storedSteps)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<Integer, ApprovalFlowStep> storedMap = storedSteps.stream().collect(Collectors.toMap(
|
||||||
|
ApprovalFlowStep::getStepNo,
|
||||||
|
item -> item,
|
||||||
|
(left, right) -> left,
|
||||||
|
LinkedHashMap::new
|
||||||
|
));
|
||||||
|
for (ApprovalFlowStepVo step : steps) {
|
||||||
|
if (step.getAssigneeTargetId() != null && step.getAssigneeType() != null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ApprovalFlowStep storedStep = storedMap.get(step.getStepNo());
|
||||||
|
if (storedStep == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
step.setAssigneeType(storedStep.getAssigneeType());
|
||||||
|
step.setAssigneeTargetId(storedStep.getAssigneeTargetId());
|
||||||
|
step.setAssigneeTargetCode(storedStep.getAssigneeTargetCode());
|
||||||
|
step.setAssigneeTargetName(storedStep.getAssigneeTargetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组装步骤创建日志载荷。
|
||||||
|
*
|
||||||
|
* @param step 步骤信息
|
||||||
|
* @return 日志载荷
|
||||||
|
*/
|
||||||
|
private Map<String, Object> buildStepCreatedPayload(ApprovalFlowStepVo step) {
|
||||||
|
Map<String, Object> payload = new LinkedHashMap<>();
|
||||||
|
payload.put("stepNo", step.getStepNo());
|
||||||
|
payload.put("stepName", step.getStepName());
|
||||||
|
payload.put("assigneeType", step.getAssigneeType());
|
||||||
|
payload.put("assigneeTargetId", step.getAssigneeTargetId());
|
||||||
|
payload.put("assigneeTargetCode", step.getAssigneeTargetCode());
|
||||||
|
payload.put("assigneeTargetName", step.getAssigneeTargetName());
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
package tech.easyflow.approval.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlow;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlowScope;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalActionType;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalFlowStatus;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalResourceType;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalScopeType;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalFlowMapper;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalFlowScopeMapper;
|
||||||
|
import tech.easyflow.approval.service.ApprovalFlowService;
|
||||||
|
import tech.easyflow.approval.service.ApprovalMatchService;
|
||||||
|
import tech.easyflow.system.entity.SysDept;
|
||||||
|
import tech.easyflow.system.service.SysDeptService;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批流程匹配服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ApprovalMatchServiceImpl implements ApprovalMatchService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalFlowMapper approvalFlowMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalFlowScopeMapper approvalFlowScopeMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalFlowService approvalFlowService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SysDeptService sysDeptService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ApprovalFlowDetailVo matchFlow(ApprovalSubmitRequest request) {
|
||||||
|
ApprovalSubmitRequest normalized = normalizeRequest(request);
|
||||||
|
QueryWrapper flowWrapper = QueryWrapper.create()
|
||||||
|
.eq(ApprovalFlow::getResourceType, normalized.getResourceType())
|
||||||
|
.eq(ApprovalFlow::getActionType, normalized.getActionType())
|
||||||
|
.eq(ApprovalFlow::getStatus, ApprovalFlowStatus.ENABLED.getCode());
|
||||||
|
List<ApprovalFlow> flows = approvalFlowMapper.selectListByQuery(flowWrapper);
|
||||||
|
if (CollectionUtil.isEmpty(flows)) {
|
||||||
|
throw new BusinessException("未找到可用的审批流程");
|
||||||
|
}
|
||||||
|
List<BigInteger> flowIds = flows.stream().map(ApprovalFlow::getId).collect(Collectors.toList());
|
||||||
|
Map<BigInteger, List<ApprovalFlowScope>> scopeMap = approvalFlowScopeMapper.selectListByQuery(
|
||||||
|
QueryWrapper.create().in(ApprovalFlowScope::getFlowId, flowIds))
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.groupingBy(ApprovalFlowScope::getFlowId, LinkedHashMap::new, Collectors.toList()));
|
||||||
|
|
||||||
|
List<MatchedFlow> matchedFlows = new ArrayList<>();
|
||||||
|
for (ApprovalFlow flow : flows) {
|
||||||
|
List<ApprovalFlowScope> scopes = scopeMap.getOrDefault(flow.getId(), List.of());
|
||||||
|
if (matches(scopes, normalized)) {
|
||||||
|
matchedFlows.add(new MatchedFlow(flow, computeSpecificity(scopes)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchedFlows.isEmpty()) {
|
||||||
|
throw new BusinessException("当前资源上下文未命中审批流程");
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedFlows.sort(Comparator
|
||||||
|
.comparing((MatchedFlow item) -> item.flow.getPriority(), Comparator.nullsLast(Integer::compareTo)).reversed()
|
||||||
|
.thenComparing(MatchedFlow::getSpecificity, Comparator.reverseOrder())
|
||||||
|
.thenComparing(item -> item.flow.getCreated(), Comparator.nullsLast(Comparator.naturalOrder())));
|
||||||
|
|
||||||
|
MatchedFlow first = matchedFlows.get(0);
|
||||||
|
if (matchedFlows.size() > 1) {
|
||||||
|
MatchedFlow second = matchedFlows.get(1);
|
||||||
|
boolean samePriority = Objects.equals(first.flow.getPriority(), second.flow.getPriority());
|
||||||
|
boolean sameSpecificity = Objects.equals(first.specificity, second.specificity);
|
||||||
|
boolean sameCreated = Objects.equals(first.flow.getCreated(), second.flow.getCreated());
|
||||||
|
if (samePriority && sameSpecificity && sameCreated) {
|
||||||
|
throw new BusinessException("审批流程匹配冲突,请调整优先级或范围配置");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return approvalFlowService.getFlowDetail(first.flow.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApprovalSubmitRequest normalizeRequest(ApprovalSubmitRequest request) {
|
||||||
|
if (request == null) {
|
||||||
|
throw new BusinessException("审批请求不能为空");
|
||||||
|
}
|
||||||
|
if (request.getResourceId() == null) {
|
||||||
|
throw new BusinessException("资源ID不能为空");
|
||||||
|
}
|
||||||
|
ApprovalSubmitRequest normalized = new ApprovalSubmitRequest();
|
||||||
|
normalized.setResourceType(ApprovalResourceType.from(request.getResourceType()).getCode());
|
||||||
|
normalized.setActionType(ApprovalActionType.from(request.getActionType()).getCode());
|
||||||
|
normalized.setResourceId(request.getResourceId());
|
||||||
|
normalized.setApplicantId(request.getApplicantId());
|
||||||
|
normalized.setCategoryId(request.getCategoryId());
|
||||||
|
normalized.setDeptId(request.getDeptId());
|
||||||
|
normalized.setSummary(request.getSummary());
|
||||||
|
normalized.setSnapshotJson(request.getSnapshotJson());
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matches(List<ApprovalFlowScope> scopes, ApprovalSubmitRequest request) {
|
||||||
|
List<ApprovalFlowScope> categoryScopes = scopes.stream()
|
||||||
|
.filter(item -> ApprovalScopeType.CATEGORY.getCode().equals(item.getScopeType()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
List<ApprovalFlowScope> deptScopes = scopes.stream()
|
||||||
|
.filter(item -> ApprovalScopeType.DEPT.getCode().equals(item.getScopeType()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return matchesCategory(categoryScopes, request.getCategoryId()) && matchesDept(deptScopes, request.getDeptId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchesCategory(List<ApprovalFlowScope> scopes, BigInteger categoryId) {
|
||||||
|
if (CollectionUtil.isEmpty(scopes)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (categoryId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return scopes.stream().anyMatch(item -> categoryId.equals(item.getScopeValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchesDept(List<ApprovalFlowScope> scopes, BigInteger deptId) {
|
||||||
|
if (CollectionUtil.isEmpty(scopes)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (deptId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SysDept dept = sysDeptService.getById(deptId);
|
||||||
|
String ancestors = dept == null ? "" : dept.getAncestors();
|
||||||
|
List<BigInteger> ancestorIds = new ArrayList<>();
|
||||||
|
if (StringUtils.hasText(ancestors)) {
|
||||||
|
for (String ancestor : ancestors.split(",")) {
|
||||||
|
if (!StringUtils.hasText(ancestor) || "0".equals(ancestor.trim())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ancestorIds.add(new BigInteger(ancestor.trim()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scopes.stream().anyMatch(item -> {
|
||||||
|
if (deptId.equals(item.getScopeValue())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return item.getIncludeChildren() != null
|
||||||
|
&& item.getIncludeChildren() == 1
|
||||||
|
&& ancestorIds.contains(item.getScopeValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computeSpecificity(List<ApprovalFlowScope> scopes) {
|
||||||
|
int score = scopes.size();
|
||||||
|
boolean hasCategory = scopes.stream().anyMatch(item -> ApprovalScopeType.CATEGORY.getCode().equals(item.getScopeType()));
|
||||||
|
boolean hasDept = scopes.stream().anyMatch(item -> ApprovalScopeType.DEPT.getCode().equals(item.getScopeType()));
|
||||||
|
if (hasCategory) {
|
||||||
|
score += 10;
|
||||||
|
}
|
||||||
|
if (hasDept) {
|
||||||
|
score += 10;
|
||||||
|
}
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MatchedFlow {
|
||||||
|
private final ApprovalFlow flow;
|
||||||
|
private final Integer specificity;
|
||||||
|
|
||||||
|
private MatchedFlow(ApprovalFlow flow, Integer specificity) {
|
||||||
|
this.flow = flow;
|
||||||
|
this.specificity = specificity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getSpecificity() {
|
||||||
|
return specificity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,402 @@
|
|||||||
|
package tech.easyflow.approval.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import com.mybatisflex.core.paginate.Page;
|
||||||
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import tech.easyflow.common.entity.LoginAccount;
|
||||||
|
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||||
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalFlowStep;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalLog;
|
||||||
|
import tech.easyflow.approval.entity.ApprovalTask;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalInstanceDetailVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalInstancePageVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalLogVo;
|
||||||
|
import tech.easyflow.approval.entity.vo.ApprovalTaskVo;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalActionType;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalResourceType;
|
||||||
|
import tech.easyflow.approval.enums.ApprovalTaskStatus;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalFlowStepMapper;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalLogMapper;
|
||||||
|
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
|
||||||
|
import tech.easyflow.approval.service.ApprovalAssigneeService;
|
||||||
|
import tech.easyflow.approval.service.ApprovalQueryService;
|
||||||
|
import tech.easyflow.system.entity.SysAccount;
|
||||||
|
import tech.easyflow.system.service.SysAccountService;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审批查询服务实现。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalInstanceMapper approvalInstanceMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalTaskMapper approvalTaskMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalLogMapper approvalLogMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalFlowStepMapper approvalFlowStepMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ApprovalAssigneeService approvalAssigneeService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SysAccountService sysAccountService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Page<ApprovalInstancePageVo> pendingPage(String resourceType, String actionType, String keyword,
|
||||||
|
Long pageNumber, Long pageSize) {
|
||||||
|
LoginAccount account = requireLoginAccount();
|
||||||
|
Set<BigInteger> roleIds = approvalAssigneeService.getAvailableRoleIds(account.getId());
|
||||||
|
Set<BigInteger> instanceIds = approvalAssigneeService.listPendingInstanceIds(account.getId(), roleIds, null);
|
||||||
|
if (CollectionUtil.isEmpty(instanceIds)) {
|
||||||
|
return new Page<>(List.of(), safePageNumber(pageNumber), safePageSize(pageSize), 0L);
|
||||||
|
}
|
||||||
|
QueryWrapper queryWrapper = buildBaseQuery(resourceType, actionType, keyword);
|
||||||
|
queryWrapper.in(ApprovalInstance::getId, instanceIds);
|
||||||
|
queryWrapper.in(ApprovalInstance::getStatus, List.of(
|
||||||
|
ApprovalInstanceStatus.PENDING.getCode(),
|
||||||
|
ApprovalInstanceStatus.PROCESSING.getCode()));
|
||||||
|
queryWrapper.orderBy("submitted_at desc, id desc");
|
||||||
|
return mapPage(queryWrapper, safePageNumber(pageNumber), safePageSize(pageSize), true, account, roleIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Page<ApprovalInstancePageVo> processedPage(String resourceType, String actionType, String keyword,
|
||||||
|
Long pageNumber, Long pageSize) {
|
||||||
|
LoginAccount account = requireLoginAccount();
|
||||||
|
QueryWrapper actedTaskWrapper = QueryWrapper.create()
|
||||||
|
.eq(ApprovalTask::getActedBy, account.getId())
|
||||||
|
.in(ApprovalTask::getStatus, List.of(
|
||||||
|
ApprovalTaskStatus.APPROVED.getCode(),
|
||||||
|
ApprovalTaskStatus.REJECTED.getCode(),
|
||||||
|
ApprovalTaskStatus.REVOKED.getCode()));
|
||||||
|
List<BigInteger> instanceIds = approvalTaskMapper.selectListByQuery(actedTaskWrapper).stream()
|
||||||
|
.map(ApprovalTask::getInstanceId)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (CollectionUtil.isEmpty(instanceIds)) {
|
||||||
|
return new Page<>(List.of(), safePageNumber(pageNumber), safePageSize(pageSize), 0L);
|
||||||
|
}
|
||||||
|
QueryWrapper queryWrapper = buildBaseQuery(resourceType, actionType, keyword);
|
||||||
|
queryWrapper.in(ApprovalInstance::getId, instanceIds);
|
||||||
|
queryWrapper.orderBy("finished_at desc, id desc");
|
||||||
|
return mapPage(queryWrapper, safePageNumber(pageNumber), safePageSize(pageSize), false, account, Set.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Page<ApprovalInstancePageVo> initiatedPage(String resourceType, String actionType, String keyword,
|
||||||
|
Long pageNumber, Long pageSize) {
|
||||||
|
LoginAccount account = requireLoginAccount();
|
||||||
|
QueryWrapper queryWrapper = buildBaseQuery(resourceType, actionType, keyword);
|
||||||
|
queryWrapper.eq(ApprovalInstance::getApplicantId, account.getId());
|
||||||
|
queryWrapper.orderBy("submitted_at desc, id desc");
|
||||||
|
return mapPage(queryWrapper, safePageNumber(pageNumber), safePageSize(pageSize), false, account, Set.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ApprovalInstanceDetailVo detail(BigInteger instanceId) {
|
||||||
|
ApprovalInstance instance = approvalInstanceMapper.selectOneById(instanceId);
|
||||||
|
if (instance == null) {
|
||||||
|
throw new BusinessException("审批实例不存在");
|
||||||
|
}
|
||||||
|
ApprovalInstanceDetailVo detail = new ApprovalInstanceDetailVo();
|
||||||
|
detail.setId(instance.getId());
|
||||||
|
detail.setFlowId(instance.getFlowId());
|
||||||
|
detail.setFlowVersion(instance.getFlowVersion());
|
||||||
|
detail.setResourceType(instance.getResourceType());
|
||||||
|
detail.setResourceId(instance.getResourceId());
|
||||||
|
detail.setActionType(instance.getActionType());
|
||||||
|
detail.setStatus(instance.getStatus());
|
||||||
|
detail.setCurrentStepNo(instance.getCurrentStepNo());
|
||||||
|
detail.setSummary(instance.getSummary());
|
||||||
|
detail.setApplicantId(instance.getApplicantId());
|
||||||
|
detail.setSubmittedAt(instance.getSubmittedAt());
|
||||||
|
detail.setFinishedAt(instance.getFinishedAt());
|
||||||
|
detail.setSnapshotJson(instance.getSnapshotJson());
|
||||||
|
|
||||||
|
List<ApprovalTask> tasks = approvalTaskMapper.selectListByQuery(
|
||||||
|
QueryWrapper.create().eq(ApprovalTask::getInstanceId, instanceId));
|
||||||
|
List<ApprovalLog> logs = approvalLogMapper.selectListByQuery(
|
||||||
|
QueryWrapper.create().eq(ApprovalLog::getInstanceId, instanceId));
|
||||||
|
Map<Integer, ApprovalFlowStepVo> frozenStepMap = resolveFrozenStepMap(instance);
|
||||||
|
Map<BigInteger, String> accountNameMap = loadAccountNameMap(instance, tasks, logs);
|
||||||
|
detail.setApplicantName(accountNameMap.get(instance.getApplicantId()));
|
||||||
|
|
||||||
|
detail.setTasks(tasks.stream()
|
||||||
|
.sorted(Comparator.comparing(ApprovalTask::getStepNo))
|
||||||
|
.map(item -> {
|
||||||
|
ApprovalTaskVo taskVo = new ApprovalTaskVo();
|
||||||
|
taskVo.setId(item.getId());
|
||||||
|
taskVo.setStepNo(item.getStepNo());
|
||||||
|
taskVo.setStepName(resolveStepName(frozenStepMap, item.getStepNo()));
|
||||||
|
taskVo.setStatus(item.getStatus());
|
||||||
|
taskVo.setAssigneeRoleCode(item.getAssigneeRoleCode());
|
||||||
|
taskVo.setAssigneeType(item.getAssigneeType());
|
||||||
|
taskVo.setAssigneeTargetId(item.getAssigneeTargetId());
|
||||||
|
taskVo.setAssigneeTargetCode(item.getAssigneeTargetCode());
|
||||||
|
taskVo.setAssigneeTargetName(item.getAssigneeTargetName());
|
||||||
|
taskVo.setActedBy(item.getActedBy());
|
||||||
|
taskVo.setActedByName(accountNameMap.get(item.getActedBy()));
|
||||||
|
taskVo.setActedAt(item.getActedAt());
|
||||||
|
taskVo.setComment(item.getComment());
|
||||||
|
return taskVo;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
detail.setLogs(logs.stream()
|
||||||
|
.sorted(Comparator.comparing(ApprovalLog::getCreated))
|
||||||
|
.map(item -> {
|
||||||
|
ApprovalLogVo logVo = new ApprovalLogVo();
|
||||||
|
logVo.setId(item.getId());
|
||||||
|
logVo.setEventType(item.getEventType());
|
||||||
|
logVo.setOperatorId(item.getOperatorId());
|
||||||
|
logVo.setOperatorName(accountNameMap.get(item.getOperatorId()));
|
||||||
|
logVo.setCreated(item.getCreated());
|
||||||
|
logVo.setPayloadJson(item.getPayloadJson());
|
||||||
|
return logVo;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
LoginAccount account = requireLoginAccount();
|
||||||
|
Set<BigInteger> roleIds = approvalAssigneeService.getAvailableRoleIds(account.getId());
|
||||||
|
boolean canOperate = !ApprovalInstanceStatus.from(instance.getStatus()).isFinished()
|
||||||
|
&& tasks.stream().anyMatch(item -> item.getStepNo().equals(instance.getCurrentStepNo())
|
||||||
|
&& ApprovalTaskStatus.PENDING.getCode().equals(item.getStatus())
|
||||||
|
&& approvalAssigneeService.canHandleTask(item, account.getId(), roleIds));
|
||||||
|
detail.setCanApprove(canOperate);
|
||||||
|
detail.setCanReject(canOperate);
|
||||||
|
detail.setCanRevoke(canOperate);
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量加载审批详情里涉及到的账号显示名称。
|
||||||
|
*
|
||||||
|
* @param instance 审批实例
|
||||||
|
* @param tasks 审批任务列表
|
||||||
|
* @param logs 审批日志列表
|
||||||
|
* @return 账号 ID 到展示名称的映射
|
||||||
|
*/
|
||||||
|
private Map<BigInteger, String> loadAccountNameMap(ApprovalInstance instance, List<ApprovalTask> tasks,
|
||||||
|
List<ApprovalLog> logs) {
|
||||||
|
Set<BigInteger> accountIds = new HashSet<>();
|
||||||
|
if (instance.getApplicantId() != null) {
|
||||||
|
accountIds.add(instance.getApplicantId());
|
||||||
|
}
|
||||||
|
tasks.stream()
|
||||||
|
.map(ApprovalTask::getActedBy)
|
||||||
|
.filter(id -> id != null)
|
||||||
|
.forEach(accountIds::add);
|
||||||
|
logs.stream()
|
||||||
|
.map(ApprovalLog::getOperatorId)
|
||||||
|
.filter(id -> id != null)
|
||||||
|
.forEach(accountIds::add);
|
||||||
|
if (CollectionUtil.isEmpty(accountIds)) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
return sysAccountService.listByIds(accountIds).stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
SysAccount::getId,
|
||||||
|
this::resolveAccountName,
|
||||||
|
(left, right) -> left,
|
||||||
|
LinkedHashMap::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析账号展示名称,优先昵称,其次登录名。
|
||||||
|
*
|
||||||
|
* @param account 账号实体
|
||||||
|
* @return 展示名称
|
||||||
|
*/
|
||||||
|
private String resolveAccountName(SysAccount account) {
|
||||||
|
if (account == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(account.getNickname())) {
|
||||||
|
return account.getNickname().trim();
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(account.getLoginName())) {
|
||||||
|
return account.getLoginName().trim();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private QueryWrapper buildBaseQuery(String resourceType, String actionType, String keyword) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create();
|
||||||
|
if (StringUtils.hasText(resourceType)) {
|
||||||
|
queryWrapper.eq(ApprovalInstance::getResourceType, ApprovalResourceType.from(resourceType).getCode());
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(actionType)) {
|
||||||
|
queryWrapper.eq(ApprovalInstance::getActionType, ApprovalActionType.from(actionType).getCode());
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(keyword)) {
|
||||||
|
queryWrapper.like(ApprovalInstance::getSummary, keyword.trim());
|
||||||
|
}
|
||||||
|
return queryWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Page<ApprovalInstancePageVo> mapPage(QueryWrapper queryWrapper, long pageNumber, long pageSize,
|
||||||
|
boolean pendingMode, LoginAccount account, Set<BigInteger> roleIds) {
|
||||||
|
Page<ApprovalInstance> page = approvalInstanceMapper.paginate(pageNumber, pageSize, queryWrapper);
|
||||||
|
List<ApprovalInstance> records = page.getRecords();
|
||||||
|
Set<BigInteger> pendingTaskInstanceIds = pendingMode
|
||||||
|
? approvalAssigneeService.listPendingInstanceIds(account.getId(), roleIds,
|
||||||
|
records.stream().map(ApprovalInstance::getId).collect(Collectors.toList()))
|
||||||
|
: Set.of();
|
||||||
|
|
||||||
|
List<ApprovalInstancePageVo> result = new ArrayList<>();
|
||||||
|
for (ApprovalInstance record : records) {
|
||||||
|
ApprovalInstancePageVo item = new ApprovalInstancePageVo();
|
||||||
|
item.setId(record.getId());
|
||||||
|
item.setResourceType(record.getResourceType());
|
||||||
|
item.setResourceId(record.getResourceId());
|
||||||
|
item.setActionType(record.getActionType());
|
||||||
|
item.setStatus(record.getStatus());
|
||||||
|
item.setCurrentStepNo(record.getCurrentStepNo());
|
||||||
|
item.setCurrentStepName(resolveCurrentStepName(record));
|
||||||
|
item.setSummary(record.getSummary());
|
||||||
|
item.setApplicantId(record.getApplicantId());
|
||||||
|
item.setSubmittedAt(record.getSubmittedAt());
|
||||||
|
item.setFinishedAt(record.getFinishedAt());
|
||||||
|
boolean canOperate = pendingMode
|
||||||
|
&& pendingTaskInstanceIds.contains(record.getId())
|
||||||
|
&& !ApprovalInstanceStatus.from(record.getStatus()).isFinished();
|
||||||
|
item.setCanApprove(canOperate);
|
||||||
|
item.setCanReject(canOperate);
|
||||||
|
item.setCanRevoke(canOperate);
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page<ApprovalInstancePageVo> voPage = new Page<>(pageNumber, pageSize, page.getTotalRow());
|
||||||
|
voPage.setRecords(result);
|
||||||
|
return voPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long safePageNumber(Long pageNumber) {
|
||||||
|
return pageNumber == null || pageNumber < 1 ? 1L : pageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long safePageSize(Long pageSize) {
|
||||||
|
return pageSize == null || pageSize < 1 ? 10L : pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoginAccount requireLoginAccount() {
|
||||||
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
if (account == null) {
|
||||||
|
throw new BusinessException("当前未登录");
|
||||||
|
}
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveCurrentStepName(ApprovalInstance instance) {
|
||||||
|
Map<Integer, ApprovalFlowStepVo> stepMap = resolveFrozenStepMap(instance);
|
||||||
|
return resolveStepName(stepMap, instance.getCurrentStepNo());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveStepName(Map<Integer, ApprovalFlowStepVo> stepMap, Integer stepNo) {
|
||||||
|
ApprovalFlowStepVo step = stepMap.get(stepNo);
|
||||||
|
return step == null ? null : step.getStepName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, ApprovalFlowStepVo> resolveFrozenStepMap(ApprovalInstance instance) {
|
||||||
|
Map<Integer, ApprovalFlowStepVo> result = new LinkedHashMap<>();
|
||||||
|
Object steps = instance.getSnapshotJson() == null ? null : instance.getSnapshotJson().get("steps");
|
||||||
|
if (steps instanceof List<?> frozenSteps) {
|
||||||
|
for (Object item : frozenSteps) {
|
||||||
|
if (!(item instanceof Map<?, ?> map)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Object stepNo = map.get("stepNo");
|
||||||
|
Object stepName = map.get("stepName");
|
||||||
|
if (!(stepNo instanceof Number) || stepName == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ApprovalFlowStepVo stepVo = new ApprovalFlowStepVo();
|
||||||
|
stepVo.setStepNo(((Number) stepNo).intValue());
|
||||||
|
stepVo.setStepName(String.valueOf(stepName));
|
||||||
|
Object assigneeType = map.get("assigneeType");
|
||||||
|
Object assigneeTargetId = map.get("assigneeTargetId");
|
||||||
|
Object assigneeTargetCode = map.get("assigneeTargetCode");
|
||||||
|
Object assigneeTargetName = map.get("assigneeTargetName");
|
||||||
|
if (assigneeType != null) {
|
||||||
|
stepVo.setAssigneeType(String.valueOf(assigneeType));
|
||||||
|
}
|
||||||
|
if (assigneeTargetId instanceof Number number) {
|
||||||
|
stepVo.setAssigneeTargetId(BigInteger.valueOf(number.longValue()));
|
||||||
|
} else if (assigneeTargetId instanceof String string && StringUtils.hasText(string)) {
|
||||||
|
stepVo.setAssigneeTargetId(new BigInteger(string));
|
||||||
|
}
|
||||||
|
if (assigneeTargetCode != null) {
|
||||||
|
stepVo.setAssigneeTargetCode(String.valueOf(assigneeTargetCode));
|
||||||
|
}
|
||||||
|
if (assigneeTargetName != null) {
|
||||||
|
stepVo.setAssigneeTargetName(String.valueOf(assigneeTargetName));
|
||||||
|
}
|
||||||
|
result.put(stepVo.getStepNo(), stepVo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!result.isEmpty() && result.values().stream().allMatch(item -> item.getAssigneeType() != null && item.getAssigneeTargetId() != null)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
List<ApprovalFlowStep> storedSteps = approvalFlowStepMapper.selectListByQuery(
|
||||||
|
QueryWrapper.create().eq(ApprovalFlowStep::getFlowId, instance.getFlowId()));
|
||||||
|
for (ApprovalFlowStep step : storedSteps) {
|
||||||
|
ApprovalFlowStepVo stepVo = result.computeIfAbsent(step.getStepNo(), key -> {
|
||||||
|
ApprovalFlowStepVo value = new ApprovalFlowStepVo();
|
||||||
|
value.setStepNo(step.getStepNo());
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
if (!StringUtils.hasText(stepVo.getStepName())) {
|
||||||
|
stepVo.setStepName(step.getStepName());
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(stepVo.getAssigneeType())) {
|
||||||
|
stepVo.setAssigneeType(step.getAssigneeType());
|
||||||
|
}
|
||||||
|
if (stepVo.getAssigneeTargetId() == null) {
|
||||||
|
stepVo.setAssigneeTargetId(step.getAssigneeTargetId());
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(stepVo.getAssigneeTargetCode())) {
|
||||||
|
stepVo.setAssigneeTargetCode(step.getAssigneeTargetCode());
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(stepVo.getAssigneeTargetName())) {
|
||||||
|
stepVo.setAssigneeTargetName(step.getAssigneeTargetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>easyflow-module-system</module>
|
<module>easyflow-module-system</module>
|
||||||
|
<module>easyflow-module-approval</module>
|
||||||
<module>easyflow-module-log</module>
|
<module>easyflow-module-log</module>
|
||||||
<module>easyflow-module-auth</module>
|
<module>easyflow-module-auth</module>
|
||||||
<module>easyflow-module-autoconfig</module>
|
<module>easyflow-module-autoconfig</module>
|
||||||
|
|||||||
@@ -0,0 +1,293 @@
|
|||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `tb_approval_flow` (
|
||||||
|
`id` bigint NOT NULL COMMENT '主键',
|
||||||
|
`name` varchar(128) NOT NULL COMMENT '流程名称',
|
||||||
|
`resource_type` varchar(32) NOT NULL COMMENT '资源类型',
|
||||||
|
`action_type` varchar(32) NOT NULL COMMENT '动作类型',
|
||||||
|
`priority` int NOT NULL DEFAULT 0 COMMENT '优先级',
|
||||||
|
`status` varchar(32) NOT NULL COMMENT '流程状态',
|
||||||
|
`version` int NOT NULL DEFAULT 1 COMMENT '流程版本',
|
||||||
|
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||||
|
`created` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`created_by` bigint DEFAULT NULL COMMENT '创建者',
|
||||||
|
`modified` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`modified_by` bigint DEFAULT NULL COMMENT '修改者',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_approval_flow_match` (`resource_type`, `action_type`, `status`, `priority`),
|
||||||
|
KEY `idx_approval_flow_modified` (`modified`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批流程';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `tb_approval_flow_scope` (
|
||||||
|
`id` bigint NOT NULL COMMENT '主键',
|
||||||
|
`flow_id` bigint NOT NULL COMMENT '流程ID',
|
||||||
|
`scope_type` varchar(32) NOT NULL COMMENT '范围类型',
|
||||||
|
`scope_value` bigint NOT NULL COMMENT '范围值',
|
||||||
|
`include_children` tinyint NOT NULL DEFAULT 0 COMMENT '是否包含子节点',
|
||||||
|
`created` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`created_by` bigint DEFAULT NULL COMMENT '创建者',
|
||||||
|
`modified` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`modified_by` bigint DEFAULT NULL COMMENT '修改者',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_approval_flow_scope` (`flow_id`, `scope_type`, `scope_value`),
|
||||||
|
KEY `idx_approval_flow_scope_type` (`scope_type`, `scope_value`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批流程范围';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `tb_approval_flow_step` (
|
||||||
|
`id` bigint NOT NULL COMMENT '主键',
|
||||||
|
`flow_id` bigint NOT NULL COMMENT '流程ID',
|
||||||
|
`step_no` int NOT NULL COMMENT '步骤序号',
|
||||||
|
`step_name` varchar(128) NOT NULL COMMENT '步骤名称',
|
||||||
|
`created` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`created_by` bigint DEFAULT NULL COMMENT '创建者',
|
||||||
|
`modified` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`modified_by` bigint DEFAULT NULL COMMENT '修改者',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_approval_flow_step` (`flow_id`, `step_no`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批流程步骤';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `tb_approval_instance` (
|
||||||
|
`id` bigint NOT NULL COMMENT '主键',
|
||||||
|
`flow_id` bigint NOT NULL COMMENT '流程ID',
|
||||||
|
`flow_version` int NOT NULL COMMENT '流程版本',
|
||||||
|
`resource_type` varchar(32) NOT NULL COMMENT '资源类型',
|
||||||
|
`resource_id` bigint NOT NULL COMMENT '资源ID',
|
||||||
|
`action_type` varchar(32) NOT NULL COMMENT '动作类型',
|
||||||
|
`status` varchar(32) NOT NULL COMMENT '实例状态',
|
||||||
|
`current_step_no` int DEFAULT NULL COMMENT '当前步骤序号',
|
||||||
|
`snapshot_json` json DEFAULT NULL COMMENT '审批快照',
|
||||||
|
`summary` varchar(255) DEFAULT NULL COMMENT '审批摘要',
|
||||||
|
`applicant_id` bigint NOT NULL COMMENT '申请人ID',
|
||||||
|
`submitted_at` datetime DEFAULT NULL COMMENT '提交时间',
|
||||||
|
`finished_at` datetime DEFAULT NULL COMMENT '完成时间',
|
||||||
|
`created` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`created_by` bigint DEFAULT NULL COMMENT '创建者',
|
||||||
|
`modified` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`modified_by` bigint DEFAULT NULL COMMENT '修改者',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_approval_instance_flow_status` (`flow_id`, `status`),
|
||||||
|
KEY `idx_approval_instance_resource` (`resource_type`, `resource_id`, `action_type`),
|
||||||
|
KEY `idx_approval_instance_applicant` (`applicant_id`, `submitted_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批实例';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `tb_approval_task` (
|
||||||
|
`id` bigint NOT NULL COMMENT '主键',
|
||||||
|
`instance_id` bigint NOT NULL COMMENT '实例ID',
|
||||||
|
`step_no` int NOT NULL COMMENT '步骤序号',
|
||||||
|
`status` varchar(32) NOT NULL COMMENT '任务状态',
|
||||||
|
`assignee_role_code` varchar(64) NOT NULL COMMENT '指派角色编码',
|
||||||
|
`acted_by` bigint DEFAULT NULL COMMENT '处理人ID',
|
||||||
|
`acted_at` datetime DEFAULT NULL COMMENT '处理时间',
|
||||||
|
`comment` varchar(500) DEFAULT NULL COMMENT '处理意见',
|
||||||
|
`created` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`created_by` bigint DEFAULT NULL COMMENT '创建者',
|
||||||
|
`modified` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`modified_by` bigint DEFAULT NULL COMMENT '修改者',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_approval_task_step` (`instance_id`, `step_no`),
|
||||||
|
KEY `idx_approval_task_status` (`status`, `assignee_role_code`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批任务';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `tb_approval_log` (
|
||||||
|
`id` bigint NOT NULL COMMENT '主键',
|
||||||
|
`instance_id` bigint NOT NULL COMMENT '实例ID',
|
||||||
|
`event_type` varchar(32) NOT NULL COMMENT '事件类型',
|
||||||
|
`operator_id` bigint DEFAULT NULL COMMENT '操作人ID',
|
||||||
|
`payload_json` json DEFAULT NULL COMMENT '事件载荷',
|
||||||
|
`created` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`created_by` bigint DEFAULT NULL COMMENT '创建者',
|
||||||
|
`modified` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`modified_by` bigint DEFAULT NULL COMMENT '修改者',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_approval_log_instance` (`instance_id`, `created`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批日志';
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000001, 258052082618335232, 0, 'menus.system.approval', '/sys/approval', '/system/approval/ApprovalManage', 'svg:approval',
|
||||||
|
1, '', 750, 0, '2026-04-06 10:00:00', 1, '2026-04-06 10:00:00', 1, '审批管理菜单'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000001
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000011, 367100000000000001, 1, '查询', '', '', '',
|
||||||
|
0, '/api/v1/approvalFlow/query', 1, 0, '2026-04-06 10:00:00', 1, '2026-04-06 10:00:00', 1, '审批流程-查询'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000011
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000012, 367100000000000001, 1, '保存', '', '', '',
|
||||||
|
0, '/api/v1/approvalFlow/save', 2, 0, '2026-04-06 10:00:00', 1, '2026-04-06 10:00:00', 1, '审批流程-保存'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000012
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000013, 367100000000000001, 1, '删除', '', '', '',
|
||||||
|
0, '/api/v1/approvalFlow/remove', 3, 0, '2026-04-06 10:00:00', 1, '2026-04-06 10:00:00', 1, '审批流程-删除'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000013
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000014, 367100000000000001, 1, '启用', '', '', '',
|
||||||
|
0, '/api/v1/approvalFlow/enable', 4, 0, '2026-04-06 10:00:00', 1, '2026-04-06 10:00:00', 1, '审批流程-启用'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000014
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000015, 367100000000000001, 1, '停用', '', '', '',
|
||||||
|
0, '/api/v1/approvalFlow/disable', 5, 0, '2026-04-06 10:00:00', 1, '2026-04-06 10:00:00', 1, '审批流程-停用'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000015
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000021, 367100000000000001, 1, '实例查询', '', '', '',
|
||||||
|
0, '/api/v1/approvalInstance/query', 6, 0, '2026-04-06 10:00:00', 1, '2026-04-06 10:00:00', 1, '审批实例-查询'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000021
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000022, 367100000000000001, 1, '通过审批', '', '', '',
|
||||||
|
0, '/api/v1/approvalInstance/approve', 7, 0, '2026-04-06 10:00:00', 1, '2026-04-06 10:00:00', 1, '审批实例-通过'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000022
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000023, 367100000000000001, 1, '驳回审批', '', '', '',
|
||||||
|
0, '/api/v1/approvalInstance/reject', 8, 0, '2026-04-06 10:00:00', 1, '2026-04-06 10:00:00', 1, '审批实例-驳回'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000023
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000024, 367100000000000001, 1, '撤回审批', '', '', '',
|
||||||
|
0, '/api/v1/approvalInstance/revoke', 9, 0, '2026-04-06 10:00:00', 1, '2026-04-06 10:00:00', 1, '审批实例-撤回'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000024
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000101, 1, 367100000000000001
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000101
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000111, 1, 367100000000000011
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000111
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000112, 1, 367100000000000012
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000112
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000113, 1, 367100000000000013
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000113
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000114, 1, 367100000000000014
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000114
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000115, 1, 367100000000000015
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000115
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000121, 1, 367100000000000021
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000121
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000122, 1, 367100000000000022
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000122
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000123, 1, 367100000000000023
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000123
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000124, 1, 367100000000000024
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000124
|
||||||
|
);
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS `sp_add_column_if_missing`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE PROCEDURE `sp_add_column_if_missing`(
|
||||||
|
IN in_table_name VARCHAR(128),
|
||||||
|
IN in_column_name VARCHAR(128),
|
||||||
|
IN in_definition TEXT
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = in_table_name
|
||||||
|
AND COLUMN_NAME = in_column_name
|
||||||
|
) THEN
|
||||||
|
SET @ddl = CONCAT('ALTER TABLE `', in_table_name, '` ADD COLUMN ', in_definition);
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
END IF;
|
||||||
|
END $$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
CALL `sp_add_column_if_missing`('tb_workflow', 'publish_status',
|
||||||
|
'`publish_status` varchar(32) NOT NULL DEFAULT ''DRAFT'' COMMENT ''发布状态''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_workflow', 'current_approval_instance_id',
|
||||||
|
'`current_approval_instance_id` bigint DEFAULT NULL COMMENT ''当前审批实例ID''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_workflow', 'published_snapshot_json',
|
||||||
|
'`published_snapshot_json` json DEFAULT NULL COMMENT ''已发布快照''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_workflow', 'published_at',
|
||||||
|
'`published_at` datetime DEFAULT NULL COMMENT ''发布时间''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_workflow', 'published_by',
|
||||||
|
'`published_by` bigint DEFAULT NULL COMMENT ''发布人''');
|
||||||
|
|
||||||
|
CALL `sp_add_column_if_missing`('tb_document_collection', 'publish_status',
|
||||||
|
'`publish_status` varchar(32) NOT NULL DEFAULT ''DRAFT'' COMMENT ''发布状态''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_document_collection', 'current_approval_instance_id',
|
||||||
|
'`current_approval_instance_id` bigint DEFAULT NULL COMMENT ''当前审批实例ID''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_document_collection', 'published_snapshot_json',
|
||||||
|
'`published_snapshot_json` json DEFAULT NULL COMMENT ''已发布快照''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_document_collection', 'published_at',
|
||||||
|
'`published_at` datetime DEFAULT NULL COMMENT ''发布时间''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_document_collection', 'published_by',
|
||||||
|
'`published_by` bigint DEFAULT NULL COMMENT ''发布人''');
|
||||||
|
|
||||||
|
CALL `sp_add_column_if_missing`('tb_bot', 'publish_status',
|
||||||
|
'`publish_status` varchar(32) NOT NULL DEFAULT ''DRAFT'' COMMENT ''发布状态''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_bot', 'current_approval_instance_id',
|
||||||
|
'`current_approval_instance_id` bigint DEFAULT NULL COMMENT ''当前审批实例ID''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_bot', 'published_snapshot_json',
|
||||||
|
'`published_snapshot_json` json DEFAULT NULL COMMENT ''已发布快照''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_bot', 'published_at',
|
||||||
|
'`published_at` datetime DEFAULT NULL COMMENT ''发布时间''');
|
||||||
|
CALL `sp_add_column_if_missing`('tb_bot', 'published_by',
|
||||||
|
'`published_by` bigint DEFAULT NULL COMMENT ''发布人''');
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS `sp_add_column_if_missing`;
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS `sp_add_column_if_missing`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE PROCEDURE `sp_add_column_if_missing`(
|
||||||
|
IN in_table_name VARCHAR(128),
|
||||||
|
IN in_column_name VARCHAR(128),
|
||||||
|
IN in_definition TEXT
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = in_table_name
|
||||||
|
AND COLUMN_NAME = in_column_name
|
||||||
|
) THEN
|
||||||
|
SET @ddl = CONCAT('ALTER TABLE `', in_table_name, '` ADD COLUMN ', in_definition);
|
||||||
|
PREPARE stmt FROM @ddl;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
END IF;
|
||||||
|
END $$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
CALL `sp_add_column_if_missing`(
|
||||||
|
'tb_approval_flow_step',
|
||||||
|
'assignee_type',
|
||||||
|
'`assignee_type` varchar(16) DEFAULT NULL COMMENT ''审批对象类型'' AFTER `step_name`'
|
||||||
|
);
|
||||||
|
CALL `sp_add_column_if_missing`(
|
||||||
|
'tb_approval_flow_step',
|
||||||
|
'assignee_target_id',
|
||||||
|
'`assignee_target_id` bigint DEFAULT NULL COMMENT ''审批对象ID'' AFTER `assignee_type`'
|
||||||
|
);
|
||||||
|
CALL `sp_add_column_if_missing`(
|
||||||
|
'tb_approval_flow_step',
|
||||||
|
'assignee_target_code',
|
||||||
|
'`assignee_target_code` varchar(128) DEFAULT NULL COMMENT ''审批对象编码'' AFTER `assignee_target_id`'
|
||||||
|
);
|
||||||
|
CALL `sp_add_column_if_missing`(
|
||||||
|
'tb_approval_flow_step',
|
||||||
|
'assignee_target_name',
|
||||||
|
'`assignee_target_name` varchar(128) DEFAULT NULL COMMENT ''审批对象名称'' AFTER `assignee_target_code`'
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE `tb_approval_task`
|
||||||
|
MODIFY COLUMN `assignee_role_code` varchar(64) DEFAULT NULL COMMENT '指派角色编码';
|
||||||
|
|
||||||
|
CALL `sp_add_column_if_missing`(
|
||||||
|
'tb_approval_task',
|
||||||
|
'assignee_type',
|
||||||
|
'`assignee_type` varchar(16) DEFAULT NULL COMMENT ''审批对象类型'' AFTER `assignee_role_code`'
|
||||||
|
);
|
||||||
|
CALL `sp_add_column_if_missing`(
|
||||||
|
'tb_approval_task',
|
||||||
|
'assignee_target_id',
|
||||||
|
'`assignee_target_id` bigint DEFAULT NULL COMMENT ''审批对象ID'' AFTER `assignee_type`'
|
||||||
|
);
|
||||||
|
CALL `sp_add_column_if_missing`(
|
||||||
|
'tb_approval_task',
|
||||||
|
'assignee_target_code',
|
||||||
|
'`assignee_target_code` varchar(128) DEFAULT NULL COMMENT ''审批对象编码'' AFTER `assignee_target_id`'
|
||||||
|
);
|
||||||
|
CALL `sp_add_column_if_missing`(
|
||||||
|
'tb_approval_task',
|
||||||
|
'assignee_target_name',
|
||||||
|
'`assignee_target_name` varchar(128) DEFAULT NULL COMMENT ''审批对象名称'' AFTER `assignee_target_code`'
|
||||||
|
);
|
||||||
|
|
||||||
|
DROP PROCEDURE IF EXISTS `sp_patch_approval_assignee`;
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE PROCEDURE `sp_patch_approval_assignee`()
|
||||||
|
BEGIN
|
||||||
|
DECLARE v_super_role_id BIGINT;
|
||||||
|
DECLARE v_super_role_code VARCHAR(128);
|
||||||
|
DECLARE v_super_role_name VARCHAR(128);
|
||||||
|
DECLARE v_index_count INT DEFAULT 0;
|
||||||
|
|
||||||
|
SELECT `id`, `role_key`, `role_name`
|
||||||
|
INTO v_super_role_id, v_super_role_code, v_super_role_name
|
||||||
|
FROM `tb_sys_role`
|
||||||
|
WHERE `role_key` = 'super_admin'
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF v_super_role_id IS NULL THEN
|
||||||
|
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'super_admin role is required for approval assignee backfill';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
UPDATE `tb_approval_flow_step`
|
||||||
|
SET `assignee_type` = 'ROLE',
|
||||||
|
`assignee_target_id` = v_super_role_id,
|
||||||
|
`assignee_target_code` = v_super_role_code,
|
||||||
|
`assignee_target_name` = v_super_role_name
|
||||||
|
WHERE `assignee_type` IS NULL
|
||||||
|
OR `assignee_target_id` IS NULL;
|
||||||
|
|
||||||
|
UPDATE `tb_approval_task`
|
||||||
|
SET `assignee_role_code` = IFNULL(`assignee_role_code`, v_super_role_code),
|
||||||
|
`assignee_type` = 'ROLE',
|
||||||
|
`assignee_target_id` = v_super_role_id,
|
||||||
|
`assignee_target_code` = v_super_role_code,
|
||||||
|
`assignee_target_name` = v_super_role_name
|
||||||
|
WHERE `assignee_type` IS NULL
|
||||||
|
OR `assignee_target_id` IS NULL;
|
||||||
|
|
||||||
|
SELECT COUNT(1)
|
||||||
|
INTO v_index_count
|
||||||
|
FROM `information_schema`.`statistics`
|
||||||
|
WHERE `table_schema` = DATABASE()
|
||||||
|
AND `table_name` = 'tb_approval_task'
|
||||||
|
AND `index_name` = 'idx_approval_task_assignee';
|
||||||
|
|
||||||
|
IF v_index_count = 0 THEN
|
||||||
|
ALTER TABLE `tb_approval_task`
|
||||||
|
ADD INDEX `idx_approval_task_assignee` (`status`, `assignee_type`, `assignee_target_id`);
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
CALL `sp_patch_approval_assignee`();
|
||||||
|
DROP PROCEDURE IF EXISTS `sp_patch_approval_assignee`;
|
||||||
|
DROP PROCEDURE IF EXISTS `sp_add_column_if_missing`;
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
UPDATE `tb_sys_menu`
|
||||||
|
SET
|
||||||
|
`component` = '',
|
||||||
|
`modified` = NOW(),
|
||||||
|
`modified_by` = 1,
|
||||||
|
`remark` = '审批管理分组菜单'
|
||||||
|
WHERE `id` = 367100000000000001;
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000002, 367100000000000001, 0, 'menus.system.approvalFlow', '/sys/approval/flow', '/system/approval/ApprovalManage', '',
|
||||||
|
1, '', 1, 0, '2026-04-07 18:00:00', 1, '2026-04-07 18:00:00', 1, '审批管理-流程配置'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000002
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000003, 367100000000000001, 0, 'menus.system.approvalPending', '/sys/approval/pending', '/system/approval/ApprovalManage', '',
|
||||||
|
1, '', 2, 0, '2026-04-07 18:00:00', 1, '2026-04-07 18:00:00', 1, '审批管理-待审批'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000003
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000004, 367100000000000001, 0, 'menus.system.approvalProcessed', '/sys/approval/processed', '/system/approval/ApprovalManage', '',
|
||||||
|
1, '', 3, 0, '2026-04-07 18:00:00', 1, '2026-04-07 18:00:00', 1, '审批管理-已审批'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000004
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_menu` (
|
||||||
|
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||||
|
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
367100000000000005, 367100000000000001, 0, 'menus.system.approvalInitiated', '/sys/approval/initiated', '/system/approval/ApprovalManage', '',
|
||||||
|
1, '', 4, 0, '2026-04-07 18:00:00', 1, '2026-04-07 18:00:00', 1, '审批管理-我发起'
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_menu` WHERE `id` = 367100000000000005
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000102, 1, 367100000000000002
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000102
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000103, 1, 367100000000000003
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000103
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000104, 1, 367100000000000004
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000104
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`)
|
||||||
|
SELECT 367100000000000105, 1, 367100000000000005
|
||||||
|
FROM DUAL
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `tb_sys_role_menu` WHERE `id` = 367100000000000105
|
||||||
|
);
|
||||||
@@ -53,6 +53,22 @@ export const removeBotFromId = (id: string) => {
|
|||||||
return api.post<RequestResult>('/api/v1/bot/remove', { id });
|
return api.post<RequestResult>('/api/v1/bot/remove', { id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 提交 Bot 发布审批 */
|
||||||
|
export const submitBotPublishApproval = (id: string) => {
|
||||||
|
return api.post<RequestResult<number | string>>(
|
||||||
|
'/api/v1/bot/submitPublishApproval',
|
||||||
|
{ id },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 提交 Bot 删除审批 */
|
||||||
|
export const submitBotDeleteApproval = (id: string) => {
|
||||||
|
return api.post<RequestResult<number | string>>(
|
||||||
|
'/api/v1/bot/submitDeleteApproval',
|
||||||
|
{ id },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export interface GetMessageListParams {
|
export interface GetMessageListParams {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
botId: string;
|
botId: string;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export type ActionTone = 'danger' | 'default';
|
|||||||
|
|
||||||
export interface ActionButton {
|
export interface ActionButton {
|
||||||
icon?: any;
|
icon?: any;
|
||||||
text: string;
|
text: ((row: any) => string) | string;
|
||||||
className?: string;
|
className?: string;
|
||||||
permission?: string;
|
permission?: string;
|
||||||
placement?: ActionPlacement;
|
placement?: ActionPlacement;
|
||||||
@@ -135,6 +135,10 @@ function handleActionClick(event: Event, action: ActionButton, item: any) {
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
action.onClick(item);
|
action.onClick(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveActionText(action: ActionButton, item: any) {
|
||||||
|
return typeof action.text === 'function' ? action.text(item) : action.text;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -215,8 +219,8 @@ function handleActionClick(event: Event, action: ActionButton, item: any) {
|
|||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-for="action in inlineActions"
|
v-for="(action, actionIndex) in inlineActions"
|
||||||
:key="action.text"
|
:key="`${item.id ?? index}-inline-${actionIndex}`"
|
||||||
:icon="typeof action.icon === 'string' ? undefined : action.icon"
|
:icon="typeof action.icon === 'string' ? undefined : action.icon"
|
||||||
size="small"
|
size="small"
|
||||||
class="card-action-btn"
|
class="card-action-btn"
|
||||||
@@ -227,7 +231,7 @@ function handleActionClick(event: Event, action: ActionButton, item: any) {
|
|||||||
<template v-if="typeof action.icon === 'string'" #icon>
|
<template v-if="typeof action.icon === 'string'" #icon>
|
||||||
<IconifyIcon :icon="action.icon" />
|
<IconifyIcon :icon="action.icon" />
|
||||||
</template>
|
</template>
|
||||||
{{ action.text }}
|
{{ resolveActionText(action, item) }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
|
|
||||||
<ElDropdown
|
<ElDropdown
|
||||||
@@ -244,8 +248,8 @@ function handleActionClick(event: Event, action: ActionButton, item: any) {
|
|||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<ElDropdownMenu>
|
<ElDropdownMenu>
|
||||||
<ElDropdownItem
|
<ElDropdownItem
|
||||||
v-for="action in menuActions"
|
v-for="(action, actionIndex) in menuActions"
|
||||||
:key="action.text"
|
:key="`${item.id ?? index}-menu-${actionIndex}`"
|
||||||
:class="{
|
:class="{
|
||||||
'card-menu-item--danger': action.tone === 'danger',
|
'card-menu-item--danger': action.tone === 'danger',
|
||||||
}"
|
}"
|
||||||
@@ -259,7 +263,7 @@ function handleActionClick(event: Event, action: ActionButton, item: any) {
|
|||||||
/>
|
/>
|
||||||
<component v-else :is="action.icon" />
|
<component v-else :is="action.icon" />
|
||||||
</ElIcon>
|
</ElIcon>
|
||||||
<span>{{ action.text }}</span>
|
<span>{{ resolveActionText(action, item) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
</ElDropdownMenu>
|
</ElDropdownMenu>
|
||||||
|
|||||||
@@ -62,6 +62,15 @@
|
|||||||
"subProcess": "SubProcess",
|
"subProcess": "SubProcess",
|
||||||
"workflowSelect": "WorkflowSelect",
|
"workflowSelect": "WorkflowSelect",
|
||||||
"bochaSearch": "BochaSearch",
|
"bochaSearch": "BochaSearch",
|
||||||
|
"publishStatusDraft": "Draft",
|
||||||
|
"publishStatusPublishPending": "Publish Pending",
|
||||||
|
"publishStatusPublished": "Published",
|
||||||
|
"publishStatusDeletePending": "Delete Pending",
|
||||||
|
"publishStatusLabel": "Release",
|
||||||
|
"submitPublishApprovalConfirm": "The current draft will enter the publish approval flow. It becomes externally available only after approval.",
|
||||||
|
"submitDeleteApprovalConfirm": "The workflow will enter the delete approval flow. It will be physically deleted only after approval.",
|
||||||
|
"publishPendingHint": "There is already an approval in progress for this workflow.",
|
||||||
|
"deletePendingHint": "There is already an approval in progress for this workflow.",
|
||||||
"check": "Check",
|
"check": "Check",
|
||||||
"checkPassed": "Workflow check passed",
|
"checkPassed": "Workflow check passed",
|
||||||
"checkFailed": "Workflow check failed. Please fix the issues first",
|
"checkFailed": "Workflow check failed. Please fix the issues first",
|
||||||
|
|||||||
161
easyflow-ui-admin/app/src/locales/langs/en-US/approval.json
Normal file
161
easyflow-ui-admin/app/src/locales/langs/en-US/approval.json
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
{
|
||||||
|
"title": "Approval Detail",
|
||||||
|
"tab": {
|
||||||
|
"flow": "Flow Config",
|
||||||
|
"pending": "Pending",
|
||||||
|
"processed": "Processed",
|
||||||
|
"initiated": "Initiated"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"bot": "Chat Assistant",
|
||||||
|
"workflow": "Workflow",
|
||||||
|
"knowledge": "Knowledge Base"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"publish": "Publish",
|
||||||
|
"delete": "Delete",
|
||||||
|
"addFlow": "New Flow",
|
||||||
|
"editFlow": "Edit Flow",
|
||||||
|
"enableFlow": "Enable Flow",
|
||||||
|
"disableFlow": "Disable Flow",
|
||||||
|
"approve": "Approve",
|
||||||
|
"reject": "Reject",
|
||||||
|
"revoke": "Revoke",
|
||||||
|
"addScope": "Add Scope",
|
||||||
|
"addStep": "Add Step"
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"category": "Category",
|
||||||
|
"dept": "Department"
|
||||||
|
},
|
||||||
|
"assignee": {
|
||||||
|
"role": "Role",
|
||||||
|
"user": "User"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"pending": "Pending",
|
||||||
|
"processing": "Processing",
|
||||||
|
"approved": "Approved",
|
||||||
|
"rejected": "Rejected",
|
||||||
|
"revoked": "Revoked"
|
||||||
|
},
|
||||||
|
"section": {
|
||||||
|
"basic": "Basic Info",
|
||||||
|
"scope": "Scope Config",
|
||||||
|
"steps": "Approval Steps",
|
||||||
|
"tasks": "Approval Tasks",
|
||||||
|
"logs": "Approval Logs",
|
||||||
|
"snapshot": "Approval Snapshot"
|
||||||
|
},
|
||||||
|
"helper": {
|
||||||
|
"scope": "Limit the flow by resource category or applicant department. Leave empty to apply globally.",
|
||||||
|
"scopeEmpty": "No scope configured. This flow applies globally."
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"flowName": "Flow Name",
|
||||||
|
"resourceType": "Resource Type",
|
||||||
|
"actionType": "Action Type",
|
||||||
|
"priority": "Priority",
|
||||||
|
"version": "Version",
|
||||||
|
"status": "Status",
|
||||||
|
"remark": "Remark",
|
||||||
|
"scopeSummary": "Scope Summary",
|
||||||
|
"stepCount": "Step Count",
|
||||||
|
"includeChildren": "Include Children",
|
||||||
|
"stepName": "Step Name",
|
||||||
|
"stepNoLabel": "Step No.",
|
||||||
|
"currentStep": "Current Step",
|
||||||
|
"summary": "Summary",
|
||||||
|
"resourceId": "Resource ID",
|
||||||
|
"taskId": "Approval Task ID",
|
||||||
|
"applicant": "Applicant",
|
||||||
|
"applicantId": "Applicant ID",
|
||||||
|
"submittedAt": "Submitted At",
|
||||||
|
"finishedAt": "Finished At",
|
||||||
|
"assigneeTarget": "Assignee",
|
||||||
|
"actedBy": "Acted By",
|
||||||
|
"actedAt": "Acted At",
|
||||||
|
"comment": "Comment",
|
||||||
|
"eventType": "Event Type",
|
||||||
|
"operatorId": "Operator ID",
|
||||||
|
"operatorName": "Operator Name",
|
||||||
|
"createdAt": "Created At",
|
||||||
|
"eventInfo": "Event Info",
|
||||||
|
"stepNo": "Step {value}"
|
||||||
|
},
|
||||||
|
"event": {
|
||||||
|
"approved": "Approved",
|
||||||
|
"rejected": "Rejected",
|
||||||
|
"revoked": "Revoked",
|
||||||
|
"stepCreated": "Step Created",
|
||||||
|
"submitted": "Submitted"
|
||||||
|
},
|
||||||
|
"placeholder": {
|
||||||
|
"flowName": "Search flow name",
|
||||||
|
"keyword": "Search summary",
|
||||||
|
"resourceType": "Filter resource type",
|
||||||
|
"actionType": "Filter action type",
|
||||||
|
"flowStatus": "Filter flow status",
|
||||||
|
"instanceStatus": "Status",
|
||||||
|
"scopeValue": "Select scope value",
|
||||||
|
"assigneeType": "Select assignee type",
|
||||||
|
"assigneeTarget": "Select assignee",
|
||||||
|
"stepName": "Enter step name",
|
||||||
|
"actionComment": "Enter a comment"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"needStep": "At least one step is required",
|
||||||
|
"needStepAssignee": "Each approval step requires an assignee",
|
||||||
|
"saveSuccess": "Flow saved",
|
||||||
|
"statusUpdated": "Flow status updated",
|
||||||
|
"deleteSuccess": "Flow deleted",
|
||||||
|
"actionSuccess": "Approval action completed",
|
||||||
|
"confirmDeleteFlow": "This action cannot be undone. Continue?",
|
||||||
|
"confirmFlowStatus": "Confirm to {title}?",
|
||||||
|
"eventApproved": "Approval completed",
|
||||||
|
"eventApprovedStep": "Step {value} approved",
|
||||||
|
"eventRejected": "Approval rejected",
|
||||||
|
"eventRejectedStep": "Step {value} rejected",
|
||||||
|
"eventRevoked": "Approval revoked",
|
||||||
|
"eventRevokedStep": "Step {value} revoked",
|
||||||
|
"workflowSnapshotUntitled": "Untitled workflow snapshot",
|
||||||
|
"workflowSnapshotMissing": "Workflow snapshot not found",
|
||||||
|
"workflowSnapshotParseFailed": "Failed to parse workflow snapshot"
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"knowledgeBasic": "Basic Info",
|
||||||
|
"knowledgeConfig": "Retrieval Config",
|
||||||
|
"botOverview": "Assistant Overview",
|
||||||
|
"botModelConfig": "Model Config",
|
||||||
|
"botBindings": "Capability Bindings",
|
||||||
|
"systemPrompt": "System Prompt",
|
||||||
|
"department": "Department",
|
||||||
|
"category": "Category",
|
||||||
|
"modelName": "Model Name",
|
||||||
|
"vectorStoreType": "Vector Store Type",
|
||||||
|
"vectorEmbedModel": "Embedding Model",
|
||||||
|
"rerankModel": "Rerank Model",
|
||||||
|
"maxMessageCount": "Max Context Messages",
|
||||||
|
"canUpdateEmbeddingModel": "Allow Embedding Update",
|
||||||
|
"anonymousEnabled": "Anonymous Access",
|
||||||
|
"anonymousDisabled": "Login Only",
|
||||||
|
"knowledgeBindings": "Knowledge Bases",
|
||||||
|
"workflowBindings": "Workflows",
|
||||||
|
"pluginBindings": "Plugins",
|
||||||
|
"mcpBindings": "MCP",
|
||||||
|
"expandPrompt": "Expand",
|
||||||
|
"collapsePrompt": "Collapse",
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"disabled": "Disabled",
|
||||||
|
"notConfigured": "Not configured",
|
||||||
|
"noBindings": "No bindings",
|
||||||
|
"untitledKnowledge": "Untitled knowledge base",
|
||||||
|
"untitledBot": "Untitled assistant",
|
||||||
|
"unnamedKnowledge": "Unnamed knowledge base",
|
||||||
|
"unnamedWorkflow": "Unnamed workflow",
|
||||||
|
"unnamedPlugin": "Unnamed plugin",
|
||||||
|
"unnamedMcp": "Unnamed MCP"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,20 @@
|
|||||||
"deepThinking": "DeepThinking",
|
"deepThinking": "DeepThinking",
|
||||||
"enableDeepThinking": "EnableDeepThinking",
|
"enableDeepThinking": "EnableDeepThinking",
|
||||||
"publish": "Publish",
|
"publish": "Publish",
|
||||||
|
"publishStatusLabel": "Current Release",
|
||||||
|
"publishStatusDraft": "Draft",
|
||||||
|
"publishStatusDraftDesc": "Only the draft is saved. External chat and Public API are still unavailable.",
|
||||||
|
"publishStatusPublishPending": "Publish Pending",
|
||||||
|
"publishStatusPublishPendingDesc": "The assistant will switch to the new release after approval.",
|
||||||
|
"publishStatusPublished": "Published",
|
||||||
|
"publishStatusPublishedDesc": "The current release is externally available. Ongoing edits still stay in draft.",
|
||||||
|
"publishStatusDeletePending": "Delete Pending",
|
||||||
|
"publishStatusDeletePendingDesc": "The current release remains available, but it is no longer offered as a new binding candidate.",
|
||||||
|
"submitPublishApprovalConfirm": "Submit the current draft to publish approval. It becomes externally available only after approval.",
|
||||||
|
"submitDeleteApprovalConfirm": "Submit the bot to delete approval. It will be physically deleted only after approval.",
|
||||||
|
"publishPendingHint": "There is already an approval in progress for this bot.",
|
||||||
|
"deletePendingHint": "There is already an approval in progress for this bot.",
|
||||||
|
"publishRequiredHint": "There is no released version yet. Submit publish approval first.",
|
||||||
"postToWeChatOfficialAccount": "PostToWeChatOfficialAccount",
|
"postToWeChatOfficialAccount": "PostToWeChatOfficialAccount",
|
||||||
"publishExternalLink": "Publish External Chat Link",
|
"publishExternalLink": "Publish External Chat Link",
|
||||||
"configured": "Configured",
|
"configured": "Configured",
|
||||||
|
|||||||
@@ -41,7 +41,10 @@
|
|||||||
"markAsResolved": "MarkAsResolved",
|
"markAsResolved": "MarkAsResolved",
|
||||||
"optimizing": "Optimizing",
|
"optimizing": "Optimizing",
|
||||||
"regenerate": "Regenerate",
|
"regenerate": "Regenerate",
|
||||||
|
"republish": "Republish",
|
||||||
"hide": "Hide",
|
"hide": "Hide",
|
||||||
"more": "Mode",
|
"more": "Mode",
|
||||||
|
"submitDeleteApproval": "Submit Delete Approval",
|
||||||
|
"submitPublishApproval": "Submit Publish Approval",
|
||||||
"viewSegmentation": "ViewSegmentation"
|
"viewSegmentation": "ViewSegmentation"
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user