diff --git a/src/main/java/com/yunhai/flowcenter/controller/oapi/OapiFlowController.java b/src/main/java/com/yunhai/flowcenter/controller/oapi/OapiFlowController.java index bdba1c0..faf1d2a 100644 --- a/src/main/java/com/yunhai/flowcenter/controller/oapi/OapiFlowController.java +++ b/src/main/java/com/yunhai/flowcenter/controller/oapi/OapiFlowController.java @@ -111,8 +111,9 @@ public class OapiFlowController { @RequestParam(value = "bizType", required = false) String bizType, @RequestParam(value = "bizId", required = false) String bizId, @RequestParam(value = "mode", required = false) String mode, + @RequestParam(value = "taskId", required = false) Long taskId, @RequestParam("schId") Long schId) { - return flowService.embedUrl(schId, instanceId, bizType, bizId, mode); + return flowService.embedUrl(schId, instanceId, bizType, bizId, mode, taskId); } /** diff --git a/src/main/java/com/yunhai/flowcenter/service/OapiFlowService.java b/src/main/java/com/yunhai/flowcenter/service/OapiFlowService.java index b3e6846..914afb5 100644 --- a/src/main/java/com/yunhai/flowcenter/service/OapiFlowService.java +++ b/src/main/java/com/yunhai/flowcenter/service/OapiFlowService.java @@ -518,10 +518,34 @@ public class OapiFlowService { * 接收人;签发侧必须与之一致,否则"抄送我的"点开得、进度嵌入页换址却拒发(2026-10-17 记)。 */ public OapiModels.EmbedUrlVo embedUrl(Long schId, Long instanceId, String bizType, String bizId, String mode) { + return embedUrl(schId, instanceId, bizType, bizId, mode, null); + } + + /** + * mode=task(F9)直跳中心任务办理页:pageUrl = front-url + {@code #/task/{taskId}}, + * 无 token(CAS 会话鉴权页,办理权由该页加载时的用户面接口按 assigneeId 复核)—— + * 子应用"在自家页放办理入口"场景用;taskId 必填,缺则 PARAM_ERROR。 + */ + public OapiModels.EmbedUrlVo embedUrl(Long schId, Long instanceId, String bizType, String bizId, + String mode, Long taskId) { // 配置缺失优先于办件定位:fail-fast 纪律要的是"无论如何都不发打不开的地址" if (embedFrontUrl == null || embedFrontUrl.trim().isEmpty()) { throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "嵌入页地址未配置:flow-center.embed.front-url"); } + String base = embedFrontUrl.trim(); + if (!base.endsWith("/")) { + base = base + "/"; + } + String normMode = mode == null || mode.trim().isEmpty() ? "auto" : mode.trim(); + if ("task".equals(normMode)) { + if (taskId == null) { + throw new BizException(ErrorCode.PARAM_ERROR, "mode=task 需要 taskId(任务办理页定位)"); + } + OapiModels.EmbedUrlVo out = new OapiModels.EmbedUrlVo(); + out.setPageUrl(base + "#/task/" + taskId); + out.setExpiresIn(0); + return out; + } Long resolvedInstanceId; if (instanceId != null) { resolvedInstanceId = requireInstance(schId, instanceId).getId(); @@ -536,12 +560,7 @@ public class OapiFlowService { resolvedInstanceId = vo.getInstanceId(); } // mode 与 DiagramTokenService 内部缺省同值("auto"),URL 里显式带上仅为前端展示冗余 - String normMode = mode == null || mode.trim().isEmpty() ? "auto" : mode.trim(); String token = tokenService.issue(schId, resolvedInstanceId, null, normMode); - String base = embedFrontUrl.trim(); - if (!base.endsWith("/")) { - base = base + "/"; - } OapiModels.EmbedUrlVo out = new OapiModels.EmbedUrlVo(); out.setPageUrl(base + "#/embed/instance?token=" + token + "&mode=" + normMode); out.setExpiresIn(DiagramTokenService.TTL_MS / 1000); diff --git a/src/test/java/com/yunhai/flowcenter/service/OapiFlowEmbedTaskUrlTest.java b/src/test/java/com/yunhai/flowcenter/service/OapiFlowEmbedTaskUrlTest.java new file mode 100644 index 0000000..4e2468a --- /dev/null +++ b/src/test/java/com/yunhai/flowcenter/service/OapiFlowEmbedTaskUrlTest.java @@ -0,0 +1,77 @@ +package com.yunhai.flowcenter.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yunhai.flowcenter.engine.AssigneeResolver; +import com.yunhai.flowcenter.engine.FlowEngine; +import com.yunhai.flowcenter.model.OapiModels; +import com.yunhai.flowcenter.repository.FlwCallbackRegRepository; +import com.yunhai.flowcenter.repository.FlwCcRepository; +import com.yunhai.flowcenter.repository.FlwDefRepository; +import com.yunhai.flowcenter.repository.FlwInstanceRepository; +import com.yunhai.flowcenter.repository.FlwTaskRepository; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.openapi.base.OpenApiAppClient; +import com.yunhai.platform.openapi.base.OpenApiUserClient; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.ObjectProvider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * F9 mode=task 直跳任务办理页(P6.2):pageUrl = front-url + #/task/{taskId}(无 token); + * taskId 缺失 PARAM_ERROR;front-url 未配置沿用 fail-fast 纪律(先于一切定位)。 + */ +@DisplayName("embed/url mode=task(F9 任务页直跳)") +class OapiFlowEmbedTaskUrlTest { + + private OapiFlowService service; + + @BeforeEach + void setUp() { + service = new OapiFlowService(Mockito.mock(FlwDefRepository.class), + Mockito.mock(FlwInstanceRepository.class), Mockito.mock(FlwTaskRepository.class), + Mockito.mock(FlwCcRepository.class), Mockito.mock(FlwCallbackRegRepository.class), + Mockito.mock(FlowInstanceService.class), Mockito.mock(FlowTaskService.class), + Mockito.mock(FlowEngine.class), Mockito.mock(AssigneeResolver.class), + new ObjectMapper(), Mockito.mock(OpenApiUserClient.class), + Mockito.mock(OpenApiAppClient.class), Mockito.mock(DiagramTokenService.class), + Mockito.mock(ObjectProvider.class)); + } + + private void frontUrl(String value) { + org.springframework.test.util.ReflectionTestUtils.setField(service, "embedFrontUrl", value); + } + + @Test + void taskModeIssuesCenterTaskPageUrlWithoutToken() { + frontUrl("http://fc.local/flow-center"); + OapiModels.EmbedUrlVo vo = service.embedUrl(1L, null, null, null, "task", 88L); + assertEquals("http://fc.local/flow-center/#/task/88", vo.getPageUrl(), + "pageUrl = front-url(补尾斜杠)+ #/task/{taskId}"); + assertEquals(0L, vo.getExpiresIn(), "无 token 模式无有效期,expiresIn=0"); + } + + @Test + void taskModeWithoutTaskIdRejected() { + frontUrl("http://fc.local/flow-center"); + BizException e = assertThrows(BizException.class, + () -> service.embedUrl(1L, null, null, null, "task", null)); + assertEquals(ErrorCode.PARAM_ERROR, e.getErrorCode(), "缺 taskId 是参数错误"); + assertTrue(e.getMessage().contains("taskId"), "报错须点名缺失参数"); + } + + @Test + void missingFrontUrlStillFailsFastEvenForTaskMode() { + frontUrl(" "); + BizException e = assertThrows(BizException.class, + () -> service.embedUrl(1L, null, null, null, "task", 88L)); + assertEquals(ErrorCode.INTERNAL_SERVER_ERROR, e.getErrorCode(), + "配置缺失优先于 mode 分支:宁可拒绝也不发打不开的地址"); + } +}