feat(area): 重构行政区划管理接口 (#72)

This commit is contained in:
leng leng authored and PIGCLOUD committed 2026-08-01 11:59:35 +08:00
1 parent fb5e1bdf6e
commit ce07c9dcb9
10 files changed
+730 -9

No files matched your search

@@ -0,0 +1,34 @@
package com.pig4cloud.pigx.admin.api.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 行政区划排序 DTO
*
* @author lengleng
* @date 2026/7/18
*/
@Data
@Schema(description = "行政区划排序")
public class SysAreaSortDTO {
/**
* 父级行政编码
*/
@NotNull(message = "父级行政编码不能为空")
@Schema(description = "父级行政编码")
private Long pid;
/**
* 同级行政区划数据库主键,按目标顺序排列
*/
@NotEmpty(message = "行政区划ID列表不能为空")
@Schema(description = "行政区划ID列表")
private List<Long> areaIds;
}
@@ -0,0 +1,33 @@
package com.pig4cloud.pigx.admin.api.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 行政区划可编辑字段
*
* @author lengleng
* @date 2026/7/18
*/
@Data
@Schema(description = "行政区划可编辑字段")
public class SysAreaUpdateDTO {
@NotNull(message = "行政区划ID不能为空")
private Long id;
@NotBlank(message = "行政区划名称不能为空")
@Size(max = 255, message = "行政区划名称长度不能超过255个字符")
private String name;
@Pattern(regexp = "[01]", message = "行政区划状态不正确")
private String areaStatus;
@Pattern(regexp = "[01]", message = "热门状态不正确")
private String hot;
}
@@ -0,0 +1,18 @@
package com.pig4cloud.pigx.admin.api.vo;
import lombok.Data;
/**
* 行政区划直属子级数量
*
* @author lengleng
* @date 2026/7/18
*/
@Data
public class SysAreaChildCountVO {
private Long pid;
private Long childCount;
}
@@ -0,0 +1,54 @@
package com.pig4cloud.pigx.admin.api.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 行政区划管理视图
*
* @author lengleng
* @date 2026/7/18
*/
@Data
@Schema(description = "行政区划管理视图")
public class SysAreaManageVO {
@Schema(description = "数据库主键")
private Long id;
@Schema(description = "父级行政编码")
private Long pid;
@Schema(description = "行政编码")
private Long adcode;
@Schema(description = "名称")
private String name;
@Schema(description = "行政区划类型")
private String areaType;
@Schema(description = "热门标记")
private String hot;
@Schema(description = "状态")
private String areaStatus;
@Schema(description = "排序值")
private Long areaSort;
@Schema(description = "是否存在下级")
private Boolean hasChildren;
@Schema(description = "直属子级数量")
private Long childCount;
@Schema(description = "完整路径行政编码")
private List<Long> pathCodes;
@Schema(description = "完整路径名称")
private String pathName;
}
@@ -4,6 +4,8 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.pig4cloud.pigx.admin.api.dto.SysAreaSortDTO;
import com.pig4cloud.pigx.admin.api.dto.SysAreaUpdateDTO;
import com.pig4cloud.pigx.admin.api.entity.SysAreaEntity;
import com.pig4cloud.pigx.admin.service.SysAreaService;
import com.pig4cloud.pigx.common.core.util.R;
@@ -13,10 +15,13 @@ import com.pig4cloud.pigx.common.security.annotation.HasPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import java.util.List;
@@ -27,6 +32,7 @@ import java.util.List;
* @date 2024-02-16 22:40:06
*/
@RestController
@Validated
@RequiredArgsConstructor
@RequestMapping("/sysArea")
@Tag(description = "sysArea", name = "行政区划管理")
@@ -39,7 +45,7 @@ public class SysAreaController {
* 分页查询
*
* @param page 分页对象
* @param sysArea 行政区划
* @param updateDTO 可编辑字段
* @return
*/
@Operation(summary = "分页查询", description = "分页查询")
@@ -62,17 +68,56 @@ public class SysAreaController {
return R.ok(sysAreaService.selectTree(sysArea));
}
/**
* 查询管理端直属子级
*
* @param pid 父级行政编码
* @return 直属子级
*/
@Operation(summary = "查询管理端直属子级", description = "包含启用和停用的直属子级")
@GetMapping("/manage/children")
@HasPermission("sys_sysArea_view")
public R getManageChildren(@RequestParam(required = false, defaultValue = "0") Long pid) {
return R.ok(sysAreaService.listManageChildren(pid));
}
/**
* 搜索管理端行政区划
*
* @param keyword 名称或行政编码
* @return 搜索结果
*/
@Operation(summary = "搜索管理端行政区划", description = "按名称或行政编码搜索")
@GetMapping("/manage/search")
@HasPermission("sys_sysArea_view")
public R searchManage(@RequestParam @Size(max = 64, message = "搜索关键词长度不能超过64个字符") String keyword) {
return R.ok(sysAreaService.searchManage(keyword));
}
/**
* 判断行政编码是否存在
*
* @param adcode 行政编码
* @return 是否存在
*/
@Operation(summary = "判断行政编码是否存在", description = "新增行政区划时校验行政编码")
@GetMapping("/exists/adcode")
@HasPermission("sys_sysArea_add")
public R existsByAdcode(@RequestParam Long adcode) {
return R.ok(sysAreaService.existsByAdcode(adcode));
}
/**
* 获取详细信息
*
* @param sysArea 查询条件
* @param id 行政区划ID
* @return {@link R }
*/
@Operation(summary = "获取详细信息", description = "获取详细信息")
@GetMapping("/details")
@HasPermission("sys_sysArea_view")
public R getDetails(@ParameterObject SysAreaEntity sysArea) {
return R.ok(sysAreaService.getOne(Wrappers.query(sysArea)));
public R getDetails(@RequestParam Long id) {
return R.ok(sysAreaService.getById(id));
}
/**
@@ -86,7 +131,7 @@ public class SysAreaController {
@PostMapping
@HasPermission("sys_sysArea_add")
public R save(@RequestBody SysAreaEntity sysArea) {
return R.ok(sysAreaService.save(sysArea));
return sysAreaService.saveArea(sysArea);
}
/**
@@ -99,10 +144,24 @@ public class SysAreaController {
@SysLog("修改行政区划")
@PutMapping
@HasPermission("sys_sysArea_edit")
public R updateById(@RequestBody SysAreaEntity sysArea) {
return R.ok(sysAreaService.updateById(sysArea));
public R updateById(@Valid @RequestBody SysAreaUpdateDTO updateDTO) {
return sysAreaService.updateArea(updateDTO);
}
/**
* 更新同级行政区划排序
*
* @param sortDTO 排序参数
* @return R
*/
@Operation(summary = "更新行政区划排序", description = "仅支持同一父级的完整直属子级排序")
@SysLog("更新行政区划排序")
@PutMapping("/sort")
@HasPermission("sys_sysArea_edit")
public R sort(@Valid @RequestBody SysAreaSortDTO sortDTO) {
return sysAreaService.updateAreaSort(sortDTO);
}
/**
* 通过id删除行政区划
*
@@ -114,7 +173,7 @@ public class SysAreaController {
@DeleteMapping
@HasPermission("sys_sysArea_del")
public R removeById(@RequestBody Long[] ids) {
return R.ok(sysAreaService.removeBatchByIds(CollUtil.toList(ids)));
return sysAreaService.removeAreas(CollUtil.toList(ids));
}
/**
@@ -1,10 +1,23 @@
package com.pig4cloud.pigx.admin.mapper;
import com.pig4cloud.pigx.admin.api.entity.SysAreaEntity;
import com.pig4cloud.pigx.admin.api.vo.SysAreaChildCountVO;
import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
@Mapper
public interface SysAreaMapper extends PigxBaseMapper<SysAreaEntity> {
/**
* 按父级行政编码统计直属子级数量
*
* @param parentCodes 父级行政编码
* @return 子级数量
*/
List<SysAreaChildCountVO> selectChildCounts(@Param("parentCodes") Collection<Long> parentCodes);
}
@@ -3,7 +3,11 @@ package com.pig4cloud.pigx.admin.service;
import cn.hutool.core.lang.tree.Tree;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.pig4cloud.pigx.admin.api.dto.SysAreaSortDTO;
import com.pig4cloud.pigx.admin.api.dto.SysAreaUpdateDTO;
import com.pig4cloud.pigx.admin.api.entity.SysAreaEntity;
import com.pig4cloud.pigx.admin.api.vo.SysAreaManageVO;
import com.pig4cloud.pigx.common.core.util.R;
import java.util.List;
@@ -26,4 +30,60 @@ public interface SysAreaService extends IService<SysAreaEntity> {
*/
Page selectPage(Page page, SysAreaEntity sysArea);
/**
* 查询管理端直属子级
*
* @param pid 父级行政编码
* @return 直属子级
*/
List<SysAreaManageVO> listManageChildren(Long pid);
/**
* 搜索管理端行政区划
*
* @param keyword 名称或行政编码
* @return 搜索结果
*/
List<SysAreaManageVO> searchManage(String keyword);
/**
* 判断行政编码是否存在
*
* @param adcode 行政编码
* @return 是否存在
*/
boolean existsByAdcode(Long adcode);
/**
* 更新同级行政区划排序
*
* @param sortDTO 排序参数
* @return 更新结果
*/
R updateAreaSort(SysAreaSortDTO sortDTO);
/**
* 新增行政区划并追加到同级末尾
*
* @param sysArea 行政区划
* @return 新增结果
*/
R saveArea(SysAreaEntity sysArea);
/**
* 更新行政区划可编辑字段
*
* @param updateDTO 可编辑字段
* @return 更新结果
*/
R updateArea(SysAreaUpdateDTO updateDTO);
/**
* 删除叶子行政区划
*
* @param ids 主键列表
* @return 删除结果
*/
R removeAreas(List<Long> ids);
}
@@ -10,16 +10,29 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pig4cloud.pigx.admin.api.dto.SysAreaSortDTO;
import com.pig4cloud.pigx.admin.api.dto.SysAreaUpdateDTO;
import com.pig4cloud.pigx.admin.api.entity.SysAreaEntity;
import com.pig4cloud.pigx.admin.api.vo.SysAreaChildCountVO;
import com.pig4cloud.pigx.admin.api.vo.SysAreaManageVO;
import com.pig4cloud.pigx.admin.mapper.SysAreaMapper;
import com.pig4cloud.pigx.admin.service.SysAreaService;
import com.pig4cloud.pigx.common.core.constant.enums.YesNoEnum;
import com.pig4cloud.pigx.common.core.util.R;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 行政区划
@@ -30,6 +43,10 @@ import java.util.Optional;
@Service
public class SysAreaServiceImpl extends ServiceImpl<SysAreaMapper, SysAreaEntity> implements SysAreaService {
private static final Long AREA_ROOT_ADCODE = 100000L;
private static final int SEARCH_RESULT_LIMIT = 50;
/**
* 查询行政区划树
*
@@ -46,7 +63,8 @@ public class SysAreaServiceImpl extends ServiceImpl<SysAreaMapper, SysAreaEntity
// 保留原有条件
wrapper.eq(SysAreaEntity::getAreaStatus, YesNoEnum.YES.getCode())
.orderByDesc(SysAreaEntity::getAreaSort);
.orderByDesc(SysAreaEntity::getAreaSort)
.orderByAsc(SysAreaEntity::getAdcode);
// 执行查询
List<SysAreaEntity> entityList = baseMapper.selectList(wrapper);
@@ -79,7 +97,243 @@ public class SysAreaServiceImpl extends ServiceImpl<SysAreaMapper, SysAreaEntity
wrapper.eq(Objects.nonNull(sysArea.getAdcode()), SysAreaEntity::getPid, sysArea.getAdcode());
wrapper.orderByAsc(SysAreaEntity::getAreaType);
wrapper.orderByDesc(SysAreaEntity::getAreaSort);
wrapper.orderByAsc(SysAreaEntity::getAdcode);
return baseMapper.selectPage(page, wrapper);
}
@Override
public List<SysAreaManageVO> listManageChildren(Long pid) {
Long parentCode = Optional.ofNullable(pid).orElse(0L);
List<SysAreaEntity> children = list(Wrappers.<SysAreaEntity>lambdaQuery()
.eq(SysAreaEntity::getPid, parentCode)
.orderByDesc(SysAreaEntity::getAreaSort)
.orderByAsc(SysAreaEntity::getAdcode));
return toManageList(children);
}
@Override
public List<SysAreaManageVO> searchManage(String keyword) {
String normalizedKeyword = StrUtil.trim(keyword);
if (StrUtil.isBlank(normalizedKeyword)) {
return List.of();
}
LambdaQueryWrapper<SysAreaEntity> wrapper = Wrappers.lambdaQuery();
Long adcode = parseAdcode(normalizedKeyword);
if (Objects.nonNull(adcode)) {
wrapper.and(query -> query.like(SysAreaEntity::getName, normalizedKeyword)
.or()
.eq(SysAreaEntity::getAdcode, adcode));
}
else {
wrapper.like(SysAreaEntity::getName, normalizedKeyword);
}
wrapper.orderByAsc(SysAreaEntity::getAreaType)
.orderByDesc(SysAreaEntity::getAreaSort)
.orderByAsc(SysAreaEntity::getAdcode);
Page<SysAreaEntity> page = new Page<>(1, SEARCH_RESULT_LIMIT, false);
return toManageList(baseMapper.selectPage(page, wrapper).getRecords());
}
@Override
public boolean existsByAdcode(Long adcode) {
return Objects.nonNull(adcode) && count(Wrappers.<SysAreaEntity>lambdaQuery()
.eq(SysAreaEntity::getAdcode, adcode)) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public R updateAreaSort(SysAreaSortDTO sortDTO) {
if (Objects.equals(sortDTO.getPid(), 0L)) {
return R.failed("全国根节点不支持排序");
}
List<Long> areaIds = sortDTO.getAreaIds();
Set<Long> uniqueIds = new HashSet<>(areaIds);
if (uniqueIds.size() != areaIds.size()) {
return R.failed("行政区划排序ID不能重复");
}
List<SysAreaEntity> siblings = list(Wrappers.<SysAreaEntity>lambdaQuery()
.eq(SysAreaEntity::getPid, sortDTO.getPid()));
Set<Long> siblingIds = siblings.stream().map(SysAreaEntity::getId).collect(Collectors.toSet());
if (siblings.size() != areaIds.size() || !siblingIds.equals(uniqueIds)) {
return R.failed("行政区划列表已变化,请刷新后重试");
}
Map<Long, SysAreaEntity> siblingMap = siblings.stream()
.collect(Collectors.toMap(SysAreaEntity::getId, Function.identity()));
if (!updateBatchById(buildSortUpdates(areaIds, siblingMap))) {
throw new IllegalStateException("行政区划排序保存失败");
}
return R.ok(true);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R saveArea(SysAreaEntity sysArea) {
Long pid = Optional.ofNullable(sysArea.getPid()).orElse(AREA_ROOT_ADCODE);
sysArea.setPid(pid);
List<SysAreaEntity> siblings = list(Wrappers.<SysAreaEntity>lambdaQuery()
.eq(SysAreaEntity::getPid, pid)
.orderByDesc(SysAreaEntity::getAreaSort)
.orderByAsc(SysAreaEntity::getAdcode));
sysArea.setAreaSort(0L);
if (!save(sysArea)) {
return R.ok(false);
}
List<Long> orderedIds = siblings.stream().map(SysAreaEntity::getId).collect(Collectors.toCollection(ArrayList::new));
orderedIds.add(sysArea.getId());
Map<Long, SysAreaEntity> areaMap = siblings.stream()
.collect(Collectors.toMap(SysAreaEntity::getId, Function.identity()));
areaMap.put(sysArea.getId(), sysArea);
if (!updateBatchById(buildSortUpdates(orderedIds, areaMap))) {
throw new IllegalStateException("行政区划新增排序初始化失败");
}
return R.ok(true);
}
@Override
@Transactional(rollbackFor = Exception.class)
public R updateArea(SysAreaUpdateDTO updateDTO) {
SysAreaEntity current = getById(updateDTO.getId());
if (Objects.isNull(current)) {
return R.failed("行政区划不存在或已删除");
}
if (Objects.equals(current.getAdcode(), AREA_ROOT_ADCODE)) {
return R.failed("全国根节点不允许修改");
}
SysAreaEntity update = new SysAreaEntity();
update.setId(updateDTO.getId());
update.setName(StrUtil.trim(updateDTO.getName()));
update.setAreaStatus(updateDTO.getAreaStatus());
update.setHot(updateDTO.getHot());
return updateById(update) ? R.ok(true) : R.failed("行政区划更新失败");
}
@Override
@Transactional(rollbackFor = Exception.class)
public R removeAreas(List<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return R.failed("行政区划ID不能为空");
}
Set<Long> uniqueIds = new HashSet<>(ids);
if (uniqueIds.size() != ids.size()) {
return R.failed("行政区划ID不能重复");
}
List<SysAreaEntity> areas = listByIds(uniqueIds);
if (areas.size() != uniqueIds.size()) {
return R.failed("行政区划不存在或已删除");
}
if (areas.stream().anyMatch(area -> Objects.equals(area.getAdcode(), AREA_ROOT_ADCODE))) {
return R.failed("全国根节点不允许删除");
}
Set<Long> adcodes = areas.stream().map(SysAreaEntity::getAdcode).collect(Collectors.toSet());
long childCount = count(Wrappers.<SysAreaEntity>lambdaQuery().in(SysAreaEntity::getPid, adcodes));
if (childCount > 0) {
return R.failed("所选行政区划包含下级节点,请先处理下级行政区划");
}
if (!removeByIds(uniqueIds)) {
throw new IllegalStateException("行政区划删除失败");
}
return R.ok(true);
}
private List<SysAreaManageVO> toManageList(List<SysAreaEntity> areas) {
if (CollUtil.isEmpty(areas)) {
return List.of();
}
Set<Long> parentCodes = areas.stream().map(SysAreaEntity::getAdcode).collect(Collectors.toSet());
Map<Long, Long> childCounts = baseMapper.selectChildCounts(parentCodes).stream()
.collect(Collectors.toMap(SysAreaChildCountVO::getPid, SysAreaChildCountVO::getChildCount));
Map<Long, SysAreaEntity> areaMap = loadAncestorMap(areas);
return areas.stream()
.map(area -> toManageVO(area, childCounts.getOrDefault(area.getAdcode(), 0L), areaMap))
.toList();
}
private Map<Long, SysAreaEntity> loadAncestorMap(List<SysAreaEntity> areas) {
Map<Long, SysAreaEntity> areaMap = areas.stream()
.collect(Collectors.toMap(SysAreaEntity::getAdcode, Function.identity(), (left, right) -> left));
Set<Long> pendingCodes = areas.stream()
.map(SysAreaEntity::getPid)
.filter(code -> Objects.nonNull(code) && code > 0 && !areaMap.containsKey(code))
.collect(Collectors.toSet());
while (CollUtil.isNotEmpty(pendingCodes)) {
List<SysAreaEntity> parents = list(Wrappers.<SysAreaEntity>lambdaQuery()
.in(SysAreaEntity::getAdcode, pendingCodes));
if (CollUtil.isEmpty(parents)) {
break;
}
parents.forEach(parent -> areaMap.put(parent.getAdcode(), parent));
pendingCodes = parents.stream()
.map(SysAreaEntity::getPid)
.filter(code -> Objects.nonNull(code) && code > 0 && !areaMap.containsKey(code))
.collect(Collectors.toSet());
}
return areaMap;
}
private SysAreaManageVO toManageVO(SysAreaEntity area, long childCount,
Map<Long, SysAreaEntity> areaMap) {
SysAreaManageVO vo = new SysAreaManageVO();
vo.setId(area.getId());
vo.setPid(area.getPid());
vo.setAdcode(area.getAdcode());
vo.setName(area.getName());
vo.setAreaType(area.getAreaType());
vo.setHot(area.getHot());
vo.setAreaStatus(area.getAreaStatus());
vo.setAreaSort(area.getAreaSort());
vo.setHasChildren(childCount > 0);
vo.setChildCount(childCount);
List<SysAreaEntity> path = buildPath(area, areaMap);
vo.setPathCodes(path.stream().map(SysAreaEntity::getAdcode).toList());
vo.setPathName(path.stream().map(SysAreaEntity::getName).collect(Collectors.joining(StrUtil.SPACE + "/" + StrUtil.SPACE)));
return vo;
}
private List<SysAreaEntity> buildPath(SysAreaEntity area, Map<Long, SysAreaEntity> areaMap) {
LinkedList<SysAreaEntity> path = new LinkedList<>();
Set<Long> visitedCodes = new HashSet<>();
SysAreaEntity current = area;
while (Objects.nonNull(current) && visitedCodes.add(current.getAdcode())) {
path.addFirst(current);
current = areaMap.get(current.getPid());
}
return path;
}
private List<SysAreaEntity> buildSortUpdates(List<Long> orderedIds, Map<Long, SysAreaEntity> areaMap) {
List<SysAreaEntity> updates = new ArrayList<>(orderedIds.size());
for (int index = 0; index < orderedIds.size(); index++) {
SysAreaEntity original = areaMap.get(orderedIds.get(index));
SysAreaEntity update = new SysAreaEntity();
update.setId(original.getId());
update.setAreaSort((long) orderedIds.size() - index);
updates.add(update);
}
return updates;
}
private Long parseAdcode(String keyword) {
try {
return Long.valueOf(keyword);
}
catch (NumberFormatException ignored) {
return null;
}
}
}
@@ -21,4 +21,20 @@
<result property="updateTime" column="update_time"/>
<result property="delFlag" column="del_flag"/>
</resultMap>
<resultMap id="sysAreaChildCountMap" type="com.pig4cloud.pigx.admin.api.vo.SysAreaChildCountVO">
<result property="pid" column="pid"/>
<result property="childCount" column="child_count"/>
</resultMap>
<select id="selectChildCounts" resultMap="sysAreaChildCountMap">
SELECT pid, COUNT(*) AS child_count
FROM sys_area
WHERE del_flag = '0'
AND pid IN
<foreach collection="parentCodes" item="parentCode" open="(" separator="," close=")">
#{parentCode}
</foreach>
GROUP BY pid
</select>
</mapper>
@@ -0,0 +1,180 @@
package com.pig4cloud.pigx.admin.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.pig4cloud.pigx.admin.api.dto.SysAreaSortDTO;
import com.pig4cloud.pigx.admin.api.dto.SysAreaUpdateDTO;
import com.pig4cloud.pigx.admin.api.entity.SysAreaEntity;
import com.pig4cloud.pigx.admin.api.vo.SysAreaChildCountVO;
import com.pig4cloud.pigx.admin.api.vo.SysAreaManageVO;
import com.pig4cloud.pigx.admin.mapper.SysAreaMapper;
import com.pig4cloud.pigx.common.core.util.R;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class SysAreaServiceImplTests {
@Test
void saveAreaUsesSubmittedEntityWithoutExtraValidation() {
SysAreaServiceImpl service = spy(new SysAreaServiceImpl());
SysAreaEntity area = area(3L, null, null);
doReturn(List.of()).when(service).list(any(Wrapper.class));
doReturn(true).when(service).save(area);
doReturn(true).when(service).updateBatchById(any(Collection.class));
R result = service.saveArea(area);
assertThat(result.isOk()).isTrue();
assertThat(area.getPid()).isEqualTo(100000L);
assertThat(area.getAreaSort()).isZero();
verify(service).save(area);
}
@Test
void listManageChildrenLoadsChildCountsFromMapper() {
SysAreaServiceImpl service = new SysAreaServiceImpl();
SysAreaMapper mapper = mock(SysAreaMapper.class);
ReflectionTestUtils.setField(service, "baseMapper", mapper);
SysAreaEntity root = area(1L, 100000L, 0L);
root.setName("全国");
SysAreaChildCountVO childCount = new SysAreaChildCountVO();
childCount.setPid(100000L);
childCount.setChildCount(32L);
when(mapper.selectList(any(Wrapper.class))).thenReturn(List.of(root));
when(mapper.selectChildCounts(Set.of(100000L))).thenReturn(List.of(childCount));
List<SysAreaManageVO> result = service.listManageChildren(0L);
assertThat(result).hasSize(1);
assertThat(result.get(0).getChildCount()).isEqualTo(32L);
assertThat(result.get(0).getHasChildren()).isTrue();
verify(mapper).selectChildCounts(Set.of(100000L));
}
@Test
void removeAreasRejectsEmptyIds() {
SysAreaServiceImpl service = new SysAreaServiceImpl();
R result = service.removeAreas(List.of());
assertThat(result.isOk()).isFalse();
assertThat(result.getMsg()).contains("不能为空");
}
@Test
void updateAreaRejectsNationwideRoot() {
SysAreaServiceImpl service = new SysAreaServiceImpl();
SysAreaMapper mapper = mock(SysAreaMapper.class);
ReflectionTestUtils.setField(service, "baseMapper", mapper);
when(mapper.selectById(1L)).thenReturn(area(1L, 100000L, 0L));
SysAreaUpdateDTO update = new SysAreaUpdateDTO();
update.setId(1L);
update.setName("全国");
update.setAreaStatus("1");
update.setHot("0");
R result = service.updateArea(update);
assertThat(result.isOk()).isFalse();
assertThat(result.getMsg()).contains("根节点");
}
@Test
void updateAreaWritesOnlyEditableFields() {
SysAreaServiceImpl service = new SysAreaServiceImpl();
SysAreaMapper mapper = mock(SysAreaMapper.class);
ReflectionTestUtils.setField(service, "baseMapper", mapper);
when(mapper.selectById(2L)).thenReturn(area(2L, 110000L, 100000L));
when(mapper.updateById(any(SysAreaEntity.class))).thenReturn(1);
SysAreaUpdateDTO update = new SysAreaUpdateDTO();
update.setId(2L);
update.setName(" 北京市 ");
update.setAreaStatus("1");
update.setHot("1");
R result = service.updateArea(update);
ArgumentCaptor<SysAreaEntity> captor = ArgumentCaptor.forClass(SysAreaEntity.class);
verify(mapper).updateById(captor.capture());
SysAreaEntity saved = captor.getValue();
assertThat(result.isOk()).isTrue();
assertThat(saved.getName()).isEqualTo("北京市");
assertThat(saved.getAreaStatus()).isEqualTo("1");
assertThat(saved.getHot()).isEqualTo("1");
assertThat(saved.getPid()).isNull();
assertThat(saved.getAdcode()).isNull();
assertThat(saved.getAreaType()).isNull();
assertThat(saved.getAreaSort()).isNull();
}
@Test
void updateAreaSortRejectsDuplicateIds() {
SysAreaServiceImpl service = new SysAreaServiceImpl();
SysAreaSortDTO sortDTO = new SysAreaSortDTO();
sortDTO.setPid(100000L);
sortDTO.setAreaIds(List.of(1L, 1L));
R result = service.updateAreaSort(sortDTO);
assertThat(result.isOk()).isFalse();
assertThat(result.getMsg()).contains("不能重复");
}
@Test
@SuppressWarnings("unchecked")
void updateAreaSortWritesDescendingValuesInVisualOrder() {
SysAreaServiceImpl service = spy(new SysAreaServiceImpl());
SysAreaEntity first = area(1L, 110000L, 100000L);
SysAreaEntity second = area(2L, 120000L, 100000L);
doReturn(List.of(first, second)).when(service).list(any(Wrapper.class));
doReturn(true).when(service).updateBatchById(any(Collection.class));
SysAreaSortDTO sortDTO = new SysAreaSortDTO();
sortDTO.setPid(100000L);
sortDTO.setAreaIds(List.of(2L, 1L));
R result = service.updateAreaSort(sortDTO);
ArgumentCaptor<Collection<SysAreaEntity>> updatesCaptor = ArgumentCaptor.forClass(Collection.class);
verify(service).updateBatchById(updatesCaptor.capture());
List<SysAreaEntity> updates = updatesCaptor.getValue().stream().toList();
assertThat(result.isOk()).isTrue();
assertThat(result.getData()).isEqualTo(true);
assertThat(updates).extracting(SysAreaEntity::getId).containsExactly(2L, 1L);
assertThat(updates).extracting(SysAreaEntity::getAreaSort).containsExactly(2L, 1L);
}
@Test
@SuppressWarnings("unchecked")
void removeAreasRejectsNationwideRoot() {
SysAreaServiceImpl service = spy(new SysAreaServiceImpl());
SysAreaEntity root = area(1L, 100000L, 0L);
doReturn(List.of(root)).when(service).listByIds(any(Collection.class));
R result = service.removeAreas(List.of(1L));
assertThat(result.isOk()).isFalse();
assertThat(result.getMsg()).contains("根节点");
}
private SysAreaEntity area(Long id, Long adcode, Long pid) {
SysAreaEntity area = new SysAreaEntity();
area.setId(id);
area.setAdcode(adcode);
area.setPid(pid);
return area;
}
}