diff --git a/src/main/java/com/yunhai/userhome/controller/api/MessageCenterController.java b/src/main/java/com/yunhai/userhome/controller/api/MessageCenterController.java index 6b4a7c7..1444fbf 100644 --- a/src/main/java/com/yunhai/userhome/controller/api/MessageCenterController.java +++ b/src/main/java/com/yunhai/userhome/controller/api/MessageCenterController.java @@ -40,13 +40,18 @@ public class MessageCenterController { return 0; } - /** 消息分页列表:{total, list[]}(元素含改写后的绝对 jumpUrl;UserMsg 不可达降级空) */ + /** 消息分页列表:{total, list[]}(元素含改写后的绝对 jumpUrl;UserMsg 不可达降级空)。 + * 可选服务端筛选:msgType 0/1/2、appKey、isRead 0/1、keyword 标题/内容模糊(不传与现状一致)。 */ @GetMapping("msgCenter/list") public Map list(BoaUser authUserWithDept, @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "count", required = false) Integer count, - @RequestParam(value = "onlyUnread", required = false) Boolean onlyUnread) { - return proxy.list(authUserWithDept.getId(), page, count, onlyUnread); + @RequestParam(value = "onlyUnread", required = false) Boolean onlyUnread, + @RequestParam(value = "msgType", required = false) Integer msgType, + @RequestParam(value = "appKey", required = false) String appKey, + @RequestParam(value = "isRead", required = false) Integer isRead, + @RequestParam(value = "keyword", required = false) String keyword) { + return proxy.list(authUserWithDept.getId(), page, count, onlyUnread, msgType, appKey, isRead, keyword); } /** 铃铛摘要:未读数 + 各应用待办角标 */ diff --git a/src/main/java/com/yunhai/userhome/service/MsgCenterProxyService.java b/src/main/java/com/yunhai/userhome/service/MsgCenterProxyService.java index c8919ce..e20635e 100644 --- a/src/main/java/com/yunhai/userhome/service/MsgCenterProxyService.java +++ b/src/main/java/com/yunhai/userhome/service/MsgCenterProxyService.java @@ -82,8 +82,13 @@ public class MsgCenterProxyService { return openCall.post(TARGET_USERMSG, path, query, body, JsonNode.class); } - /** 消息分页列表(已过滤删除/过期):{total, list[]},元素含改写后的 jumpUrl 与 showJump 判权出参 */ - public Map list(Integer userId, Integer pageIndex, Integer pageSize, Boolean onlyUnread) { + /** + * 消息分页列表(已过滤删除/过期):{total, list[]},元素含改写后的 jumpUrl 与 showJump 判权出参。 + * 可选筛选(null/空白不透传,旧 UserMsg 收到未知参数会忽略——不传时行为与历史版本一致): + * msgType 0/1/2、appKey 精确、isRead 0/1、keyword 标题/内容模糊。 + */ + public Map list(Integer userId, Integer pageIndex, Integer pageSize, Boolean onlyUnread, + Integer msgType, String appKey, Integer isRead, String keyword) { Map empty = emptyPage(); if (!enabled()) { return empty; @@ -95,6 +100,14 @@ public class MsgCenterProxyService { query.put("pageIndex", String.valueOf(pageIndex == null || pageIndex < 1 ? 1 : pageIndex)); query.put("pageSize", String.valueOf(pageSize == null || pageSize < 1 ? 10 : Math.min(pageSize, 100))); query.put("onlyUnread", String.valueOf(Boolean.TRUE.equals(onlyUnread))); + if (msgType != null) { + query.put("msgType", String.valueOf(msgType)); + } + putIfText(query, "appKey", appKey); + if (isRead != null) { + query.put("isRead", String.valueOf(isRead)); + } + putIfText(query, "keyword", keyword); JsonNode data = get("/oapi/msg/list", query); List> rows = new ArrayList<>(); if (data != null && data.path("content").isArray()) { @@ -112,7 +125,11 @@ public class MsgCenterProxyService { } } - /** 铃铛摘要:未读数 + 各应用待办角标(停机各自降级 0/空) */ + /** + * 铃铛摘要:未读数 + 各应用待办角标 + 分类计数(停机各自降级 0/空)。 + * 分类计数(sysUnread/bizUnread/approvalUnread/total/readCount)来自 UserMsg /oapi/msg/summary + * (消息中心左栏一次喂饱);旧版 UserMsg 无该端点 → 字段缺省不出现,前端按降级处理。 + */ public Map summary(Integer userId, Integer schId) { Map out = new HashMap<>(); out.put("unreadCount", 0L); @@ -120,13 +137,33 @@ public class MsgCenterProxyService { if (!enabled()) { return out; } + boolean summaryFetched = false; try { Map q = new LinkedHashMap<>(); q.put("userId", String.valueOf(userId)); - JsonNode unread = get("/oapi/msg/unreadCount", q); - out.put("unreadCount", unread == null || !unread.isNumber() ? 0L : unread.asLong()); + JsonNode vo = get("/oapi/msg/summary", q); + if (vo != null && vo.has("unreadCount")) { + out.put("unreadCount", vo.get("unreadCount").asLong()); + out.put("sysUnread", vo.path("sysUnread").asLong(0)); + out.put("bizUnread", vo.path("bizUnread").asLong(0)); + out.put("approvalUnread", vo.path("approvalUnread").asLong(0)); + out.put("total", vo.path("total").asLong(0)); + out.put("readCount", vo.path("readCount").asLong(0)); + summaryFetched = true; + } } catch (Exception e) { - log.warn("[msgcenter] UserMsg 未读数不可达(降级 0)userId={}:{}", userId, e.toString()); + // 旧版 UserMsg 无 summary 端点:降级走 unreadCount 单查,字段缺省由前端兜底 + log.debug("[msgcenter] UserMsg 摘要端点不可用(降级未读单查)userId={}:{}", userId, e.toString()); + } + if (!summaryFetched) { + try { + Map q = new LinkedHashMap<>(); + q.put("userId", String.valueOf(userId)); + JsonNode unread = get("/oapi/msg/unreadCount", q); + out.put("unreadCount", unread == null || !unread.isNumber() ? 0L : unread.asLong()); + } catch (Exception e) { + log.warn("[msgcenter] UserMsg 未读数不可达(降级 0)userId={}:{}", userId, e.toString()); + } } try { Map q = new LinkedHashMap<>(); @@ -400,6 +437,13 @@ public class MsgCenterProxyService { return out; } + /** 有值(非空白)才进 query——空串不透传,保持与不传一致 */ + private static void putIfText(Map query, String key, String value) { + if (StringUtils.hasText(value)) { + query.put(key, value.trim()); + } + } + private static String text(JsonNode node, String field) { return node != null && node.has(field) && !node.get(field).isNull() ? node.get(field).asText() : null; } diff --git a/src/test/java/com/yunhai/userhome/service/MsgCenterProxyServiceTest.java b/src/test/java/com/yunhai/userhome/service/MsgCenterProxyServiceTest.java index 179ec11..98464c2 100644 --- a/src/test/java/com/yunhai/userhome/service/MsgCenterProxyServiceTest.java +++ b/src/test/java/com/yunhai/userhome/service/MsgCenterProxyServiceTest.java @@ -87,7 +87,7 @@ class MsgCenterProxyServiceTest { when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/list"), anyMap(), eq(JsonNode.class))) .thenReturn(null); - service.list(201, 1, 10, null); + service.list(201, 1, 10, null, null, null, null, null); ArgumentCaptor spec = ArgumentCaptor.forClass(TargetSpec.class); verify(openCall).get(spec.capture(), eq("/oapi/msg/list"), anyMap(), eq(JsonNode.class)); @@ -109,7 +109,7 @@ class MsgCenterProxyServiceTest { + "{\"msgId\":2,\"title\":\"无跳转\",\"content\":\"系统通知\",\"msgType\":0," + "\"jumpType\":0,\"jumpPath\":null,\"bizType\":null,\"isRead\":1}]}")); - Map out = service.list(201, 2, 20, Boolean.TRUE); + Map out = service.list(201, 2, 20, Boolean.TRUE, null, null, null, null); @SuppressWarnings("unchecked") ArgumentCaptor> query = ArgumentCaptor.forClass(Map.class); @@ -135,7 +135,7 @@ class MsgCenterProxyServiceTest { void list_defaultsPageParams() { when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/list"), anyMap(), eq(JsonNode.class))) .thenReturn(null); - service.list(201, 0, 999, null); + service.list(201, 0, 999, null, null, null, null, null); @SuppressWarnings("unchecked") ArgumentCaptor> query = ArgumentCaptor.forClass(Map.class); verify(openCall).get(any(TargetSpec.class), eq("/oapi/msg/list"), query.capture(), eq(JsonNode.class)); @@ -150,7 +150,7 @@ class MsgCenterProxyServiceTest { when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/list"), anyMap(), eq(JsonNode.class))) .thenReturn(json("{\"total\":1,\"content\":[{\"msgId\":1,\"title\":\"通知公告\"," + "\"content\":\"您有一条通知\",\"msgType\":0,\"appKey\":\"100617\",\"isRead\":0}]}")); - Map out = service.list(201, 1, 10, null); + Map out = service.list(201, 1, 10, null, null, null, null, null); Map first = (Map) ((java.util.List) out.get("list")).get(0); assertEquals("通知公告", first.get("appName"), "appKey 命中映射补应用名"); } @@ -259,7 +259,7 @@ class MsgCenterProxyServiceTest { void list_userMsgDown_degradesToEmpty() { when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/list"), anyMap(), eq(JsonNode.class))) .thenThrow(new OpenApiException("OpenAPI request failed: httpStatus=401")); - Map out = service.list(201, 1, 10, null); + Map out = service.list(201, 1, 10, null, null, null, null, null); assertEquals(0, out.get("total")); assertEquals(0, ((java.util.List) out.get("list")).size()); } @@ -309,6 +309,68 @@ class MsgCenterProxyServiceTest { assertEquals(1, ((java.util.List) out2.get("todos")).size()); } + /** /msg 页服务端筛选(消息对接 2026-09-30):msgType/appKey/isRead/keyword 透传,空值不下发 */ + @Test + void list_filtersPassedThrough() throws Exception { + when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/list"), anyMap(), eq(JsonNode.class))) + .thenReturn(json("{\"total\":0,\"content\":[]}")); + + service.list(201, 1, 10, null, 2, " 100019 ", 0, "请假"); + + @SuppressWarnings("unchecked") + ArgumentCaptor> query = ArgumentCaptor.forClass(Map.class); + verify(openCall).get(any(TargetSpec.class), eq("/oapi/msg/list"), query.capture(), eq(JsonNode.class)); + assertEquals("2", query.getValue().get("msgType")); + assertEquals("100019", query.getValue().get("appKey"), "appKey 去首尾空白后透传"); + assertEquals("0", query.getValue().get("isRead")); + assertEquals("请假", query.getValue().get("keyword")); + + // 全空筛选不出现新键(与历史调用逐字一致) + service.list(201, 1, 10, null, null, " ", null, ""); + @SuppressWarnings("unchecked") + ArgumentCaptor> query2 = ArgumentCaptor.forClass(Map.class); + verify(openCall, org.mockito.Mockito.times(2)) + .get(any(TargetSpec.class), eq("/oapi/msg/list"), query2.capture(), eq(JsonNode.class)); + assertFalse(query2.getAllValues().get(1).containsKey("msgType")); + assertFalse(query2.getAllValues().get(1).containsKey("appKey")); + assertFalse(query2.getAllValues().get(1).containsKey("isRead")); + assertFalse(query2.getAllValues().get(1).containsKey("keyword")); + } + + /** 摘要优先走 /oapi/msg/summary(分类计数一次喂饱),且不再单查 unreadCount */ + @Test + void summary_prefersSummaryEndpoint() throws Exception { + when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/summary"), anyMap(), eq(JsonNode.class))) + .thenReturn(json("{\"unreadCount\":5,\"sysUnread\":2,\"bizUnread\":1,\"approvalUnread\":2," + + "\"total\":9,\"readCount\":4}")); + when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/todo/list"), anyMap(), eq(JsonNode.class))) + .thenReturn(json("[]")); + + Map out = service.summary(201, 1); + assertEquals(5L, out.get("unreadCount")); + assertEquals(2L, out.get("sysUnread")); + assertEquals(1L, out.get("bizUnread")); + assertEquals(2L, out.get("approvalUnread")); + assertEquals(9L, out.get("total")); + assertEquals(4L, out.get("readCount")); + verify(openCall, never()).get(any(TargetSpec.class), eq("/oapi/msg/unreadCount"), anyMap(), eq(JsonNode.class)); + } + + /** 旧版 UserMsg 无 summary 端点(404/异常)→ 降级 unreadCount 单查,分类字段缺省 */ + @Test + void summary_legacyUserMsg_fallsBackToUnreadCount() throws Exception { + when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/summary"), anyMap(), eq(JsonNode.class))) + .thenThrow(new OpenApiException("OpenAPI request failed: httpStatus=404")); + when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/unreadCount"), anyMap(), eq(JsonNode.class))) + .thenReturn(json("7")); + when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/todo/list"), anyMap(), eq(JsonNode.class))) + .thenReturn(json("[]")); + + Map out = service.summary(201, 1); + assertEquals(7L, out.get("unreadCount")); + assertFalse(out.containsKey("sysUnread"), "旧端点无分类计数,字段缺省由前端降级"); + } + @Test void read_sendsOwnerScopedPayload() { service.read(201, java.util.Collections.singletonList(7L)); @@ -320,7 +382,7 @@ class MsgCenterProxyServiceTest { void disabled_degradesWithoutOutbound() { properties.setUsermsgBaseUrl(""); when(openCall.hasTarget("usermsg")).thenReturn(false); - assertEquals(0, service.list(201, 1, 10, null).get("total")); + assertEquals(0, service.list(201, 1, 10, null, null, null, null, null).get("total")); service.read(201, java.util.Collections.singletonList(7L)); verify(openCall, never()).get(any(TargetSpec.class), anyString(), anyMap(), any()); verify(openCall, never()).post(any(TargetSpec.class), anyString(), anyMap(), any(), any()); @@ -377,7 +439,7 @@ class MsgCenterProxyServiceTest { } private Map firstRow() throws Exception { - Map out = service.list(201, 1, 10, null); + Map out = service.list(201, 1, 10, null, null, null, null, null); return (Map) ((java.util.List) out.get("list")).get(0); } @@ -450,7 +512,7 @@ class MsgCenterProxyServiceTest { .thenReturn(json("{\"total\":2,\"content\":[" + "{\"msgId\":1,\"jumpType\":1,\"jumpPath\":\"/list/1\",\"jumpApp\":\"notice\",\"isRead\":0}," + "{\"msgId\":2,\"jumpType\":1,\"jumpPath\":\"/list/2\",\"jumpApp\":\"notice\",\"isRead\":0}]}")); - Map out = service.list(201, 1, 10, null); + Map out = service.list(201, 1, 10, null, null, null, null, null); java.util.List rows = (java.util.List) out.get("list"); assertEquals(Boolean.TRUE, ((Map) rows.get(0)).get("showJump")); assertEquals(Boolean.TRUE, ((Map) rows.get(1)).get("showJump")); @@ -467,7 +529,7 @@ class MsgCenterProxyServiceTest { .thenReturn(json("{\"total\":2,\"content\":[" + "{\"msgId\":1,\"jumpType\":1,\"jumpPath\":\"/task/1\",\"appKey\":\"100009\",\"bizType\":\"flow.task\",\"isRead\":0}," + "{\"msgId\":2,\"jumpType\":1,\"jumpPath\":\"/task/2\",\"appKey\":\"100009\",\"bizType\":\"flow.task\",\"isRead\":0}]}")); - Map out = service.list(201, 1, 10, null); + Map out = service.list(201, 1, 10, null, null, null, null, null); java.util.List rows = (java.util.List) out.get("list"); assertNull(((Map) rows.get(0)).get("jumpUrl"), "注册表不可达无兜底 → null"); assertNull(((Map) rows.get(1)).get("jumpUrl"));