推送订阅添加批量删除功能
This commit is contained in:
@@ -12,6 +12,8 @@ public class DWebMvcConfig implements WebMvcConfigurer
|
||||
{
|
||||
@Autowired
|
||||
private BoaUserContextInterceptor boaUserContextInterceptor;
|
||||
@Autowired
|
||||
private OapiCallerAppInterceptor oapiCallerAppInterceptor;
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry)
|
||||
@@ -25,6 +27,9 @@ public class DWebMvcConfig implements WebMvcConfigurer
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// oapi 调用方来源注入:仅命中 /oapi/**,供推送来源排除使用
|
||||
registry.addInterceptor(oapiCallerAppInterceptor)
|
||||
.addPathPatterns("/oapi/**");
|
||||
registry.addInterceptor(boaUserContextInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns("/login", "/logout", "/static/**", "/error", "/upload/**");
|
||||
|
||||
@@ -87,11 +87,12 @@ public class GroupController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除组:组下存在子组或用户时拒绝删除(根节点不可删)。无返回值。
|
||||
* 删除组:组下存在子组或用户时拒绝删除(根节点不可删)。<br>
|
||||
* 新增 {@code schoolId} 边界:服务端强制 id 属于该校子树且不等于 schoolId,避免删学校根组/跨校删组。
|
||||
*/
|
||||
@PostMapping("delete/{id}")
|
||||
public void delete(@PathVariable Integer id) {
|
||||
sysGroupService.delete(id);
|
||||
@PostMapping("delete/{id}/{schoolId}")
|
||||
public void delete(@PathVariable Integer id, @PathVariable Integer schoolId) {
|
||||
sysGroupService.delete(id, schoolId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.yunhai.platform.utils.model.BoaUser;
|
||||
import com.yunhai.userbase.common.validation.ValidationGroups;
|
||||
import com.yunhai.userbase.config.Config;
|
||||
import com.yunhai.userbase.entity.ApplicationInfo;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserGroupDeleteItem;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserMoveRequest;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserRequest;
|
||||
import com.yunhai.userbase.repository.ApplicationInfoRepository;
|
||||
@@ -168,11 +169,20 @@ public class UserController {
|
||||
* 删除用户(按组解绑,语义同管理端「从组中删除用户」):仅解除该用户与指定组的关联;<br>
|
||||
* 仅当用户已无任何组关联时,才级联删除 sys_user 及其角色绑定。<br>
|
||||
* 这样多校用户只会被退出指定学校,而不会被整体误删。<br>
|
||||
* 若用户仅归属这一个组,调用后该用户将被真正删除(组关联清空即触发级联删)。
|
||||
* 新增 {@code schoolId} 边界:服务端强制 groupId 属于该校子树且不等于 schoolId,避免跨校/整校移除。
|
||||
*/
|
||||
@PostMapping("delete/{id}/{groupId}")
|
||||
public void delete(@PathVariable Integer id, @PathVariable Integer groupId) {
|
||||
sysUserService.deleteFromGroup(id, groupId);
|
||||
@PostMapping("delete/{id}/{groupId}/{schoolId}")
|
||||
public void delete(@PathVariable Integer id, @PathVariable Integer groupId, @PathVariable Integer schoolId) {
|
||||
sysUserService.deleteFromGroup(id, groupId, schoolId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除用户-组关系:请求体为 {@code SysUserGroupDeleteItem} 数组,每条携带自身 (userId, groupId, schoolId);<br>
|
||||
* 服务端逐条校验学校边界后解绑,语义与单条 delete 一致。
|
||||
*/
|
||||
@PostMapping("deleteUsers")
|
||||
public void deleteUsers(@RequestBody List<SysUserGroupDeleteItem> items) {
|
||||
sysUserService.deleteUsers(items);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,4 +24,7 @@ public class DataSyncEvent {
|
||||
private List<Integer> userIds;
|
||||
/** 组维度的关联事件(如删除用户=解除组关系)携带的组 id */
|
||||
private Integer groupId;
|
||||
/** 触发本次变更的 oapi 调用方 appKey;非 oapi 来源(如管理后台)为 null。
|
||||
* 推送分发器据此回推时排除来源应用自身,避免收到自己的回声。 */
|
||||
private Integer sourceAppKey;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,18 @@ public interface ISysGroupService {
|
||||
|
||||
void delete(Integer id);
|
||||
|
||||
/**
|
||||
* 开放接口专用:在学校(组)范围内删除组。<br>
|
||||
* 与 {@link #delete(Integer)} 删除逻辑一致,但额外强制边界:<br>
|
||||
* ① id 不能等于 schoolId(禁止删学校根组);<br>
|
||||
* ② id 必须属于 schoolId 子树(禁止跨校删组)。<br>
|
||||
* 任意一条不满足即抛异常。
|
||||
*
|
||||
* @param id 待删除组ID
|
||||
* @param schoolId 学校组ID(限定删除范围)
|
||||
*/
|
||||
void delete(Integer id, Integer schoolId);
|
||||
|
||||
List<SysGroupVo> list(SysGroupQuery query);
|
||||
|
||||
/** 获取组织架构树;表为空时自动创建根节点「教育云中心」(pid=0)。 */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.yunhai.userbase.service;
|
||||
|
||||
import com.yunhai.userbase.model.sysuser.BatchImportResult;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserGroupDeleteItem;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserQuery;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserRequest;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserVo;
|
||||
@@ -46,6 +47,26 @@ public interface ISysUserService {
|
||||
*/
|
||||
void batchDeleteFromGroup(List<Integer> userIds, Integer groupId);
|
||||
|
||||
/**
|
||||
* 开放接口专用:在学校(组)范围内按组解绑用户。<br>
|
||||
* 与 {@link #deleteFromGroup(Integer, Integer)} 语义一致,但额外强制边界:<br>
|
||||
* ① groupId 不能等于 schoolId(禁止对学校根组解绑,避免整校移除);<br>
|
||||
* ② groupId 必须属于 schoolId 子树(禁止跨校解绑)。<br>
|
||||
* 任意一条不满足即抛异常。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param groupId 当前组ID
|
||||
* @param schoolId 学校组ID(限定解绑范围)
|
||||
*/
|
||||
void deleteFromGroup(Integer userId, Integer groupId, Integer schoolId);
|
||||
|
||||
/**
|
||||
* 开放接口专用:批量删除用户-组关系;每条携带自身 (userId, groupId, schoolId),逐条做上述学校边界校验后解绑。
|
||||
*
|
||||
* @param items 待删除项列表({@link SysUserGroupDeleteItem})
|
||||
*/
|
||||
void deleteUsers(List<SysUserGroupDeleteItem> items);
|
||||
|
||||
/**
|
||||
* 启用/禁用用户。<br>
|
||||
*
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.yunhai.platform.utils.model.BoaGroup;
|
||||
import com.yunhai.userbase.enums.GroupTypeEnum;
|
||||
import com.yunhai.userbase.enums.IEnum;
|
||||
import com.yunhai.userbase.event.DataSyncEvent;
|
||||
import com.yunhai.userbase.common.context.CallerAppContext;
|
||||
import com.yunhai.userbase.model.push.PushInterfaceType;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import com.yunhai.userbase.model.sysgroup.SysGroupBatchRequest;
|
||||
@@ -199,6 +200,18 @@ public class SysGroupService implements ISysGroupService {
|
||||
@Override
|
||||
@Transactional
|
||||
public void delete(Integer id) {
|
||||
deleteInternal(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void delete(Integer id, Integer schoolId) {
|
||||
// 开放接口边界:id 必须属于 schoolId 子树且不能等于 schoolId(禁止删学校根组/跨校删组)
|
||||
assertWithinSchool(id, schoolId);
|
||||
deleteInternal(id);
|
||||
}
|
||||
|
||||
private void deleteInternal(Integer id) {
|
||||
SysGroup entity = sysGroupRepository.findById(id)
|
||||
.orElseThrow(() -> new DException("系统组不存在"));
|
||||
// 根节点不可删除
|
||||
@@ -218,6 +231,31 @@ public class SysGroupService implements ISysGroupService {
|
||||
publishGroupEvent(PushInterfaceType.DELETE_GROUP, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 学校边界校验:确保待删除的组属于指定学校范围,且不是学校根组本身。
|
||||
*
|
||||
* @param groupId 待删除组ID
|
||||
* @param schoolId 学校组ID(限定范围)
|
||||
*/
|
||||
private void assertWithinSchool(Integer groupId, Integer schoolId) {
|
||||
if (schoolId == null) {
|
||||
throw new DException("学校ID不能为空");
|
||||
}
|
||||
if (groupId == null) {
|
||||
throw new DException("组ID不能为空");
|
||||
}
|
||||
if (!sysGroupRepository.existsById(schoolId)) {
|
||||
throw new DException("学校分组不存在");
|
||||
}
|
||||
if (groupId.equals(schoolId)) {
|
||||
throw new DException("禁止删除学校根组,请指定具体子组");
|
||||
}
|
||||
List<Integer> schoolScope = listSelfAndDescendantIds(schoolId);
|
||||
if (!schoolScope.contains(groupId)) {
|
||||
throw new DException("组不属于该学校范围,禁止跨校操作");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysGroupVo> list(SysGroupQuery query) {
|
||||
List<SysGroup> list;
|
||||
@@ -653,6 +691,7 @@ public class SysGroupService implements ISysGroupService {
|
||||
DataSyncEvent e = new DataSyncEvent();
|
||||
e.setType(type);
|
||||
e.setGroupIds(Collections.singletonList(groupId));
|
||||
e.setSourceAppKey(CallerAppContext.getAppKey());
|
||||
eventPublisher.publishEvent(e);
|
||||
}
|
||||
|
||||
@@ -663,6 +702,7 @@ public class SysGroupService implements ISysGroupService {
|
||||
DataSyncEvent e = new DataSyncEvent();
|
||||
e.setType(type);
|
||||
e.setGroupIds(groupIds);
|
||||
e.setSourceAppKey(CallerAppContext.getAppKey());
|
||||
eventPublisher.publishEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.yunhai.userbase.enums.UserTypeEnum;
|
||||
import com.yunhai.userbase.entity.SysUser;
|
||||
import com.yunhai.userbase.entity.SysUserGroupRel;
|
||||
import com.yunhai.userbase.model.sysuser.BatchImportResult;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserGroupDeleteItem;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserQuery;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserRequest;
|
||||
import com.yunhai.userbase.model.sysuser.SysUserVo;
|
||||
@@ -19,6 +20,7 @@ import com.yunhai.userbase.repository.SysUserRepository;
|
||||
import com.yunhai.userbase.service.ISysGroupService;
|
||||
import com.yunhai.userbase.service.ISysUserService;
|
||||
import com.yunhai.userbase.event.DataSyncEvent;
|
||||
import com.yunhai.userbase.common.context.CallerAppContext;
|
||||
import com.yunhai.userbase.model.push.PushInterfaceType;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
@@ -243,6 +245,53 @@ public class SysUserService implements ISysUserService {
|
||||
publishUserEvent(PushInterfaceType.DELETE_USER, userIds, groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteFromGroup(Integer userId, Integer groupId, Integer schoolId) {
|
||||
// 开放接口边界:groupId 必须属于 schoolId 子树且不能等于 schoolId(禁止跨校/整校移除)
|
||||
assertGroupWithinSchool(groupId, schoolId);
|
||||
batchDeleteFromGroup(Collections.singletonList(userId), groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteUsers(List<SysUserGroupDeleteItem> items) {
|
||||
if (items == null || items.isEmpty()) {
|
||||
throw new DException("批量删除用户-组关系列表不能为空");
|
||||
}
|
||||
for (SysUserGroupDeleteItem item : items) {
|
||||
deleteFromGroup(item.getUserId(), item.getGroupId(), item.getSchoolId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 学校边界校验:确保待操作的组属于指定学校范围,且不是学校根组本身。<br>
|
||||
* 与 {@link #moveUserWithinSchool} 的「学校子树范围」判定一致,复用 {@code listSelfAndDescendantIds}。
|
||||
*
|
||||
* @param groupId 待操作组ID(解绑/删除目标)
|
||||
* @param schoolId 学校组ID(限定范围)
|
||||
*/
|
||||
private void assertGroupWithinSchool(Integer groupId, Integer schoolId) {
|
||||
if (schoolId == null) {
|
||||
throw new DException("学校ID不能为空");
|
||||
}
|
||||
if (groupId == null) {
|
||||
throw new DException("组ID不能为空");
|
||||
}
|
||||
if (!sysGroupRepository.existsById(schoolId)) {
|
||||
throw new DException("学校分组不存在");
|
||||
}
|
||||
// 拿学校根组当 groupId = 把用户从整所学校移除,禁止
|
||||
if (groupId.equals(schoolId)) {
|
||||
throw new DException("禁止对学校根组执行该操作,请指定具体子组");
|
||||
}
|
||||
// groupId 必须落在 schoolId 子树(含子孙,不含学校根组自身——上面已拦截)
|
||||
List<Integer> schoolScope = sysGroupService.listSelfAndDescendantIds(schoolId);
|
||||
if (!schoolScope.contains(groupId)) {
|
||||
throw new DException("组不属于该学校范围,禁止跨校操作");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void changeStatus(Integer id, boolean disabled) {
|
||||
@@ -1008,6 +1057,7 @@ public class SysUserService implements ISysUserService {
|
||||
DataSyncEvent e = new DataSyncEvent();
|
||||
e.setType(type);
|
||||
e.setUserIds(Collections.singletonList(userId));
|
||||
e.setSourceAppKey(CallerAppContext.getAppKey());
|
||||
eventPublisher.publishEvent(e);
|
||||
}
|
||||
|
||||
@@ -1018,6 +1068,7 @@ public class SysUserService implements ISysUserService {
|
||||
DataSyncEvent e = new DataSyncEvent();
|
||||
e.setType(type);
|
||||
e.setUserIds(userIds);
|
||||
e.setSourceAppKey(CallerAppContext.getAppKey());
|
||||
eventPublisher.publishEvent(e);
|
||||
}
|
||||
|
||||
@@ -1030,6 +1081,7 @@ public class SysUserService implements ISysUserService {
|
||||
e.setType(type);
|
||||
e.setUserIds(userIds);
|
||||
e.setGroupId(groupId);
|
||||
e.setSourceAppKey(CallerAppContext.getAppKey());
|
||||
eventPublisher.publishEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,10 @@ public class PushDispatcher {
|
||||
? event.getGroupIds() : event.getUserIds();
|
||||
Integer groupId = event.getGroupId();
|
||||
for (ApplicationInfo app : applicationInfoRepository.findByPushEnabled(1)) {
|
||||
// 来源排除:应用自己触发的变更(如 oapi 写接口)不回推给自己,避免收到自己的回声
|
||||
if (event.getSourceAppKey() != null && event.getSourceAppKey().equals(app.getAppKey())) {
|
||||
continue;
|
||||
}
|
||||
dispatchDeleteToOne(app, type, ids, groupId, "auto");
|
||||
}
|
||||
return;
|
||||
@@ -88,6 +92,10 @@ public class PushDispatcher {
|
||||
users = sysUserRepository.findAll();
|
||||
}
|
||||
for (ApplicationInfo app : applicationInfoRepository.findByPushEnabled(1)) {
|
||||
// 来源排除:应用自己触发的变更(如 oapi 写接口)不回推给自己,避免收到自己的回声
|
||||
if (event.getSourceAppKey() != null && event.getSourceAppKey().equals(app.getAppKey())) {
|
||||
continue;
|
||||
}
|
||||
dispatchToOne(app, type, groups, users, "auto", false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
Reference in New Issue
Block a user