package com.sinata.appointment.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.sinata.appointment.constants.CacheNames; import com.sinata.appointment.constants.BizErrorCode; import com.sinata.appointment.dto.PassAreaCreateRequest; import com.sinata.appointment.dto.PassAreaDetailVo; import com.sinata.appointment.dto.PassAreaListVo; import com.sinata.appointment.dto.PassAreaUpdateRequest; import com.sinata.appointment.entity.MpPassAccessArea; import com.sinata.appointment.exception.BizException; import com.sinata.appointment.mapper.MpPassAccessAreaMapper; import com.sinata.appointment.util.UuidAssert; import java.util.Map; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; @Service public class PassAreaManageApplicationService { private final MpPassAccessAreaMapper mpPassAccessAreaMapper; private final CacheManager cacheManager; public PassAreaManageApplicationService( MpPassAccessAreaMapper mpPassAccessAreaMapper, CacheManager cacheManager) { this.mpPassAccessAreaMapper = mpPassAccessAreaMapper; this.cacheManager = cacheManager; } public IPage page(long page, long size, Long pid, String areaName) { long p = pid == null ? 0L : pid; LambdaQueryWrapper q = new LambdaQueryWrapper() .eq(MpPassAccessArea::getDelFlag, 0) .eq(MpPassAccessArea::getPid, p) .like(StringUtils.hasText(areaName), MpPassAccessArea::getAreaName, trim(areaName)) .orderByDesc(MpPassAccessArea::getCreateTime); Page pg = mpPassAccessAreaMapper.selectPage(new Page<>(page, size), q); return pg.convert(this::toListVo); } public PassAreaDetailVo detail(Long id, String uuid) { MpPassAccessArea e = loadActive(id, uuid); PassAreaDetailVo vo = new PassAreaDetailVo(); vo.setId(e.getId()); vo.setUuid(e.getUuid()); vo.setPid(e.getPid()); vo.setAreaName(e.getAreaName()); vo.setRemark(e.getRemark()); vo.setAreaDesc(e.getAreaDesc()); if (e.getPid() != null && e.getPid() > 0) { MpPassAccessArea parent = mpPassAccessAreaMapper.selectById(e.getPid()); if (parent != null && (parent.getDelFlag() == null || parent.getDelFlag() == 0) && StringUtils.hasText(parent.getAreaName())) { vo.setParentAreaName(parent.getAreaName()); } } return vo; } @Transactional(rollbackFor = Exception.class) public Map create(PassAreaCreateRequest req) { long pid = req.getPid() == null ? 0L : req.getPid(); validateParentPid(pid); String name = req.getAreaName().trim(); assertNameUniqueUnderPid(pid, name, null); MpPassAccessArea e = new MpPassAccessArea(); e.setPid(pid); e.setAreaName(name); e.setRemark(trimToNull(req.getRemark())); e.setAreaDesc(trimToNull(req.getAreaDesc())); mpPassAccessAreaMapper.insert(e); MpPassAccessArea fresh = mpPassAccessAreaMapper.selectById(e.getId()); if (fresh == null || !StringUtils.hasText(fresh.getUuid())) { throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "创建后读取失败"); } evictMiniPassAccessAreaTreeCache(); return Map.of("id", fresh.getId(), "uuid", fresh.getUuid()); } @Transactional(rollbackFor = Exception.class) public void update(Long id, PassAreaUpdateRequest req) { MpPassAccessArea e = loadActive(id, req.getUuid()); String name = req.getAreaName().trim(); long targetPid; if (e.getPid() == null || e.getPid() == 0) { targetPid = 0L; } else { targetPid = req.getPid() != null ? req.getPid() : e.getPid(); validateParentPid(targetPid); } assertNameUniqueUnderPid(targetPid, name, id); e.setPid(targetPid); e.setAreaName(name); if (req.getRemark() != null) { e.setRemark(trimToNull(req.getRemark())); } e.setAreaDesc(trimToNull(req.getAreaDesc())); mpPassAccessAreaMapper.updateById(e); evictMiniPassAccessAreaTreeCache(); } @Transactional(rollbackFor = Exception.class) public void delete(Long id, String uuid) { MpPassAccessArea e = loadActive(id, uuid); long child = mpPassAccessAreaMapper.selectCount( new LambdaQueryWrapper() .eq(MpPassAccessArea::getDelFlag, 0) .eq(MpPassAccessArea::getPid, e.getId())); if (child > 0) { throw new BizException(BizErrorCode.DEPT_HAS_CHILDREN, "请先删除子区域"); } int affected = mpPassAccessAreaMapper.deleteById(e.getId()); if (affected <= 0) { throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "删除失败,请刷新后重试"); } evictMiniPassAccessAreaTreeCache(); } private void evictMiniPassAccessAreaTreeCache() { Cache cache = cacheManager.getCache(CacheNames.MINI_LOOKUP); if (cache != null) { cache.evict(CacheNames.MINI_LOOKUP_PASS_ACCESS_AREA_TREE_KEY); } } private void validateParentPid(long pid) { if (pid == 0) { return; } MpPassAccessArea parent = mpPassAccessAreaMapper.selectById(pid); if (parent == null || (parent.getDelFlag() != null && parent.getDelFlag() == 1)) { throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "上级区域不存在"); } if (parent.getPid() == null || parent.getPid() != 0) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "仅允许两级:上级须为一级区域"); } } private void assertNameUniqueUnderPid(long pid, String name, Long excludeId) { LambdaQueryWrapper w = new LambdaQueryWrapper() .eq(MpPassAccessArea::getDelFlag, 0) .eq(MpPassAccessArea::getPid, pid) .eq(MpPassAccessArea::getAreaName, name); if (excludeId != null) { w.ne(MpPassAccessArea::getId, excludeId); } if (mpPassAccessAreaMapper.selectCount(w) > 0) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "同级下区域名称已存在"); } } private MpPassAccessArea loadActive(Long id, String uuid) { MpPassAccessArea e = mpPassAccessAreaMapper.selectById(id); if (e == null || (e.getDelFlag() != null && e.getDelFlag() == 1)) { throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "资源不存在或无权访问"); } UuidAssert.match(e, uuid); return e; } private PassAreaListVo toListVo(MpPassAccessArea e) { PassAreaListVo vo = new PassAreaListVo(); vo.setId(e.getId()); vo.setUuid(e.getUuid()); vo.setPid(e.getPid()); vo.setAreaName(e.getAreaName()); vo.setAreaDesc(e.getAreaDesc()); vo.setCreateTime(e.getCreateTime()); return vo; } private static String trim(String s) { return s == null ? null : s.trim(); } private static String trimToNull(String s) { if (!StringUtils.hasText(s)) { return null; } String t = s.trim(); return t.isEmpty() ? null : t; } }