Parcourir la source

完成国家树的生成,相关表的关联

一条糖醋鱼 il y a 2 semaines
Parent
commit
83852d6d94

+ 44 - 0
edu-travel-remote/edu-travel-remote-commodity/src/main/java/edu/travel/commodity/dto/BaseCountryDto.java

@@ -0,0 +1,44 @@
+package edu.travel.commodity.dto;
+
+import edu.travel.po.PagePO;
+import lombok.Data;
+
+/**
+ * 国家表
+ */
+@Data
+public class BaseCountryDto extends PagePO {
+    /**
+     * ID
+     */
+    private Long id;
+
+    /**
+     * 所属洲
+     */
+    private Long parentId;
+    /**
+     * 图片
+     */
+    private String imageUrl;
+
+    /**
+     * 国家/洲中文名称
+     */
+    private String countryNameZh;
+
+    /**
+     * 国家/洲英文名称
+     */
+    private String countryNameEn;
+
+    /**
+     * 当地国家/洲名称
+     */
+    private String countryNameLocal;
+    /**
+     * 国家区号
+     */
+    private Integer areaCode;
+
+}

+ 20 - 0
edu-travel-remote/edu-travel-remote-commodity/src/main/java/edu/travel/commodity/remote/BaseCountryRemoteController.java

@@ -0,0 +1,20 @@
+package edu.travel.commodity.remote;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import edu.travel.commodity.dto.BaseCountryDto;
+import edu.travel.commodity.vo.BaseCountryVo;
+import edu.travel.rpc.RPCBaseResponse;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+
+import java.util.List;
+
+@FeignClient(path = "/base_country",name = "commodity")
+public interface BaseCountryRemoteController {
+    //国家树
+    @GetMapping("/getCountryTree")
+    public RPCBaseResponse<List<BaseCountryVo>> getCountryTree();
+    //国家分页
+    @GetMapping("/getCountryPage")
+    public RPCBaseResponse<IPage<BaseCountryVo>> getCountryPage(BaseCountryDto baseCountryDTO);
+}

+ 52 - 0
edu-travel-remote/edu-travel-remote-commodity/src/main/java/edu/travel/commodity/vo/BaseCountryVo.java

@@ -0,0 +1,52 @@
+package edu.travel.commodity.vo;
+
+import edu.travel.entity.BaseEntity;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 国家表
+ */
+@Data
+
+public class BaseCountryVo extends BaseEntity {
+    /**
+     * ID
+     */
+    private Long id;
+
+    /**
+     * 所属洲
+     */
+    private Long parentId;
+    /**
+     * 图片
+     */
+    private String imageUrl;
+
+    /**
+     * 国家/洲中文名称
+     */
+    private String countryNameZh;
+
+    /**
+     * 国家/洲英文名称
+     */
+    private String countryNameEn;
+
+    /**
+     * 当地国家/洲名称
+     */
+    private String countryNameLocal;
+
+    /**
+     * 国家区号
+     */
+    private Integer areaCode;
+    /**
+     * 子类
+     */
+    private List<BaseCountryVo> children = new ArrayList<>();
+}

+ 1 - 4
edu-travel-service/edu-travel-service-commodity/src/main/java/edu/travel/commodity/dto/BaseCountryDto.java

@@ -43,10 +43,7 @@ public class BaseCountryDto extends PagePO {
      * 当地国家/洲名称
      */
     private String countryNameLocal;
-    /**
-     * 国家图片
-     */
-    private Integer countryImg;
+
     /**
      * 国家区号
      */

+ 0 - 5
edu-travel-service/edu-travel-service-commodity/src/main/java/edu/travel/commodity/entity/BaseCountry.java

@@ -55,11 +55,6 @@ public class BaseCountry extends BaseEntity {
     @TableField(value = "country_name_local")
     private String countryNameLocal;
     /**
-     * 国家图片
-     */
-    @TableField(value = "country_img")
-    private Integer countryImg;
-    /**
      * 国家区号
      */
     @TableField(value = "area_code")

+ 29 - 1
edu-travel-service/edu-travel-service-commodity/src/main/java/edu/travel/commodity/service/impl/BaseCountryServiceImpl.java

@@ -51,7 +51,35 @@ public class BaseCountryServiceImpl extends SysServiceImpl<BaseCountryMapper, Ba
         //map,快速查找国家VO对象
         Map<Long,BaseCountryVo> countryMap = new HashMap<>();
         List<BaseCountryVo> rootCountries = new ArrayList<>();
-        return null;
+
+        //构建每个国家VO对象
+        for(BaseCountry baseCountry : baseCountryList){
+            BaseCountryVo baseCountryVo = new BaseCountryVo();
+            baseCountryVo.setId(baseCountry.getId());
+            baseCountryVo.setParentId(baseCountry.getParentId());
+            baseCountryVo.setImageUrl(baseCountry.getImageUrl());
+            baseCountryVo.setCountryNameZh(baseCountry.getCountryNameZh());
+            baseCountryVo.setCountryNameEn(baseCountry.getCountryNameEn());
+            baseCountryVo.setCountryNameLocal(baseCountry.getCountryNameLocal());
+            baseCountryVo.setAreaCode(baseCountry.getAreaCode());
+            countryMap.put(baseCountry.getId(),baseCountryVo);
+        }
+
+        //构建树形结构
+        for (BaseCountry baseCountry : baseCountryList){
+            BaseCountryVo baseCountryVo = countryMap.get(baseCountry.getId());
+            Long parentId = baseCountry.getParentId();
+
+            if (parentId == 0|| parentId == null){
+                rootCountries.add(baseCountryVo);
+            }else{
+                BaseCountryVo parentCountryVo = countryMap.get(parentId);
+                if (parentCountryVo != null){
+                    parentCountryVo.getChildren().add(baseCountryVo);
+                }
+            }
+        }
+        return rootCountries;
     }
 
 }

+ 161 - 0
edu-travel-service/edu-travel-service-commodity/src/main/java/edu/travel/commodity/utils/TreeUtils.java

@@ -0,0 +1,161 @@
+package edu.travel.commodity.utils;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.BiConsumer;
+
+/**
+ * 通用树形结构构建工具类
+ *
+ * <p><strong>功能特性:</strong></p>
+ * <ul>
+ *   <li>支持任意 Java 对象(实体类/DTO/VO)</li>
+ *   <li>自动检测循环依赖(死循环)</li>
+ *   <li>零依赖(无需继承接口或父类)</li>
+ *   <li>编译期类型安全检查</li>
+ * </ul>
+ *
+ * <p><strong>使用示例:</strong></p>
+ * <pre>{@code
+ * List<Department> tree = TreeUtils.buildTree(
+ *     departments,
+ *     Department::getId,
+ *     Department::getParentId,
+ *     Department::setChildren
+ * );
+ * }</pre>
+ */
+public class TreeUtils {
+
+    /**
+     * 构建树形结构的核心方法
+     *
+     * @param nodes          待处理的节点列表(必须包含所有节点)
+     * @param idGetter       节点ID的获取方法(方法引用,如:User::getId)
+     * @param pidGetter      父节点ID的获取方法(方法引用,如:User::getParentId)
+     * @param childrenSetter 子节点列表的设置方法(如:(node, children) -> node.setChildren(children))
+     * @param <T>            节点类型(如:User.class)
+     * @param <ID>           ID 的类型(需正确实现 equals 和 hashCode)
+     * @return 树形结构的根节点列表
+     *
+     * @throws IllegalArgumentException 如果存在以下情况:
+     *                                  <ul>
+     *                                    <li>节点列表为null</li>
+     *                                    <li>存在重复ID</li>
+     *                                    <li>ID字段为null</li>
+     *                                  </ul>
+     * @throws IllegalStateException    如果检测到循环依赖
+     */
+    public static <T, ID> List<T> buildTree(List<T> nodes,
+                                            Function<T, ID> idGetter,
+                                            Function<T, ID> pidGetter,
+                                            BiConsumer<T, List<T>> childrenSetter) {
+        // 防御性拷贝,避免修改原始数据
+        List<T> copyNodes = new ArrayList<>(Optional.ofNullable(nodes).orElseGet(ArrayList::new));
+
+        //========== 数据校验 ==========//
+        validateNodes(copyNodes, idGetter);
+
+        //========== 初始化数据结构 ==========//
+        Map<ID, T> nodeMap = new HashMap<>(copyNodes.size());
+        List<T> rootNodes = new ArrayList<>();
+
+        // 构建 id -> node 映射表
+        copyNodes.forEach(node -> nodeMap.put(idGetter.apply(node), node));
+
+        //========== 构建父子关系 ==========//
+        for (T node : copyNodes) {
+            ID currentId = idGetter.apply(node);
+            ID parentId = pidGetter.apply(node);
+
+            // 判断根节点条件(父ID为空/等于自身/不存在于Map中)
+            if (isRootNode(parentId, currentId, nodeMap)) {
+                rootNodes.add(node);
+                continue;
+            }
+
+            // 获取父节点并建立关联
+            T parent = nodeMap.get(parentId);
+            if (parent != null) {
+                linkParentAndChild(parent, node, childrenSetter);
+                checkCircularDependency(parent, currentId, idGetter, pidGetter, nodeMap);
+            }
+        }
+
+        return rootNodes;
+    }
+
+    //========== 私有方法 ==========//
+
+    /**
+     * 数据校验
+     */
+    private static <T, ID> void validateNodes(List<T> nodes, Function<T, ID> idGetter) {
+        if (nodes == null) {
+            throw new IllegalArgumentException("节点列表不能为null");
+        }
+
+        Set<ID> idSet = new HashSet<>();
+        for (T node : nodes) {
+            ID id = idGetter.apply(node);
+            if (id == null) {
+                throw new IllegalArgumentException("节点存在空ID: " + node);
+            }
+            if (idSet.contains(id)) {
+                throw new IllegalArgumentException("存在重复ID: " + id);
+            }
+            idSet.add(id);
+        }
+    }
+
+    /**
+     * 判断是否为根节点
+     */
+    private static <ID> boolean isRootNode(ID parentId, ID currentId, Map<ID, ?> nodeMap) {
+        return parentId == null
+                || parentId.equals(currentId)
+                || !nodeMap.containsKey(parentId);
+    }
+
+    /**
+     * 建立父子关联关系
+     */
+    private static <T> void linkParentAndChild(T parent,
+                                               T child,
+                                               BiConsumer<T, List<T>> childrenSetter) {
+        List<T> children = new ArrayList<>();
+        childrenSetter.accept(parent, children);
+        children.add(child);
+    }
+
+    /**
+     * 循环依赖检测(核心安全机制)
+     */
+    private static <T, ID> void checkCircularDependency(T parent,
+                                                        ID childId,
+                                                        Function<T, ID> idGetter,
+                                                        Function<T, ID> pidGetter,
+                                                        Map<ID, T> nodeMap) {
+        Set<ID> visited = new HashSet<>();
+        T current = parent;
+
+        while (current != null) {
+            ID currentParentId = pidGetter.apply(current);
+            if (currentParentId == null) break;
+
+            // 发现直接循环(A→B→A)
+            if (currentParentId.equals(childId)) {
+                throw new IllegalStateException("检测到循环依赖:节点 "
+                        + idGetter.apply(current) + " → " + childId);
+            }
+
+            // 发现间接循环(A→B→C→A)
+            if (visited.contains(currentParentId)) {
+                throw new IllegalStateException("检测到循环依赖路径:" + visited);
+            }
+
+            visited.add(currentParentId);
+            current = nodeMap.get(currentParentId);
+        }
+    }
+}

+ 1 - 4
edu-travel-service/edu-travel-service-commodity/src/main/java/edu/travel/commodity/vo/BaseCountryVo.java

@@ -40,10 +40,7 @@ public class BaseCountryVo extends BaseEntity {
      * 当地国家/洲名称
      */
     private String countryNameLocal;
-    /**
-     * 国家图片
-     */
-    private Integer countryImg;
+
     /**
      * 国家区号
      */

+ 4 - 1
edu-travel-service/edu-travel-service-commodity/src/main/java/edu/travel/commodity/web/BaseCountryController.java

@@ -2,9 +2,12 @@ package edu.travel.commodity.web;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import edu.travel.commodity.dto.BaseCountryDto;
+import edu.travel.commodity.entity.BaseCountry;
+import edu.travel.commodity.remote.BaseCountryRemoteController;
 import edu.travel.commodity.service.BaseCountryService;
 import edu.travel.commodity.vo.BaseCountryVo;
 import edu.travel.rpc.RPCBaseResponse;
+import edu.travel.web.BaseController;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -19,7 +22,7 @@ import java.util.List;
  */
 @RestController
 @RequestMapping("/baseCountry")
-public class BaseCountryController {
+public class BaseCountryController extends BaseController<BaseCountry> implements BaseCountryRemoteController {
 
     /**
      * 服务对象

+ 1 - 0
edu-travel-service/edu-travel-service-commodity/src/main/java/edu/travel/commodity/web/BaseCountryServeController.java

@@ -9,6 +9,7 @@ import edu.travel.interfaces.UpdateGroups;
 import edu.travel.resp.BaseResponse;
 import edu.travel.rpc.RPCBaseResponse;
 import edu.travel.web.BaseController;
+import org.springframework.beans.BeanUtils;
 import org.springframework.validation.Errors;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;