推送订阅添加批量删除功能
ci-userbase-service / build-and-push (push) Successful in 1m56s

This commit is contained in:
chenxing
2026-08-19 20:38:12 +08:00
parent f3656057d4
commit d472902957
3 changed files with 116 additions and 0 deletions
@@ -0,0 +1,27 @@
package com.yunhai.userbase.common.context;
/**
* 开放接口(oapi)调用方来源上下文:在 oapi 请求链路中保存"触发本次数据变更的订阅方 appKey",
* 供业务层发布 {@code DataSyncEvent} 时写入事件来源,推送分发器据此回推时排除来源应用自身
* (避免应用 A 自己调 oapi 写接口后又收到自己的回声)。<br><br>
* <b>生命周期:</b>由 {@code OapiCallerAppInterceptor} 在 {@code preHandle} 写入、
* {@code afterCompletion} 强制清除(防线程池复用泄漏)。管理后台 /api/* 写接口不走 oapi 拦截器,
* 此处为 null,表示"来源非订阅方",推送不对其做来源排除(全量投递)。<br><br>
* 设计与 {@link UserContext} 同套路(ThreadLocal + remove)。
*/
public class CallerAppContext {
private static final ThreadLocal<Integer> CALLER_APP_KEY = new ThreadLocal<>();
public static void setAppKey(Integer appKey) {
CALLER_APP_KEY.set(appKey);
}
public static Integer getAppKey() {
return CALLER_APP_KEY.get();
}
public static void clear() {
CALLER_APP_KEY.remove();
}
}
@@ -0,0 +1,49 @@
package com.yunhai.userbase.config;
import com.yunhai.platform.appauth.AppAuthFilter;
import com.yunhai.userbase.common.context.CallerAppContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* oapi 调用方来源注入拦截器:在 oapi 请求处理前,从 yh-app-auth 验签过滤器写入的
* {@link AppAuthFilter#APP_AUTH_ATTR}(调用方 appKey)解析出整型 appKey,
* 存入 {@link CallerAppContext};请求结束(无论成功/异常)在 {@code afterCompletion} 强制清除,
* 防止线程池复用导致 appKey 串号、进而误把其他订阅方排除。<br><br>
* 配合 {@code PushDispatcher} 的来源排除逻辑,实现"应用自己触发的变更不回推给自己"。
* 管理后台 /api/* 不走此拦截器 → CallerAppContext 为 null → 推送对该来源不做排除(全量投递)。
*/
@Component
public class OapiCallerAppInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
Object attr = request.getAttribute(AppAuthFilter.APP_AUTH_ATTR);
if (attr == null) {
return true;
}
String appKeyStr = (attr instanceof String) ? (String) attr : String.valueOf(attr);
if (StringUtils.isBlank(appKeyStr)) {
return true;
}
try {
CallerAppContext.setAppKey(Integer.valueOf(appKeyStr.trim()));
} catch (NumberFormatException ignored) {
// 非法 appKey 格式:不设置来源,等同"非订阅方来源",推送全量投递
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
CallerAppContext.clear();
}
}
@@ -0,0 +1,40 @@
package com.yunhai.userbase.model.sysuser;
/**
* 批量删除用户-组关系单项(与 BasePlatform 开放接口模型 {@code SysUserGroupDeleteItem} 字段对齐)。<br>
* 每条记录携带自身所属学校范围 {@code schoolId},供 {@code SysUserService.deleteUsers} 逐条校验:<br>
* groupId 必须属于 schoolId 子树且不能等于 schoolId(禁止跨校解绑 / 整校移除)。
*/
public class SysUserGroupDeleteItem {
/** 待解绑用户 ID */
private Integer userId;
/** 待解绑的组 ID(必须属于 schoolId 子树,且不能等于 schoolId) */
private Integer groupId;
/** 学校组 ID:限定该条解绑的学校范围 */
private Integer schoolId;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getGroupId() {
return groupId;
}
public void setGroupId(Integer groupId) {
this.groupId = groupId;
}
public Integer getSchoolId() {
return schoolId;
}
public void setSchoolId(Integer schoolId) {
this.schoolId = schoolId;
}
}