snakeyaml处理多文件配置

1:需要在源文件中node.nb下添加指定的yml数据

源文件

---
node:
  env: n1
  n1: node 1
  n2: node 2

---
node:
  env: n2
  na: node na
  nb:
    nb1: node nb1

需要插入的模板文件

insert1:
  - image: http:1232324/asdfasd
    name: testtestname
    command: [ "bash" ]
    args:
      [
          "args1",
          "args2",
      ]
    vo:
      - voPath: /vo/path
        voName: vvvName

 

1:引入jar包

<dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.26</version>
        </dependency>

 当然下面代码里是引入了糊涂工具类,它也涵盖了这个包

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.16</version>
        </dependency>

  

2:实现思路

1:加载多文件配置内容
2:循环每一个文件层次,转换为map,然后递归目标节点,直到找到该节点,找到就插入模板
3:写出新文件

3:实现代码

先把一些参数动态配置

#允许上传下载的文件大小
spring:
  servlet:
    multipart:
      max-file-size: 5MB
      max-request-size: 5MB
#基本的下载路径
application:
  #上传的格式化文件保存路径
  fileUploadPath: /fileUpload/
  #模板文件服务器路径
  fileTemplateUploadPath: /fileTemplate/
  #代表多文件配置的第二个文件配置 用于内部解析时,别的节点就直接跳过,是否开启节点数据跳过,如果关闭了,那么多文件配置,只要有相同的节点,都会插入模板数据
  enableSkipNodeIndex: true
  nodeIndex: 2
  #是否创建不存在的节点
  isCreateNode: true

  #需要注意的是,中间节点如果不存在,则会主动添加。如果上传的文件已经有了该配置,且位置一样,则基本上不会做出改变,因为key和value是一样的
  nodeList: node.nb.t1.t2.t3.t4.t5
server:
  port: 9528

 

接口

public interface UploadService {
    /**
     * 上传并插入模板文件
     *
     * @param file 文件
     * @param response 响应
     * @return 上传详细信息
     */
    LayuiPageInfo upload(MultipartFile file, HttpServletResponse response);

    /**
     * 文件下载
     *
     * @param url      文件路径
     * @param fileName 文件名称
     * @param response 响应
     */
    void download(String url, String fileName, HttpServletResponse response);

    /**
     * 文件列表
     * @return
     * @param type 类型,目前就模板
     */
    LayuiPageInfo fileList(String type);

    /**
     * 删除文件
     * @param url 文件路径
     * @return
     */
    LayuiPageInfo removeFile(String url);
}

实现类

 

@Service
public class UploadServiceImpl implements UploadService {

    public static final List<String> FILE_TYPE = Arrays.asList(".yml", ".YML", ".yaml", ".YAML");
    private static final String TEMPLATE_FILE_TYPE = "template";
    private static final ThreadLocal<List<InputStream>> INPUT_STREAMS = new ThreadLocal<>();
    Log log = LogFactory.get();

    /**
     * 文件在服务器保存的基本路径
     */
    @Value("${application.fileUploadPath:/fileUpload/}")
    private String basePath;
    /**
     * 模板文件在服务器保存的基本路径
     */
    @Value("${application.fileTemplateUploadPath:/fileTemplate/}")
    private String fileTemplateUploadPath;


    /**
     * 模板文件插入的多文件配置节点
     */
    @Value("${application.nodeIndex:2}")
    private int nodeIndex;

    /**
     * 节点集合
     */
    @Value("${application.nodeList}")
    private String nodeList;

    /**
     * 是否开启跳过其他多文件配置节点
     */
    @Value("${application.enableSkipNodeIndex:false}")
    private boolean enableSkipNodeIndex;

    /**
     * 是否创建节点,默认开启。
     */
    @Value("${application.isCreateNode:true}")
    private boolean isCreateNode;

    @Override
    public LayuiPageInfo upload(MultipartFile file, HttpServletResponse response) {
        //获取当前路径
        String currentPath = System.getProperty("user.dir");
        if (file.equals("") || file.getSize() <= 0) {
            return LayuiPageFactory.createErrInfo("文件不存在或异常!", -1);
        } else {

            /*获取文件原名称*/
            String originalFilename = file.getOriginalFilename();
            /*获取文件格式*/
            String fileFormat = originalFilename.substring(originalFilename.lastIndexOf("."));

            log.info("接收预处理文件:{}", originalFilename);
            if (!FILE_TYPE.contains(fileFormat)) {
                log.error("不支持的文件类型!");
                return LayuiPageFactory.createErrInfo("不支持的文件类型!", -1);
            }
            try {
                // 1:解析文件类型
                Yaml yaml = new Yaml();
                //多文件配置,需要用loadAll
                Iterable<Object> objects = yaml.loadAll(file.getInputStream());

                //用来保存每一个层次的数据
                ArrayList<Object> list = new ArrayList<>();
                //用来记录层次的位置
                int nIndex = 0;
                //每一个循环 都对应一个多文件层次
                for (Object object : objects) {
                    if (enableSkipNodeIndex) {
                        nIndex++;
                        if (nodeIndex != nIndex) {
                            //不是指定的节点,就不操作,但是也要把它添加到集合里,不然会失去这些多文件配置,如果后面需要给所有类似的节点都加,
                            //那就把enableSkipNodeIndex改为false即可
                            list.add(object);
                            continue;
                        }
                    }
                    //把数据转换成map
                    Map<String, Object> yamlData = (Map<String, Object>) object;
                    Map<String, Object> lastNode = (Map<String, Object>) queryLastNode(yamlData);
                    if (lastNode != null) {
                        log.info("开始插入模板文件:", originalFilename);
                        //读取模板文件
                        Iterable<Object> templateIterable = getTemplateIterable();
                        Iterator<Object> iterator = templateIterable.iterator();
                        while (iterator.hasNext()) {
                            Map<String, Object> next = (LinkedHashMap) iterator.next();
                            Iterator<Map.Entry<String, Object>> nextIterator = next.entrySet().iterator();
                            while (nextIterator.hasNext()) {
                                //用nb节点put数据
                                Map.Entry<String, Object> nextNode = nextIterator.next();
                                lastNode.put(nextNode.getKey(), nextNode.getValue());
                            }
                        }
                    }
                    list.add(object);
                }
                //写数据\定义格式
                DumperOptions options = new DumperOptions();
                options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
                options.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN);
                options.setPrettyFlow(false);
                Yaml outputYaml = new Yaml(options);
                //检查保存的文件路径 不存在则创建
                String filePath = currentPath + basePath;
                File f = new File(filePath);
                if (!f.exists()) {
                    f.mkdirs();
                }
                //用uuid + 源文件名,防止重复
                String fileNewName = (UUID.randomUUID().toString().replace("-", "") + "-" + originalFilename).replace(" ", "");

                try (FileWriter writer = new FileWriter(filePath + fileNewName)) {
                    //注意:用这种方法才能写出多文件配置效果
                    outputYaml.dumpAll(list.iterator(), writer);
                }
                log.info("文件:{},处理成功!", originalFilename);
                LayuiPageInfo msgInfo = LayuiPageFactory.createMsgInfo(fileNewName);
                msgInfo.setData(basePath + fileNewName);
                return msgInfo;
            } catch (Exception e) {
                log.error("处理文件异常!");
                e.printStackTrace();
                return LayuiPageFactory.createErrInfo(e.getMessage(), -1);
            } finally {
                //关闭IO
                closeStream();
            }
        }
    }

    /**
     * 关闭当前线程拥有的流
     */
    private void closeStream() {
        for (InputStream stream : INPUT_STREAMS.get()) {
            if (stream != null) {
                try {
                    stream.close();
                } catch (Exception e) {
                    log.error("流关闭失败!");
                    e.printStackTrace();
                }
            }
        }
        System.out.println();
    }

    @Override
    public void download(String url, String fileName, HttpServletResponse response) {
        try {
            String currentPath = System.getProperty("user.dir");
            if (!StringUtils.isEmpty(url)) {
                File uploadFile = new File(currentPath + url);
                ServletOutputStream outputStream = response.getOutputStream();
                if (StringUtils.isEmpty(fileName)) {
                    fileName = uploadFile.getName();
                }
                //把uuid去掉
                fileName = fileName.substring(fileName.indexOf("-") + 1);
                response.addHeader("Content-Disposition", "attachment;filename =" + URLEncoder.encode(fileName, "UTF-8"));
                response.setContentType("application/octet-stream");
                byte[] bytes = FileUtil.readBytes(uploadFile);
                outputStream.write(bytes);
                outputStream.flush();
                outputStream.close();
                log.warn("文件写出结束,开始删除源文件:{}", fileName);
                removeFile(url);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("文件下载异常!详细信息:{}", e.getMessage());
        }
    }

    @Override
    public LayuiPageInfo fileList(String type) {
        String currentPath = System.getProperty("user.dir");
        String path = basePath;
        if (TEMPLATE_FILE_TYPE.equals(type)) {
            path = fileTemplateUploadPath;
        }
        File file = new File(currentPath + path);

        if (file.exists()) {
            File[] files = file.listFiles();
            String finalPath = path;
            List<String> fileList = Arrays.stream(files).map(f -> finalPath + f.getName()).collect(Collectors.toList());
            return LayuiPageFactory.createLayuiPageInfo(fileList, Long.valueOf(fileList.size()));
        }
        return LayuiPageFactory.createLayuiPageInfo();
    }

    @Override
    public LayuiPageInfo removeFile(String url) {
        String currentPath = System.getProperty("user.dir");
        File file = new File(currentPath + url);
        if (file.exists()) {
            file.delete();
        }
        log.warn("删除源文件[{}]结束!", file.getName());
        System.out.println();
        return LayuiPageFactory.createMsgInfo("删除文件成功!");
    }

    /**
     * 配置的模板
     *
     * @return
     */
    private Iterable<Object> getTemplateIterable() throws Exception {
        // 读取YAML模板文件
        String currentPath = System.getProperty("user.dir");
        File file = new File(currentPath + fileTemplateUploadPath + "template.yml");
        if (!file.exists()) {
            throw new FileNotFoundException("模板文件不存在!");
        }
        FileInputStream templateFileInputStream = new FileInputStream(currentPath + fileTemplateUploadPath + "template.yml");
        //等后面操作完了 在关闭流,否则会出现问题
        setLocalInputStream(templateFileInputStream);
        Yaml yaml = new Yaml();
        return yaml.loadAll(templateFileInputStream);
    }

    /**
     * 需要关闭的流设置
     *
     * @param stream
     */
    private void setLocalInputStream(InputStream stream) {
        List<InputStream> inputStreams = INPUT_STREAMS.get();
        if (!CollectionUtils.isEmpty(inputStreams)) {
            inputStreams.add(stream);
        } else {
            inputStreams = Arrays.asList(stream);
        }
        INPUT_STREAMS.set(inputStreams);
    }

    /**
     * 查询节点数据
     *
     * @param yamlData 多文件配置的每一个层级数据
     * @return 最后需要插入数据的父节点
     */
    private Object queryLastNode(Map<String, Object> yamlData) {
        //首次入参,index就是第一个
        return queryLastNode(yamlData, 0);
    }

    /**
     * 核心方法,用来找需要插入模板数据的节点
     *
     * @param yamlData 上一个节点的数据
     * @param index    配置的节点每一个循环的下一个索引位置
     * @return 需要插入数据的节点
     */
    private Object queryLastNode(Map<String, Object> yamlData, int index) {
        Object nextNode = new HashMap<>();
        List<String> node_list = Arrays.asList(nodeList.split("\\."));
        //获取索引位置数据
        Object obj = yamlData.get(node_list.get(index));
        //判断是否是循环到最后需要插入模板数据的那个节点 也就是配置中节点的最后一级
        if (index == node_list.size() - 1) {
            if (obj == null) {
                log.warn("末尾节点:{}不存在,创建该节点!", node_list.get(index));
                //如果是空的,表示节点无法抵达,就需要给当前节点添加key,value就是空的集合对象
                yamlData.put(node_list.get(index), nextNode);
                obj = nextNode;
            }
            //获取的节点是配置的最后一级节点
            nextNode = obj;
        } else {
            if (obj == null) {
                //当节点不存在时,根据配置判断是否要手动添加节点,如果不需要则直接抛出异常
                if (isCreateNode) {
                    log.warn("节点:{}不存在,创建该节点!", node_list.get(index));
                    obj = nextNode;
                    yamlData.put(node_list.get(index), nextNode);
                } else {
                    throw new NullPointerException("节点" + node_list.get(index) + "不存在!");
                }
            }
            //不为空则继续往下找,但是index也得继续加.
            nextNode = queryLastNode((Map<String, Object>) obj, ++index);
        }
        return nextNode;
    }
}

 

执行结果

其中数组的格式被替换成了-,数据正确的插入到t5位置


node:
  env: n1
  n1: node 1
  n2: node 2
---
node:
  env: n2
  na: node na
  nb:
    nb1: node nb1
    t1:
      t2:
        t3:
          t4:
            t5:
              insert1:
              - image: http:1232324/asdfasd
                name: testtestname
                command:
                - bash
                args:
                - args1
                - args2
                vo:
                - voPath: /vo/path
                  voName: vvvName
 

 

posted @ 2023-12-01 11:27  鸭猪是的念来过倒  阅读(265)  评论(0编辑  收藏  举报