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

先回顾下在此系列第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. 第一章 初始C语言

    第一章 初始C语言 目录 第一章 初始C语言 1. C语言起源 2. 选择C语言的理由 2.1 设计特性 2.2 高效性 2.3 可移植性 2.4 强大而灵活 2.5 面向程序员 3. C语言的应用范 ...

  2. 将vue文档下载到本地预览

    1下载:https://github.com/vuejs/cn.vuejs.org   到本地 2. npm install npm start # 开发服务器地址为 http://localhost ...

  3. 查询多个count展示结果

    <select id="getCountByDISC" resultType="com.rm.algo.entity.AlgoResult"> SE ...

  4. Python pandas merge不能根据列名合并两个数据框(Key Error)?

    目录 折腾 解决方法 折腾 数据分析用惯了R,感觉pandas用起来就有点反人类了.今天用python的pandas处理数据时两个数据框硬是合并不起来. 我有两个数据框,列名是未知的,只能知道索引,以 ...

  5. 毕业设计之zabbix+微信企业号报警

    需要自己申请一个微信企业号 创建应用 AgentId 1000003 Secret SOI8b20G96yUVM29K02-bP5N5o6dovwSF2RrDaXHJNg 企业ID(自己再企业信息里面 ...

  6. C# 设计模式(1)——简单工厂模式、工厂模式、抽象工厂模式

    1.前言 上一篇写了设计模式原则有助于我们开发程序的时候能写出高质量的代码(牵一发而不动全身),这个系列还是做个笔记温习一下各种设计模式,下面就看看简单工厂模式.工厂模式.抽象工厂模式. 2.简单工厂 ...

  7. OC Swift 走马灯效果

    我们常见走马灯样式的功能,下面整理一下 Object-C 与 Swift 的实现代码 OC UILabel *label3 = [[UILabel alloc] initWithFrame:CGRec ...

  8. Linux学习 - 分区与文件系统

    一.分区类型 1 主分区:总共最多只能分四个 2 扩展分区:只能有一个(主分区中的一个分区),不能存储数据和格式化,必须再划分成逻辑分区                               才 ...

  9. AFNetworking 网络错误提示data转换字符串

    AFN在进行网络交互时,有时候会碰到返回502.500.404的时候.后台的总需要你配合他查出问题所在.但是AFN在返回数据序列化时解析错误只会转成NSData类型的数据,如果直接扔给后台Data的数 ...

  10. 【Service】【Database】【MySQL】基础概念

    1. 数据模型:层次模型.网状模型.关系模型 关系模型: 二维关系: 表:row, column 索引:index 视图:view 2. SQL接口:Structured Query Language ...