当前位置: 首页 > 编程日记 > 正文

使用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会使我们的开发更加简洁。部分配置如图:

  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
复制代码

前端页面效果如下:

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

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

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

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

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/";@Autowiredprivate LoadService loadService;@PostMapping("/upload")public List<Step> singleFileUpload(MultipartFile file) {try {// Get the file and save it somewherebyte[] bytes = file.getBytes();Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());Files.write(path, bytes);} catch (IOException e) {e.printStackTrace();}// Print file data to htmlList<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 {@Overridepublic 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 stepsfor (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,运行前后端服务,实现上传:

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

结果展示

结语

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

附录

源码地址:

前端项目——gitee.com/daniel_ddd/…

后端项目——gitee.com/daniel_ddd/…

相关文章:

国信优易首席科学家周涛:大数据的商业应用

国信优易首席科学家周涛认为&#xff0c;大数据已经成为一个非常热的概念&#xff0c;但是当一个概念热到一定程度就会存在风险&#xff0c;甚至风险比机会还多。在大数据沙龙中&#xff0c;他和数据从业者们分享了自己所经历的大数据应用。 以下为周涛发言&#xff1a; 大数据…

【Python】Radiobutton组件 LabelFrame组件 Entry组件

Radiobutton组件 Radiobutton组件跟Checkbutton组件的用法基本一致&#xff0c;唯一不同的是Radiobutton实现的是“单选”的效果。 要实现这种互斥的效果&#xff0c;同组内的所有Radiobutton只能共享一个variable选项&#xff0c;并且需要设置不同的value选项值。 注释掉的也…

索引与联合索引使用注意

索引和联合索引看似很简单但是往往不一定用的对。 假设数据库2个字段a,b都是查询条件 第一个问题&#xff1a;是建立2个索引还是一个联合索引&#xff1f; 第二个问题&#xff1a;如果建立联合索引那么字段的顺序有什么讲究&#xff1f; 原则&#xff1a; 1.如果需要多个字…

用canvas实现一个vue弹幕组件

看B站时&#xff0c;对弹幕的实现产生了兴趣&#xff0c;一开始想到用css3动画去实现&#xff0c;后来感觉这样性能不是很好&#xff0c;查了下资料&#xff0c;发现可以用canvas实现&#xff0c;于是就摸索着写了一个简单的弹幕。弹幕功能支持动态添加弹幕弹幕不重叠自定义弹幕…

系统架构的过程 浮现式设计

系统架构如果设计之初就设计错了&#xff0c;那么必然是南辕北辙。 很多人做系统设计总是东一下&#xff0c;西一下&#xff0c;杂乱无章&#xff0c;想到那是那&#xff0c;然后系统的边界很大&#xff0c;总会有疏漏。 那么系统架构应该怎么设计呢&#xff1f; 首先来说分…

【Python】Listbox组件 Scrollbar组件 Scale组件

Listbox组件 在选项特别多的时候&#xff0c;Listbox是以列表的形式显示出来&#xff0c;并支持滚动条操作&#xff0c;所以在对于需要提供大量选项的情况下会更适用。 from tkinter import * root Tk() theLB Listbox(root,setgridTrue) theLB.pack() for item in ["…

Hive 按某列的部分排序 以及 删列操作

Hive 按某列的部分排序 以及 删列操作 脑袋果然还是智商不足。 涉及到的小需求&#xff1a; 某个表test 有一列 tc&#xff1a; a字符串b字符串c字符串 拼接组成把test表&#xff0c;按b字符串排序 输出遇到的问题&#xff1a; select 里面必须包含 order by 的列按b字符串排序…

docker 安装 RabbitMQ

1、镜像中国&#xff08;http://www.docker-cn.com/registry-mirror&#xff09;&#xff1a;直接使用https://hub.docker.com下载镜像比较慢&#xff0c;使用镜像中国加速 使用例子&#xff1a;$ docker pull registry.docker-cn.com/library/rabbitmq:3.6-management 2、拉取…

编程容易犯的错

1.数字 比如分页默认从第0页开始&#xff0c;你不了解&#xff0c;写个1&#xff0c;这样数据也出来&#xff0c;但是就是少了第一条&#xff0c;这种错误比较难发现。 写代码对于数字需要敏感&#xff0c;不懂一定要搞懂。 2.参数 多参数做缓存拼凑一个key&#xff0c;之前…

深圳杯---无线回传拓扑规划

B题-无线回传拓扑规划&#xff08;3人完成&#xff09; 背景介绍 在城区建设基站&#xff0c;传输光纤部署最后一公里的成本高&#xff0c;光纤到站率低&#xff0c;全球综合来看低于60%&#xff1b;如果使用微波传输&#xff0c;由于微波只能在LOS&#xff08;视距&#xff0…

Jmeter脚本 GUI和非GUI启动方式

2019独角兽企业重金招聘Python工程师标准>>> 1&#xff0e;下载Jmeter 地址&#xff1a;http://jmeter.apache.org/download_jmeter.cgi 2&#xff0e;启动jmeter 运行bin/jmeter.bat 3&#xff0e;添加线程组 在TestPlan节点上右键&#xff0c;Add-->Threads(U…

前端效果参考地址

今天项目内容基本完善&#xff0c;没什么事情&#xff0c;就找了一些插件和好用的css动画&#xff0c;下面将一些链接地址分享出来 1、如果需要写阴影、圆角、渐变、弹性盒子等&#xff0c;请参考一下方式&#xff1a; 点击 2、轮播图、全屏滚动等动画&#xff1a; swiper效果 …

随机变量的数字特征(数学期望,方差,协方差与相关系数)

戳这里&#xff1a;概率论思维导图 &#xff01;&#xff01;&#xff01; 数学期望 离散型随机变量的数学期望 &#xff08;这里要求级数绝对收敛&#xff0c;若不绝对收敛&#xff0c;则E(X)不存在&#xff09; 如果有绝对收敛&#xff0c;则有 &#xff0c;其中 连续型…

Spring @bean冲突解决方案

引用2个jar都实现了相同的bean注入&#xff0c;这个是feign的Level Bean public Level feignLoggerLevel() {return Level.FULL; } 这样报错: escription:xxx required a single bean, but 2 were found:- feignLoggerLevel: defined by method feignLoggerLevel in class p…

javascript中实例方法与类方法的区别

在javascript中&#xff0c;类有静态属性和实例属性之分&#xff0c;也有静态方法和实例方法之分 类属性&#xff08;静态属性&#xff09;&#xff1a;通过类直接访问&#xff0c;不需要声明类的实例来访问 类方法&#xff08;静态方法&#xff09;&#xff1a;通过类直接访问…

vue 集成富文本tinymce

开发环境 1. vscode开发语言 1. vue 2. javaScript插件安装 1. npm install tinymce -S 2. 可以使用里面的文件&#xff0c; 下载后可以在node_modules 里面查看如下未目录结构3. 可以将整个结构拷在static里面&#xff0c;为了节省打包后的文件大小可以将tinymce.min.js以cdn方…

c语言中如何设计和编写一个应用系统?

C程序中,如何设计和编写一个应用系统?一、 C语言文件的操作1、 文件操作的基本方法&#xff1a;C语言将计算机的输入输出设备都看作是文件。例如&#xff0c;键盘文件、屏幕文件等.向屏幕输出一个信息&#xff0c;例如“Hello”是#include.h>int main(){printf("Hello…

深圳杯---人才吸引力评价模型研究

人才吸引力评价模型研究 在世界各国和全国各地都加大争夺人才的背景下&#xff0c;一个城市要保持其竞争活力和创新力&#xff0c;必须与时俱进地但不盲目地调整相关人才吸引政策。2018年深圳市将加大营商环境改革力度作为一项重要工作&#xff0c;以吸引更多优秀的高新企业和…

不写容易出错的代码

下面2段代码都是完成商品名称的更新&#xff0c;只是第一种情况数据源是list第二种是map 第一代代码是从List里获取第0个 entity.setProduct_name(productList.get(0).getName()); 第二段代码从map里获取键值 entity.setProduct_name(productMap.get(pid).getName())); 如果…

【Vue】IView之table组件化学习(二)

最基本的绑定table是这样的&#xff0c;需要columns和data两个属性。 <template><Card><h4>表格栗子</h4><Table :columns"cols" :data"stuList"></Table></Card> </template><script> export defa…

show-busy-java-threads查找CPU占用高

背景&#xff1a;需要查找线上CPU占用过高的Java线程在做什么。 可以使用top命令找出占CPU高的进程 #top 然后按shiftC 按CPU占比排序 然后把进程中占比高的线程id找出来&#xff0c;这个是常见的套路&#xff0c;但是这样做比较繁琐。 可以使用show-busy-java-threads工具…

了解机器学习的八大专业术语

转自&#xff1a;https://www.sohu.com/a/217453268_178466 1 自然语言处理 自然语言处理对于许多机器学习方法来说是一个常用的概念&#xff0c;它使得计算机理解并使用人所读或所写的语言来执行操作成为了可能。 自然语言处理最重要的最有用的实例&#xff1a; ① 文本分类…

34.TokenInterceptor防止表单重复提交

转自&#xff1a;https://wenku.baidu.com/view/84fa86ae360cba1aa911da02.html 由于某些原因&#xff0c;用户在进行类似表单提交的操作后&#xff0c;以为表单未被提交&#xff0c;会进行多次的重复提交。为了避免用户多次提交给服务器带来负荷。我们会对表单提交这样的操作进…

使用arthas采集火焰图

火焰图是用图形化的方式来展现profiler工具采集的性能数据&#xff0c;对数据进行统计和分析&#xff0c;方便找出性能热点。 现在我们使用arthas采集JVM的火焰图。 1.首先你需要安装arthas 说是安装其实就是下载解压&#xff0c;arthas是不需要安装的。 下载 — Arthas 3.5…

sudo配置文件详解及实战

2019独角兽企业重金招聘Python工程师标准>>> 安装NGINX之后每次都需要切换ROOT用户做配置文件修改和启动&#xff0c;为了加强安全&#xff0c;ROOT用户一般是不允许直接提供给应用开发人员或者运维人员的&#xff0c;所以需要提供一种方法可以一般用户执行ROOT用户…

Centos中文输入法安装以及切换

鼓捣鼓捣&#xff08;我是一只菜鸟&#xff09;&#xff0c;终于在我的Centos上面装上我的大中华输入法了&#xff0c;哈哈哈哈下面就简单描述下安装过程吧&#xff01;&#xff01;&#xff01;centos6.5用yum安装中文输入法打开终端&#xff0c;进入root用户&#xff08;命令…

【MATLAB】矩阵信息的获取

1、矩阵结构 矩阵的结构是指矩阵子元素的排列方式。 函数名称函数功能isempty(A)检测矩阵是否为空isscalar(A)检测矩阵是否是单元素的标量矩阵isvector(A)检测矩阵是否是只具有一行或一列元素的一维向量issparse(A)检测数组是否是系数矩阵 返回1表示该矩阵是某一特定类型的矩…

Android Gradle Plugin 源码解析(上)

一、源码依赖 本文基于: android gradle plugin版本&#xff1a; com.android.tools.build:gradle:2.3.0 gradle 版本&#xff1a;4.1 Gradle源码总共30个G&#xff0c;为简单起见&#xff0c;方便大家看源码&#xff0c;此处通过gradle依赖的形式来查看源码&#xff0c;依赖源…

Guice系列之用户指南(七)

原文地址&#xff1a;https://code.google.com/p/google-guice/wiki/ToConstructorBindings Constructor Bindings&#xff08;构造器绑定&#xff09;&#xff1a;在父类型上绑定子类实现的构造函数。 贴代码&#xff1a; 12345678910111213141516171819202122232425262728293…

Linux系统火焰图

CentOS7.8 安装perf #yum install perf 执行perf 执行perf record 命令&#xff0c;记录该PID的行为 #perf record -a -g -p 14851 -- sleep 30 --30秒后退出 需要注意后面生成svg图片的所有命令要和当前perf在同一目录&#xff0c;不然会报错。 #perf report 安装git …