Browse Source

UploadAdapter接口完善上传。

Sakana 4 days ago
parent
commit
9b3e63d482
20 changed files with 602 additions and 22 deletions
  1. 21 1
      edu-travel-adapter/edu-travel-adapter-upload/src/main/java/edu/travel/adapter/service/upload/UploadAdapter.java
  2. 6 0
      edu-travel-remote/edu-travel-remote-upload/pom.xml
  3. 9 1
      edu-travel-remote/edu-travel-remote-upload/src/main/java/edu/travel/remote/upload/UploadRemoteController.java
  4. 56 0
      edu-travel-remote/edu-travel-remote-upload/src/main/java/edu/travel/remote/upload/vo/EduFileBlobVo.java
  5. 50 0
      edu-travel-remote/edu-travel-remote-upload/src/main/java/edu/travel/remote/upload/vo/EduFileVo.java
  6. 0 6
      edu-travel-service/edu-travel-service-tenement/pom.xml
  7. 43 0
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/constant/BaseConstant.java
  8. 19 0
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/constant/RedisKey.java
  9. 2 3
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/service/impl/ShopBannerServiceImpl.java
  10. 17 0
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/FIleUtil.java
  11. 13 0
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/IdUtils.java
  12. 19 0
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/ObjectUtils.java
  13. 28 0
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/PageUtil.java
  14. 36 0
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/RedisUtil.java
  15. 26 0
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/StringUtil.java
  16. 16 0
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/TokenData.java
  17. 161 0
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/TreeUtils.java
  18. 4 9
      edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/web/ShopBannerController.java
  19. 75 1
      edu-travel-service/edu-travel-service-upload/src/main/java/edu/travel/upload/web/UploadController.java
  20. 1 1
      edu-travel-service/edu-travel-service-upload/src/main/resources/bootstrap.yml

+ 21 - 1
edu-travel-adapter/edu-travel-adapter-upload/src/main/java/edu/travel/adapter/service/upload/UploadAdapter.java

@@ -5,10 +5,10 @@ import edu.travel.adapter.annotation.AdapterAnnotation;
 import edu.travel.remote.upload.UploadRemoteController;
 import edu.travel.remote.upload.dto.EduFileBlobDTO;
 import edu.travel.remote.upload.dto.EduFileDTO;
+import edu.travel.remote.upload.vo.EduFileVo;
 import edu.travel.rpc.RPCBaseResponse;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
-import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
@@ -48,4 +48,24 @@ public class UploadAdapter  {
         RPCBaseResponse rpcBaseResponse = uploadRemoteController.uploadFile(JSON.toJSONString(eduFileDTO),file);
         return rpcBaseResponse;
     }
+    @AdapterAnnotation
+    public RPCBaseResponse<EduFileVo> getFormId(String id) {
+        return uploadRemoteController.getFormId(id);
+    }
+    @AdapterAnnotation
+    public RPCBaseResponse<EduFileVo> updateTargetFormId(EduFileDTO entity) {
+        return uploadRemoteController.updateTargetFormId(entity);
+    }
+    @AdapterAnnotation
+    public RPCBaseResponse<EduFileVo> saveFormTarget(EduFileDTO entity) {
+        return uploadRemoteController.saveFormTarget(entity);
+    }
+    @AdapterAnnotation
+    public RPCBaseResponse<EduFileVo> deleteTargetFormId(List<String> ids) {
+        return uploadRemoteController.deleteTargetFormId(ids);
+    }
+    @AdapterAnnotation
+    public RPCBaseResponse<List<EduFileVo>> getAllForm() {
+        return uploadRemoteController.getAllForm();
+    }
 }

+ 6 - 0
edu-travel-remote/edu-travel-remote-upload/pom.xml

@@ -28,5 +28,11 @@
             <artifactId>edu-travel-common-openfeign</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>edu.travel</groupId>
+            <artifactId>edu-travel-remote-base</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 </project>

+ 9 - 1
edu-travel-remote/edu-travel-remote-upload/src/main/java/edu/travel/remote/upload/UploadRemoteController.java

@@ -1,7 +1,9 @@
 package edu.travel.remote.upload;
 
+import edu.travel.remote.base.RemoteBaseController;
 import edu.travel.remote.upload.dto.EduFileBlobDTO;
 import edu.travel.remote.upload.dto.EduFileDTO;
+import edu.travel.remote.upload.vo.EduFileVo;
 import edu.travel.rpc.RPCBaseResponse;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.http.MediaType;
@@ -13,7 +15,7 @@ import java.io.Serializable;
 import java.util.List;
 
 @FeignClient(name = "upload-dev",path = "/upload")
-public interface UploadRemoteController {
+public interface UploadRemoteController extends RemoteBaseController<EduFileVo, EduFileDTO> {
     @PostMapping("/init")
     public RPCBaseResponse<String> initializeUpload();
     /**
@@ -55,4 +57,10 @@ public interface UploadRemoteController {
      */
     @PostMapping(value = "/upload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
     RPCBaseResponse uploadFile(@RequestParam("eduFileDTO") String dto,@RequestPart("file") MultipartFile file) throws IOException;
+
+    /**
+     * 通过id获取文件
+     */
+    @GetMapping("/getFormId")
+    RPCBaseResponse<EduFileVo> getFormId(String id);
 }

+ 56 - 0
edu-travel-remote/edu-travel-remote-upload/src/main/java/edu/travel/remote/upload/vo/EduFileBlobVo.java

@@ -0,0 +1,56 @@
+package edu.travel.remote.upload.vo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import edu.travel.entity.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "edu.edu_file_blob")
+public class EduFileBlobVo extends BaseEntity {
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 分片名字
+     */
+    @TableField(value = "blob_name")
+    private String blobName;
+
+    /**
+     * 传了第几片
+     */
+    @TableField(value = "blob_num")
+    private Integer blobNum;
+
+    /**
+     * 分片地址
+     */
+    @TableField(value = "blob_path")
+    private String blobPath;
+
+    /**
+     * 分片md5
+     */
+    @TableField(value = "blob_md5")
+    private String blobMd5;
+
+    /**
+     * 文件md5
+     */
+    @TableField(value = "file_md5")
+    private String fileMd5;
+
+    /**
+     * 总共多少片
+     */
+    @TableField(value = "total_count")
+    private Integer totalCount;
+
+}

+ 50 - 0
edu-travel-remote/edu-travel-remote-upload/src/main/java/edu/travel/remote/upload/vo/EduFileVo.java

@@ -0,0 +1,50 @@
+package edu.travel.remote.upload.vo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import edu.travel.entity.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "edu.edu_file")
+public class EduFileVo extends BaseEntity {
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 文件md5
+     */
+    @TableField(value = "file_md5")
+    private String fileMd5;
+
+    /**
+     * 文件名
+     */
+    @TableField(value = "file_name")
+    private String fileName;
+
+    /**
+     * 文件系统名
+     */
+    @TableField(value = "file_sys_name")
+    private String fileSysName;
+
+    /**
+     * 文件路径
+     */
+    @TableField(value = "file_path")
+    private String filePath;
+
+    /**
+     * 文件类型
+     */
+    @TableField(value = "file_type")
+    private String fileType;
+
+}

+ 0 - 6
edu-travel-service/edu-travel-service-tenement/pom.xml

@@ -139,12 +139,6 @@
             <version>1.0-SNAPSHOT</version>
             <scope>compile</scope>
         </dependency>
-        <dependency>
-            <groupId>edu.travel</groupId>
-            <artifactId>edu-travel-service-commodity</artifactId>
-            <version>1.0-SNAPSHOT</version>
-            <scope>compile</scope>
-        </dependency>
 
     </dependencies>
     <profiles>

+ 43 - 0
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/constant/BaseConstant.java

@@ -0,0 +1,43 @@
+package edu.travel.tenant.constant;
+
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class BaseConstant {
+    /**
+     * 删除标记 正常
+     */
+    public static final Integer BASIC_STATUS_NO_NUM = 0;
+    public static final String BASIC_STATUS_NO_STR = "0";
+
+    /**
+     * 删除标记 删除
+     */
+    public static final Integer BASIC_STATUS_YES_NUM = 1;
+    public static final String BASIC_STATUS_YES_STR = "1";
+
+    /**
+     * 删除标记 正常
+     */
+    public static final Integer BASIC_IS_DEFAULT_YES = 0;
+    public static final Integer BASIC_IS_DEFAULT_YES_NO = 1;
+
+    /**
+     * 商品文件上传图片类型
+     */
+    private static final Set<String> PRODUCT_IMAGE_TYPES = new HashSet<>(
+            Arrays.asList("jpg", "jpeg", "png")
+    );
+
+    public static Set<String> getProductImageTypes() {
+        return PRODUCT_IMAGE_TYPES;
+    }
+    /**
+     * 获取banner 图片类型
+     */
+    public static Set<String> getBannerImageTypes() {
+        return PRODUCT_IMAGE_TYPES;
+    }
+}

+ 19 - 0
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/constant/RedisKey.java

@@ -0,0 +1,19 @@
+package edu.travel.tenant.constant;
+
+public interface RedisKey {
+    /**商品类型*/
+    String BASH = "shop:";
+    /**商品类型*/
+   String PRODUCT_TYPE = BASH +"product:type";
+   /**热门商品类型*/
+   String PRODUCT_HOT_TYPE = BASH +"product:hot:type";
+    /**热门二级商品类型*/
+   String PRODUCT_TO_HOT_TYPE = BASH +"product:to:hot:type";
+    /**
+     * 加上国家
+     */
+
+
+    String PRODUCT_ORDER = BASH +"product:order";
+
+}

+ 2 - 3
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/service/impl/ShopBannerServiceImpl.java

@@ -9,15 +9,15 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import edu.travel.adapter.service.upload.UploadAdapter;
-import edu.travel.commodity.constant.BaseConstant;
-import edu.travel.commodity.utils.FIleUtil;
 import edu.travel.remote.feign.mode.dto.tenant.BannerDto;
 import edu.travel.remote.feign.mode.vo.banner.BannerVo;
 import edu.travel.remote.upload.dto.EduFileDTO;
 import edu.travel.rpc.RPCBaseResponse;
+import edu.travel.tenant.constant.BaseConstant;
 import edu.travel.tenant.entity.ShopBanner;
 import edu.travel.tenant.mapper.ShopBannerMapper;
 import edu.travel.tenant.service.ShopBannerService;
+import edu.travel.tenant.utils.FIleUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
@@ -76,7 +76,6 @@ public class ShopBannerServiceImpl extends ServiceImpl<ShopBannerMapper, ShopBan
                 eduFileDTO.setUploadId(stringRPCBaseResponse.getData()); // 设置上传 ID
                 eduFileDTO.setFileType(fileType); // 设置文件类型
                 eduFileDTO.setFileName(filename); // 设置文件原始名称
-
                 // 生成系统文件名,确保唯一性
                 eduFileDTO.setFileSysName(IdUtil.fastSimpleUUID() + DateUtil.format(new Date(), "yyyyMMddHHmmss") + "." + fileType);
                 // 计算文件的 MD5 值以便后续验证

+ 17 - 0
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/FIleUtil.java

@@ -0,0 +1,17 @@
+package edu.travel.tenant.utils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class FIleUtil {
+
+    public static String calculateFileMd5(byte[] data) throws NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance("MD5");
+        byte[] digest = md.digest(data);
+        StringBuilder sb = new StringBuilder();
+        for (byte b : digest) {
+            sb.append(String.format("%02x", b));
+        }
+        return sb.toString();
+    }
+}

+ 13 - 0
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/IdUtils.java

@@ -0,0 +1,13 @@
+package edu.travel.tenant.utils;
+
+import cn.hutool.core.lang.Singleton;
+import cn.hutool.core.lang.Snowflake;
+import lombok.Data;
+
+@Data
+public class IdUtils {
+    public static long getSnowflakeId() {
+        Snowflake snowflake = Singleton.get(Snowflake.class, 1L, 1L, true);
+        return  snowflake.nextId();
+    }
+}

+ 19 - 0
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/ObjectUtils.java

@@ -0,0 +1,19 @@
+package edu.travel.tenant.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ObjectUtils {
+
+
+    public static <T> List<T> castList(Object obj, Class<T> clazz) {
+        List<T> result = new ArrayList<T>();
+        if (obj instanceof List<?>) {
+            for (Object o : (List<?>) obj) {
+                result.add(clazz.cast(o));
+            }
+            return result;
+        }
+        return null;
+    }
+}

+ 28 - 0
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/PageUtil.java

@@ -0,0 +1,28 @@
+package edu.travel.tenant.utils;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springframework.beans.BeanUtils;
+
+public class PageUtil{
+
+    /**
+     * Page类型entity转换
+     * @param source
+     * @param type
+     * @return
+     */
+    public static <T,E> Page<E> toPageEntity(Page<T> source, Class<E> type) {
+        Page<E> target = new Page<E>();
+        BeanUtils.copyProperties(source, target);
+        target.setRecords(BeanUtil.copyToList(source.getRecords(), type));
+        return target;
+    }
+    public static <T,E> Page<E> toPageEntity(IPage<T> source, Class<E> type) {
+        Page<E> target = new Page<E>();
+        BeanUtils.copyProperties(source, target);
+        target.setRecords(BeanUtil.copyToList(source.getRecords(), type));
+        return target;
+    }
+}

+ 36 - 0
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/RedisUtil.java

@@ -0,0 +1,36 @@
+package edu.travel.tenant.utils;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class RedisUtil {
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+    /**
+     *通过KEY 获取值
+     */
+    public String getString(String key) {
+        return stringRedisTemplate.opsForValue().get(key);
+    }
+    /**
+     * 设置KEY 和 VALUE
+     */
+    public void setString(String key, String value) {
+        stringRedisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * @param key KEY
+     * @param value 值
+     * @param expire 过期时间
+     * @param timeUnit 时间单位
+     */
+    public void setString(String key, String value,long expire,TimeUnit timeUnit) {
+        stringRedisTemplate.opsForValue().set(key, value,expire, timeUnit);
+    }
+
+}

+ 26 - 0
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/StringUtil.java

@@ -0,0 +1,26 @@
+package edu.travel.tenant.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Data
+public class StringUtil {
+
+    public static List<String> toList(String str){
+        if(!ObjectUtil.isEmpty(str)){
+            return Arrays.asList(str.split(","));
+        }
+        return new ArrayList<>();
+    }
+    public static String toListString(List<String> list){
+        String result="";
+        for (String s : list) {
+            result=s+",";
+        }
+        return result;
+    }
+}

+ 16 - 0
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/utils/TokenData.java

@@ -0,0 +1,16 @@
+package edu.travel.tenant.utils;
+
+import edu.travel.entity.EduTenantPO;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+public class TokenData {
+
+    /**
+     *  获取用户ID
+     * @return {@link String }
+     */
+    public static String getUserId(){
+        EduTenantPO principal =(EduTenantPO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+        return principal.getId().toString();
+    }
+}

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

@@ -0,0 +1,161 @@
+package edu.travel.tenant.utils;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+/**
+ * 通用树形结构构建工具类
+ *
+ * <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);
+        }
+    }
+}

+ 4 - 9
edu-travel-service/edu-travel-service-tenement/src/main/java/edu/travel/tenant/web/ShopBannerController.java

@@ -10,11 +10,7 @@ import edu.travel.tenant.service.ShopBannerService;
 import edu.travel.web.BaseController;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
@@ -51,11 +47,10 @@ public class ShopBannerController extends BaseController<ShopBanner> implements
      * @param
      * @return
      */
-    @GetMapping("/uploadBannerImage")
-    public ResponseEntity<RPCBaseResponse<String>> uploadBannerImage(@RequestBody MultipartFile file){
-        return ResponseEntity.ok(shopBannerService.uploadBannerImage(file));
+    @PostMapping("/uploadBannerImage")
+    public RPCBaseResponse<String> uploadBannerImage(MultipartFile file){
+        return shopBannerService.uploadBannerImage(file);
     }
-
     @Override
     @GetMapping("/getFormId")
     public RPCBaseResponse<BannerVo> getFormId(String id) {

+ 75 - 1
edu-travel-service/edu-travel-service-upload/src/main/java/edu/travel/upload/web/UploadController.java

@@ -7,6 +7,7 @@ import com.obs.services.model.*;
 import edu.travel.remote.upload.UploadRemoteController;
 import edu.travel.remote.upload.dto.EduFileBlobDTO;
 import edu.travel.remote.upload.dto.EduFileDTO;
+import edu.travel.remote.upload.vo.EduFileVo;
 import edu.travel.rpc.RPCBaseResponse;
 import edu.travel.upload.config.MD5Calculator;
 import edu.travel.upload.entity.EduFile;
@@ -14,6 +15,7 @@ import edu.travel.upload.entity.EduFileBlob;
 import edu.travel.upload.obs.property.ObsProperties;
 import edu.travel.upload.service.EduFileBlobService;
 import edu.travel.upload.service.EduFileService;
+import edu.travel.web.BaseController;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,7 +37,7 @@ import java.util.concurrent.ThreadPoolExecutor;
 
 @RestController
 @RequestMapping("/upload")
-public class UploadController implements UploadRemoteController {
+public class UploadController extends BaseController<EduFile> implements UploadRemoteController {
     @Autowired
     private EduFileService eduFileService;
     @Autowired
@@ -150,4 +152,76 @@ public class UploadController implements UploadRemoteController {
         }
         return RPCBaseResponse.error();
     }
+
+    /**
+     * 通过id上传文件
+     * @param id
+     * @return
+     */
+    @Override
+    @GetMapping("/getFormId")
+    public RPCBaseResponse<EduFileVo> getFormId(String id) {
+        RPCBaseResponse<EduFile> eduFileRPCBaseResponse = super.getId(id);
+        RPCBaseResponse<EduFileVo> eduFileVoRPCBaseResponse = new RPCBaseResponse<>();
+        BeanUtils.copyProperties(eduFileRPCBaseResponse, eduFileVoRPCBaseResponse);
+        return eduFileVoRPCBaseResponse;
+    }
+
+    /**
+     * 更新文件
+     * @param entity
+     * @return
+     */
+    @Override
+    @GetMapping("/updateTargetFormId")
+    public RPCBaseResponse<EduFileVo> updateTargetFormId(@RequestBody EduFileDTO entity) {
+        EduFile eduFile = new EduFile();
+        BeanUtils.copyProperties(entity, eduFile);
+        RPCBaseResponse<EduFile> eduFileRPCBaseResponse = super.updateTargetById(eduFile);
+        RPCBaseResponse<EduFileVo> eduFileVoRPCBaseResponse = new RPCBaseResponse<>();
+        BeanUtils.copyProperties(eduFileRPCBaseResponse, eduFileVoRPCBaseResponse);
+        return eduFileVoRPCBaseResponse;
+    }
+
+    /**
+     * 新增文件
+     * @param entity
+     * @return
+     */
+    @Override
+    @GetMapping("/saveFormTarget")
+    public RPCBaseResponse<EduFileVo> saveFormTarget(@RequestBody EduFileDTO entity) {
+        EduFile eduFile = new EduFile();
+        BeanUtils.copyProperties(entity, eduFile);
+        RPCBaseResponse<EduFile> eduFileRPCBaseResponse = super.saveTarget(eduFile);
+        RPCBaseResponse<EduFileVo> eduFileVoRPCBaseResponse = new RPCBaseResponse<>();
+        BeanUtils.copyProperties(eduFileRPCBaseResponse, eduFileVoRPCBaseResponse);
+        return eduFileVoRPCBaseResponse;
+    }
+
+    /**
+     * 删除文件
+     * @param ids
+     * @return
+     */
+    @Override
+    @GetMapping("/deleteTargetFormId")
+    public RPCBaseResponse<EduFileVo> deleteTargetFormId(@RequestBody List<String> ids) {
+        RPCBaseResponse<EduFile> eduFileRPCBaseResponse = super.deleteTargetById(ids);
+        RPCBaseResponse<EduFileVo> eduFileVoRPCBaseResponse = new RPCBaseResponse<>();
+        BeanUtils.copyProperties(eduFileRPCBaseResponse, eduFileVoRPCBaseResponse);
+        return eduFileVoRPCBaseResponse;
+    }
+
+    /**
+     * 获取全部文件
+     * @return
+     */
+    @Override
+    public RPCBaseResponse<List<EduFileVo>> getAllForm() {
+        RPCBaseResponse<List<EduFile>> eduFileRPCBaseResponse = super.listAll();
+        RPCBaseResponse<List<EduFileVo>> eduFileVoRPCBaseResponse = new RPCBaseResponse<>();
+        BeanUtils.copyProperties(eduFileRPCBaseResponse, eduFileVoRPCBaseResponse);
+        return eduFileVoRPCBaseResponse;
+    }
 }

+ 1 - 1
edu-travel-service/edu-travel-service-upload/src/main/resources/bootstrap.yml

@@ -1,5 +1,5 @@
 server:
-  port: 10005
+  port: 10010
 spring:
   application:
     name: upload-@env@