美文网首页
JavaWeb——使用Vue+Spring Boot实现Exce

JavaWeb——使用Vue+Spring Boot实现Exce

作者: LightningDC | 来源:发表于2018-11-29 14:48 被阅读116次

    写在最前

    在上期教程中我们介绍了读写Excel与使用Selenium的入门方法,本期将介绍通过Vue+Spring Boot实现在WebApp中上传Excel导入测试脚本的功能。使用前后端分离的技术是因为这样便于后续功能的迭代,在本文中我们只涉及一个简单的前端界面及一个简单的后台服务。

    运行结果展示与源码地址在文末,上期传送门:Java自动化——使用Selenium+POI实现Excel自动化批量查单词

    步骤一览

    1. 使用Vue-Cli创建前端项目
    2. Navbar编写
    3. 脚本表格编写
    4. 使用Spring Initializr创建后端项目
    5. pojo类的编写
    6. UploadController的编写
    7. UploadService的编写
    8. 搭建简单的RESTful API
    9. 运行服务,编写脚本并上传

    现在开始

    1. 使用Vue-Cli创建前端项目

      运用vue-cli工具可以很轻松地构建前端项目,当然,使用WebStorm来构建会更加简洁(如图)。本文推荐使用WebStorm,因为在后续开发中,IDE会使我们的开发更加简洁。部分配置如图:

    image image image image
    1. Navbar编写

      作为一个WebApp,Navbar作为应用的导航栏是必不可少的。在本项目中,笔者引入了bootstrap对Navbar进行了轻松地构建。在vue中我们需要在components文件夹中将我们的组件加进去,对于本工程来说,Navbar是我们要加入的第一个组件,他独立于router之外,一直固定在网页上方。

      2.1 首先,我们使用npm来安装vue,vue-cli,bootstrap

      npm install vue
      npm install -g vue-cli
      npm install --save bootstrap jquery popper.js
      

      2.2 接下来我们在components目录下new一个vue组件,并且在main.js中引入bootstrap依赖:

      import 'bootstrap/dist/css/bootstrap.min.css'
      import 'bootstrap/dist/js/bootstrap.min'
      

      2.3 下面就可以开始写代码了,由于本文只关注table相关的功能,所以导航栏中除了Script意外的元素都已经disable,代码如下:

    <template>
      <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <span class="navbar-brand mb-0 h1">Vue-SpringBoot</span>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
          <ul class="navbar-nav">
            <li class="nav-item">
              <router-link class="nav-link" to="/home">Home</router-link>
            </li>
            <li class="nav-item active">
              <router-link to="/" class="nav-link">Script</router-link>
            </li>
            <li class="nav-item">
              <router-link to="/history" class="nav-link">History</router-link>
            </li>
          </ul>
        </div>
      </nav>
    </template>
    
    <script>
        export default {
            name: "MyNavbar"
        }
    </script>
    
    <style scoped>
    
    </style>
    
    

    2.3 在App.vue中引入MyNavbar

    1. Script Table编写

      作为自动化工具,必不可少的一部分就是引入Script,我们希望用户能够自由地使用H5界面进行Script的编写,因此在这里使用了vue的数据双向绑定进行Table CRUD。

      3.1 新建一个vue组件ScriptTable,代码如下:

    <template>
      <div class="container-fluid" id="scriptTable">
        <h3>My Script</h3>
        <form style="margin-top: 1rem">
          <input type="file" @change="getFile($event)" class="" multiple/>
          <input type="button" value="upload" @click="submit($event)" class="btn btn-dark">
        </form>
        <table class="table table-hover text-center table-bordered"
               style="word-break: break-all; word-wrap: break-word;margin-top: 1rem;">
          <thead>
          <th>#</th>
          <th>Platform</th>
          <th>Action</th>
          <th>Path</th>
          <th>Value</th>
          <th>Wait</th>
          <th>Screenshot</th>
          <th>Change</th>
          </thead>
          <tbody>
          <tr v-cloak v-for="(item, index) in steps">
            <th>{{index+1}}</th>
            <td>{{item.platform}}</td>
            <td>{{item.action}}</td>
            <td>{{item.path}}</td>
            <td>{{item.value}}</td>
            <td>{{item.wait}}</td>
            <td>{{item.screenshot}}</td>
            <td><a href="#" v-on:click="edit(item)">Edit</a> | <a href="#" v-on:click='aaa(index)'>Delete</a>
            </td>
          </tr>
          <tr>
            <th></th>
            <td><select class="form-control" v-model="stepstemp.platform">
              <option>Web</option>
              <option>Android</option>
            </select></td>
            <td><select class="form-control" v-model="stepstemp.action">
              <option>click</option>
              <option>get</option>
              <option>input</option>
              <option>swipe</option>
            </select></td>
            <td><input class="form-control" v-model="stepstemp.path" placeholder="Enter the xPath"></td>
            <td><input class="form-control" v-model="stepstemp.value" placeholder="Enter the input value"></td>
            <td><input class="form-control" v-model="stepstemp.wait" placeholder="Waiting seconds"></td>
            <td><select class="form-control" v-model="stepstemp.screenshot">
              <option>yes</option>
              <option>no</option>
            </select></td>
            <td>
              <button class="btn btn-sm btn-dark" v-on:click='save' v-if="isNotEdit">Save</button>
              <button class="btn btn-sm btn-primary" v-on:click='saveEdit' v-else>SaveEdit</button>
            </td>
          </tr>
          </tbody>
        </table>
        <hr/>
      </div>
    </template>
    
    <script>
      import Vue from 'vue'
      import axios from 'axios'
    
      export default {
        name: "ScriptTable",
        data() {
          return ({
            steps: [],
            stepstemp: {
              platform: '',
              action: '',
              path: '',
              value: '',
              wait: '',
              screenshot: ''
            },
            isNotEdit: true
          });
        },
        methods: {
          save: function () {
            this.steps.push(this.stepstemp);
            this.stepstemp = {
              platform: '',
              action: '',
              path: '',
              value: '',
              wait: '',
              screenshot: ''
            };
          },
          aaa: function (index) {
            this.steps.splice(index, 1)
          },
          edit: function (item) {
            this.isNotEdit = false;
            this.stepstemp = item;
          },
          saveEdit: function () {
            this.isNotEdit = true;
            this.stepstemp = {
              platform: '',
              action: '',
              path: '',
              value: '',
              wait: '',
              screenshot: ''
            };
          }
        }
      }
    </script>
    
    
    <style scoped>
    
    </style>
    
    

    3.3 运行dev,打开localhost:8080

    npm run dev
    

    前端页面效果如下:

    image

    至此,本文相关的纯前端部分完成地差不多了,加上mock的数据后,我们可以开始进行后端的开发了。

    1. 使用Spring Initializr创建后端项目

      为了更轻松地构建工程,构建RESTful API以及更轻松地配置请求处理,笔者选择了Spring Boot作为后端框架。

      4.1 首先我们使用IDEA集成的Spring Initializr来构建项目,部分配置如图:

    image image

    4.2 接下来在pom.xml中引入poi依赖,点击import change。如下所示:

     <dependency>
         <groupId>org.apache.poi</groupId>
         <artifactId>poi-ooxml</artifactId>
         <version>4.0.0</version>
     </dependency>
    

    4.3 接下来我们在application.properties中配置server.port=8088,与前端项目分开

    1. pojo类Step的编写

      下面是对pojo类的编写,本文所需的pojo只有Step一种,与前端的table相对应,代码如下:

    import lombok.Data;
    @Data
    public class Step {
        private String platform;
        private String action;
        private String path;
        private String value;
        private int wait;
        private String screenshot;
    }
    
    1. UploadController的编写

      接下来是对前端Post请求的Handler(Controller)进行编写,我们将上传这个Post请求与"/uploadfile"相对应,注意加入@CrossOrigin注解实现跨域,代码如下:

    package com.daniel.vuespringbootuploadbe;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.File;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.util.List;
    
    @Controller
    @CrossOrigin
    @ResponseBody
    public class UploadController {
    
        private static String UPLOADED_FOLDER = "src/main/resources/static/temp/";
    
        @Autowired
        private LoadService loadService;
    
        @PostMapping("/upload")
        public List<Step> singleFileUpload(MultipartFile file) {
            try {
                // Get the file and save it somewhere
                byte[] bytes = file.getBytes();
                Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
                Files.write(path, bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
            // Print file data to html
            List<Step> result = loadService.castToStep(new File(UPLOADED_FOLDER + file.getOriginalFilename()));
            return result;
        }
    
    }
    
    
    1. LoadService的编写

      下面该编写Service来读取请求中传送的文件了,简单地来说只有一个步骤,将Excel中的Script转换为pojo的链表并在Controller中作为ResponseBody返回.

      7.1 首先创建Service接口,代码如下:

    package com.daniel.vuespringbootuploadbe;
    
    import org.springframework.stereotype.Service;
    
    import java.io.File;
    import java.util.List;
    
    @Service
    public interface LoadService {
        List<Step> castToStep(File file);
    }
    
    

    7.2 接下来创建Service实现类,代码如下:

    package com.daniel.vuespringbootuploadbe;
    
    import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
    import org.apache.poi.ss.usermodel.Row;
    import org.apache.poi.ss.usermodel.Sheet;
    import org.apache.poi.ss.usermodel.Workbook;
    import org.apache.poi.xssf.usermodel.XSSFWorkbook;
    import org.springframework.stereotype.Service;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    @Service
    public class LoadServiceImpl implements LoadService {
    
    
        @Override
        public List<Step> castToStep(File file) {
            List<Step> steps = new ArrayList<>();
            Workbook workbook = null;
            try {
                workbook = new XSSFWorkbook(file);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InvalidFormatException e) {
                e.printStackTrace();
            }
            Sheet sheet = workbook.getSheetAt(0);
            int num = sheet.getLastRowNum() - sheet.getFirstRowNum();
            //Read steps
            for (int i = 0; i < num; i++) {
                Row row = sheet.getRow(i+1);
                Step step = new Step();
                step.setPlatform(row.getCell(0).getStringCellValue());
                step.setAction(row.getCell(1).getStringCellValue());
                step.setPath(row.getCell(2).getStringCellValue());
                step.setValue(row.getCell(3).getStringCellValue());
                step.setWait((int) row.getCell(4).getNumericCellValue());
                step.setScreenshot(row.getCell(5).getStringCellValue());
                steps.add(step);
            }
            try {
                workbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return steps;
        }
    }
    
    
    1. 搭建简单的RESTful API

      文章临近尾声,现在前后端的独立代码基本开发完毕,是时候搭建RESTful了,本文中的API非常简单,就是对上传做出响应,并将返回的json写入界面上的Table中,完成Script导入,npm安装axios后,在ScriptTable组件中加入如下代码:

          getFile: function (event) {
            this.file = event.target.files[0];
            console.log(this.file);
          },
          submit: function (event) {
            event.preventDefault();
            let formData = new FormData();
            formData.append("file", this.file);
            axios.post('http://localhost:8088/upload', formData)
              .then(function (response) {
                for (let i = 0; i < response.data.length; i++) {
                  var tempData = {
                    platform: response.data[i].platform,
                    action: response.data[i].action,
                    path: response.data[i].path,
                    value: response.data[i].value,
                    wait: response.data[i].wait,
                    screenshot: response.data[i].screenshot
                  };
                  this.steps.push(tempData);
                }
              }.bind(this))
              .catch(function (error) {
                alert("Fail");
                console.log(error);
              });
          }
    
    1. 运行服务,编写Script并上传

      接下来我们创建一个Excel,按如图格式编写简单Script,运行前后端服务,实现上传:

    image

    运行后,Excel文件会上传到后端工程的static的temp目录中

    结果展示

    image

    结语

    本文只是实现了基础的上传脚本功能,要实现脚本运行,我们还要在BE项目中实现相关服务进行封装,需要Selenium的帮助。之后的教程中会做详细阐述,敬请期待。

    附录

    源码地址:

    前端项目——https://gitee.com/daniel_ddd/vue-springboot-upload-fe

    后端项目——https://gitee.com/daniel_ddd/vue-springboot-upload-be

    相关文章

      网友评论

          本文标题:JavaWeb——使用Vue+Spring Boot实现Exce

          本文链接:https://www.haomeiwen.com/subject/ivhfcqtx.html