- 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;
|
||||
|
||||
/**
|
||||
* 推送订阅控制器:测试推送 + 推送日志记录 / 重试。
|
||||
* 推送订阅控制器:手动触发推送(组/用户全量同步)+ 推送日志记录 / 重试。
|
||||
*
|
||||
* @author chenx
|
||||
* @date 2026/8/14
|
||||
@@ -45,17 +45,14 @@ public class PushSubscriptionController {
|
||||
@Autowired
|
||||
private AccessScopeService accessScopeService;
|
||||
|
||||
/** 测试推送:手动触发一次推送(投递 MQ),并返回请求预览(URL/头/体,密钥不外露)。 */
|
||||
/** 手动触发一次推送(投递 MQ),供应用推送设置的组/用户全量同步等场景调用。 */
|
||||
@PostMapping("Test")
|
||||
public Map<String, Object> test(@RequestBody PushTestRequest request) {
|
||||
accessScopeService.requireSuper(); // 日志/推送管理仅超管
|
||||
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<>();
|
||||
result.put("sent", true);
|
||||
result.put("message", "已投递推送队列,可在「推送日志」查看实际响应与耗时");
|
||||
result.put("preview", preview);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 测试推送请求(测试推送页调用)。
|
||||
* 手动触发推送请求(应用推送设置的组/用户全量同步调用)。
|
||||
*
|
||||
* @author chenx
|
||||
* @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.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;
|
||||
/**
|
||||
* 推送分发器(生产方):<br>
|
||||
* ① 自动触发:监听 {@link DataSyncEvent}(AFTER_COMMIT),遍历所有开启推送的订阅方,按配置过滤后投递 MQ;<br>
|
||||
* ② 手动测试:{@link #dispatchManual} 供测试推送页调用。<br><br>
|
||||
* ② 手动触发:{@link #dispatchManual} 供应用推送设置的「组/用户全量同步」调用。<br><br>
|
||||
* 签名在此处由基础平台计算一次(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 / 头 / 体),供测试推送页展示。<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 构建删除体。 */
|
||||
@@ -331,6 +273,13 @@ public class PushDispatcher {
|
||||
if (schoolSet.isEmpty() && roleSet.isEmpty()) {
|
||||
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 -> {
|
||||
if (!schoolSet.isEmpty()) {
|
||||
List<SysUserGroupRel> rels = sysUserGroupRelRepository.findByUserId(u.getId());
|
||||
@@ -342,11 +291,9 @@ public class PushDispatcher {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!roleSet.isEmpty()) {
|
||||
if (u.getRoles() == null || u.getRoles().stream().noneMatch(roleSet::contains)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user