diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/controller/SysMenuController.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/controller/SysMenuController.java index 5234e9a25aeddef89e15782dfbfaabc60864c5cb..49f4e3acc6c3e84f04c5a1d89cf4886958238d5d 100644 --- a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/controller/SysMenuController.java +++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/controller/SysMenuController.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import org.springframework.validation.annotation.Validated; @@ -49,8 +50,8 @@ public class SysMenuController { } @DeleteMapping("/delete") - @Operation(summary = "批量删除") - @CommonLog(value = "批量删除", module = SysConfiguration.MODULE_NAME, type = LogTypeEnum.DELETE) + @Operation(summary = "普通批量删除") + @CommonLog(value = "普通批量删除", module = SysConfiguration.MODULE_NAME, type = LogTypeEnum.DELETE) public CommonResult delete( @RequestBody @Size(min = 1, message = "请至少传递一个删除对象") @@ -61,6 +62,22 @@ public class SysMenuController { return CommonResult.ok(); } + @DeleteMapping("/replaceDelete") + @Operation(summary = "取代删除") + @CommonLog(value = "取代删除", module = SysConfiguration.MODULE_NAME, type = LogTypeEnum.DELETE) + public CommonResult replaceDelete(@Validated @RequestBody SysMenuIdParam idParam) { + sysMenuService.replaceDelete(idParam); + return CommonResult.ok(); + } + + @DeleteMapping("/cascadingDelete") + @Operation(summary = "级联删除") + @CommonLog(value = "级联删除", module = SysConfiguration.MODULE_NAME, type = LogTypeEnum.DELETE) + public CommonResult cascadingDelete(@Validated @RequestBody SysMenuIdParam idParam) { + sysMenuService.cascadingDelete(idParam); + return CommonResult.ok(); + } + @PutMapping("/edit") @Operation(summary = "修改", description = "修改一条数据") @CommonLog(value = "修改", description = "修改一条数据", diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/entity/SysMenuEntity.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/entity/SysMenuEntity.java index ef540d07a38d997a0f48794c9ad46ec1e6dae1bc..cab41d1a11b1a1ecf726f5851fa8f69dcdff9b79 100644 --- a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/entity/SysMenuEntity.java +++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/entity/SysMenuEntity.java @@ -42,7 +42,7 @@ public class SysMenuEntity extends CommonEntity implements Serializable { */ @Schema(title = "上级id", description = "上级id") - private String parentId; + private Integer parentId; /** * 中文名 @@ -96,9 +96,9 @@ public class SysMenuEntity extends CommonEntity implements Serializable { * 是否可见;隐藏后可以访问页面,但不在菜单列表显示。1可见,0不可见 */ @Schema(title = "是否可见", - description = "是否可见。隐藏后可以访问页面,但不在菜单列表显示。1可见,0不可见") + description = "是否可见。隐藏后可以访问页面,但不在菜单列表显示。true可见,false不可见,null不可见") @TableField(updateStrategy = FieldStrategy.ALWAYS) - private Integer isShow; + private Boolean isShow; /** * 类型;菜单MENU或目录CATALOG diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/enums/SysMenuTypeEnum.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/enums/SysMenuTypeEnum.java index 03cb4e49c61536526b6b8c91af77797f1bd9ecc7..a67bfa2e3480e14153563db434f88e382af4a71a 100644 --- a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/enums/SysMenuTypeEnum.java +++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/enums/SysMenuTypeEnum.java @@ -14,7 +14,7 @@ import lombok.AllArgsConstructor; public enum SysMenuTypeEnum { MENU("菜单", "MENU"), - CATEGORY("目录", "CATEGORY"); + CATALOG("目录", "CATALOG"); private final String label; diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuAddParam.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuAddParam.java index 587fa20edd4d16c769a10c9b58b245280bd426f5..08d4bfe7d97427cfc858a7d7c4d2808e90e8f509 100644 --- a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuAddParam.java +++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuAddParam.java @@ -1,6 +1,8 @@ package top.milkbox.sys.modular.menu.param; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; import lombok.Data; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; @@ -25,9 +27,10 @@ public class SysMenuAddParam implements Serializable { * 上级id */ @Schema(title = "上级id", - description = "上级id") - @NotBlank(message = "上级id不能为空") - private String parentId; + description = "上级id,传递0或不传递表示顶级") + @DecimalMax(value = "2147483647", message = "上级id不可超过2147483647") + @DecimalMin(value = "0", message = "上级id不可为负数") + private Integer parentId; /** * 中文名 @@ -75,8 +78,8 @@ public class SysMenuAddParam implements Serializable { * 是否可见;隐藏后可以访问页面,但不在菜单列表显示。1可见,0不可见 */ @Schema(title = "是否可见", - description = "是否可见。隐藏后可以访问页面,但不在菜单列表显示。1可见,0不可见") - private Integer isShow; + description = "是否可见。隐藏后可以访问页面,但不在菜单列表显示。true可见,false不可见,null不可见") + private Boolean isShow; /** * 类型;菜单MENU或目录CATALOG diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuEditParam.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuEditParam.java index b07a658151a4a292a4a413fff4d10df75f0157e2..57cb05f1977f3ae86e7d108eede93c54dbe59d22 100644 --- a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuEditParam.java +++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuEditParam.java @@ -1,6 +1,8 @@ package top.milkbox.sys.modular.menu.param; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; import lombok.Data; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; @@ -34,9 +36,10 @@ public class SysMenuEditParam implements Serializable { * 上级id */ @Schema(title = "上级id", - description = "上级id") - @NotBlank(message = "上级id不能为空") - private String parentId; + description = "上级id,传递0或不传递表示顶级") + @DecimalMax(value = "2147483647", message = "上级id不可超过2147483647") + @DecimalMin(value = "0", message = "上级id不可为负数") + private Integer parentId; /** * 中文名 @@ -84,8 +87,8 @@ public class SysMenuEditParam implements Serializable { * 是否可见;隐藏后可以访问页面,但不在菜单列表显示。1可见,0不可见 */ @Schema(title = "是否可见", - description = "是否可见。隐藏后可以访问页面,但不在菜单列表显示。1可见,0不可见") - private Integer isShow; + description = "是否可见。隐藏后可以访问页面,但不在菜单列表显示。true可见,false不可见,null不可见") + private Boolean isShow; /** * 类型;菜单MENU或目录CATALOG diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuPageParam.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuPageParam.java index 94097ab40d5b2625b2fe62ee8273641f32fa3c6b..6cbbc4d2f877030fd6624708edd60fbe3afcc1de 100644 --- a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuPageParam.java +++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/param/SysMenuPageParam.java @@ -28,7 +28,7 @@ public class SysMenuPageParam extends CommonPageParam implements Serializable { */ @Schema(title = "上级id", description = "上级id") - private String parentId; + private Integer parentId; /** * 中文名 @@ -76,8 +76,8 @@ public class SysMenuPageParam extends CommonPageParam implements Serializable { * 是否可见;隐藏后可以访问页面,但不在菜单列表显示。1可见,0不可见 */ @Schema(title = "是否可见", - description = "是否可见。隐藏后可以访问页面,但不在菜单列表显示。1可见,0不可见") - private Integer isShow; + description = "是否可见。隐藏后可以访问页面,但不在菜单列表显示。true可见,false不可见,null不可见") + private Boolean isShow; /** * 类型;菜单MENU或目录CATALOG diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/service/SysMenuService.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/service/SysMenuService.java index ea32e88e9a1f6c032914544c70877613c6324954..af32e5def53d34fd76a76d9866e734db223269bf 100644 --- a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/service/SysMenuService.java +++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/service/SysMenuService.java @@ -28,12 +28,30 @@ public interface SysMenuService extends IService { void add(SysMenuAddParam addParam); /** - * 删除 + * 普通删除 + * 直接删除当前节点,不考虑后续子节点,在定时任务中自动清理不可达节点 * * @param paramList 删除id对象集合 */ void delete(List paramList); + /** + * 取代删除
+ * 出于性能考虑,此操作仅支持单个删除 + * + * @param idParam 删除这个id的菜单 + */ + void replaceDelete(SysMenuIdParam idParam); + + /** + * 级联删除
+ * 出于性能考虑,此操作仅支持单个删除
+ * 高耗时操作。树越深,执行的查询越多,效率越低 + * + * @param idParam 删除这个id的菜单以及其子节点 + */ + void cascadingDelete(SysMenuIdParam idParam); + /** * 通过id编辑 * diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/service/impl/SysMenuServiceImpl.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/service/impl/SysMenuServiceImpl.java index bc6b17e69f94711d41920e8d33089e89822df9f9..792548e42ac4c88a8f0483b74b892bcbee87946c 100644 --- a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/service/impl/SysMenuServiceImpl.java +++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/service/impl/SysMenuServiceImpl.java @@ -4,25 +4,24 @@ import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import top.milkbox.common.enums.CommonSortTypeEnum; +import org.springframework.transaction.annotation.Transactional; import top.milkbox.common.exceprion.CommonServiceException; import top.milkbox.sys.modular.menu.entity.SysMenuEntity; import top.milkbox.sys.modular.menu.mapper.SysMenuMapper; import top.milkbox.sys.modular.menu.param.SysMenuAddParam; import top.milkbox.sys.modular.menu.param.SysMenuEditParam; import top.milkbox.sys.modular.menu.param.SysMenuIdParam; -import top.milkbox.sys.modular.menu.param.SysMenuPageParam; import top.milkbox.sys.modular.menu.service.SysMenuService; import top.milkbox.sys.modular.menu.vo.SysMenuVo; -import top.milkbox.common.utils.CommonUtil; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -39,20 +38,99 @@ public class SysMenuServiceImpl extends ServiceImpl paramList) { super.removeByIds(paramList.stream().map(SysMenuIdParam::getId).toList()); } @Override + @Transactional(rollbackFor = Exception.class) + public void replaceDelete(SysMenuIdParam idParam) { + SysMenuEntity entity = findEntity(idParam.getId()); + // 从数据库中查询当前节点的下级节点 + List childrenList = + super.list(new LambdaUpdateWrapper().eq(SysMenuEntity::getParentId, idParam.getId())); + // 如果下级节点不为空,则批量更新下级节点的父级id + if (ObjectUtil.isNotEmpty(childrenList)) { + // 提取下级节点的id + List childrenIdList = childrenList.stream().map(SysMenuEntity::getId).toList(); + // 批量更新下级节点的父级id,下级节点的父级id等于当前节点的父级id + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.set(SysMenuEntity::getParentId, entity.getParentId()) + .in(SysMenuEntity::getId, childrenIdList); + super.update(updateWrapper); + } + // 删除当前节点 + super.removeById(idParam.getId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void cascadingDelete(SysMenuIdParam idParam) { + // 检查当前节点是否存在 + findEntity(idParam.getId()); + // 存储待删除的节点id + ArrayList deleteIdList = new ArrayList<>(); + // 存储每一层节点的父id,第一次为当前节点的id + ArrayList parentIdList = new ArrayList<>(Collections.singletonList(idParam.getId())); + // 循环查询parentIdList的下级节点(广度优先) + while (true) { + // 查询parentIdList的下级节点 + List menuList = + super.list(new LambdaQueryWrapper().in(SysMenuEntity::getParentId, parentIdList)); + // 如果下级节点不为空,则将下级节点的id加入到待删除集合中,并作为下一层的父级id + if (ObjectUtil.isNotEmpty(menuList)) { + // 提取下级节点的id + List menuIdList = menuList.stream().map(SysMenuEntity::getId).toList(); + // 将当前一层的节点id加入到待删除集合中 + deleteIdList.addAll(menuIdList); + // 将当前一层的节点id作为下一层的父级id + parentIdList = new ArrayList<>(menuIdList); + } else { + // 如果下级节点为空,则跳出循环 + break; + } + } + // 删除所有待删除的节点 + super.removeByIds(deleteIdList); + } + + @Override + @Transactional(rollbackFor = Exception.class) public void edit(SysMenuEditParam editParam) { - findEntity(editParam.getId()); + SysMenuEntity oldEntity = findEntity(editParam.getId()); SysMenuEntity entity = BeanUtil.toBean(editParam, SysMenuEntity.class); + // 如果未指定父级id,则默认为0 + if (ObjectUtil.isEmpty(entity.getParentId())) { + entity.setParentId(0); + } else if (entity.getParentId() != 0 && + !entity.getParentId().equals(oldEntity.getParentId()) && + ObjectUtil.isEmpty(super.getById(entity.getParentId()))) { + // 验证上级id是否存在,若不存在,则抛出异常 + throw new CommonServiceException("上级id({})不存在", entity.getParentId()); + } + // 若未指定可见性,则默认为false不可见 + if (ObjectUtil.isEmpty(entity.getIsShow())) { + entity.setIsShow(false); + } super.updateById(entity); } @@ -100,6 +178,7 @@ public class SysMenuServiceImpl extends ServiceImpl> forest() { int loginUserId = StpUtil.getLoginIdAsInt(); // TODO 获取当前登录用户拥有权限的菜单森林 diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/vo/SysMenuVo.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/vo/SysMenuVo.java index d06abb7d2db310e1f412cf9edc6296cd2dba54bd..75d62a3db921d876636089cbe6a1ecde30171348 100644 --- a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/vo/SysMenuVo.java +++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/vo/SysMenuVo.java @@ -36,7 +36,7 @@ public class SysMenuVo extends CommonVo implements Serializable { */ @Schema(title = "上级id", description = "上级id") - private String parentId; + private Integer parentId; /** * 中文名 @@ -84,8 +84,8 @@ public class SysMenuVo extends CommonVo implements Serializable { * 是否可见;隐藏后可以访问页面,但不在菜单列表显示。1可见,0不可见 */ @Schema(title = "是否可见", - description = "是否可见。隐藏后可以访问页面,但不在菜单列表显示。1可见,0不可见") - private Integer isShow; + description = "是否可见。隐藏后可以访问页面,但不在菜单列表显示。true可见,false不可见,null不可见") + private Boolean isShow; /** * 类型;菜单MENU或目录CATALOG diff --git "a/\345\244\207\345\277\230\345\275\225.md" "b/\345\244\207\345\277\230\345\275\225.md" index 9598fdec84e38eb7490b6266a7fb74c459e350a2..cc7fba1b26bfddbad70175d9128bc60e8933b21e 100644 --- "a/\345\244\207\345\277\230\345\275\225.md" +++ "b/\345\244\207\345\277\230\345\275\225.md" @@ -6,9 +6,11 @@ - [ ] 考虑进行单元测试,两种方案。1:在各个子模块中编写测试用例,但是需要解决模块依赖启动类的问题。2:专门编写一个测试子模块,将其他模块的测试用例写到这个测试子模块中。 - [ ] 考虑使用一个注解对密级字段进行权限控制。将注解放到实体类的指定字段上,就可以控制整条数据的权限。 - [ ] 需要接口调用频率的控制。 +- [ ] 级联删除与取代删除不支持批量操作。普通批量删除方式删除当前节点后,子节点将无法通过根节点找到,需要通过定时任务进行定期删除。 # 正在进行 -* 菜单表基础功能 +- [x] 菜单表基础功能 +- [ ] 为角色和用户授予菜单权限