目中需要上传图片可谓是经常遇到的需求,本文将介绍 3 种不同的图片上传方式,在这总结分享一下,有什么建议或者意见,请大家踊跃提出来。
没有业务场景的功能都是耍流氓,那么我们先来模拟一个需要实现的业务场景。假设我们要做一个后台系统添加商品的页面,有一些商品名称、信息等字段,还有需要上传商品轮播图的需求。
我们就以Vue、Element-ui,封装组件为例子聊聊如何实现这个功能。其他框架或者不用框架实现的思路都差不多,本文主要聊聊实现思路。
1.云储存
常见的 七牛云,OSS(阿里云)等,这些云平台提供API接口,调用相应的接口,文件上传后会返回图片存储在服务器上的路径,前端获得这个路径保存下来提交给后端即可。此流程处理相对简单。
主要步骤
代码范例
我们以阿里的 OSS 服务来实现,们试着来封装一个OSS的图片上传组件。
通过element-ui的upLoad组件的 http-request 参数来自定义我们的文件上传,仅仅使用他组件的样式,和其他上传前的相关钩子(控制图片大小,上传数量限制等)。
<template> <el-upload list-type="picture-card" action="''" :http-request="upload" :before-upload="beforeAvatarUpload"> <i class="el-icon-plus"></i> </el-upload> </template> <script> import {getAliOSSCreds} from '@/api/common' // 向后端获取 OSS秘钥信息 import {createId} from '@/utils' // 一个生产唯一的id的方法 import OSS from 'ali-oss' export default { name: 'imgUpload', data () { return {} }, methods: { // 图片上传前验证 beforeAvatarUpload (file) { const isLt2M = file.size / 1024 / 1024 < 2 if (!isLt2M) { this.$message.error('上传头像图片大小不能超过 2MB!') } return isLt2M }, // 上传图片到OSS 同时派发一个事件给父组件监听 upload (item) { getAliOSSCreds().then(res => { // 向后台发请求 拉取OSS相关配置 let creds = res.body.data let client = new OSS.Wrapper({ region: 'oss-cn-beijing', // 服务器集群地区 accessKeyId: creds.accessKeyId, // OSS帐号 accessKeySecret: creds.accessKeySecret, // OSS 密码 stsToken: creds.securityToken, // 签名token bucket: 'imgXXXX' // 阿里云上存储的 Bucket }) let key = 'resource/' + localStorage.userId + '/images/' + createId() + '.jpg' // 存储路径,并且给图片改成唯一名字 return client.put(key, item.file) // OSS上传 }).then(res => { console.log(res.url) this.$emit('on-success', res.url) // 返回图片的存储路径 }).catch(err => { console.log(err) }) } } } </script>
传统文件服务器上传图片
此方法就是上传到自己文件服务器硬盘上,或者云主机的硬盘上,都是通过 formdata 的方式进行文件上传。具体的思路和云文件服务器差不多。
主要步骤
代码示例
此种图片上传根据element-ui的upLoad组件只要传入后端约定的相关字段即可实现,若使用元素js也是生成formdata对象,通过Ajax去实现上传也是类似的。
这里只做一个简单的示例,具体请看el-upload组件相文档就能实现
<template> <el-upload ref="imgUpload" :on-success="imgSuccess" :on-remove="imgRemove" accept="image/gif,image/jpeg,image/jpg,image/png,image/svg" :headers="headerMsg" :action="upLoadUrl" multiple> <el-button type="primary">上传图片</el-button> </el-upload> </template> <script> import {getAliOSSCreds} from '@/api/common' // 向后端获取 OSS秘钥信息 import {createId} from '@/utils' // 一个生产唯一的id的方法 import OSS from 'ali-oss' export default { name: 'imgUpload', data () { return { headerMsg:{Token:'XXXXXX'}, upLoadUrl:'xxxxxxxxxx' } }, methods: { // 上传图片成功 imgSuccess (res, file, fileList) { console.log(res) console.log(file) console.log(fileList) // 这里可以获得上传成功的相关信息 } } } </script>
图片转 base64 后上传
有时候做一些私活项目,或者一些小图片上传可能会采取前端转base64后成为字符串上传。当我们有这一个需求,有一个商品轮播图多张,转base64编码后去掉data:image/jpeg;base64,将字符串以逗号的形势拼接,传给后端。我们如何来实现呢。
1.本地文件如何转成 base64
我们通过H5新特性 readAsDataURL 可以将文件转base64格式,轮播图有多张,可以在点击后立马转base64也可,我是在提交整个表单钱一次进行转码加工。
具体步骤
在这里要注意一下,因为 readAsDataURL 操作是异步的,我们如何将存在数组中的若干的 file对象,进行编码,并且按照上传的顺序,把编码后端图片base64字符串储存在一个新数组内呢,首先想到的是promise的链式调用,可是不能并发进行转码,有点浪费时间。我们可以通过循环 async 函数进行并发,并且排列顺序。请看 methods 的 submitData 方法
utils.js
export function uploadImgToBase64 (file) { return new Promise((resolve, reject) => { const reader = new FileReader() reader.readAsDataURL(file) reader.onload = function () { // 图片转base64完成后返回reader对象 resolve(reader) } reader.onerror = reject }) }
添加商品页面 部分代码
<template> <div> <el-upload ref="imgBroadcastUpload" :auto-upload="false" multiple :file-list="diaLogForm.imgBroadcastList" list-type="picture-card" :on-change="imgBroadcastChange" :on-remove="imgBroadcastRemove" accept="image/jpg,image/png,image/jpeg" action=""> <i class="el-icon-plus"></i> <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过2M</div> </el-upload> <el-button>submitData</el-button> </div> </template> <script> import { uploadImgToBase64 } from '@/utils' // 导入本地图片转base64的方法 export default { name: 'imgUpload', data () { return { diaLogForm: { goodsName:'', // 商品名称字段 imgBroadcastList:[], // 储存选中的图片列表 imgsStr:'' // 后端需要的多张图base64字符串 , 分割 } } }, methods: { // 图片选择后 保存在 diaLogForm.imgBroadcastList 对象中 imgBroadcastChange (file, fileList) { const isLt2M = file.size / 1024 / 1024 < 2 // 上传头像图片大小不能超过 2MB if (!isLt2M) { this.diaLogForm.imgBroadcastList = fileList.filter(v => v.uid !== file.uid) this.$message.error('图片选择失败,每张图片大小不能超过 2MB,请重新选择!') } else { this.diaLogForm.imgBroadcastList.push(file) } }, // 有图片移除后 触发 imgBroadcastRemove (file, fileList) { this.diaLogForm.imgBroadcastList = fileList }, // 提交弹窗数据 async submitDialogData () { const imgBroadcastListBase64 = [] console.log('图片转base64开始...') // 并发 转码轮播图片list => base64 const filePromises = this.diaLogForm.imgBroadcastList.map(async file => { const response = await uploadImgToBase64(file.raw) return response.result.replace(/.*;base64,/, '') // 去掉data:image/jpeg;base64, }) // 按次序输出 base64图片 for (const textPromise of filePromises) { imgBroadcastListBase64.push(await textPromise) } console.log('图片转base64结束..., ', imgBroadcastListBase64) this.diaLogForm.imgsStr = imgBroadcastListBase64.join() console.log(this.diaLogForm) const res = await addCommodity(this.diaLogForm) // 发请求提交表单 if (res.status) { this.$message.success('添加商品成功') // 一般提交成功后后端会处理,在需要展示商品地方会返回一个图片路径 } }, } } </script>
这样本地图片上传的时候转base64上传就完成了。可是轮播图有可以进行编辑,我们该如何处理呢?一般来说商品增加页面和修改页面可以公用一个组件,那么我们继续在这个页面上修改。
编辑时我们首先会拉取商品原有数据,进行展示,在进行修改,这时候服务器返回的图片是一个路径 http://xxx.xxx.xxx/abc.jpg 这样,当我们新增一张图片的还是和上面的方法一样转码即可。可是后端说,没有修改的图片也要赚base64转过来,好吧那就做把。这是一个在线链接 图片,不是本地图片,怎么做呢?
2. 在线图片转base64
具体步骤
utils.js 文件添加在线图片转base64的方法,利用canvas
编辑商品,先拉取原来的商品信息展示到页面
提交表单之前,区分在线图片还是本地图片进行转码
utils.js
export function uploadImgToBase64 (file) { return new Promise((resolve, reject) => { function getBase64Image (img) { const canvas = document.createElement('canvas') canvas.width = img.width canvas.height = img.height const ctx = canvas.getContext('2d') ctx.drawImage(img, 0, 0, canvas.width, canvas.height) var dataURL = canvas.toDataURL() return dataURL } const image = new Image() image.crossOrigin = '*' // 允许跨域图片 image.src = img + '?v=' + Math.random() // 清除图片缓存 console.log(img) image.onload = function () { resolve(getBase64Image(image)) } image.onerror = reject }) }
添加商品页面 部分代码
<template> <div> <el-upload ref="imgBroadcastUpload" :auto-upload="false" multiple :file-list="diaLogForm.imgBroadcastList" list-type="picture-card" :on-change="imgBroadcastChange" :on-remove="imgBroadcastRemove" accept="image/jpg,image/png,image/jpeg" action=""> <i class="el-icon-plus"></i> <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过2M</div> </el-upload> <el-button>submitData</el-button> </div> </template> <script> import { uploadImgToBase64, URLImgToBase64 } from '@/utils' export default { name: 'imgUpload', data () { return { diaLogForm: { goodsName:'', // 商品名称字段 imgBroadcastList:[], // 储存选中的图片列表 imgsStr:'' // 后端需要的多张图base64字符串 , 分割 } } }, created(){ this.getGoodsData() }, methods: { // 图片选择后 保存在 diaLogForm.imgBroadcastList 对象中 imgBroadcastChange (file, fileList) { const isLt2M = file.size / 1024 / 1024 < 2 // 上传头像图片大小不能超过 2MB if (!isLt2M) { this.diaLogForm.imgBroadcastList = fileList.filter(v => v.uid !== file.uid) this.$message.error('图片选择失败,每张图片大小不能超过 2MB,请重新选择!') } else { this.diaLogForm.imgBroadcastList.push(file) } }, // 有图片移除后 触发 imgBroadcastRemove (file, fileList) { this.diaLogForm.imgBroadcastList = fileList }, // 获取商品原有信息 getGoodsData () { getCommodityById({ cid: this.diaLogForm.id }).then(res => { if (res.status) { Object.assign(this.diaLogForm, res.data) // 把 '1.jpg,2.jpg,3.jpg' 转成[{url:'http://xxx.xxx.xx/j.jpg',...}] 这种格式在upload组件内展示。 imgBroadcastList 展示原有的图片 this.diaLogForm.imgBroadcastList = this.diaLogForm.imgsStr.split(',').map(v => ({ url: this.BASE_URL + '/' + v })) } }).catch(err => { console.log(err.data) }) }, // 提交弹窗数据 async submitDialogData () { const imgBroadcastListBase64 = [] console.log('图片转base64开始...') this.dialogFormLoading = true // 并发 转码轮播图片list => base64 const filePromises = this.diaLogForm.imgBroadcastList.map(async file => { if (file.raw) { // 如果是本地文件 const response = await uploadImgToBase64(file.raw) return response.result.replace(/.*;base64,/, '') } else { // 如果是在线文件 const response = await URLImgToBase64(file.url) return response.replace(/.*;base64,/, '') } }) // 按次序输出 base64图片 for (const textPromise of filePromises) { imgBroadcastListBase64.push(await textPromise) } console.log('图片转base64结束...') this.diaLogForm.imgs = imgBroadcastListBase64.join() console.log(this.diaLogForm) if (!this.isEdit) { // 新增编辑 公用一个组件。区分接口调用 const res = await addCommodity(this.diaLogForm) // 提交表单 if (res.status) { this.$message.success('添加成功') } } else { const res = await modifyCommodity(this.diaLogForm) // 提交表单 if (res.status) { this.$router.push('/goods/goods-list') this.$message.success('编辑成功') } } } } } </script>
结语
至此常用的三种图片上传方式就介绍完了,转base64方式一般在小型项目中使用,大文件上传还是传统的 formdata或者 云服务,更合适。但是 通过转base64方式也使得,在前端进行图片编辑成为了可能,不需要上传到服务器就能预览。主要收获还是对于异步操作的处理。
以下是总结出来最全前端框架视频,包含: javascript/vue/react/angualrde/express/koa/webpack 等学习资料。
.需求
最近我们公司在做一个官网涉及到图片视频上传的问题,时间比较紧急(实际后端想偷懒),就让我直传视频到阿里云的oss上。我一听,也是一脸懵,人在家中坐,锅从天上来。话不多说,我直冲度娘,网上的案例真的是非常的多,眼花缭乱,我也试了几个发现都是有问题的,又联系不到博主,最终我还是放弃了,干脆自己搞一个吧,经过三天三夜的奋战(其实是一晚上)!所以分享出来给各位小伙伴们,希望早日脱坑!
先上效果图
2.阿里oss的安装
npm install ali-oss --save
3.封装oss通用上传js工具类
"use strict";
import { dateFormat } from './utils.js'
var OSS = require("ali-oss");
let url='';
export default {
/**
* 创建随机字符串
* @param num
* @returns {string}
*/
randomString(num) {
const chars = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z"
];
let res = "";
for (let i = 0; i < num; i++) {
var id = Math.ceil(Math.random() * 35);
res += chars[id];
}
return res;
},
/**
* 创建oss客户端对象
* @returns {*}
*/
createOssClient() {
return new Promise((resolve, reject) => {
const client = new OSS({
region: "XXXXX", // 请设置成你的
accessKeyId: "XXXXX", // 请设置成你的
accessKeySecret: "XXXXX", // 请设置成你的
bucket: "XXXXX", // 请设置成你的
secure: true // 上传链接返回支持https
});
resolve(client);
});
},
/**
* 文件上传
* @param option 参考csdn: https://blog.csdn.net/qq_27626333/article/details/81463139
*/
ossUploadFile(option) {
const file = option.file;
const self = this;
// var url = '';
return new Promise((resolve, reject) => {
const date = dateFormat(new Date(), "yyyyMMdd"); // 当前时间
const dateTime = dateFormat(new Date(), "yyyyMMddhhmmss"); // 当前时间
const randomStr = self.randomString(4); // 4位随机字符串
const extensionName = file.name.substr(file.name.indexOf(".")); // 文件扩展名
const fileName =
"video/" + date + "/" + dateTime + "_" + randomStr + extensionName; // 文件名字(相对于根目录的路径 + 文件名)
// 执行上传
self.createOssClient().then(client => {
// 异步上传,返回数据
resolve({
fileName: file.name,
fileUrl: fileName
});
// 上传处理
// 分片上传文件
client
.multipartUpload(fileName, file, {
progress: function(p) {
const e = {};
e.percent = Math.floor(p * 100);
// console.log('Progress: ' + p)
option.onProgress(e);
}
})
.then(
val => {
window.url = val
console.info('woc',url);
if (val.res.statusCode === 200) {
option.onSuccess(val);
return val;
} else {
option.onError("上传失败");
}
},
err => {
option.onError("上传失败");
reject(err);
}
);
});
});
}
};
4.在src下的util文件创建utils.js工具类
/**
* 时间日期格式化
* @param format
* @returns {*}
*/
export const dateFormat = function(dateObj, format) {
const date = {
"M+": dateObj.getMonth() + 1,
"d+": dateObj.getDate(),
"h+": dateObj.getHours(),
"m+": dateObj.getMinutes(),
"s+": dateObj.getSeconds(),
"q+": Math.floor((dateObj.getMonth() + 3) / 3),
"S+": dateObj.getMilliseconds()
};
if (/(y+)/i.test(format)) {
format = format.replace(
RegExp.$1,
(dateObj.getFullYear() + "").substr(4 - RegExp.$1.length)
);
}
for (const k in date) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(
RegExp.$1,
RegExp.$1.length === 1
? date[k]
: ("00" + date[k]).substr(("" + date[k]).length)
);
}
}
return format;
};
5.vue页面中的使用
<template>
<div>
<el-form :model="form" label-width="80px" size="small">
<el-form-item label="上传视频">
<el-upload class="upload-demo" action=""
:http-request="fnUploadRequest"
:show-file-list="true"
:limit=1
:on-exceed="beyondFile"
:on-success="handleVideoSuccess"
:before-upload="beforeUploadVideo">
<div tabindex="0" class="el-upload-video">
<i class="el-upload-video-i el-icon-plus avatar-uploader-icon"></i>
</div>
<div class="el-upload__tip" slot="tip">上传视频文件,且不超过500mb</div>
</el-upload>
<el-input type="textarea" rows="5" v-model="urls"></el-input>
</el-form-item>
</el-form>
</div>
</template>
6.js中的代码
<script>
import oss from '../../util/oss.js'
export default {
data: function() {
return {
form: {
status: true
},
url:[],
urls:''
}
},
methods: {
/**
* @description [fnUploadRequest 覆盖默认的上传行为,实现自定义上传]
* @param {object} option [上传选项]
* @return {null} [没有返回]
*/
async fnUploadRequest(option) {
oss.ossUploadFile(option);
},
// 视频上传
beforeUploadVideo(file) {
// todo
},
// 视频上传成功后
handleVideoSuccess(response, file, fileList) {
console.log('url',window.url);
console.log('url',window.url.res.requestUrls);
this.url = window.url.res.requestUrls;
console.log('3322',this.url.length)
for(var i = 0;i<this.url.length;i++){
console.log('href',this.url[i])
this.urls = this.url[i].split('?')[0]
console.log('jjjj',this.url)
}
// todo
},
// 视频添加多个视频文件事件
beyondFile(files, fileList) {
},
}
}
</script>
7.css代码
<style lang="less" scoped>
.el-upload-video {
width: 100px;
height: 100px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.el-upload-video-i{
font-size: 36px;
padding-top: 25px;
color: #8c939d;
width: 50px;
height: 50px;
line-height: 50px;
text-align: center;
}
</style>
8.总结
我在这里讲一下需要注意的几点:
1.这个我没有贴出阿里云的一些配置,自己可以问问度娘,网上有很多资料,注意跨域的配置就行。
2.这样做的优点就是不用和后端交互,图片直传到你阿里云的oss然后返回链接,不需要后端的传值,省去了很多和后端人员联调的时间,大大提高了开发的效率。
代码我已经都贴出来了,样式的话可以自己改,这个是可以根据自己的需求来更改的,如果还有什么问题的话,欢迎留言评论!
图片来源于网络
是一个包含了函数计算每种 Runtime 结合 HTTP Trigger 实现文件上传和文件下载的示例集。每个示例包括:
我们知道不同语言在处理 HTTP 协议上传下载时都有很多中方法和社区库,特别是结合函数计算的场景,开发人员往往需要耗费不少精力去学习和尝试。本示例集编撰的目的就是节省开发者甄别的精力和时间,为每种语言提供一种有效且符合社区最佳实践的方法,可以拿来即用。
当前已支持的 Runtime 包括:
计划支持的 Runtime 包括:
不打算支持的 Runtime 包括:
由于函数计算对于 HTTP 的 Request 和 Response 的 Body 大小限制均为 6M,所以该示例集只适用于借助函数计算上传和下载文件小于 6M 的场景。对于大于 6M 的情况,可以考虑如下方法:
在开始之前请确保开发环境已经安装了如下工具:
克隆代码:
git clone https://github.com/vangie/fc-file-transfer
本地启动函数:
$ make start
...
HttpTrigger httpTrigger of file-transfer/nodejs was registered
url: http://localhost:8000/2016-08-15/proxy/file-transfer/nodejs
methods: [ 'GET', 'POST' ]
authType: ANONYMOUS
HttpTrigger httpTrigger of file-transfer/python was registered
url: http://localhost:8000/2016-08-15/proxy/file-transfer/python
methods: [ 'GET', 'POST' ]
authType: ANONYMOUS
HttpTrigger httpTrigger of file-transfer/java was registered
url: http://localhost:8000/2016-08-15/proxy/file-transfer/java
methods: [ 'GET', 'POST' ]
authType: ANONYMOUS
HttpTrigger httpTrigger of file-transfer/php was registered
url: http://localhost:8000/2016-08-15/proxy/file-transfer/php
methods: [ 'GET', 'POST' ]
authType: ANONYMOUS
function compute app listening on port 8000!
make start 命令会调用 Makefile 文件中的指令,通过 fun local 在本地的 8000 端口开放 HTTP 服务,控制台会打印出每个 HTTP Trigger 的 URL 、支持的 HTTP 方法,以及认证方式。
上面四个 URL 地址随便选一个在浏览器中打开示例页面。
所有示例都实现了下述四个 HTTP 接口:
此外为了能正确的计算相对路径,在访问根路径时如果不是以/结尾,都会触发一个 301 跳转,在 URL 末尾加上一个/。
查看更多:https://yq.aliyun.com/articles/743642?utm_content=g_1000103098
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
*请认真填写需求信息,我们会在24小时内与您取得联系。