diff --git a/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/pom.xml b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/pom.xml
new file mode 100644
index 0000000..f9e5bc6
--- /dev/null
+++ b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/pom.xml
@@ -0,0 +1,25 @@
+
+
+
+ jeecg-system-api
+ org.jeecgframework.boot
+ ${jeecgProjectVersion}
+
+ 4.0.0
+
+ jeecg-system-openapi
+
+
+
+
+ org.jeecgframework.boot
+ jeecg-system-local-api
+
+
+
+
diff --git a/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/controller/TaskApiController.java b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/controller/TaskApiController.java
new file mode 100644
index 0000000..8b739f7
--- /dev/null
+++ b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/controller/TaskApiController.java
@@ -0,0 +1,46 @@
+package org.jeecg.openapi.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.openapi.service.ITaskApi;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 对外接口 - 任务/待办
+ *
+ * 统一前缀 /openapi/*,认证(签名)与放行由框架层按路径统一处理,本类不含任何鉴权代码。
+ */
+@Tag(name = "对外接口-任务")
+@RestController
+@RequestMapping("/openapi")
+@Slf4j
+public class TaskApiController {
+
+ @Resource
+ private ITaskApi taskApi;
+
+ /**
+ * 门户查询待办数
+ *
调用方(门户)通过 username 参数传入 sys_user.username(登录账号)。
+ */
+ @Operation(summary = "门户查询待办数", description = "门户查询待办数")
+ @GetMapping("/countTaskforporter")
+ public Result countTaskforporter(HttpServletRequest request) {
+ String username = request.getParameter("username");
+ if (oConvertUtils.isEmpty(username)) {
+ log.warn("【对外接口-门户待办数】未传入用户账号,无法统计, 参数名: username");
+ return Result.error("参数 username 不能为空");
+ }
+ Long count = taskApi.countTodoByUsername(username);
+ log.info("【对外接口-门户待办数】统计完成, username: {}, 待办数: {}", username, count);
+ return Result.OK(count);
+ }
+}
diff --git a/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/mapper/TaskApiMapper.java b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/mapper/TaskApiMapper.java
new file mode 100644
index 0000000..aa9e37d
--- /dev/null
+++ b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/mapper/TaskApiMapper.java
@@ -0,0 +1,30 @@
+package org.jeecg.openapi.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+/**
+ * 对外接口 - 任务/待办 通用查询(直查 Flowable 运行时表,不依赖任何业务模块)。
+ */
+@Mapper
+public interface TaskApiMapper {
+
+ /**
+ * 用户待办任务数(直接分配 + 候选人/候选组),仅统计未挂起任务。
+ * 与 ActivitiServiceImpl#findBaseTodoTasks 的待办口径一致。
+ *
+ * @param username 登录账号,对应 ACT_RU_TASK.ASSIGNEE_
+ */
+ @Select("SELECT COUNT(*) FROM ("
+ + "(SELECT DISTINCT RES.ID_ FROM ACT_RU_TASK RES "
+ + "INNER JOIN ACT_RU_IDENTITYLINK I ON I.TASK_ID_ = RES.ID_ "
+ + "WHERE RES.ASSIGNEE_ IS NULL AND I.TYPE_ = 'candidate' "
+ + "AND (I.USER_ID_ = #{username} OR I.GROUP_ID_ IN (SELECT g.GROUP_ID_ FROM ACT_ID_MEMBERSHIP g WHERE g.USER_ID_ = #{username})) "
+ + "AND RES.SUSPENSION_STATE_ = 1) "
+ + "UNION "
+ + "(SELECT DISTINCT RES.ID_ FROM ACT_RU_TASK RES "
+ + "WHERE RES.ASSIGNEE_ = #{username} AND RES.SUSPENSION_STATE_ = 1)"
+ + ") T")
+ Long countTodo(@Param("username") String username);
+}
diff --git a/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/service/ITaskApi.java b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/service/ITaskApi.java
new file mode 100644
index 0000000..db9bceb
--- /dev/null
+++ b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/service/ITaskApi.java
@@ -0,0 +1,17 @@
+package org.jeecg.openapi.service;
+
+/**
+ * 对外接口 - 任务/待办能力契约
+ *
+ * @author openapi
+ */
+public interface ITaskApi {
+
+ /**
+ * 统计某用户的待办任务数(Flowable:直接分配 + 候选人/候选组)
+ *
+ * @param username sys_user.username(登录账号,对应 Flowable ACT_RU_TASK.ASSIGNEE_)
+ * @return 待办数,无数据返回 0
+ */
+ Long countTodoByUsername(String username);
+}
diff --git a/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/service/impl/TaskApiImpl.java b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/service/impl/TaskApiImpl.java
new file mode 100644
index 0000000..ef13878
--- /dev/null
+++ b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/main/java/org/jeecg/openapi/service/impl/TaskApiImpl.java
@@ -0,0 +1,25 @@
+package org.jeecg.openapi.service.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.openapi.mapper.TaskApiMapper;
+import org.jeecg.openapi.service.ITaskApi;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * {@link ITaskApi} 的实现。通用能力,直查 Flowable 表,不依赖业务模块。
+ */
+@Service
+@Slf4j
+public class TaskApiImpl implements ITaskApi {
+
+ @Resource
+ private TaskApiMapper taskApiMapper;
+
+ @Override
+ public Long countTodoByUsername(String username) {
+ Long count = taskApiMapper.countTodo(username);
+ return count == null ? 0L : count;
+ }
+}
diff --git a/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/test/java/org/jeecg/openapi/controller/TaskApiControllerTest.java b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/test/java/org/jeecg/openapi/controller/TaskApiControllerTest.java
new file mode 100644
index 0000000..90c8419
--- /dev/null
+++ b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/test/java/org/jeecg/openapi/controller/TaskApiControllerTest.java
@@ -0,0 +1,65 @@
+package org.jeecg.openapi.controller;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.openapi.service.ITaskApi;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class TaskApiControllerTest {
+
+ @Mock
+ private ITaskApi taskApi;
+
+ @Mock
+ private HttpServletRequest request;
+
+ @InjectMocks
+ private TaskApiController controller;
+
+ @Test
+ void 传入用户账号时应返回待办数() {
+ when(request.getParameter("username")).thenReturn("admin");
+ when(taskApi.countTodoByUsername("admin")).thenReturn(3L);
+
+ Result result = controller.countTaskforporter(request);
+
+ assertTrue(result.isSuccess());
+ assertEquals(3L, result.getResult());
+ verify(taskApi).countTodoByUsername("admin");
+ }
+
+ @Test
+ void 用户账号为空时应返回参数错误且不查询() {
+ when(request.getParameter("username")).thenReturn(null);
+
+ Result result = controller.countTaskforporter(request);
+
+ assertFalse(result.isSuccess());
+ assertEquals("参数 username 不能为空", result.getMessage());
+ verify(taskApi, never()).countTodoByUsername(any());
+ }
+
+ @Test
+ void 用户账号为空串时应返回参数错误且不查询() {
+ when(request.getParameter("username")).thenReturn("");
+
+ Result result = controller.countTaskforporter(request);
+
+ assertFalse(result.isSuccess());
+ verify(taskApi, never()).countTodoByUsername(any());
+ }
+}
diff --git a/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/test/java/org/jeecg/openapi/service/impl/TaskApiImplTest.java b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/test/java/org/jeecg/openapi/service/impl/TaskApiImplTest.java
new file mode 100644
index 0000000..1586d92
--- /dev/null
+++ b/jeecg-module-system/jeecg-system-api/jeecg-system-openapi/src/test/java/org/jeecg/openapi/service/impl/TaskApiImplTest.java
@@ -0,0 +1,35 @@
+package org.jeecg.openapi.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+
+import org.jeecg.openapi.mapper.TaskApiMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class TaskApiImplTest {
+
+ @Mock
+ private TaskApiMapper taskApiMapper;
+
+ @InjectMocks
+ private TaskApiImpl taskApi;
+
+ @Test
+ void 应返回Mapper查询到的待办数() {
+ when(taskApiMapper.countTodo("admin")).thenReturn(5L);
+
+ assertEquals(5L, taskApi.countTodoByUsername("admin"));
+ }
+
+ @Test
+ void Mapper返回null时应归零() {
+ when(taskApiMapper.countTodo("nobody")).thenReturn(null);
+
+ assertEquals(0L, taskApi.countTodoByUsername("nobody"));
+ }
+}
diff --git a/jeecg-module-system/jeecg-system-api/pom.xml b/jeecg-module-system/jeecg-system-api/pom.xml
index 0be72d5..c6c72e0 100644
--- a/jeecg-module-system/jeecg-system-api/pom.xml
+++ b/jeecg-module-system/jeecg-system-api/pom.xml
@@ -15,6 +15,7 @@
jeecg-system-local-api
jeecg-system-cloud-api
+ jeecg-system-openapi
diff --git a/jeecg-module-system/jeecg-system-start/pom.xml b/jeecg-module-system/jeecg-system-start/pom.xml
index 58c1834..21d84d9 100644
--- a/jeecg-module-system/jeecg-system-start/pom.xml
+++ b/jeecg-module-system/jeecg-system-start/pom.xml
@@ -31,7 +31,12 @@
org.jeecgframework.boot
jeecg-module-flow
-
+
+
+ org.jeecgframework.boot
+ jeecg-system-openapi
+
+
org.flywaydb
diff --git a/jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml b/jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml
index 98e4d16..2c930ce 100644
--- a/jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml
+++ b/jeecg-module-system/jeecg-system-start/src/main/resources/application-prod.yml
@@ -251,7 +251,7 @@ jeecg:
#webapp文件路径
webapp: /opt/jeecg-boot/webapp
shiro:
- excludeUrls: /test/jeecgDemo/demo3,/test/jeecgDemo/redisDemo/**,/bigscreen/category/**,/bigscreen/visual/**,/bigscreen/map/**,/jmreport/bigscreen2/**,/api/getUserInfo
+ excludeUrls: /test/jeecgDemo/demo3,/test/jeecgDemo/redisDemo/**,/bigscreen/category/**,/bigscreen/visual/**,/bigscreen/map/**,/jmreport/bigscreen2/**,/api/getUserInfo,/openapi/*
#阿里云oss存储和大鱼短信秘钥配置
oss:
accessKey: ??
diff --git a/jeecg-module-system/jeecg-system-start/src/main/resources/application-test.yml b/jeecg-module-system/jeecg-system-start/src/main/resources/application-test.yml
index b700a3d..a40b0d7 100644
--- a/jeecg-module-system/jeecg-system-start/src/main/resources/application-test.yml
+++ b/jeecg-module-system/jeecg-system-start/src/main/resources/application-test.yml
@@ -250,7 +250,7 @@ jeecg:
#webapp文件路径
webapp: D://opt//webapp
shiro:
- excludeUrls: /test/jeecgDemo/demo3,/test/jeecgDemo/redisDemo/**,/bigscreen/category/**,/bigscreen/visual/**,/bigscreen/map/**,/jmreport/bigscreen2/**
+ excludeUrls: /test/jeecgDemo/demo3,/test/jeecgDemo/redisDemo/**,/bigscreen/category/**,/bigscreen/visual/**,/bigscreen/map/**,/jmreport/bigscreen2/**,/openapi/*
#阿里云oss存储和大鱼短信秘钥配置
oss:
accessKey: ??
diff --git a/pom.xml b/pom.xml
index 5357266..27311de 100644
--- a/pom.xml
+++ b/pom.xml
@@ -267,6 +267,12 @@
jeecg-system-cloud-api
${jeecgProjectVersion}
+
+
+ org.jeecgframework.boot
+ jeecg-system-openapi
+ ${jeecgProjectVersion}
+