在cordova中使用HTML5的多文件上传
2019独角兽企业重金招聘Python工程师标准>>>
我们先看看linkface给开放的接口:
字段 | 类型 | 必需 | 描述 |
---|---|---|---|
api_id | string | 是 | API 账户 |
api_secret | string | 是 | API 密钥 |
selfie_file | file | 见下方注释 | 需上传的图片文件 1,上传本地图片进行检测时选取此参数 |
selfie_url | string | 见下方注释 | 图片 1 的网络地址,采用抓取网络图片方式时需选取此参数 |
selfie_image_id | file | 见下方注释 | 图片 1 的id,在云端上传过图片可采用 |
historical_selfie_file | file | 见下方注释 | 需上传的图片文件 2,上传本地图片进行检测时选取此参数 |
historical_selfie_url | string | 见下方注释 | 图片 2 的网络地址,采用抓取网络图片方式时需选取此参数 |
historical_selfie_image_id | string | 见下方注释 | 图片 2 的id,在云端上传过图片可采用 |
selfie_auto_rotate | boolean | 否 | 值为 true 时,对图片 1 进行自动旋转。默认值为 false,不旋转 |
historical_selfie_auto_rotate | boolean | 否 | 值为 true 时,对图片 2 进行自动旋转。默认值为 false,不旋转 |
如文件所示,接口需要同时上传两个文件和两个字段,一般我们的web前端就很简单了,两个file类型的input组成的form提交就可以,若想实现文件的异步上传通俗的方式就是安装浏览器安全插件,或者就是使用form表单的提交target指向为iframe,然后将iframe隐藏,使用视窗的父子级调用完成,但是这仍需要我们使用form组件选择文件,很显然这样会使得我们的移动APP体验极差,我们期望的就是使用相机拍完照然后直接异步上传执行检测,当然我们可以使用XMLHTTPReauest2拼接一个formatdata上传
//不完全代码
let formData = new FormData();
formData.append('fileName',input.files[0]);
xhr.open("post", encodeURI(url));
xhr.send(formData);
但是,在web端,如果用户不使用input选择文件,我们是无法私自获取并上传文件的,这个浏览器的安全机制,想想如果可以拼接file://私自获取文件,我们还安全么?
那么针对于cordova plugin 就相当于我们浏览器的插件了,道理是一定的,通过js的方式调用底层接口。我们首先能够想得到的就是file-transfer这个插件,但是很遗憾的告诉你,这个插件一次只能上传一个文件, https://github.com/apache/cordova-plugin-file-transfer,
Parameters:fileURL: Filesystem URL representing the file on the device or a data URI. For backwards compatibility, this can also be the full path of the file on the device. (See Backwards Compatibility Notes below)server: URL of the server to receive the file, as encoded by encodeURI().successCallback: A callback that is passed a FileUploadResult object. (Function)errorCallback: A callback that executes if an error occurs retrieving the FileUploadResult. Invoked with a FileTransferError object. (Function)options: Optional parameters (Object). Valid keys:fileKey: The name of the form element. Defaults to file. (DOMString)
fileName: The file name to use when saving the file on the server. Defaults to image.jpg. (DOMString)
httpMethod: The HTTP method to use - either PUT or POST. Defaults to POST. (DOMString)
mimeType: The mime type of the data to upload. Defaults to image/jpeg. (DOMString)
params: A set of optional key/value pairs to pass in the HTTP request. (Object, key/value - DOMString)
chunkedMode: Whether to upload the data in chunked streaming mode. Defaults to true. (Boolean)
headers: A map of header name/header values. Use an array to specify more than one value. On iOS, FireOS, and Android, if a header named Content-Type is present, multipart form data will NOT be used. (Object)
trustAllHosts: Optional parameter, defaults to false. If set to true, it accepts all security certificates. This is useful since Android rejects self-signed security certificates. Not recommended for production use. Supported on Android and iOS. (boolean)
我真搞不懂既然cordova plugin封装,为啥不封装成文件数组接口呢,支持多文件和困难么?那么我们就来看看他的源码:
boolean multipartFormUpload = (headers == null) || !headers.has("Content-Type");if (multipartFormUpload) {conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);}// Set the cookies on the responseString cookie = getCookies(target);if (cookie != null) {conn.setRequestProperty("Cookie", cookie);}// Handle the other headersif (headers != null) {addHeadersToRequest(conn, headers);}/** Store the non-file portions of the multipart data as a string, so that we can add it* to the contentSize, since it is part of the body of the HTTP request.*/StringBuilder beforeData = new StringBuilder();try {for (Iterator<?> iter = params.keys(); iter.hasNext();) {Object key = iter.next();if(!String.valueOf(key).equals("headers")){beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END);beforeData.append("Content-Disposition: form-data; name=\"").append(key.toString()).append('"');beforeData.append(LINE_END).append(LINE_END);beforeData.append(params.getString(key.toString()));beforeData.append(LINE_END);}}} catch (JSONException e) {Log.e(LOG_TAG, e.getMessage(), e);}beforeData.append(LINE_START).append(BOUNDARY).append(LINE_END);beforeData.append("Content-Disposition: form-data; name=\"").append(fileKey).append("\";");beforeData.append(" filename=\"").append(fileName).append('"').append(LINE_END);beforeData.append("Content-Type: ").append(mimeType).append(LINE_END).append(LINE_END);byte[] beforeDataBytes = beforeData.toString().getBytes("UTF-8");byte[] tailParamsBytes = (LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END).getBytes("UTF-8");
看到了吗,它是拼接了报文,这就是能够解释它为啥还需要依赖 cordova-plugin-file这个插件了,它可以直接获取文件ArrayBuffer,很聪明啊,真的很聪明,为什么拼报文?岂不是很麻烦,正常我么使用java的http client是需要依赖 httpclient-4.0.1.jar commons-codec-1.3.jar apache-mime4j-0.6.jar httpcore-4.0.1.jar httpmime-4.0.1.jar ,这无形之中就增大了app的大小,作为卡插拔式的插件,大小也是一个硬伤,所以封装插件的同学们学习吧,人家可不是盖的,拼接报文自然使得插件不需要依赖那些包了。
我们开脑补一下http报文协议:
一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成,下图给出了请求报文的一般格式。
所以按照标准拼写报文也是可以的。
但是我是一个H5工程师,我首先会使用H5技术去解决这件事,不然我就只能发挥java技能更改file-transfer这个插件了。XHR拼接formdata,可以是file也可以是一个blob,我曾将想过是不是有接口能够模拟封装input的file或者使用FileReader,然而还是那句话,浏览器为了安全不会让我们自己拼接file:// 的,但是cordova跨平台可以访问文件系统(你可以看一下 https://github.com/apache/cordova-plugin-file里http-equiv="Content-Security-Policy"相关的描述),毕竟我们开发的是移动app,这个功能是不可缺少的,我们使用cordova的file plugin还是可以获取文件的我们来看看ionic2提供的接口(http://ionicframework.com/docs/v2/native/file/ ):
readAsArrayBuffer(path, file)
Read file and return data as an ArrayBuffer.
Param | Type | Details |
---|---|---|
path |
string
|
Base FileSystem. Please refer to the iOS and Android filesystems above |
file |
string
|
Name of file, relative to path. |
Returns: Promise<ArrayBuffer|FileError>
Returns a Promise that resolves with the contents of the file as ArrayBuffer or rejects with an error.
惊喜吧!有个这个我们就能够自己拼写blob类型的formdata了,话不多说我们直接上代码:
先写封装一个文件转换类,file-convert-util.ts:
import {File, FileError} from "ionic-native";
/**** @author 赵俊明*/export class FileConvertUtil {constructor() {}//讲文件转换为Blobpublic static convertFileToBlob(fullFilePath: string): Promise<Blob|FileError> {return new Promise((resolve, reject)=> {FileConvertUtil.convertFileToArrayBuffer(fullFilePath).then((arrayBuffer)=> {resolve(new Blob([arrayBuffer], {type: "image/" + FileConvertUtil.extractFileType(fullFilePath)}));}).catch((reason)=> {reject(reason);});});}//将文件装换为ArrayBufferpublic static convertFileToArrayBuffer(fullFilePath: string): Promise<ArrayBuffer | FileError> {return File.readAsArrayBuffer(FileConvertUtil.extractFilePath(fullFilePath), FileConvertUtil.extractFileName(fullFilePath));}//截取文件路径public static extractFilePath(fullFilePath: string): string {return fullFilePath.substr(0, fullFilePath.lastIndexOf('/'));}//截取文件名称public static extractFileName(fullFilePath: string): string {return fullFilePath.substr(fullFilePath.lastIndexOf('/') + 1);}//截取文件类型public static extractFileType(fullFilePath: string): string {return fullFilePath.split(".")[1];}}
基于XHR2的upload,xhr-multipart-upload.ts:
import {BrowserXhr} from "@angular/http";
import {FileConvertUtil} from "./file-convert-util";
import {FileError} from "ionic-native";
import {Injectable, Component} from "@angular/core";
/*** @author zhaojunming*/export class XHRMultipartFileUpload {private static browserXhr = new BrowserXhr();constructor() {}public static upload(url: string, files: {name: string,path: string}[], params: any): Promise<any> {const xhr = XHRMultipartFileUpload.browserXhr.build();xhr.open("post", encodeURI(url));let formData = new FormData();return new Promise((resolve, reject)=> {if (params) {for (let _v in params) {if (params.hasOwnProperty(_v)) {formData.append(_v, params[_v]);}}}let blobPromiseList: Array<Promise<Blob|FileError>> = [];files.forEach((file)=> {blobPromiseList.push(FileConvertUtil.convertFileToBlob(file.path));});Promise.all(blobPromiseList).then((result)=> {result.forEach((blob, index)=> {formData.append(files[index].name, blob, FileConvertUtil.extractFileName(files[index].path));});xhr.onreadystatechange = ()=> {if (xhr.readyState == 4) {if (xhr.status == 200) {resolve(JSON.parse(xhr.responseText));} else {reject({code: xhr.status, message: JSON.parse(xhr.responseText)});}}}xhr.send(formData);}).catch((reason)=> {reject(reason);});});}}
调用linkface的provider,linkface-verfication.ts:
/**** @author 赵俊明*/import {Injectable, Component} from "@angular/core";
import {XHRMultipartFileUpload} from "./xhr-multipart-upload";
import {Storage, LocalStorage} from "ionic-angular";//400 错误码对应信息
const ERROR_MAPPING = {"ENCODING_ERROR": "参数非UTF-8编码","DOWNLOAD_TIMEOUT": "网络地址图片获取超时","DOWNLOAD_ERROR": "网络地址图片获取失败","IMAGE_FILE_SIZE_TOO_BIG": "图片体积过大 ","IMAGE_ID_NOT_EXIST": "图片不存在","NO_FACE_DETECTED": "图片未检测出人脸 ","CORRUPT_IMAGE": "文件不是图片文件或已经损坏","INVALID_IMAGE_FORMAT_OR_SIZE": "图片大小或格式不符合要求","INVALID_ARGUMENT": "请求参数错误","UNAUTHORIZED": "账号或密钥错误","KEY_EXPIRED": "账号过期","RATE_LIMIT_EXCEEDED": "调用频率超出限额","NO_PERMISSION": "无调用权限","NOT_FOUND": "请求路径错误","INTERNAL_ERROR": "服务器内部错误"
};@Injectable()
export class LinkFaceVerfication {//普通照片比对URLprivate historicalSelfieVerificationURL = "https://v1-auth-api.visioncloudapi.com/identity/historical_selfie_verification";//公安水印照片与普通照片比对URLprivate selfieWatermarkVerificationURL = "https://v1-auth-api.visioncloudapi.com/identity/selfie_watermark_verification";private apiId: string;private apiSecret: string;//LocalStorageprivate storage = new Storage(LocalStorage);constructor() {this.getApiId().then(apiId=> {this.apiId = apiId || "6b666502c4324026b8604c8001a2cd14";}).catch(()=> {this.apiId = "6b666502c4324026b8604c8001a2cd14";});this.getApiSecret().then(apiSecret=> {this.apiSecret = apiSecret || "28cf8b8693e54d0b930d0a5089831841";}).catch(()=> {this.apiSecret = "28cf8b8693e54d0b930d0a5089831841";});}//普通照片比对public historicalSelfieVerification(selfie_file: string, historical_selfie_file: string, selfie_auto_rotate: boolean = true, historical_selfie_auto_rotate: boolean = true): Promise<any> {let params = {api_id: this.apiId,api_secret: this.apiSecret,selfie_auto_rotate: selfie_auto_rotate,historical_selfie_auto_rotate: historical_selfie_auto_rotate};let files = []files.push({name: "selfie_file", path: selfie_file});files.push({name: "historical_selfie_file", path: historical_selfie_file});return new Promise((resolve, reject)=> {XHRMultipartFileUpload.upload(this.historicalSelfieVerificationURL, files, params).then(result=> {resolve(result);}).catch(error=> {if (error && error.code == 400) {reject(ERROR_MAPPING[error.message.status]);} else {reject(JSON.stringify(error));}});});}//公安水印照片与普通照片比对public selfieWatermarkVerification(selfie_file: string, watermark_picture_file: string): Promise<any> {let params = {api_id: this.apiId, api_secret: this.apiSecret};let files = []files.push({name: "selfie_file", path: selfie_file});files.push({name: "watermark_picture_file", path: watermark_picture_file});return new Promise((resolve, reject)=> {XHRMultipartFileUpload.upload(this.selfieWatermarkVerificationURL, files, params).then(result=> {resolve(result);}).catch(error=> {if (error && error.code == 400) {reject(ERROR_MAPPING[error.message.status]);} else {reject(JSON.stringify(error));}});});}setApiId(apiId): boolean {if (apiId) {this.apiId = apiId;this.storage.set("apiId", apiId);return true;}return false;}setApiSecret(apiSecret): boolean {if (apiSecret) {this.apiSecret = apiSecret;this.storage.set("apiSecret", apiSecret);return true;}return false;}getApiId(): Promise<string> {return this.storage.get("apiId");}getApiSecret(): Promise<string> {return this.storage.get("apiSecret");}}
看看我们怎么调用:
this.linkFaceVerfication.historicalSelfieVerification(this.selfie_file, this.historical_selfie_file, true, true).then(result=> {this.confidence = (result.confidence * 100).toFixed(2);this.uploading = false;}).catch(reason=> {this.toastMessage(reason);this.uploading = false;});
我们来看看效果:
转载于:https://my.oschina.net/u/259422/blog/747399
在cordova中使用HTML5的多文件上传相关推荐
- HTML5实现大文件上传
需求: 项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在20G内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以20G来进行限制. PC端全平台支持,要求支持Window ...
- php利用ajax文件上传,如何在PHP中利用AjaxForm实现一个文件上传功能
如何在PHP中利用AjaxForm实现一个文件上传功能 发布时间:2020-12-18 14:52:38 来源:亿速云 阅读:94 作者:Leah 如何在PHP中利用AjaxForm实现一个文件上传功 ...
- winform中通过FileStream实现将文件上传
winform中通过FileStream实现将文件上传 本实例实现功能:通过OpenFileDialog选择待上传的文件,并将所选文件的完整路径绑定到TreeView控间中显示,然后通过FolderB ...
- 在PHP中通过POST方法实现文件上传功能
(本文内容属于转载,原文链接http://blog.sina.com.cn/s/blog_4fe447d501009rzh.html) 在PHP 5中通过POST方法实现文件上传非常简单,可以上传文本 ...
- [RFC1867] HTML中基于表单的文件上传
网络工作组:E. Nebel 征求意见:1867 L. Masinter 类别:试验 施乐公司 十一月 1995 HTML中基于表单的文件上传 这个备忘录的状态 这个备忘录为互联网社区定义了一个试验协 ...
- html资源文件记载进度条,HTML5矢量实现文件上传进度条
在HTML中,在文件上传的过程中,很多情况都是没有任何的提示,这在体验上很不好,用户都不知道到时有没有在上传.上传成功了没有,所以今天给大家介绍的内容是通过HT for Web矢量来实现HTML5文件 ...
- HTML5矢量实现文件上传进度条
在HTML中,在文件上传的过程中,很多情况都是没有任何的提示,这在体验上很不好,用户都不知道到时有没有在上传.上传成功了没有,所以今天给大家介绍的内容是通过HT for Web矢量来实现HTML5文件 ...
- HTML5拖拽文件上传
上传文件 HTML5新增了文件API,提供客户端本地操作文件的可能. 我们可以通过file表单或拖放操作选择文件,还可以通过JavaScript读取文件的名称.大小.类型.和修改时间. file类型的 ...
- vue+Element ui中使用 upload实现Excel文件上传
文章目录 业务需求: 实现方式: 具体步骤: 1.引入组件: 2.data中定义: 3.methods中方法: 业务需求: 批量导入Excel文件 实现方式: 使用组件upload 具体步骤: 1.引 ...
最新文章
- mysql数据库c 操作o_linux下c操作mysql数据库。gcc报错。谁能帮忙看下啊。
- python caffe报错:No module named google
- 【持久化框架】SpringMVC+Spring4+Mybatis3集成,开发简单Web项目+源码下载【转】
- python多分支实现四则运算器代码_一份让Python疯狂加速的工具合集!
- 使用Docker启动Grafana环境
- android xml左边,Android XML – 如何将项目对齐到最左侧,中间和最右侧
- 一个简单的shell文件备份脚本
- nginx模块nginx_upstream_check_module来检查后端服务器的健康情况
- 深入理解 Nginx 之架构篇
- 锐捷长ping_锐捷实战系列(二) Wireshark抓包分析Ping过程
- Slat撒盐加密原理
- DICOMRT:XXX Target Volume,各种目标体积浅析
- 转转品牌升级,不只换了个LOGO
- 2021金昌市一中高考成绩查询,2021届甘肃省金昌市一中高三下学期高考一模考试数学(理)试卷参考答案.docx...
- 王道数据结构3.2.5——1、循环队列中设置tag区分头指针front和队尾指针rear相等时队列的状态是空还是满
- @hapi/joi 报错 “message“: “Cannot mix different versions of joi schemas“
- 雷电模拟器 + Xposed框架 + 北京消费券
- 黑马程序员---Java 容器集合
- java图片合成视频、音频及音频裁剪_MQ
- 华云大咖说 | 安超OS全面升级 最新亮点解密
热门文章
- 计算机语言由低到高就被分为了什么阶段,2004年4月高等教育自学考试认知心理试题...
- 鸿蒙测试机型微博,华为多款机型开启鸿蒙尝鲜:微博已适配HarmonyOS小尾巴
- 17 软件源_9成职场人支持“准点下班”,2020年度职场报告:工作是最大焦虑源
- php创建输入文本框,Asp:文本框与输入文本(PHP开发人员学习ASP)
- android文件读取工具类,Android 下读取Assets Properties操作封装工具类
- 转网口显示未识别的网络_已有1700万用户携号转网 超99%用户1小时内办结
- 2011年计算机一级考试题,2011年计算机一级考试试题及答案
- 《团队激励与沟通》第 7 讲——团队合作概述 重点部分总结
- nltk和python的关系_NLTK学习笔记(一):语言处理和Python
- java中父类与子类, 不同的两个类中的因为构造函数由于递归调用导致栈溢出问题...