功能设计:如何实现一个扫码上传附件的功能
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
需求背景
虽然,我们项目客户大部分为pc端操作,当客户要把手机拍中照片,要上传至系统中,如果APP没有提供专门的接口,客户需要先把照片传到电脑中,然后通过网页上传,这样比较麻烦,于是,产品便提出需要开发扫码上传附件的功能。
流程图
注册服务
上传附件
功能设计
要实现扫码上传附件的功能,
前端功能
- 请求扫描上传附件的申请接口,返回上传地址,生产二维码
- 首先用户扫描二维码后,跳转到网页,点击上传吊起手机选择相册或者拍照的功能,然后选择上传附件,最后再提交
后端功能
- 业务发起申请,后端接受到请求后,注册当前申请的信息,然后获取到一个上传地址
- 用户提交功能,需要根据请求类型,找到相应业务实现类,将之前申请业务的id和当前附件id都交给实现类
从这个几个流程,可以看出几个要点,授权机制、业务处理
授权机制
二维码生成
关于授权机制,我模仿了Oauth2 的授权码的模式。首先由业务发送申请,后端根据请求的生成一个临时的UUID,以UUID为key,请求中的类型、业务的id,保存至Redis缓存,时效性大概30分钟左右,将上传附件的地址和UUID合并,返回给接口,后面业务又提出一个静态二维码的问题,即保存在文件中的二维码,于是又增加一个注册静态二维码的接口,这里只是把生成的UUID,保存到库
源码
/**
上传附处理类这个类即接受参数,也做为返回参数,严格来说,需要分类两个类,一个是参数类,一个结果类*/
@Data
public class FileByQRCodeDTO implements Serializable {/*** 记录的id*/private String recordId;/*** 类型*/private String type;/*** 文件的id*/private String files;/*** 用户的token*/private String token;/*** 上传限制*/private Integer maxCount;/*** 临时的用户凭证*/private String temporaryUuid;
}
/*** ITjFileInfoService:: getUploadFileQRCodeRedirectUrl* <p>TO:获取二维码跳转的地址* <p>HISTORY: 2021/1/18 liuha : Created.* @param fileByQRCodeDTO 请求参数* @return String 上传的地址*/public String getUploadFileQRCodeRedirectUrl(FileByQRCodeDTO fileByQRCodeDTO) {String uuid = UUID.randomUUID().toString().replaceAll("-", "");String callbackUrl = "";try {callbackUrl = URLEncoder.encode(uploadFileQRCode,"utf8");callbackUrl=String.format(uploadFileQRCode,uuid);
//保存至redisredisUtil.set(uuid,fileByQRCodeDTO,3600);} catch (UnsupportedEncodingException e) {throw new JeecgBootException("生成上传文件的地址失败");}return callbackUrl;}/*** ITjFileInfoService::* <p>TO:获取静态二维码的上传地址* <p>HISTORY: 2021/1/30 liuha : Created.** @param fileByQRCodeDTO 请求参数* @return String 静态二维码的地址*/@Overridepublic String getStaticQRCodeRedirectUrl(FileByQRCodeDTO fileByQRCodeDTO) {String uuid = UUID.randomUUID().toString().replaceAll("-", "");String callbackUrl = "";try {//uploadFileQRCode 跳转到上传附件界面的地址 比如 //http://localhost:8081/uploadqrcodefile/callbackUrl = URLEncoder.encode(uploadFileQRCode,"utf8");callbackUrl=String.format(uploadFileQRCode,uuid);//将关联信息存入至关联表中TjFileQrcodeLink fileQrcodeLink =new TjFileQrcodeLink();fileQrcodeLink.setRecordId(fileByQRCodeDTO.getRecordId());fileQrcodeLink.setType(fileByQRCodeDTO.getType());fileQrcodeLink.setUuid(uuid);fileQrcodeLinkService.save(fileQrcodeLink);} catch (UnsupportedEncodingException e) {throw new JeecgBootException("生成上传文件的地址失败");}return callbackUrl;}
业务处理
这里处理主要是后端如何把上传后的附件id给相应的业务实现类,我在这块这里设计时,采用了策略模式,即,我把处理接口定义好,由业务实现接口,我根据接口定义的type,找到相应的实现类bean对象,把附件id和业务id交给业务处理,这个在我之前写的文章提过类似的实现方式,项目重构:设计模式(策略模式)
源码
//上传接口定义
public interface IQRCodeUpload {/*** IQRCodeUpload:: getType* <p>TO:获取实现类的类型* <p>HISTORY: 2021/1/18 liuha : Created.* @return String */String getType();/*** IQRCodeUpload:: handleUploadFile* <p>TO:上传附件的后处理接口* <p>HISTORY: 2021/1/18 liuha : Created.* @param recordId 记录id* @param files 附件的id,以逗号隔开的方式传递*/void handleUploadFile(String recordId,String files);
}
//将实现类的在初始化spring完成后,加载到map中
@Component
public class UploadQRCodeFactory implements InitializingBean, ApplicationContextAware {private ApplicationContext appContext;public static finalMap<String, IQRCodeUpload> UPLOAD_IMPL_MAP = new HashMap<>(16);/*** UploadContextConfig:: getHandler* <p>TO:通过type获取IQRCodeUpload的实现类* <p>HISTORY: 2021/1/19 liuha : Created.** @param type 实现类的的类型* @return IQRCodeUpload 实现类*/public IQRCodeUpload getHandler(String type) {return UPLOAD_IMPL_MAP.get(type);}@Overridepublic void afterPropertiesSet() {appContext.getBeansOfType(IQRCodeUpload.class).values().forEach(handler -> UPLOAD_IMPL_MAP.put(handler.getType(), handler));}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {appContext = applicationContext;}
}
调用业务实现类的接口
/*** ITjFileInfoService::submitFileByQRCode* <p>TO:上传二维码上传文件* <p>HISTORY: 2021/1/19 liuha : Created.* @param fileByQRCodeDTO 需要上传的文件
*/public void submitFileByQRCode(FileByQRCodeDTO fileByQRCodeDTO) {String type = fileByQRCodeDTO.getType();if(!StringUtils.isEmpty(type)){//获取业务的实现类IQRCodeUpload handler = handlerFactory.getHandler(type);if (handler == null) {throw new JeecgBootException("未找到提交处理的实现类");}handler.handleUploadFile(fileByQRCodeDTO.getRecordId(),fileByQRCodeDTO.getFiles());}}
获取授权TOEKN处理
这里主要为了处理上传附件的接口需要token的认证,一开始,考虑到如果把上传附件的接口去掉token的限制,但是会带来一个问题,如果接口被泄露,会被恶意利用上传附件,所以还是要考虑处理授权的问题,如果是动态的二维码其实比较好处理,就是在缓存中保存当时申请的用户的token,请求时,返回给前端即可,但是静态的二维码,缓存中保存当时申请的用户的token的方式,则失去了时效性。
于是对框架做了部分的调整
1.首先通过uuid授权时候,先查询动态库是否含有当前uuid,对应的id,不存在的时则去查询静态的注册库,查找到信息后,生成一个临时token
2.在验证token时,当发现前缀是uploadFile,则特殊处理
@ApiOperation(value = "通过uuid获取二维码的信息", notes = "通过uuid获取二维码的信息")@GetMapping(value = "/getqrcodeinfo")public Result<?> getQRCodeInfo(@RequestParam(name = "uuid", required = true)String uuid) {uuid= StringUtils.lowerCase(uuid);FileByQRCodeDTO fileByQRCodeDTO= (FileByQRCodeDTO) redisUtil.get(uuid);//如果为空,查询静态库if (fileByQRCodeDTO == null) {LambdaQueryWrapper<TjFileQrcodeLink> query= new LambdaQueryWrapper();query.eq(TjFileQrcodeLink::getUuid,uuid);TjFileQrcodeLink fileQrcodeLink = fileQrcodeLinkService.getOne(query);if (fileQrcodeLink != null) {fileByQRCodeDTO =new FileByQRCodeDTO();fileByQRCodeDTO.setRecordId(fileQrcodeLink.getRecordId());fileByQRCodeDTO.setType(fileQrcodeLink.getType());
//生成一个临时的用户密码String temporaryUuid = UUID.randomUUID().toString().replaceAll("-", "");//生成临时tokenString token =JwtUtil.sign("uploadFile"+uuid,temporaryUuid);fileByQRCodeDTO.setToken(token);redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME*2 / 1000);fileByQRCodeDTO.setMaxCount(fileQrcodeLink.getMaxCount());fileByQRCodeDTO.setTemporaryUuid(temporaryUuid);redisUtil.set(uuid,fileByQRCodeDTO,3600);}}return Result.ok(fileByQRCodeDTO);}
修改ShiroRealm验证的地方
}else if(username.startsWith("uploadFile")){String[] uploadFiles = username.split("uploadFile");FileByQRCodeDTO fileByQRCodeDTO= (FileByQRCodeDTO) redisUtil.get(uploadFiles[1]);loginUser.setUsername(username);loginUser.setPassword(fileByQRCodeDTO.getTemporaryUuid());loginUser.setStatus(1);}
这块地方使用红色提醒,主要不建议使用这段代码,由于静态二维码是后面提出来的,一开始设计没考虑到二维码的类型不同,请求不同授权接口,所以我对这块代码,很不喜欢,一、代码不规范写法;二、接口功能糅合了两个处理方式,可读性差
前端界面
前端,我采用了有赞的vant-vue框架,贴上核心上传界面的代码,
整个前端的界面交互都在整个页面上,获取授权、上传附件、提交附件
UploadFile.vue
<template><div style="height: 100%"><div v-show="uploadDiv"><van-grid :column-num="3" :gutter="20"><van-uploader preview-size="120" :max-count="maxCount" v-model="fileList" multiple :after-read="afterRead" /></van-grid><van-tabbar><van-button size="large" type="primary" @click="submitFile()">提交</van-button></van-tabbar></div><div v-show="success" class="success-div"><div><van-icon name="success" size="5em" color="#00aa00" /><div>提交成功</div></div></div><div v-show="fail" class="success-div"><div><van-icon name="fail" size="5em" color="#ff0000" /><div>操作失败</div></div></div><van-overlay :show="show" /></div></template>
<script>import {setToken} from '../api/auth.js'import {Toast} from 'vant';export default {name: 'app',components: {setToken,},data() {return {fileList: [],recordId: "",type: "",token: "",show: false,uploadDiv: true,success: false,fail: false,maxCount: 9};},mounted() { //页面初始化方法this.getUrlParameter();},methods: {submitFile() {var fileList = this.fileList;console.log(fileList);var files = [];fileList.forEach(function(item) {if (item.fileId) {files.push(item.fileId);}})if (files.length == 0) {this.$notify('未上传附件');return;}var data = {recordId: this.recordId,type: this.type,files: files.join(",")};this.$toast.loading({message: '提交中...',forbidClick: true,});this.$api.post('/file/tjFileInfo/submitfilebyqrcode', data,response => {this.$toast.clear();if (response.status == 200) {if (response.data.code == 200) {this.$toast.success('提交成功');this.uploadDiv = falsethis.success = true} else {this.$toast.fail('提交失败');this.uploadDiv = falsethis.fail = true}} else {this.$notify('提交失败');this.uploadDiv = falsethis.fail = true}});},getUrlParameter() {var url = window.location.href;var dz_url = url.split('#')[0];var cs = dz_url.split('?')[1];var cs_arr = cs.split('&');var cs = {};for (var i = 0; i < cs_arr.length; i++) { //遍历数组,拿到json对象cs[cs_arr[i].split('=')[0]] = cs_arr[i].split('=')[1]}var that = this;this.$api.get('/file/tjFileInfo/getqrcodeinfo', {uuid: cs.uuid},response => {if (response.status == 200) {if (response.data.code == 200) {if (response.data.result) {that.recordId = response.data.result.recordId;that.type = response.data.result.type;that.token = response.data.result.token;if (response.data.result.maxCount > 0) {that.maxCount = response.data.result.maxCount;}setToken(that.token);} else {this.$notify('连接已失效,请重新获取二维码');that.show = true;}} else {this.$notify('获取用户信息失败');that.show = true;}} else {this.$notify('获取用户信息失败');that.show = true;}});},uploadFile(file) {let formData = new FormData()//上传文件到上传至服务器formData.append('file', file.file)file.status = 'uploading';file.message = '上传中...';this.$api.post('/sys/commonBase/upload', formData,response => {if (response.status == 200) {if (response.data.code == 0) {file.status = 'done';file.fileId = response.data.result;} else {file.status = 'failed';file.message = '上传失败...';}} else {file.status = 'failed';file.message = '上传失败...';}});},afterRead(files) {var that = this;if (files.length) {files.forEach(function(file) {that.uploadFile(file);})} else {that.uploadFile(files);}},},};
</script><style>.success-div {height: 100%;display: flex;display: -webkit-flex;align-items: center;justify-content: center;}
</style>
总结
扫描上传附件的功能,从开始到整个模块开发完,尤其当前后端都是我一个独立完成时,还是有很大的成就感,整个功能还算比较独立,由于策略模式的加入,也变得更加灵活,耦合性降低,有了小程序开发的经验,所以前端的界面开发上手比较快、后端,模仿了授权码的机制。但是有设计上的不足,尤其静态和动态二维码的获取授权,还是要多练习、多思考。
功能设计:如何实现一个扫码上传附件的功能相关推荐
- 移动端扫码上传数据信息
随着移动端设备的普及,越来越多的工作环节我们可以借助移动设备进行.移动端扫码(支持二维码.条形码)上传数据信息可以很大程度上减少数据录入纰漏,便捷地联动相关数据,提高信息准确率. 在百数的多个解决方案 ...
- 20100519 学习记录:asp CreateFolder/上传附件
新增一个上传附件的功能. 在网上找了一下,基本都是在化境HTTP上传程序基础上改的,灰常感谢这个源代码的开发者,深深鞠躬. 不过这个代码要求在上传图片时,输入的文件夹必须是已存在的文件夹,不然就会出错 ...
- typecho 不能上传附件,上传附件失败
很多人在用typecho时(我用的1.0,其他的没测试过),都会遇见附件上传失败的问题,其实很简单,你用的是虚拟主机,typecho把上传附件的功能给屏蔽了. 修改方法详见:http://www.ph ...
- android 魅族扫码,魅族Flyme8扫码快传太实用,轻松实现文件高速传输
原标题:魅族Flyme8扫码快传太实用,轻松实现文件高速传输 在经过了几个月的内测之后,一直在热销的魅族16T手机也迎来了Flyme8 稳定版的更新,在流畅度和交互方面带来了较大的提升,进一步提升了用 ...
- ROS探索总结(七)(八)(九)——smartcar源码上传 键盘控制 操作杆控制
ROS探索总结(七)--smartcar源码上传 看到前面写的博客还是帮助了很多ROS的学习者,我感到非常荣幸.其实我也是一名ROS的新手,ROS的相关资料少,上手难度大,我现在也在摸索着学习,还希望 ...
- php 上传多个txt文件上传,一个多文件上传的例子(原创)
一个多文件上传的例子(原创) 更新时间:2006年10月09日 00:00:00 作者: //filename:multi_upload.php if($ifupload) { $path=Add ...
- python实战扫码下载_实例:用 Python 做一个扫码工具
原标题:实例:用 Python 做一个扫码工具 来自公众号: 新建文件夹X 链接:https://blog.csdn.net/ZackSock/article/details/108610957Pyt ...
- hdfs 多个文件合并_hadoop学习笔记3 hadoop程序将本地文件夹中多个文件,合并为一个文件并上传到hdfs中--梦飞翔的地方(梦翔天空)...
今天梦翔儿,成功实现hadoop编程,将本地文件夹中多个文件,合并为一个文件并上传到hdfs中 直接上代码:PutMerge.java import java.io.IOException; impo ...
- spring boot 自动跳转登录页面_徒手撸一个扫码登录示例工程
徒手撸一个扫码登录示例工程 不知道是不是微信的原因,现在出现扫码登录的场景越来越多了,作为一个有追求.有理想新四好码农,当然得紧跟时代的潮流,得徒手撸一个以儆效尤 本篇示例工程,主要用到以下技术栈 q ...
- 我把3个镜头手机拍的照片发微信群,哥们说,现在手机摄像头越来越多,我有一个扫码就够了...
为了测试一加8的超广角摄影效果,我在深圳福田区拍了一张图 我哥们看到这张图,感叹手机镜头畸变的同时,又讲出了无数普通手机用户的心声: 现在手机摄像头越来越多,我有一个扫码就够了 手机为什么会有越来越多 ...
最新文章
- 在Mac OS X 10.13.2中安装nltk 和numpy
- 【入门经典】在母版页中使用CSS
- SLAM-ch2-cmake中使用库
- Boosted Tree:一篇很有见识的文章
- 【REST】REST和JAX-RS相关知识介绍
- 图像二维离散傅里叶变换、幅度谱、相位谱
- 共筑计算新生态 共赢数字新时代
- Qt学习笔记-带TCP数据传输的局域网聊天软件
- php周日,PHP减去一周周日
- [Vue.js] 基础 -- Vue常用特性
- 【2017-3-17】视图,事务,备份还原,分离附加
- FPGA_电机控制(Verilog)
- Android WebView 下载没反应
- python有颜色进度条库_来看看Python炫酷的颜色输出与进度条打印
- Unity3D优化:分场景烘焙,综合场景加载
- Java抽象类与接口详解
- 少儿编程是智商税吗?不花钱让孩子赢在起跑线
- Linux中禁用命令历史记录
- 4G物联网卡的几点优势
- 英语12个月份的缩写