SystemMenuServiceImpl.java 7.18 KB
package com.diligrp.rider.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.diligrp.rider.common.enums.MenuScopeEnum;
import com.diligrp.rider.common.exception.BizException;
import com.diligrp.rider.dto.AdminMenuSaveDTO;
import com.diligrp.rider.entity.SysMenu;
import com.diligrp.rider.mapper.SysMenuMapper;
import com.diligrp.rider.service.SystemMenuService;
import com.diligrp.rider.vo.AdminRoleMenuTreeVO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class SystemMenuServiceImpl implements SystemMenuService {

    private final SysMenuMapper sysMenuMapper;

    @Override
    public List<AdminRoleMenuTreeVO> tree() {
        List<SysMenu> menus = listAllMenus();
        return buildTree(menus, Map.of(), false);
    }

    @Override
    public void add(AdminMenuSaveDTO dto) {
        validateSave(dto, null);
        SysMenu menu = new SysMenu();
        apply(menu, dto);
        menu.setCreateTime(System.currentTimeMillis() / 1000);
        sysMenuMapper.insert(menu);
    }

    @Override
    public void edit(AdminMenuSaveDTO dto) {
        if (dto.getId() == null || dto.getId() < 1) {
            throw new BizException("菜单ID不能为空");
        }
        SysMenu existing = requireMenu(dto.getId());
        validateSave(dto, existing);
        apply(existing, dto);
        sysMenuMapper.updateById(existing);
    }

    @Override
    @Transactional
    public void delete(Long id) {
        SysMenu menu = requireMenu(id);
        Long childCount = sysMenuMapper.selectCount(new LambdaQueryWrapper<SysMenu>()
                .eq(SysMenu::getParentId, id));
        if (childCount != null && childCount > 0) {
            throw new BizException("请先删除子菜单");
        }
        sysMenuMapper.deleteById(menu.getId());
    }

    @Override
    public void setVisible(Long id, int visible) {
        requireMenu(id);
        sysMenuMapper.update(null, new LambdaUpdateWrapper<SysMenu>()
                .eq(SysMenu::getId, id)
                .set(SysMenu::getVisible, visible));
    }

    @Override
    public void setStatus(Long id, int status) {
        requireMenu(id);
        sysMenuMapper.update(null, new LambdaUpdateWrapper<SysMenu>()
                .eq(SysMenu::getId, id)
                .set(SysMenu::getStatus, status));
    }

    public List<SysMenu> listAllMenus() {
        return sysMenuMapper.selectList(new LambdaQueryWrapper<SysMenu>()
                .orderByAsc(SysMenu::getListOrder)
                .orderByAsc(SysMenu::getId));
    }

    public List<AdminRoleMenuTreeVO> buildTree(List<SysMenu> menus, Map<Long, Boolean> checkedMap, boolean disableByScope) {
        Map<Long, AdminRoleMenuTreeVO> voMap = new LinkedHashMap<>();
        List<AdminRoleMenuTreeVO> roots = new ArrayList<>();
        for (SysMenu menu : menus) {
            AdminRoleMenuTreeVO vo = toVO(menu, checkedMap.get(menu.getId()));
            if (disableByScope && checkedMap.get(menu.getId()) == null) {
                vo.setDisabled(true);
            }
            voMap.put(menu.getId(), vo);
            Long parentId = menu.getParentId() == null ? 0L : menu.getParentId();
            if (parentId > 0 && voMap.containsKey(parentId)) {
                voMap.get(parentId).getChildren().add(vo);
            } else {
                roots.add(vo);
            }
        }
        return roots;
    }

    private void validateSave(AdminMenuSaveDTO dto, SysMenu existing) {
        validateTypeAndPath(dto);
        validateScope(dto.getMenuScope());

        SysMenu duplicate = sysMenuMapper.selectOne(new LambdaQueryWrapper<SysMenu>()
                .eq(SysMenu::getCode, dto.getCode())
                .ne(existing != null, SysMenu::getId, existing != null ? existing.getId() : null)
                .last("LIMIT 1"));
        if (duplicate != null) {
            throw new BizException("菜单编码已存在");
        }

        Long parentId = dto.getParentId() == null ? 0L : dto.getParentId();
        if (parentId > 0) {
            SysMenu parent = requireMenu(parentId);
            if (existing != null && existing.getId().equals(parent.getId())) {
                throw new BizException("父级菜单不能选择自己");
            }
            if (existing != null) {
                ensureNotDescendant(parent.getId(), existing.getId());
            }
        }
    }

    private void validateTypeAndPath(AdminMenuSaveDTO dto) {
        if (!"DIR".equals(dto.getType()) && !"MENU".equals(dto.getType())) {
            throw new BizException("菜单类型只能是 DIR 或 MENU");
        }
        if ("MENU".equals(dto.getType()) && !StringUtils.hasText(dto.getPath())) {
            throw new BizException("菜单路由不能为空");
        }
    }

    private void validateScope(String scope) {
        for (MenuScopeEnum value : MenuScopeEnum.values()) {
            if (value.name().equals(scope)) {
                return;
            }
        }
        throw new BizException("菜单范围不合法");
    }

    private void ensureNotDescendant(Long parentId, Long selfId) {
        Long currentId = parentId;
        while (currentId != null && currentId > 0) {
            if (currentId.equals(selfId)) {
                throw new BizException("父级菜单不能选择当前菜单的子节点");
            }
            SysMenu current = sysMenuMapper.selectById(currentId);
            currentId = current == null ? null : current.getParentId();
        }
    }

    private SysMenu requireMenu(Long id) {
        SysMenu menu = sysMenuMapper.selectById(id);
        if (menu == null) {
            throw new BizException("菜单不存在");
        }
        return menu;
    }

    private void apply(SysMenu menu, AdminMenuSaveDTO dto) {
        menu.setCode(dto.getCode());
        menu.setName(dto.getName());
        menu.setType(dto.getType());
        menu.setPath(StringUtils.hasText(dto.getPath()) ? dto.getPath().trim() : "");
        menu.setIcon(StringUtils.hasText(dto.getIcon()) ? dto.getIcon().trim() : "");
        menu.setParentId(dto.getParentId());
        menu.setMenuScope(dto.getMenuScope());
        menu.setListOrder(dto.getListOrder() == null ? 0 : dto.getListOrder());
        menu.setVisible(dto.getVisible() == null ? 1 : dto.getVisible());
        menu.setStatus(dto.getStatus() == null ? 1 : dto.getStatus());
    }

    private AdminRoleMenuTreeVO toVO(SysMenu menu, Boolean checked) {
        AdminRoleMenuTreeVO vo = new AdminRoleMenuTreeVO();
        vo.setId(menu.getId());
        vo.setCode(menu.getCode());
        vo.setName(menu.getName());
        vo.setType(menu.getType());
        vo.setPath(menu.getPath());
        vo.setIcon(menu.getIcon());
        vo.setMenuScope(menu.getMenuScope());
        vo.setVisible(menu.getVisible());
        vo.setStatus(menu.getStatus());
        vo.setListOrder(menu.getListOrder());
        vo.setChecked(Boolean.TRUE.equals(checked));
        return vo;
    }
}