mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 04:39:00 +08:00
feat(area): 重构行政区划管理接口 (#72)
This commit is contained in:
1 parent
fb5e1bdf6e
commit
ce07c9dcb9
10 files changed
+730
-9
No files matched your search
+34
@@ -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;
|
||||
|
||||
}
|
||||
+33
@@ -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;
|
||||
|
||||
}
|
||||
+18
@@ -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;
|
||||
|
||||
}
|
||||
+54
@@ -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;
|
||||
|
||||
}
|
||||
+67
-8
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
@@ -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);
|
||||
|
||||
}
|
||||
+60
@@ -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);
|
||||
|
||||
}
|
||||
+255
-1
@@ -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>
|
||||
+180
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user