body = new HashMap<>();
body.put("userId", userId);
- JsonNode data = http.post(url("/oapi/msg/ws/ticket"), body, JsonNode.class);
+ JsonNode data = post("/oapi/msg/ws/ticket", new HashMap<>(), body);
String ticket = data == null || !data.hasNonNull("ticket") ? null : data.get("ticket").asText();
if (!StringUtils.hasText(ticket)) {
return null;
diff --git a/src/test/java/com/yunhai/userhome/service/MsgCenterProxyServiceTest.java b/src/test/java/com/yunhai/userhome/service/MsgCenterProxyServiceTest.java
index a79e0d8..98805c8 100644
--- a/src/test/java/com/yunhai/userhome/service/MsgCenterProxyServiceTest.java
+++ b/src/test/java/com/yunhai/userhome/service/MsgCenterProxyServiceTest.java
@@ -1,22 +1,24 @@
package com.yunhai.userhome.service;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.yunhai.platform.http.DHttpException;
-import com.yunhai.platform.utils.http.IDHttp;
+import com.yunhai.platform.openapi.core.OpenApiException;
+import com.yunhai.platform.openapi.core.TargetSpec;
+import com.yunhai.platform.openapi.core.YschOpenCall;
import com.yunhai.userhome.config.MsgCenterProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -28,31 +30,53 @@ import static org.mockito.Mockito.when;
* (list 信封 {total,content[]} / unreadCount 裸数字 / todo list 数组)与
* jumpUrl 改写规则(jumpType=1 + bizType 命中基址 → 基址 + "/#" + jumpPath)。
* UserMsg 不可达降级空结果(铃铛不 500)。
+ *
+ * 出站通道 2026-09-20 起由 IDHttp 换 YschOpenCall(泛化签名直调,Tier 0):
+ * 160 事故根因 = IDHttp 共享签名拦截器只剥本应用 base-path,目标带 ctx(/usermsg)时
+ * 签名路径多一层前缀 → UserMsg AppAuthFilter 401。回归断言:TargetSpec 从
+ * usermsg-base-url 解析出 basePath=/usermsg(per-target 剥离),调用 path 为 ctx 裸路径。
*/
-@DisplayName("MsgCenterProxyService - UserMsg 代理与 jumpUrl 改写")
+@DisplayName("MsgCenterProxyService - YschOpenCall 代理与 jumpUrl 改写")
class MsgCenterProxyServiceTest {
- private IDHttp http;
+ /** 160 生产形态的 UserMsg 直连地址(含目标 context-path) */
+ private static final String PROD_BASE_URL = "http://usermsg-service:10009/usermsg";
+
+ private YschOpenCall openCall;
private MsgCenterProperties properties;
private MsgCenterProxyService service;
@BeforeEach
void setUp() {
- http = mock(IDHttp.class);
+ openCall = mock(YschOpenCall.class);
properties = new MsgCenterProperties();
- properties.setUsermsgBaseUrl("http://localhost:8080");
+ properties.setUsermsgBaseUrl(PROD_BASE_URL);
properties.getJumpBases().put("flow.task", "http://localhost:8088/");
- service = new MsgCenterProxyService(http, properties, new ObjectMapper());
+ service = new MsgCenterProxyService(openCall, properties);
}
- private static com.fasterxml.jackson.databind.JsonNode json(String s) throws Exception {
+ private static JsonNode json(String s) throws Exception {
return new ObjectMapper().readTree(s);
}
@Test
- void list_rewritesJumpUrlForFlowTask() throws Exception {
+ void list_targetSpecStripsUsermsgCtx_forPerTargetSign() {
+ // 160 回归:签名剥离的载体是 TargetSpec.basePath,必须等于 /usermsg
+ when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/list"), anyMap(), eq(JsonNode.class)))
+ .thenReturn(null);
+
+ service.list(201, 1, 10, null);
+
+ ArgumentCaptor spec = ArgumentCaptor.forClass(TargetSpec.class);
+ verify(openCall).get(spec.capture(), eq("/oapi/msg/list"), anyMap(), eq(JsonNode.class));
+ assertEquals("http://usermsg-service:10009", spec.getValue().getBaseHost());
+ assertEquals("/usermsg", spec.getValue().getBasePath(), "basePath=目标 ctx,AppSignInterceptor 据此剥离后签名");
+ }
+
+ @Test
+ void list_buildsQueryParams_andRewritesJumpUrl() throws Exception {
properties.getAppNames().put("100617", "通知公告");
- when(http.get(contains("/oapi/msg/list?userId=201"), eq(com.fasterxml.jackson.databind.JsonNode.class)))
+ when(openCall.get(any(TargetSpec.class), eq("/oapi/msg/list"), anyMap(), eq(JsonNode.class)))
.thenReturn(json("{\"total\":2,\"content\":["
+ "{\"msgId\":1,\"title\":\"新待办\",\"content\":\"您有新的审批任务\",\"msgType\":2,"
+ "\"jumpType\":1,\"jumpPath\":\"/task/123\",\"appKey\":\"100019\",\"bizType\":\"flow.task\","
@@ -60,7 +84,16 @@ class MsgCenterProxyServiceTest {
+ "{\"msgId\":2,\"title\":\"无跳转\",\"content\":\"系统通知\",\"msgType\":0,"
+ "\"jumpType\":0,\"jumpPath\":null,\"bizType\":null,\"isRead\":1}]}"));
- Map out = service.list(201, 1, 10, null);
+ Map out = service.list(201, 2, 20, Boolean.TRUE);
+
+ @SuppressWarnings("unchecked")
+ ArgumentCaptor