JavaWeb——使用Vue+Spring Boot实现Excel上传

写在最前

在上期教程中我们介绍了读写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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容