头图

使用springBoot实现服务端XML文件的前端读写

JenK
English

简介

通常我们修改某个服务的配置文件的时候,需要登入服务器,进入指定目录然后对配置文件例如xml进行修改,偶尔一次操作还可以,但是频繁操作确实有点麻烦。
所以我们直接把这项功能放到前端界面来进行操作,来提升运维的效率。

思路

  1. 后端通过I/O来将服务端XML文件读取并发送给前端
  2. 前端把接收回来的xml文件在前端web编辑器里进行渲染
  3. 前端把修改过的xml文件再次发送给后端
  4. 后端把接收回来的xml写回服务端配置文件

image.png

依赖

1.springBoot
2.dom4j(xml读写库)
3.vue-element-admin
4.ace.js(web编辑器)

实现

后端

导入依赖

<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
</dependency>

resources目录下新建hdfs-site.xml,并写入如下内容进行测试

测试demo中路径为写死的,后续可改为动态配置即可
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration> 
  <!-- 指定HDFS副本的数量 -->  
  <property> 
    <name>dfs.replication</name>  
    <value>1.2</value> 
  </property>  
  <!-- 指定Hadoop辅助名称节点主机配置 -->  
  <property> 
    <name>dfs.namenode.secondary.http-address</name>  
    <value>java151:50090</value> 
  </property>  
  <property> 
    <name>dfs.webhdfs.enabled</name>  
    <value>true</value> 
  </property> 
</configuration>

新建XmlUtil工具类

import org.dom4j.*;
import org.dom4j.io.*;
import java.io.*;

public class XmlUtil {
    /**
     * 获取xml文档
     */
    public static Document getDocument(String filename) {
        File xmlFile = new File(filename);
        Document document = null;
        if (xmlFile.exists()) {
            try {
                SAXReader saxReader = new SAXReader();
                document = saxReader.read(xmlFile);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return document;
    }

    /**
     * 写入xml节点,没有就新建,存在就覆盖
     */
    public static void coverElement(String filePath, Document document) throws Exception {
        if (document != null) {
            OutputFormat format = OutputFormat.createPrettyPrint();
            File xmlFile = new File(filePath);
            format.setEncoding("UTF-8");
            XMLWriter writer = new XMLWriter(new FileOutputStream(xmlFile), format);
            writer.write(document);
            writer.close();
        }
    }
}

新建getXmlFile接口,借助dom4jasXML()将xml以字符串形式返回给前端,Result类自行根据项目返回需求实现即可

@ApiOperation(value = "获取xml文件", notes = "获取xml文件")
@GetMapping(value = "/getXmlFile")
public Object getXmlFile(HttpServletRequest request, HttpServletResponse response) throws Exception {
    String filePath = "src/main/resources/hdfs-site.xml";
    Document document = XmlUtil.getDocument(filePath);
    String docXmlText = document.asXML();//输出xml字符串
    return new Result(true, 20000, "获取hdfs-site.xml成功", docXmlText);
}

新建putXmlFile接口,借助DocumentHelper.parseText将前端返回的字符串转为xml

@ApiOperation(value = "修改xml文件", notes = "修改xml文件")
@ResponseBody()
@PostMapping(value = "/putXmlFile")
public Object putXmlFile(@RequestBody XmlDO body, HttpServletResponse response) throws Exception {
    Document document = DocumentHelper.parseText(body.getData());//字符串转xml
    String filePath = "src/main/resources/hdfs-site.xml";
    XmlUtil.coverElement(filePath, document);
    return new Result(true, 20000, "修改hdfs-site.xml成功", body.getData());
}

XmlDO

import lombok.Data;
@Data
public class XmlDO {
    private String type;
    private String data;
}

前端

插件依赖

"xml-formatter": "^2.4.0"
"lodash": "^4.17.20",

api目录下新建xml.js接口,url根据自己的实际情况修改即可

import request from '@/utils/request'

export function getXmlFile() {
  return request({
    url: '/api/test/getXmlFile',
    method: 'get'
  })
}
export function putXmlFile(data) {
  return request({
    url: '/api/test/putXmlFile',
    method: 'post',
    data
  })
}

导入ace.js,包括主题以及语言
public/index.html写入

//css
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/ace/1.4.9/theme-xcode.css">
//js
<script src="https://cdn.bootcdn.net/ajax/libs/ace/1.4.9/ace.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/ace/1.4.9/ext-beautify.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/ace/1.4.9/ext-language_tools.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/ace/1.4.9/theme-xcode.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/ace/1.4.9/mode-xml.js"></script>

新建编辑器组件src/components/codeEditor/index.vue, lodash依赖自行isntall

<template>
  <div :id="Id" ref="editor" :style="{ 'max-height': height }" class="ace-editor" />
</template>

<script>
import uniqueId from 'lodash/uniqueId'
export default {
  name: 'AceEditor',
  props: {
    value: {
      type: String,
      default: ''
    },
    height: {
      type: String,
      default: '300px'
    },
    readOnly: {
      type: Boolean,
      default: false
    },
    mode: {
      type: String,
      default: 'json'
    }
  },
  data() {
    this.editor = null
    this.Id = uniqueId('aceEditor')
    return {
      annot: []
    }
  },
  computed: {
    code: {
      // 数据更新通知父组件更新数据
      set(val) {
        this.$emit('input', val)
      },
      get() {
        return this.value
      }
    }
  },
  watch: {
    code() {
      // 父组件中数据变化,同步到ace Editor
      // aceEditor.setValue调用后默认会全选所有文本内容,需要对光标进行特殊处理
      // 缓存光标位置
      const position = this.editor.getCursorPosition()
      this.syncData()
      this.editor.clearSelection()
      this.editor.moveCursorToPosition(position)
    },
    mode(mode) {
      this.changeMode(mode)
    },
    readOnly(b) {
      this.setReadOnly(b)
    }
  },
  mounted() {
    this.initEditor()
  },
  methods: {
    initEditor() {
      // eslint-disable-next-line no-undef
      this.editor = ace.edit(this.Id)
      this.editor.setTheme('ace/theme/xcode')
      // 编辑时同步数据
      this.editor.session.on('change', () => {
        this.code = this.editor.getValue()
      })
      this.editor.getSession().on('changeAnnotation', () => {
        this.annot = this.editor.getSession().getAnnotations()
        this.$emit('annot', this.annot)
        for (var key in this.annot) {
          // eslint-disable-next-line no-prototype-builtins
          if (this.annot.hasOwnProperty(key)) { console.log(this.annot[key].text + 'on line ' + ' ' + this.annot[key].row) }
        }
      })
      // 字体大小
      this.editor.setFontSize(14)
      this.syncData()
      this.syncOptions()
    },

    changeMode(modeName) {
      const mode = {
        yaml: 'ace/mode/yaml',
        json: 'ace/mode/json',
        xml: 'ace/mode/xml',
        javascript: 'ace/mode/javascript'
      }
      this.editor.session.setMode(mode[modeName])
    },
    setReadOnly(readOnly) {
      this.editor.setReadOnly(readOnly)
    },
    syncOptions() {
      this.setReadOnly(this.readOnly)
      this.changeMode(this.mode)
    },
    syncData() {
      this.editor.setValue(this.code)
    }
  }
}
</script>

<style scoped>
.ace-editor {
  position: relative;
  height: 800px;
  border: 1px solid #ccc;
  border-radius: 2px;
}
.ace-editor /deep/ .ace_print-margin {
  display: none;
}
</style>

新建页面src/views/xmlTest/index.vue

<template>
  <div id="contain">
    <div>
      <div id="select">
        <h3>修改配置文件(xml)</h3>
        <el-button type="primary" @click="testApi">修改</el-button>
      </div>
      <aceEditor
        v-model="resquestCode"
        height="800px"
        mode="xml"
        @annot="annot"
      />
    </div>
  </div>
</template>

<script>
import { getXmlFile, putXmlFile } from '@/api/xml'
import aceEditor from '@/components/codeEditor'
export default {
  name: '',
  components: { aceEditor },
  props: {},
  data() {
    return {
      errcode: [],
      resquestCode: ``
    }
  },
  watch: {

  },
  mounted() {
    this.getXml()
  },
  methods: {
    isError() {
      if (this.errcode.length > 0) {
        this.$message.error(`代码第${this.errcode[0].row + 1}行语法错误,${this.errcode[0].text}`)
        return true
      }
      return false
    },
    testApi() {
      if (!this.isError()) {
        this.format()
        this.select()
      }
    },
    getXml() {
      getXmlFile().then(res => {
        console.log('res: ', res)
        this.resquestCode = res.data || ''
        this.format()
      })
    },
    annot(e) {
      this.errcode = e
    },
    format() {
      var format = require('xml-formatter')
      this.resquestCode = format(this.resquestCode, {
        collapseContent: true
      })
    },
    select() {
      putXmlFile({ data: this.resquestCode }).then(res => {
        console.log('res: ', res)
        this.$message.success('修改成功')
      })
    }
  }
}
</script>

<style lang="scss">
#contain {
  margin: 0 20px;
  display: block !important;
  #select {
    .el-select .el-input {
      width: 100px;
    }
    .input-with-select .el-input-group__prepend {
      background-color: #fff;
    }
  }
}
#editor {
  width: 100%;
  height: 300px;
}
</style>

添加路由配置

  {
    path: '/xml',
    component: Layout,
    children: [
      {
        path: '',
        name: 'xml',
        component: () => import('@/views/xmlTest/index'),
        meta: { title: 'xml', icon: 'xml' }
      }
    ]
  }

界面

image.png

image.png

更多问题欢迎加入前端交流群交流749539640

阅读 674
avatar
JenK
北京中电兴发 前端工程师

理想的光芒很难照进现实

7.4k 声望
5.5k 粉丝
0 条评论
你知道吗?

avatar
JenK
北京中电兴发 前端工程师

理想的光芒很难照进现实

7.4k 声望
5.5k 粉丝
宣传栏