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