From 6cc1bc23f7f89dd667deafdd080629aac3ae5376 Mon Sep 17 00:00:00 2001 From: chenxing Date: Fri, 18 Sep 2026 11:13:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor(push):=20=E6=B5=8B=E8=AF=95=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E6=94=B9=E4=B8=BA=E6=89=8B=E5=8A=A8=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=EF=BC=88=E7=A7=BB=E9=99=A4=E8=AF=B7=E6=B1=82=E9=A2=84=E8=A7=88?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PushDispatcher 删除 previewManual(预览体构建),保留 dispatchManual 手动投递 - PushSubscriptionController /push/test 不再返回 preview,改为「手动触发一次推送」语义(前端推送测试页已下线) - PushTestRequest 注释同步;同文件的「推送角色」过滤改走身份行(findUserIdsWithAnyRole),与 roleList 同源 --- .../api/PushSubscriptionController.java | 7 +- .../userbase/model/push/PushTestRequest.java | 2 +- .../userbase/service/push/PushDispatcher.java | 90 +++---------------- 3 files changed, 17 insertions(+), 82 deletions(-) diff --git a/src/main/java/com/yunhai/userbase/controller/api/PushSubscriptionController.java b/src/main/java/com/yunhai/userbase/controller/api/PushSubscriptionController.java index 31ae1ad..1ffda77 100644 --- a/src/main/java/com/yunhai/userbase/controller/api/PushSubscriptionController.java +++ b/src/main/java/com/yunhai/userbase/controller/api/PushSubscriptionController.java @@ -27,7 +27,7 @@ import java.util.LinkedHashMap; import java.util.Map; /** - * 推送订阅控制器:测试推送 + 推送日志记录 / 重试。 + * 推送订阅控制器:手动触发推送(组/用户全量同步)+ 推送日志记录 / 重试。 * * @author chenx * @date 2026/8/14 @@ -45,17 +45,14 @@ public class PushSubscriptionController { @Autowired private AccessScopeService accessScopeService; - /** 测试推送:手动触发一次推送(投递 MQ),并返回请求预览(URL/头/体,密钥不外露)。 */ + /** 手动触发一次推送(投递 MQ),供应用推送设置的组/用户全量同步等场景调用。 */ @PostMapping("Test") public Map test(@RequestBody PushTestRequest request) { accessScopeService.requireSuper(); // 日志/推送管理仅超管 pushDispatcher.dispatchManual(request.getAppId(), request.getInterfacePath(), request.getTargetIds()); - Map preview = pushDispatcher.previewManual( - request.getAppId(), request.getInterfacePath(), request.getTargetIds()); Map result = new LinkedHashMap<>(); result.put("sent", true); result.put("message", "已投递推送队列,可在「推送日志」查看实际响应与耗时"); - result.put("preview", preview); return result; } diff --git a/src/main/java/com/yunhai/userbase/model/push/PushTestRequest.java b/src/main/java/com/yunhai/userbase/model/push/PushTestRequest.java index 4859229..f05eb12 100644 --- a/src/main/java/com/yunhai/userbase/model/push/PushTestRequest.java +++ b/src/main/java/com/yunhai/userbase/model/push/PushTestRequest.java @@ -5,7 +5,7 @@ import lombok.Data; import java.util.List; /** - * 测试推送请求(测试推送页调用)。 + * 手动触发推送请求(应用推送设置的组/用户全量同步调用)。 * * @author chenx * @date 2026/8/14 diff --git a/src/main/java/com/yunhai/userbase/service/push/PushDispatcher.java b/src/main/java/com/yunhai/userbase/service/push/PushDispatcher.java index 3a56ddb..376787e 100644 --- a/src/main/java/com/yunhai/userbase/service/push/PushDispatcher.java +++ b/src/main/java/com/yunhai/userbase/service/push/PushDispatcher.java @@ -7,6 +7,7 @@ import com.yunhai.userbase.entity.ApplicationInfo; import com.yunhai.userbase.entity.SysGroup; import com.yunhai.userbase.entity.SysUser; import com.yunhai.userbase.entity.SysUserGroupRel; +import com.yunhai.userbase.enums.StatusEnum; import com.yunhai.userbase.event.DataSyncEvent; import com.yunhai.userbase.model.push.PushInterfaceType; import com.yunhai.userbase.model.push.PushTaskMessage; @@ -14,6 +15,7 @@ import com.yunhai.userbase.repository.ApplicationInfoRepository; import com.yunhai.userbase.repository.SysGroupRepository; import com.yunhai.userbase.repository.SysUserGroupRelRepository; import com.yunhai.userbase.repository.SysUserRepository; +import com.yunhai.userbase.repository.SysUserRoleRelRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; @@ -28,7 +30,7 @@ import java.util.stream.Collectors; /** * 推送分发器(生产方):
* ① 自动触发:监听 {@link DataSyncEvent}(AFTER_COMMIT),遍历所有开启推送的订阅方,按配置过滤后投递 MQ;
- * ② 手动测试:{@link #dispatchManual} 供测试推送页调用。

+ * ② 手动触发:{@link #dispatchManual} 供应用推送设置的「组/用户全量同步」调用。

* 签名在此处由基础平台计算一次(HMAC-SHA256(业务JSON+t, secret)),消息携带 t/sign,消费方原样 POST。 * * @author chenx @@ -47,6 +49,8 @@ public class PushDispatcher { @Autowired private SysUserGroupRelRepository sysUserGroupRelRepository; @Autowired + private SysUserRoleRelRepository sysUserRoleRelRepository; + @Autowired private GroupScopeHelper groupScopeHelper; @Autowired private PushPayloadAssembler pushPayloadAssembler; @@ -132,68 +136,6 @@ public class PushDispatcher { dispatchToOne(app, type, groups, users, "manual", force); } - /** - * 生成一次推送请求的预览(URL / 头 / 体),供测试推送页展示。
- * 签名在服务端用 secret 计算,前端不持有密钥;实际投递仍走 {@link #dispatchManual} → MQ。 - */ - public Map previewManual(Integer appId, String interfacePath, List targetIds) { - ApplicationInfo app = applicationInfoRepository.findById(appId).orElse(null); - if (app == null) { - throw new DException("应用不存在"); - } - PushInterfaceType type = PushInterfaceType.getByPath(interfacePath); - if (type == null) { - throw new DException("不支持的接口路径:" + interfacePath); - } - List groups = Collections.emptyList(); - List users = Collections.emptyList(); - if (type == PushInterfaceType.SYNC_GROUP) { - groups = sysGroupRepository.findAll(); - } else if (type == PushInterfaceType.SYNC_USER) { - users = sysUserRepository.findAll(); - } else if (type.isGroup()) { - groups = (targetIds == null || targetIds.isEmpty()) - ? sysGroupRepository.findAll() : sysGroupRepository.findAllById(targetIds); - } else { - users = (targetIds == null || targetIds.isEmpty()) - ? sysUserRepository.findAll() : sysUserRepository.findAllById(targetIds); - } - List bodies = buildBodies(type, groups, users); - if (bodies.isEmpty()) { - Map empty = new LinkedHashMap<>(); - empty.put("url", joinUrl(app.getPushBaseUrl(), type.getPath())); - empty.put("method", "POST"); - empty.put("headers", buildHeaders("", "")); - empty.put("body", "{}"); - empty.put("batch", 0); - return empty; - } - try { - String body = objectMapper.writeValueAsString(bodies.get(0)); - String t = String.valueOf(System.currentTimeMillis()); - String secret = (app.getPushSecret() != null && !app.getPushSecret().isEmpty()) - ? app.getPushSecret() : app.getAppSecret(); - String sign = PushSigner.sign(body, secret, t); - Map preview = new LinkedHashMap<>(); - preview.put("url", joinUrl(app.getPushBaseUrl(), type.getPath())); - preview.put("method", "POST"); - preview.put("headers", buildHeaders(t, sign)); - preview.put("body", body); - preview.put("batch", bodies.size()); - return preview; - } catch (Exception e) { - throw new DException("预览生成失败:" + e.getMessage()); - } - } - - private static Map buildHeaders(String t, String sign) { - Map headers = new LinkedHashMap<>(); - headers.put("Content-Type", "application/json"); - headers.put("t", t); - headers.put("sign", sign); - return headers; - } - // ===================== 核心 ===================== /** 删除类事件专用投递:跳过 DB 重载与范围过滤,直接用事件携带的 id 构建删除体。 */ @@ -331,6 +273,13 @@ public class PushDispatcher { if (schoolSet.isEmpty() && roleSet.isEmpty()) { return users; } + // 角色过滤一次性批量取「命中推送角色的 userId」:事实源=身份行 sys_user_role_rel(与 roleList 同源), + // 此前读 sys_user.roles 快照列——该列已停写,新建/改过身份的用户恒为空,会被静默过滤掉不推送 + Set roleMatchedUserIds = (roleSet.isEmpty() || users.isEmpty()) + ? null + : new HashSet<>(sysUserRoleRelRepository.findUserIdsWithAnyRole( + users.stream().map(SysUser::getId).collect(Collectors.toList()), + roleSet, StatusEnum.NORMAL)); return users.stream().filter(u -> { if (!schoolSet.isEmpty()) { List rels = sysUserGroupRelRepository.findByUserId(u.getId()); @@ -342,10 +291,8 @@ public class PushDispatcher { return false; } } - if (!roleSet.isEmpty()) { - if (u.getRoles() == null || u.getRoles().stream().noneMatch(roleSet::contains)) { - return false; - } + if (roleMatchedUserIds != null && !roleMatchedUserIds.contains(u.getId())) { + return false; } return true; }).collect(Collectors.toList()); @@ -382,13 +329,4 @@ public class PushDispatcher { } return set; } - - private static String joinUrl(String base, String path) { - if (base == null) { - return path; - } - String b = base.endsWith("/") ? base : base + "/"; - String p = path.startsWith("/") ? path.substring(1) : path; - return b + p; - } }