微信搜索【大奇测试开】,关注这个坚持分享测试开发干货的家伙。

先回顾下在此系列第8次分享给出的预期实现的产品原型和需求说明,如下图整体上和前两节实现很相似,只不过一般测试报告要写的内容可能比较多,就多用了些多行输入框组件,另外一个特别的全新功能操作就是 附件上传,这是需要先解决和要掌握的重点内容。

后端服务实现附件的保存,要写个上传接口,服务端通过request.files进行获取实现,Postman模拟请求的话,方法使用POST,文件通过form-data格式中的file进行上传,一个基本的实现代码接口如下:

  1. 定义请求方法和路径

  2. 拼接一个项目保存文件夹的一个绝对路径

  3. 获取form-data指定key的文件,通过save保存后返回成功消息

@test_manager.route("/api/report/upload",methods=['POST'])
def uploadFile():
# 保存文件的路径
save_path = os.path.join(os.path.abspath(os.path.dirname(__file__)).split('TPMService')[0], 'TPMService/static')
# 获取文件
attfile = request.files.get('file')
attfile.save(os.path.join(save_path, attfile.filename))
return {"code":200, "message":"上传请求成功"}

  

这里对于文件上传,一般来说不能无限制上传,需要对格式、大小做一些限制,还要做一些安全的处理,方式是通过FileField做要求如格式的限制,用secure_filename做文件名安全处理

优化后完整的代码分两个片段

1. 引入依赖和做fileForm类

import os
# 涉及的相关依赖引用
from wtforms import Form,FileField
from flask_wtf.file import FileRequired,FileAllowed
from werkzeug.utils import secure_filename
from werkzeug.datastructures import CombinedMultiDict # 表单提交相关校验
class fileForm(Form):
file = FileField(validators=[FileRequired(), FileAllowed(['jpg', 'png', 'gif', 'pdf', 'zip'])])

2. 增加格式校验和安全校验,另外这里需要注意下,我拿的直接是上传文件的名字,我并没有对文件名做一个随机生成处理,这样如果有重名文件再次上传会被覆盖掉,一般作为一个静态资源或者文件服务来说是要做生成唯一码名称,python可以使用uuid,大家可以尝试扩展下,如果是生成自己的串码名还带来另外一个问题,真的是统一文件多次反复上传如何处理,那可能就要做真正的数据文件信息存储,然后做MD5校验,由于我们只是做个简单附件服务,就不再做更多上传服务的讨论了。

@test_manager.route("/api/report/upload",methods=['POST'])
def uploadFile():
# 初始化返回对象
resp_success = format.resp_format_success
resp_failed = format.resp_format_failed file_form = fileForm(CombinedMultiDict([request.form, request.files]))
if file_form.validate():
# 获取项目路径+保存文件夹,组成服务保存绝对路径
save_path = os.path.join(os.path.abspath(os.path.dirname(__file__)).split('TPMService')[0], 'TPMService/static')
# 通过表单提交的form-data获取选择上传的文件
attfile = request.files.get('file')
# 进行安全名称检查处理
file_name = secure_filename(attfile.filename)
# 保存文件文件中
attfile.save(os.path.join(save_path, file_name)) resp_success['data'] = {"fileName": file_name}
return resp_success
else:
resp_failed['message'] = '文件格式不符合预期'
return resp_failed

上边只是实现了文件格式的校验,对于上传限制大小从网上搜的资料来看,flask一般通过全局配置,比如下边是配置限制一个16MB大小的文件限制,如果超过会返回 413 Request Entity Too Large,网上资料说16M也是默认大小,但实际我测试了下,如果不设置全局限制,我传300+M除了慢点也能上传成功,并且搜索了源码 MAX_CONTENT_LENGTH = None,可能是由于版本的原因,现在没有这个限制了。

from flask import Flask, Requestapp = Flask(__name__)app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000

关于Flask文件上传更多的解释和例子请参考 [链接1],还有一种第三方插件也可以对文件进行友好的操作参考 [链接2],不过这两种方式都是全局控制的,如果想不同的接口单独控制大小,目前尝试的方式是读取文件然后获取长度 len(attfile.read()) 其实就是字节大小,对其进行比较返回即可,如果你有更好的方案,记得告诉我。

上传文件接口搞定了,自然少不了下载接口,这个比较简单,通过flask提供的send_from_directory方法实现,代码如下,详细解释参考 [链接1] 后半部分。

from flask import send_from_directory

@test_manager.route("/api/file/download",methods=['GET'])
def downloadFile():
fimeName = request.args.get('name') # 保存文件的相对路径
save_path = os.path.join(os.path.abspath(os.path.dirname(__file__)).split('TPMService')[0], 'TPMService/static') result = send_from_directory(save_path, fimeName) return result

重启后端服务后,用postman请求做个上传测试,效果如图,一开始是个超大提示,后来正常上传返回结果成功。

刷新查看代码服务存储位置,文件已经正确上传

下载的测试可以通过浏览GET请求服务+路径 /api/file/download?=文件名 进行下载验证。

前端Vue的实现,用到的组件是“Upload上传”,官网给出了多种样式和方式,比如多文件,头像上传,拖拽上传,列表形式等等,具体可参考 [链接3]。

如图其中action就是上传地址,这块就可以替换成刚刚实现的上传接口 http://127.0.0.1:5000/api/report/upload 表示上传地址,默认为选择文件后自动上传,其实就是帮助你实现postman演示的表单文件自动提交,可以通过:auto-upload="false" 设置关闭,也可以通过 http-request 覆盖默认的上传行为自定义实现。

这两种方式都会实际写个Demo实践下

1. 自动上传 新建一个文件上传页面,路由绑定到跟目录,编写<template>和<script>部分代码,这里在方法中用 :on-success 钩子打印下上传成功的返回信息

<template>
<div class="app-container">
<el-form>
<el-form-item label="附件" prop="test_file">
<el-upload
:limit="1"
:file-list="fileList"
:auto-upload="true"
action="http://127.0.0.1:5000/api/report/upload"
:on-success="uploadFile"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png/zip/pdf文件,且不超过1M</div>
</el-upload>
</el-form-item>
</el-form>
</div>
</template> <script>
export default {
name: 'DemoUpload',
data() {
return {
fileList: []
}
},
methods: {
uploadFile(response, file, fileList) {
console.log(response)
console.log(file)
console.log(fileList)
}
}
}
</script>

启动前后端,选择一个小于10M的文件进行上传测试,可以看到正常返回2000,并且能正常拿到钩子中三个参数信息,后边报告功能实现其实就是拿到返回的文件名赋值给一个变量即可。

再测试一种情况,文件格式不符合要求,大小超出服务端限制,发现在文件不符合格式的情况是40000,但文件列表还是显示了,做个优化处理,在状态码不正确的情况,清空filelist

还有另外一个问题就是服务端大小超限制的时候回返回403,但element vue 及upload 直接在返回的时候时候拦截处理了,所以没办法精细异常处理,就进行了模糊提示处理。优化后及 增加了 :on-success 使用的方式的代码如下:

<template>
<div class="app-container">
<el-form>
<el-form-item label="实现一" prop="test_file">
<el-upload
ref="fileOne"
:limit="1"
:file-list="fileList"
:auto-upload="true"
action="http://127.0.0.1:5000/api/report/upload"
:on-success="uploadSuccess"
:on-error="uploadErrors"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png/zip/pdf文件,且不超过10M</div>
</el-upload>
</el-form-item>
<el-form-item label="实现二" prop="test_file">
<el-upload
:limit="1"
:file-list="fileList"
action="http://127.0.0.1:5000/api/report/upload"
:http-request="uploadeFile"
:on-success="uploadSuccess"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png/zip/pdf文件,且不超过10M</div>
</el-upload>
</el-form-item>
</el-form>
</div>
</template> <script>
import axios from 'axios' export default {
name: 'DemoUpload',
data() {
return {
fileList: [],
fileNanme: ''
}
},
methods: {
uploadSuccess(response, file, fileList) {
if (response.code === 40000) {
this.$message({
message: '格式不正确或者上传异常',
type: 'warning'
})
this.fileList = []
} else {
this.$message({
message: '上传成功',
type: 'success'
})
}
},
uploadErrors(err, file, fileList) {
this.$message({
message: '大小不符合要求或服务器异常',
type: 'warning'
})
},
uploadeFile(params) {
console.log(params.file)
const fd = new FormData()
fd.append('file', params.file)
fd.append('FileName', params.file.name)
fd.append('async', true)
const config = {
headers: { 'Content-Type': 'multipart/form-data' }
}
axios
.post(params.action, fd, config)
.then(res => {
console.log(res.data)
})
.catch(Error => {
this.fileList = []
this.$message({
message: '大小不符合要求或服务器异常',
type: 'warning'
})
})
}
}
}
</script>

为什么有了自动上传还是要讲个自定义上传,这里有两点:

目前为止校验都依赖后端,但实际上服务端校验是一个后置校验,文件已经上传了,如果文件大或者量大会很占用IO,所以可以自定义提交进行一些前端的上传校验。

作为实践尽量为大家趟一下坑 :on-success的使用官方并没有给出例子,而要拿到组件信息经过验证是通过参数方法参数获取,如下边红色框圈出的一些重要信息。

再做一个异常的情况下的上传测试,这是由uploadErrors钩子 或者 axios catch(error)捕获实现。

到此本篇的分享大概是这些内容,下一次将组合完成报告部分,也就是【提出平台】的第一阶段,顺便也会公布下上次调查结果,以及一些后续安排,欢迎大家持续关注和交流,方式可以通过私信或者公众号联系加微信入群均可。

【参考链接】

[链接1] https://dormousehole.readthedocs.io/en/latest/patterns/fileuploads.html

[链接2] https://pythonhosted.org/Flask-Uploads/

[链接3] https://element.eleme.io/#/zh-CN/component/upload

坚持原创,坚持实践,坚持干货,如果你觉得有用,请点击推荐,也欢迎关注我博客园和微信公众号。

测试开发实战[提测平台]17-Flask&Vue文件上传实现的更多相关文章

  1. 测试开发实战[提测平台]20-图表G2Plot在项目的实践实录

    微信搜索[大奇测试开],关注这个坚持分享测试开发干货的家伙. G2Plot项目应用 上一篇<提测平台19-Echarts图表在项目的实践>讲解了Echarts的图表应用,此篇来看下开箱即用 ...

  2. 测试开发实战[提测平台]19-Echarts图表在项目的应用

    微信搜索[大奇测试开],关注这个坚持分享测试开发干货的家伙. 在图表统计展示方面,笔者目前使用过的两种开源,分别是 Echats 和 G2Plot 组件,从个人使用上来讲前者应用更广.自定义开发更灵活 ...

  3. Element Vue 开箱即用框架如何使用-测试开发【提测平台】阶段小结(二)

    微信搜索[大奇测试开],关注这个坚持分享测试开发干货的家伙. 上一篇总结了后端服务接口的开发,这篇我们主要来总结下前后端分离开发中的前端部分,主要是开箱即用的框架介绍和之前章节组件的梳理和部分的扩展内 ...

  4. 测试开发【提测平台】分享13-远程搜索和路由$route使用实现新建提测需求

    微信搜索[大奇测试开],关注这个坚持分享测试开发干货的家伙. 本篇继续提测平台开发,按惯例先给出学习的思维导图,以便快速了解学习知识和平台功能实现的重点. 基本知识点学习 远程搜索 显示的数据通过输入 ...

  5. iOS开发之网络编程--使用NSURLConnection实现文件上传

    前言:使用NSURLConnection实现文件上传有点繁琐.    本文并没有介绍使用第三方框架上传文件. 正文: 这里先提供用于编码测试的接口:http://120.25.226.186:3281 ...

  6. flask完成文件上传功能

    在使用flask定义路由完成文件上传时,定义upload视图函数 from flask import Flask, render_template from werkzeug.utils import ...

  7. Flask入门文件上传flask-uploads(八)

    1 视图传递多个参数 (1) 普通传参 : 关键字参数传递 return render_template('模板名称.html',arg1=val1,arg2=val2...) (2) 字典传参 : ...

  8. Spring Boot 2.x(十六):玩转vue文件上传

    为什么使用Vue-Simple-Uploader 最近用到了Vue + Spring Boot来完成文件上传的操作,踩了一些坑,对比了一些Vue的组件,发现了一个很好用的组件--Vue-Simple- ...

  9. iOS开发之结合asp.net webservice实现文件上传下载

    iOS开发中会经常用到文件上传下载的功能,这篇文件将介绍一下使用asp.net webservice实现文件上传下载. 首先,让我们看下文件下载. 这里我们下载cnblogs上的一个zip文件.使用N ...

随机推荐

  1. 十.Go并发编程--channel使用

    一.设计原理 Go 语言中最常见的.也是经常被人提及的设计模式就是: "不要通过共享内存来通信,我们应该使用通信来共享内存" 通过共享内存来通信是直接读取内存的数据,而通过通信来共 ...

  2. 文本分类:Keras+RNN vs传统机器学习

    摘要:本文通过Keras实现了一个RNN文本分类学习的案例,并详细介绍了循环神经网络原理知识及与机器学习对比. 本文分享自华为云社区<基于Keras+RNN的文本分类vs基于传统机器学习的文本分 ...

  3. [APIO2020]有趣的旅途

    注意到第一个点是可以钦定的. 那么我们考虑在重心的子树里反复横跳. 每次选择不同子树里的深度最大的点. 在同一颗子树里可能会在lca处出现问题. 那么我们选择重心,要考虑到会不会出现一颗子树不够选的操 ...

  4. CF1368F Lamps on a Circle

    思考我们一定有最后一个状态是空着的灯是按照一个间隔\(k\) 只要将原来\(n\)个灯,每\(k\)个分一组,强制将最后一盏灯不选,并且第n盏灯不选,需要注意的是某一组一定会被第二个人全部关掉,那么可 ...

  5. JSOI2021 游记

    Day 0 - 2021.4.9 写一波最近的事情吧( 3 月 20 号出头 cnblogs 抽风,说 25 号恢复来着,我就囤了一堆博客在本地准备 25 号发,结果到时候就懒得动了.干脆越屯越多,省 ...

  6. jupyter 远程访问

    Jupyter 远程访问 jupyter 远程访问的工作方法是,在本地通过浏览器打开jupyter,但是代码和服务运行在远程集群中. 集群设置 首先需要确保集群中安装有python和jupyter. ...

  7. 【R shiny】一些应用记录

    目录 DT和downloadButton应用 downloadButton 中验证结果输出 添加进度条 如何确保仅在使用Shiny按下操作按钮时才触发操作 其他 DT和downloadButton应用 ...

  8. Linux系统的开机启动顺序

    Linux系统的开机启动顺序加载BIOS–>读取MBR–>Boot Loader–>加载内核–>用户层init一句inittab文件来设定系统运行的等级(一般3或者5,3是多用 ...

  9. js判断undefined nan等

    1,js判断undefined 主要用typeof(),typeof的返回值有:undefined,object,boolean,number,string,symbol,function等, if( ...

  10. Learning Spark中文版--第六章--Spark高级编程(1)

    Introduction(介绍) 本章介绍了之前章节没有涵盖的高级Spark编程特性.我们介绍两种类型的共享变量:用来聚合信息的累加器和能有效分配较大值的广播变量.基于对RDD现有的transform ...