feat(msgcenter): 列表筛选参数透传 + summary 聚合 UserMsg 分类计数(消息对接 2026-09-30)
- MessageCenterController.msgCenter/list:可选参数 msgType(0/1/2)/appKey/isRead(0/1)/keyword 透传代理,不传与现状一致(Header 回归无影响) - MsgCenterProxyService.list:非空才下发 query(旧 UserMsg 收到未知参数会被忽略——UserMsg 部署前线上筛选静默失效,属预期过渡,待人工清单含 UserMsg 部署) - MsgCenterProxyService.summary:优先调 UserMsg /oapi/msg/summary(新端点:unreadCount/sysUnread/bizUnread/approvalUnread/total/readCount 一次喂饱左栏);旧版无该端点(404/异常)自动降级 unreadCount 单查,分类字段缺省由前端兜底 - 单测:list_filtersPassedThrough(透传+空值不下发)、summary_prefersSummaryEndpoint、summary_legacyUserMsg_fallsBackToUnreadCount;既有 28 用例回归全绿(31 run 0 fail)
This commit is contained in:
@@ -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<String, Object> 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);
|
||||
}
|
||||
|
||||
/** 铃铛摘要:未读数 + 各应用待办角标 */
|
||||
|
||||
@@ -82,8 +82,13 @@ public class MsgCenterProxyService {
|
||||
return openCall.post(TARGET_USERMSG, path, query, body, JsonNode.class);
|
||||
}
|
||||
|
||||
/** 消息分页列表(已过滤删除/过期):{total, list[]},元素含改写后的 jumpUrl 与 showJump 判权出参 */
|
||||
public Map<String, Object> 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<String, Object> list(Integer userId, Integer pageIndex, Integer pageSize, Boolean onlyUnread,
|
||||
Integer msgType, String appKey, Integer isRead, String keyword) {
|
||||
Map<String, Object> 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<Map<String, Object>> 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<String, Object> summary(Integer userId, Integer schId) {
|
||||
Map<String, Object> out = new HashMap<>();
|
||||
out.put("unreadCount", 0L);
|
||||
@@ -120,13 +137,33 @@ public class MsgCenterProxyService {
|
||||
if (!enabled()) {
|
||||
return out;
|
||||
}
|
||||
boolean summaryFetched = false;
|
||||
try {
|
||||
Map<String, String> 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<String, String> 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<String, String> q = new LinkedHashMap<>();
|
||||
@@ -400,6 +437,13 @@ public class MsgCenterProxyService {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 有值(非空白)才进 query——空串不透传,保持与不传一致 */
|
||||
private static void putIfText(Map<String, String> 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;
|
||||
}
|
||||
|
||||
@@ -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<TargetSpec> 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<String, Object> out = service.list(201, 2, 20, Boolean.TRUE);
|
||||
Map<String, Object> out = service.list(201, 2, 20, Boolean.TRUE, null, null, null, null);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<Map<String, String>> 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<Map<String, String>> 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<String, Object> out = service.list(201, 1, 10, null);
|
||||
Map<String, Object> 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<String, Object> out = service.list(201, 1, 10, null);
|
||||
Map<String, Object> 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<Map<String, String>> 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<Map<String, String>> 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<String, Object> 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<String, Object> 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<String, Object> out = service.list(201, 1, 10, null);
|
||||
Map<String, Object> 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<String, Object> out = service.list(201, 1, 10, null);
|
||||
Map<String, Object> 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<String, Object> out = service.list(201, 1, 10, null);
|
||||
Map<String, Object> 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"));
|
||||
|
||||
Reference in New Issue
Block a user