feat(msgcenter): jumpUrl 基址改走应用注册表——by-code 主查/by-key 兜底取 pc_url(§3.2 归一化丢 fragment 拼 #),jump-bases 降为兜底 + 30s 不可达短路;附 findMessage 共享定位方法(供 B3 直批)
This commit is contained in:
@@ -21,8 +21,8 @@ import java.util.Map;
|
||||
* <ul>
|
||||
* <li>列表/角标:/oapi/msg/list、/oapi/msg/unreadCount、/oapi/msg/todo/list;</li>
|
||||
* <li>已读:/oapi/msg/read、/oapi/msg/readAll(按当前登录人代理,userId 服务端取);</li>
|
||||
* <li>jumpUrl 改写:jumpType=1 且 jumpPath 非空且 bizType 配了基址 → 基址 + "/#" + jumpPath
|
||||
* (jumpPath 是裸相对路径如 /task/123,各应用落地页是 hash 路由)。</li>
|
||||
* <li>jumpUrl 改写:绝对 URL 直通;相对路径按「注册表 by-code/by-key 取 pc_url(B2 主链路)
|
||||
* → jump-bases 手工表兜底」解析基址,归一化后拼 "#" + jumpPath(裸相对路径如 /task/123)。</li>
|
||||
* </ul>
|
||||
* 2026-09-20 事故修复:原实现用共享 IDHttp 出站,其 AppSignInterceptor 只剥<b>本应用</b>
|
||||
* base-path(/userbase),目标 URL 带 /usermsg ctx 时签名路径多一层前缀 → UserMsg 验签 401,
|
||||
@@ -36,10 +36,21 @@ import java.util.Map;
|
||||
public class MsgCenterProxyService {
|
||||
|
||||
private static final String TARGET_USERMSG = "usermsg";
|
||||
private static final String TARGET_USERBASE = "userbase";
|
||||
|
||||
/** UserBase 查址失败短路窗口:列表逐行查址,下游停摆时避免每行都吃一次连接超时 */
|
||||
static final long USERBASE_RETRY_MS = 30_000L;
|
||||
|
||||
/** 消息定位扫描上限:5 页 × 100 条(UserMsg 无按 id 直查端点,逐页扫描兜底) */
|
||||
static final int MSG_SCAN_PAGES = 5;
|
||||
static final int MSG_SCAN_PAGE_SIZE = 100;
|
||||
|
||||
private final YschOpenCall openCall;
|
||||
private final MsgCenterProperties properties;
|
||||
|
||||
/** 0=正常;>0=UserBase 查址失败后的自愈时间戳(System.currentTimeMillis 基准) */
|
||||
private volatile long userbaseDownUntil;
|
||||
|
||||
public MsgCenterProxyService(YschOpenCall openCall, MsgCenterProperties properties) {
|
||||
this.openCall = openCall;
|
||||
this.properties = properties;
|
||||
@@ -155,6 +166,41 @@ public class MsgCenterProxyService {
|
||||
post("/oapi/msg/readAll", new HashMap<>(), body);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 msgId 定位当前登录人的消息(卡片直批 B3 用)。归属校验=按登录人分页拉列表后命中:
|
||||
* 他人消息/已删除/已过期一律查无,统一返回 null(不区分原因,防越权探测)。
|
||||
*/
|
||||
public JsonNode findMessage(Integer userId, Long msgId) {
|
||||
if (!enabled() || msgId == null) {
|
||||
return null;
|
||||
}
|
||||
for (int page = 1; page <= MSG_SCAN_PAGES; page++) {
|
||||
try {
|
||||
Map<String, String> query = new LinkedHashMap<>();
|
||||
query.put("userId", String.valueOf(userId));
|
||||
query.put("pageIndex", String.valueOf(page));
|
||||
query.put("pageSize", String.valueOf(MSG_SCAN_PAGE_SIZE));
|
||||
JsonNode data = get("/oapi/msg/list", query);
|
||||
if (data == null || !data.path("content").isArray()) {
|
||||
return null;
|
||||
}
|
||||
for (JsonNode row : data.get("content")) {
|
||||
if (row.has("msgId") && row.get("msgId").asLong() == msgId) {
|
||||
return row;
|
||||
}
|
||||
}
|
||||
if (data.get("content").size() < MSG_SCAN_PAGE_SIZE
|
||||
|| data.path("total").asLong() <= (long) page * MSG_SCAN_PAGE_SIZE) {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[msgcenter] UserMsg 消息定位不可达 msgId={}:{}", msgId, e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* WS 握手票据:以应用身份为登录人换一次性票据(UserMsg /oapi/msg/ws/ticket),
|
||||
* 拼上浏览器可达的 WS 端点返回。未配置 WS 地址 / UserMsg 不可达 → null(前端维持轮询兜底表现)。
|
||||
@@ -204,10 +250,15 @@ public class MsgCenterProxyService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 基址 + "/#" + jumpPath。基址查询优先「页面归属应用」(jumpApp,代发场景页面属业务方时由
|
||||
* 发送方声明),缺省回落 bizType(=发送方自己的消息分类);都查不到返回 null(前端不渲染跳转)。
|
||||
* jumpPath 已是绝对 URL(http/https)时直接透传——兼容存量已落库的绝对地址消息,且不再
|
||||
* 重复拼基址产出双前缀畸形链接。非应用内跳转(jumpType≠1)/无路径 → null。
|
||||
* 基址解析(B2 跳转基址统一):jumpType=1 且 jumpPath 非空时——
|
||||
* <ol>
|
||||
* <li>绝对 URL(http/https)直接透传,不二次拼基址(存量兼容);</li>
|
||||
* <li>注册表:归属应用 jumpApp(值域=app_code)主查 → appKey 数字兜底(自发场景发送方=页面归属),
|
||||
* 取 pc_url 按 §3.2 归一化(丢 fragment、保留 index.htm(l))后拼 "#";</li>
|
||||
* <li>兜底:jump-bases 手工表(jumpApp/bizType 双维度 key),join 形态维持旧 "/#";</li>
|
||||
* <li>均无 → null(前端不渲染跳转入口)。</li>
|
||||
* </ol>
|
||||
* 注册表是多端地址(pc/app/小程序/钉钉)唯一可持续来源,手工表仅为过渡兜底。
|
||||
*/
|
||||
private String jumpUrl(JsonNode row) {
|
||||
Integer jumpType = row.has("jumpType") && row.get("jumpType").isNumber() ? row.get("jumpType").asInt() : null;
|
||||
@@ -221,6 +272,24 @@ public class MsgCenterProxyService {
|
||||
if (path.startsWith("http://") || path.startsWith("https://")) {
|
||||
return path;
|
||||
}
|
||||
String p = path.startsWith("/") ? path : "/" + path;
|
||||
String appKey = text(row, "appKey");
|
||||
|
||||
if (!userbaseDown()) {
|
||||
String pcUrl = StringUtils.hasText(jumpApp) ? pcUrlByCode(jumpApp.trim()) : null;
|
||||
if (!StringUtils.hasText(pcUrl) && StringUtils.hasText(appKey)) {
|
||||
pcUrl = pcUrlByKey(appKey.trim());
|
||||
}
|
||||
if (StringUtils.hasText(pcUrl)) {
|
||||
String base = pcUrl.trim();
|
||||
int hash = base.indexOf('#');
|
||||
if (hash >= 0) {
|
||||
base = base.substring(0, hash);
|
||||
}
|
||||
return base + "#" + p;
|
||||
}
|
||||
}
|
||||
|
||||
String base = StringUtils.hasText(jumpApp)
|
||||
? properties.getJumpBases().get(jumpApp.trim()) : null;
|
||||
if (!StringUtils.hasText(base) && StringUtils.hasText(bizType)) {
|
||||
@@ -233,10 +302,36 @@ public class MsgCenterProxyService {
|
||||
while (b.endsWith("/")) {
|
||||
b = b.substring(0, b.length() - 1);
|
||||
}
|
||||
String p = path.startsWith("/") ? path : "/" + path;
|
||||
return b + "/#" + p;
|
||||
}
|
||||
|
||||
/** 注册表 pc_url 查询(by-code 主查)。UserBase 不可达/查无 → null 走手工表兜底,失败进短路窗口 */
|
||||
private String pcUrlByCode(String appCode) {
|
||||
return lookupPcUrl("/oapi/app/by-code/" + appCode);
|
||||
}
|
||||
|
||||
/** 注册表 pc_url 查询(by-key 兜底,appKey 是 app_key 列非主键 id) */
|
||||
private String pcUrlByKey(String appKey) {
|
||||
return lookupPcUrl("/oapi/app/by-key/" + appKey);
|
||||
}
|
||||
|
||||
private String lookupPcUrl(String path) {
|
||||
try {
|
||||
JsonNode app = openCall.get(TARGET_USERBASE, path, null, JsonNode.class);
|
||||
boolean ok = app != null && app.hasNonNull("pcUrl") && StringUtils.hasText(app.get("pcUrl").asText());
|
||||
userbaseDownUntil = 0L;
|
||||
return ok ? app.get("pcUrl").asText() : null;
|
||||
} catch (Exception e) {
|
||||
userbaseDownUntil = System.currentTimeMillis() + USERBASE_RETRY_MS;
|
||||
log.warn("[msgcenter] UserBase 查址不可达({}ms 内走手工表兜底)path={}:{}", USERBASE_RETRY_MS, path, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean userbaseDown() {
|
||||
return System.currentTimeMillis() < userbaseDownUntil;
|
||||
}
|
||||
|
||||
private static Map<String, Object> emptyPage() {
|
||||
Map<String, Object> out = new HashMap<>();
|
||||
out.put("total", 0);
|
||||
|
||||
Reference in New Issue
Block a user