- PushDispatcher 删除 previewManual(预览体构建),保留 dispatchManual 手动投递 - PushSubscriptionController /push/test 不再返回 preview,改为「手动触发一次推送」语义(前端推送测试页已下线) - PushTestRequest 注释同步;同文件的「推送角色」过滤改走身份行(findUserIdsWithAnyRole),与 roleList 同源
This commit is contained in:
@@ -27,7 +27,7 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 推送订阅控制器:测试推送 + 推送日志记录 / 重试。
|
* 推送订阅控制器:手动触发推送(组/用户全量同步)+ 推送日志记录 / 重试。
|
||||||
*
|
*
|
||||||
* @author chenx
|
* @author chenx
|
||||||
* @date 2026/8/14
|
* @date 2026/8/14
|
||||||
@@ -45,17 +45,14 @@ public class PushSubscriptionController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AccessScopeService accessScopeService;
|
private AccessScopeService accessScopeService;
|
||||||
|
|
||||||
/** 测试推送:手动触发一次推送(投递 MQ),并返回请求预览(URL/头/体,密钥不外露)。 */
|
/** 手动触发一次推送(投递 MQ),供应用推送设置的组/用户全量同步等场景调用。 */
|
||||||
@PostMapping("Test")
|
@PostMapping("Test")
|
||||||
public Map<String, Object> test(@RequestBody PushTestRequest request) {
|
public Map<String, Object> test(@RequestBody PushTestRequest request) {
|
||||||
accessScopeService.requireSuper(); // 日志/推送管理仅超管
|
accessScopeService.requireSuper(); // 日志/推送管理仅超管
|
||||||
pushDispatcher.dispatchManual(request.getAppId(), request.getInterfacePath(), request.getTargetIds());
|
pushDispatcher.dispatchManual(request.getAppId(), request.getInterfacePath(), request.getTargetIds());
|
||||||
Map<String, Object> preview = pushDispatcher.previewManual(
|
|
||||||
request.getAppId(), request.getInterfacePath(), request.getTargetIds());
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
result.put("sent", true);
|
result.put("sent", true);
|
||||||
result.put("message", "已投递推送队列,可在「推送日志」查看实际响应与耗时");
|
result.put("message", "已投递推送队列,可在「推送日志」查看实际响应与耗时");
|
||||||
result.put("preview", preview);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import lombok.Data;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测试推送请求(测试推送页调用)。
|
* 手动触发推送请求(应用推送设置的组/用户全量同步调用)。
|
||||||
*
|
*
|
||||||
* @author chenx
|
* @author chenx
|
||||||
* @date 2026/8/14
|
* @date 2026/8/14
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.yunhai.userbase.entity.ApplicationInfo;
|
|||||||
import com.yunhai.userbase.entity.SysGroup;
|
import com.yunhai.userbase.entity.SysGroup;
|
||||||
import com.yunhai.userbase.entity.SysUser;
|
import com.yunhai.userbase.entity.SysUser;
|
||||||
import com.yunhai.userbase.entity.SysUserGroupRel;
|
import com.yunhai.userbase.entity.SysUserGroupRel;
|
||||||
|
import com.yunhai.userbase.enums.StatusEnum;
|
||||||
import com.yunhai.userbase.event.DataSyncEvent;
|
import com.yunhai.userbase.event.DataSyncEvent;
|
||||||
import com.yunhai.userbase.model.push.PushInterfaceType;
|
import com.yunhai.userbase.model.push.PushInterfaceType;
|
||||||
import com.yunhai.userbase.model.push.PushTaskMessage;
|
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.SysGroupRepository;
|
||||||
import com.yunhai.userbase.repository.SysUserGroupRelRepository;
|
import com.yunhai.userbase.repository.SysUserGroupRelRepository;
|
||||||
import com.yunhai.userbase.repository.SysUserRepository;
|
import com.yunhai.userbase.repository.SysUserRepository;
|
||||||
|
import com.yunhai.userbase.repository.SysUserRoleRelRepository;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -28,7 +30,7 @@ import java.util.stream.Collectors;
|
|||||||
/**
|
/**
|
||||||
* 推送分发器(生产方):<br>
|
* 推送分发器(生产方):<br>
|
||||||
* ① 自动触发:监听 {@link DataSyncEvent}(AFTER_COMMIT),遍历所有开启推送的订阅方,按配置过滤后投递 MQ;<br>
|
* ① 自动触发:监听 {@link DataSyncEvent}(AFTER_COMMIT),遍历所有开启推送的订阅方,按配置过滤后投递 MQ;<br>
|
||||||
* ② 手动测试:{@link #dispatchManual} 供测试推送页调用。<br><br>
|
* ② 手动触发:{@link #dispatchManual} 供应用推送设置的「组/用户全量同步」调用。<br><br>
|
||||||
* 签名在此处由基础平台计算一次(HMAC-SHA256(业务JSON+t, secret)),消息携带 t/sign,消费方原样 POST。
|
* 签名在此处由基础平台计算一次(HMAC-SHA256(业务JSON+t, secret)),消息携带 t/sign,消费方原样 POST。
|
||||||
*
|
*
|
||||||
* @author chenx
|
* @author chenx
|
||||||
@@ -47,6 +49,8 @@ public class PushDispatcher {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SysUserGroupRelRepository sysUserGroupRelRepository;
|
private SysUserGroupRelRepository sysUserGroupRelRepository;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private SysUserRoleRelRepository sysUserRoleRelRepository;
|
||||||
|
@Autowired
|
||||||
private GroupScopeHelper groupScopeHelper;
|
private GroupScopeHelper groupScopeHelper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PushPayloadAssembler pushPayloadAssembler;
|
private PushPayloadAssembler pushPayloadAssembler;
|
||||||
@@ -132,68 +136,6 @@ public class PushDispatcher {
|
|||||||
dispatchToOne(app, type, groups, users, "manual", force);
|
dispatchToOne(app, type, groups, users, "manual", force);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成一次推送请求的预览(URL / 头 / 体),供测试推送页展示。<br>
|
|
||||||
* 签名在服务端用 secret 计算,前端不持有密钥;实际投递仍走 {@link #dispatchManual} → MQ。
|
|
||||||
*/
|
|
||||||
public Map<String, Object> previewManual(Integer appId, String interfacePath, List<Integer> 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<SysGroup> groups = Collections.emptyList();
|
|
||||||
List<SysUser> 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<Object> bodies = buildBodies(type, groups, users);
|
|
||||||
if (bodies.isEmpty()) {
|
|
||||||
Map<String, Object> 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<String, Object> 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<String, String> buildHeaders(String t, String sign) {
|
|
||||||
Map<String, String> headers = new LinkedHashMap<>();
|
|
||||||
headers.put("Content-Type", "application/json");
|
|
||||||
headers.put("t", t);
|
|
||||||
headers.put("sign", sign);
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================== 核心 =====================
|
// ===================== 核心 =====================
|
||||||
|
|
||||||
/** 删除类事件专用投递:跳过 DB 重载与范围过滤,直接用事件携带的 id 构建删除体。 */
|
/** 删除类事件专用投递:跳过 DB 重载与范围过滤,直接用事件携带的 id 构建删除体。 */
|
||||||
@@ -331,6 +273,13 @@ public class PushDispatcher {
|
|||||||
if (schoolSet.isEmpty() && roleSet.isEmpty()) {
|
if (schoolSet.isEmpty() && roleSet.isEmpty()) {
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
// 角色过滤一次性批量取「命中推送角色的 userId」:事实源=身份行 sys_user_role_rel(与 roleList 同源),
|
||||||
|
// 此前读 sys_user.roles 快照列——该列已停写,新建/改过身份的用户恒为空,会被静默过滤掉不推送
|
||||||
|
Set<Integer> 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 -> {
|
return users.stream().filter(u -> {
|
||||||
if (!schoolSet.isEmpty()) {
|
if (!schoolSet.isEmpty()) {
|
||||||
List<SysUserGroupRel> rels = sysUserGroupRelRepository.findByUserId(u.getId());
|
List<SysUserGroupRel> rels = sysUserGroupRelRepository.findByUserId(u.getId());
|
||||||
@@ -342,11 +291,9 @@ public class PushDispatcher {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!roleSet.isEmpty()) {
|
if (roleMatchedUserIds != null && !roleMatchedUserIds.contains(u.getId())) {
|
||||||
if (u.getRoles() == null || u.getRoles().stream().noneMatch(roleSet::contains)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
@@ -382,13 +329,4 @@ public class PushDispatcher {
|
|||||||
}
|
}
|
||||||
return set;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user