您的位置 首页 > 产业综合

图片上传,详解用户图片上传流程

本篇文章给大家谈谈图片上传,以及详解用户图片上传流程对应的知识点,文章可能有点长,但是希望大家可以阅读完,增长自己的知识,最重要的是希望对各位有所帮助,可以解决了您的问题,不要忘了收藏本站喔。

最近在开发新版博客网站时,有几个页面需要使用图片上传功能。整个项目前端基于vue3的element-plue和vue-cropper组件库封装一个图片上传组件,后端使DjangoRESTframework开发api接口,存储使用七牛对象存储,以及腾讯CDN加速,总结了完整的前后端代码以及运维配置,以供大家参考。

用户图片上传以及显示整个过程可分为以下几个阶段:

当用户访问https://www.cuiliangblog.cn/applyLink时,前端nginx服务器接收请求(如果配置了CDN,DNS会智能解析到CDN节点处理请求),返回页面数据给用户,浏览器加载并显示页面

用户选择好本地图片文件,点击开始上传操作后,浏览器向后端API接口发送请求,获取此次上传操作的token

API后端接收到请求后,使用七牛SDK请求七牛云存储服务,获取上传token后将token返回给客户端

客户端使用token上传文件至七牛对象存储服务,上传成功后,七牛存储返回客户端资源的URL地址给客户端

客户端根据图片URL地址请求CDN服务,CDN节点发现没有找到资源后回源至七牛对象存储服务,获取文件资源成功后缓存至CDN并返回给客户端

用户提交表单,表单内容中包含资源的URL地址请求后端API接口

后端API接口保存图片资源URL地址

3.开发需求分析

本案例使用如今最流行的前后端分离开发模式。

4.运维配置分析

本案例使用主流的企业网站项目配置,使用公有云的OSS对象存储服务、CDN内容分发网络以及DNS域名解析。此处选用七牛云对象存储和腾讯云CDN以及阿里云的域名解析,其他公有云厂商产品名称和配置项可能略有差异,但基本原理都是一样的,操作步骤也并无差别。

没有注册的小伙伴们可以使用以下链接进行注册

此处使用七牛对象存储,有10G免费空间,对于一般小业务场景完全够用。

1.创建云存储空间

登录七牛云——>进入控制台——>点击对象存储——>然后点击新建空间

填写表单完成存储空间的创建,完后后七牛云会自动为我分配一个测试域名,这样我们就可以使用这个域名进行上传/下载文件了。

需要注意的是:测试域名只能使用30天!!并且测试域名只能使用HTTP协议,不支持HTTPS协议

2.对象存储服务绑定域名

因为我已经购买过域名cuiliangblog.cn。所以接下来绑定oss.cuiliangblog.cn给这个存储空间即可。需要注意的是,虽然七牛云的对象存储服务免费,但是CDN加速服务是收费的,我已经够买过腾讯CDN服务,所以此处配置了自定义源站域名,大家可以按照自己的实际情况选择最合适的配置。

1.添加CDN加速域名

登录腾讯云——>控制台——>CDN内容分发网络——>域名管理——>添加域名

2.配置回源及缓存等策略

此处以我的oss.cuiliangblog.cn对象存储域名举例,其中回源策略填写七牛云对象存储的CNAME。

1.添加域名解析记录

登录阿里云——>控制台——>域名——>解析

至此,存储服务和CDN以及DNS配置已全部完成,接下来做一个简单的测试

经过访问测试,上传图片后生成的外链可以正常打开访问,且远程地址为腾讯云CDN加速节点。到这儿,运维的工作已经完成了,接下来角色转换,现在是一位专业的后端开发工程师。

1.后端功能模块概述

要想使用七牛的对象存储服务上传文件,就需要在后端通过七牛SDK生成的一个安全凭证,只有客户端拿着这个上传凭证上传文件才是有效的,否则七牛服务器是不接受的。七牛云的开发者中心提供了很多版本的SDK,例如Go,JavaScript,PHP,Python,Node.js,Ruby,C#,C/C++等等,这里是我使用的是python的SDK,详见开发者文档:https://developer.qiniu.com/kodo/1242/python

2.获取accessKey和secretKey

通过查看开发者文档可知,调用SDK需要传入bucket、accessKey、secretKey三个参数

bucket的值就是存储空间的名称,accessKey和secretKey可以将鼠标悬浮在右上角的头像上然后点击密钥管理,然后创新密钥

3.DRF项目相关功能代码实现

pipinstallqiniusetting.py中存放密钥信息

#七牛OSS存储配置\n\nQINIU_AK='XXXXXXXXXXXXX'\n\nQINIU_SK='XXXXXXXXXXXXX'\n\nQINIU_BUCKET='cuiliangoss'\n\nQINIU_DOMAIN='https://oss.cuiliangblog.cn/'配置请求token的API接口路由(urls.py)

fromdjango.urlsimportpath\nfromrest_frameworkimportrouters\nfrompublicimportviews\n\napp_name="public"\nurlpatterns=[\npath('qiniuToken/',views.QiniuTokenAPIView.as_view()),\n#获取七牛上传token\n…………\n]\nrouter=routers.DefaultRouter()\nurlpatterns+=router.urls编写视图函数(views.py),因为此处仅处理简单的响应,不涉及到模型操作,直接使用一级视图即可

fromrest_frameworkimportstatus\nfromrest_framework.responseimportResponse\nfromrest_framework.viewsimportAPIView\nfromqiniuimportAuth\nfromdjango.confimportsettings\n\n\nclassQiniuTokenAPIView(APIView):\n"""\n获取七牛上传文件token\n"""\n\ndefget(request):\nq=Auth(settings.QINIU_AK,settings.QINIU_SK)\ntoken=q.upload_token(settings.QINIU_BUCKET)\nreturnResponse({'token':token,'domain':settings.QINIU_DOMAIN},status=status.HTTP_200_OK)使用API接口工具访问测试

至此,后端API接口开发完成,短短几行代码,轻松而愉快的完成了后端的开发。接来下才是整个项目最核心的部分,苦逼的前端工程师上岗了。

1.上传组件分析

七牛对象存储支持多种多样的类型文件上传,虽然官方提供了详细的demo示例,但是在实际开发使用过程中,为了便于多个不同项目的移植和以及vue组件调用,因此将其封装为js的模块,当需要调用使用七牛的对象存储服务上传文件时,只需要传入上传文件的路径和文件对象即可。函数在执行时,先请求后端API接口,获取本次上传文件的token和domain,并提取文件名加入时间戳,避免同一时间传入多张图片导致文件名冲突,最后调用七牛JavaScript-SDK实现文件上传,并返回成功上传的文件URL地址。详细说明请参考官方文档:https://developer.qiniu.com/kodo/1283/javascript

2.上传组件代码实现

importindexfrom'./index'\n\n//获取七牛图片上传token\nexportfunctiongetQiNiuToken(){\nreturnindex.get('public/qiniuToken/')\n}七牛文件上传模块

import*asqiniufrom"qiniu-js";\nimport{getQiNiuToken}from"@/api/public";\n\nfunctionqiniuUpload(){//file是选择的文件对象\nconstupload=(dir,file)=>{\nreturnnewPromise((resolve,reject)=>{\ngetQiNiuToken().then((response)=>{\nletdomain=response.domain\nlettoken=response.token\nletkey=dir+'/'+file.name.substring(0,file.name.lastIndexOf('.'))+'-'+newDate().getTime()\n+file.name.substring(file.name.lastIndexOf('.'))\nletconfig={\nuseCdnDomain:true,//表示是否使用cdn加速域名,为布尔值,true表示使用,默认为false。\nregion:qiniu.region.z1//根据具体提示修改上传地区,当为null或undefined时,自动分析上传域名区域\n}\nletputExtra={\nfname:"",//文件原文件名\nparams:{},//用来放置自定义变量\nmimeType:null//用来限制上传文件类型,为null时表示不对文件类型限制;限制类型放到数组里:["image/png","image/jpeg","image/gif"]\n};\nconstobservable=qiniu.upload(file,key,token,putExtra,config)\nobservable.subscribe({\nnext:(result)=>{\n//主要用来展示进度\nconsole.log(result)\n},\nerror:(error)=>{\n//上传错误后触发\nconsole.log(error);\nreject(error)\n},\ncomplete:(result)=>{\n//上传成功后触发。包含文件地址。\nleturl=domain+result.key\n//console.log(url)\nresolve(url)\n},\n});\n}).catch(response=>{\n//发生错误时执行的代码\nconsole.log(response)\n});\n})\n}\nreturn{\nupload\n}\n}\n\nexportdefaultqiniuUpload七、前端-裁剪组件开发

1.裁剪模块分析

用户上传图片并镜像预览裁剪操作在多个页面中都会使用到,因此非常有必要将它封装成一个公共的子组件。其他页面使用这个组件时,图片上传地址、图片宽度、图片高度都不尽相同。因此将这个三个值设为子组件的参数变量,当用户完成图片裁剪后,点击上传时,调用上传组件,并给父组件传递success事件,并包含最终图片的URL地址参数。

2.裁剪组件代码实现

裁剪组件基于element-plus(参考地址:https://github.com/element-plus/element-plus)和vue-cropper(参考地址:https://github.com/xyxiao001/vue-cropper)二次封装实现

<template>\n<div>\n<el-uploadaccept=".jpg,.jpeg,.png"\naction="./"\n:auto-upload="false"\n:on-change="uploadChange"\n:show-file-list="false"\n>\n<el-buttonclass="upload-btn">\n<MyIconclass="upload-icon"type="icon-upload-img"/>\n<p>选择图片</p>\n</el-button>\n</el-upload>\n<el-dialogtitle="图片裁剪"v-model="showCopper"append-to-bodycenter>\n<divclass="cropper"v-loading="loading"element-loading-text="图片上传中...">\n<spanclass="cropper-area">\n<vueCropper\nref="cropper"\n:img="cropImg"\n:autoCrop="true"\n:autoCropWidth="props.width"\n:autoCropHeight="props.height"\n:fixedNumber="[props.width/props.height,1]"\n:fixed="true"\n@realTime="realTime"\n></vueCropper>\n</span>\n<spanclass="preview-area">\n<p>图片预览</p>\n<divclass="show-preview">\n<div:style="previews.div"class="preview">\n<img:src="previews.url":style="previews.img">\n</div>\n</div>\n</span>\n</div>\n<template#footer>\n<el-buttonsize="medium"type="success">\n<labelclass="pointer"for="uploads">更换图片</label>\n</el-button>\n<inputtype="file"id="uploads"style="position:absolute;clip:rect(0000);"\naccept="image/png,image/jpeg,image/jpg"@change="uploadChange($event)">\n<el-button-groupclass="cropper-btn-group">\n<el-buttonsize="medium"type="primary"plain@click="changeScale(1)">\n<MyIcontype="icon-amplification"/>\n</el-button>\n<el-buttonsize="medium"type="primary"plain@click="changeScale(-1)">\n<MyIcontype="icon-narrow"/>\n</el-button>\n<el-buttonsize="medium"type="primary"plain@click="changeReset()">\n<MyIcontype="icon-reset"/>\n</el-button>\n<el-buttonsize="medium"type="primary"plain@click="changeRotate(1)">\n<MyIcontype="icon-clockwise-sense"/>\n</el-button>\n<el-buttonsize="medium"type="primary"plain@click="changeRotate(-1)">\n<MyIcontype="icon-clockwise-dirction"/>\n</el-button>\n</el-button-group>\n<el-buttonsize="medium"@click="showCopper=false">取消</el-button>\n<el-buttontype="primary"@click="confirmFn"size="medium">确定</el-button>\n</template>\n</el-dialog>\n</div>\n</template>\n<scriptsetup>\nimport{reactive,ref}from'vue'\nimporticonfrom"@/utils/icon";\nimporttimeFormatfrom"@/utils/timeFormat";\nimport'vue-cropper/dist/index.css'\nimport{VueCropper}from"vue-cropper";\nimportqiniuUploadfrom"@/utils/qiniuUpload";\nimport{ElMessage}from'element-plus'\n\nlet{MyIcon}=icon()\n//格式化处理时间\nlet{timeFile}=timeFormat()\n//七牛图片上传\nlet{upload}=qiniuUpload()\nconstprops=defineProps({\n//图片宽度\nwidth:{\ntype:Number,\nrequired:false,\ndefault:200\n},\n//图片高度\nheight:{\ntype:Number,\nrequired:false,\ndefault:200\n},\n//图片保存目录\ndir:{\ntype:String,\nrequired:true,\ndefault:'upload'\n}\n})\n//定义事件(子组件向父组件传参)\nconstemit=defineEmits(['saveImg']);\n//图像裁剪组件对象\nconstcropper=ref(null);\n//裁剪后的图片文件\nconstcropImg=ref('');\n//图片裁剪对话框是否显示\nconstshowCopper=ref(false);\n//文件上传组件选取图片事件\nconstuploadChange=(file)=>{\nletfileObj\nif('raw'infile){\nconsole.log("element对象")\nfileObj=file.raw\n}else{\nconsole.log("原生对象")\nfileObj=file.target.files[0]\n}\nconstreader=newFileReader();\nreader.onload=(event)=>{\ncropImg.value=event.target.result;\n};\nreader.readAsDataURL(fileObj)\nshowCopper.value=true;\n}\n//图片裁剪预览数据\nconstpreviews=reactive({})\n//图片裁剪预览事件\nconstrealTime=(data)=>{\nObject.assign(previews,data)\n}\n//图片裁剪缩放事件\nconstchangeScale=(num)=>{\nnum=num||1\ncropper.value.changeScale(num)\n}\n//图片裁剪旋转事件\nconstchangeRotate=(num)=>{\nif(num===1){\ncropper.value.rotateLeft()\n}else{\ncropper.value.rotateRight()\n}\n}\n//图片裁剪重置事件\nconstchangeReset=()=>{\ncropper.value.refresh()\n}\n//文件上传动画状态\nconstloading=ref(false)\n//图片裁剪完成上传事件\nconstconfirmFn=()=>{\n//获取blob对象\ncropper.value.getCropBlob(blobData=>{\nconsole.log(blobData)\nloading.value=true\n//blob转file\nconstfile=newFile([blobData],timeFile(Date.now())+'.jpg',{type:blobData.type});\nconsole.log(file)\nupload(props.dir,file).then((response)=>{\nconsole.log(response)\nElMessage({\nmessage:'图片上传成功!',\ntype:'success',\n})\nemit('saveImg',response)\nshowCopper.value=false\nloading.value=false\n}).catch(response=>{\n//发生错误时执行的代码\nconsole.log(response)\nElMessage.error('图片上传失败!')\nloading.value=false\n});\n})\n}\n</script>\n<stylescopedlang="scss">\n.upload-btn{\n.upload-icon{\nfont-size:24px;\ncolor:$color-text-secondary;\nvertical-align:-7px!important;\nmargin-right:5px;\n}\n\np{\ndisplay:inline-block;\nvertical-align:4px;\n}\n}\n\n.cropper{\ndisplay:flex;\nheight:50vh;\n\n.cropper-area{\nflex:2;\n}\n\n.preview-area{\nflex:1;\nmargin-left:20px;\n\np{\ntext-align:center;\nmargin-bottom:20px;\n}\n\n.show-preview{\nflex:1;\n-webkit-flex:1;\ndisplay:flex;\ndisplay:-webkit-flex;\njustify-content:center;\n-webkit-justify-content:center;\n\n.preview{\noverflow:hidden;\nborder-radius:50%;\nborder:1pxsolid#cccccc;\nbackground:#cccccc;\n}\n}\n}\n}\n\n.cropper-btn-group{\nmargin:040px;\n\n.anticon{\nfont-size:18px;\n}\n}\n</style>其他vue页面调用图片上传组件时,传入裁剪完成后图片的宽度,高度,以及文件上传目录。当完成上传操作后,图片裁剪组件会返回一个上传完成事件,并携带图片URL地址。

<template>\n<divclass="page">\n<divclass="animate__animatedanimate__zoomIn">\n<el-card>\n<template#header>\n<spanclass="card-titleno-choose"><MyIcontype="icon-form-color"/>申请表单</span>\n</template>\n<div>\n<el-formref="linkFormRef":model="linkForm"label-width="120px":rules="rules">\n<el-form-itemlabel="网站名称"prop="name">\n<el-inputv-model="linkForm.name"></el-input>\n</el-form-item>\n<el-form-itemlabel="网站地址"prop="url">\n<el-inputv-model="linkForm.url"placeholder="请输入完整地址,https://开头"></el-input>\n</el-form-item>\n<el-form-itemlabel="网站简介"prop="describe">\n<el-inputv-model="linkForm.describe"></el-input>\n</el-form-item>\n<el-form-itemlabel="网站logo"prop="logo">\n<spanv-if="linkForm.logo===''">\n<UploadImg:width="150":height="150":dir="'logo'"@saveImg="saveImg"></UploadImg>\n</span>\n<spanv-else><el-avatar:size="100":src="linkForm.logo"></el-avatar></span>\n</el-form-item>\n<el-form-item>\n<el-buttontype="primary"@click="onSubmit">提交</el-button>\n<el-button@click="reset">重置</el-button>\n</el-form-item>\n</el-form>\n</div>\n</el-card>\n</div>\n</div>\n</template>\n\n<scriptsetup>\nimportUploadImgfrom"@/components/common/UploadImg.vue"\nimport{onMounted,reactive,ref}from"vue";\nimport{getSiteConfig,postLink}from"@/api/management";\nimporticonfrom"@/utils/icon";\nimport{ElMessage}from"element-plus";\n\nlet{MyIcon}=icon()\n//图片上传成功事件\nconstsaveImg=(url)=>{\nconsole.log(url)\nlinkForm.logo=url\n}\n//提交友链表单对象\nconstlinkFormRef=ref(null)\n//提交友链表单\nconstlinkForm=reactive({\nurl:'',\nname:'',\ndescribe:'',\nlogo:'',\n})\n//表单验证规则\nconstrules={\nurl:[{required:true,message:'请输入网站地址',trigger:'blur',}],\nname:[{required:true,message:'请输入网站名称',trigger:'blur',}],\ndescribe:[{required:true,message:'请输入网站描述',trigger:'blur',}],\nlogo:[{required:true,message:'请上传网站logo',trigger:'blur',}],\n}\n//提交表单事件\nconstonSubmit=()=>{\nconsole.log('submit!')\nlinkFormRef.value.validate((valid)=>{\nif(valid){\npostLink(linkForm).then((response)=>{\nconsole.log(response)\nElMessage({\nmessage:'友链申请提交成功,请耐心等待审核!',\ntype:'success',\n})\nlinkForm.url=''\nlinkForm.name=''\nlinkForm.describe=''\nlinkForm.logo=''\n}).catch(response=>{\n//发生错误时执行的代码\nconsole.log(response)\nfor(letiinresponse){\nElMessage.error(response[i][0])\n}\n});\n}\n})\n}\n//重置表单\nconstreset=()=>{\nlinkFormRef.value.resetFields()\n}\nonMounted(()=>{\nsiteConfigData()\n})\n</script>\n\n<stylescopedlang="scss">\n.demo{\nmargin:15px0\n}\n\n.point-text{\nline-height:30px;\ncolor:$color-text-primary;\n}\n</style>\n八、功能验证与演示

一切准备就绪,接下来演示图片上传效果,也可查看在线地址查看效果https://www.cuiliangblog.cn/applyLink

至此,整个用户图片上传流程开发完成!

更多运维开发相关文章,欢迎访问崔亮的博客https://www.cuiliangblog.cn

文章分享结束,图片上传和详解用户图片上传流程的答案你都知道了吗?欢迎再次光临本站哦!

本站涵盖的内容、图片、视频等数据,部分未能与原作者取得联系。若涉及版权问题,请及时通知我们并提供相关证明材料,我们将及时予以删除!谢谢大家的理解与支持!

Copyright © 2023