授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。

需求背景

虽然,我们项目客户大部分为pc端操作,当客户要把手机拍中照片,要上传至系统中,如果APP没有提供专门的接口,客户需要先把照片传到电脑中,然后通过网页上传,这样比较麻烦,于是,产品便提出需要开发扫码上传附件的功能。

流程图

注册服务

上传附件

功能设计

要实现扫码上传附件的功能,
前端功能

  1. 请求扫描上传附件的申请接口,返回上传地址,生产二维码
  2. 首先用户扫描二维码后,跳转到网页,点击上传吊起手机选择相册或者拍照的功能,然后选择上传附件,最后再提交

后端功能

  1. 业务发起申请,后端接受到请求后,注册当前申请的信息,然后获取到一个上传地址
  2. 用户提交功能,需要根据请求类型,找到相应业务实现类,将之前申请业务的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>

总结

扫描上传附件的功能,从开始到整个模块开发完,尤其当前后端都是我一个独立完成时,还是有很大的成就感,整个功能还算比较独立,由于策略模式的加入,也变得更加灵活,耦合性降低,有了小程序开发的经验,所以前端的界面开发上手比较快、后端,模仿了授权码的机制。但是有设计上的不足,尤其静态和动态二维码的获取授权,还是要多练习、多思考。

功能设计:如何实现一个扫码上传附件的功能相关推荐

  1. 移动端扫码上传数据信息

    随着移动端设备的普及,越来越多的工作环节我们可以借助移动设备进行.移动端扫码(支持二维码.条形码)上传数据信息可以很大程度上减少数据录入纰漏,便捷地联动相关数据,提高信息准确率. 在百数的多个解决方案 ...

  2. 20100519 学习记录:asp CreateFolder/上传附件

    新增一个上传附件的功能. 在网上找了一下,基本都是在化境HTTP上传程序基础上改的,灰常感谢这个源代码的开发者,深深鞠躬. 不过这个代码要求在上传图片时,输入的文件夹必须是已存在的文件夹,不然就会出错 ...

  3. typecho 不能上传附件,上传附件失败

    很多人在用typecho时(我用的1.0,其他的没测试过),都会遇见附件上传失败的问题,其实很简单,你用的是虚拟主机,typecho把上传附件的功能给屏蔽了. 修改方法详见:http://www.ph ...

  4. android 魅族扫码,魅族Flyme8扫码快传太实用,轻松实现文件高速传输

    原标题:魅族Flyme8扫码快传太实用,轻松实现文件高速传输 在经过了几个月的内测之后,一直在热销的魅族16T手机也迎来了Flyme8 稳定版的更新,在流畅度和交互方面带来了较大的提升,进一步提升了用 ...

  5. ROS探索总结(七)(八)(九)——smartcar源码上传 键盘控制 操作杆控制

    ROS探索总结(七)--smartcar源码上传 看到前面写的博客还是帮助了很多ROS的学习者,我感到非常荣幸.其实我也是一名ROS的新手,ROS的相关资料少,上手难度大,我现在也在摸索着学习,还希望 ...

  6. php 上传多个txt文件上传,一个多文件上传的例子(原创)

    一个多文件上传的例子(原创) 更新时间:2006年10月09日 00:00:00   作者: //filename:multi_upload.php if($ifupload) { $path=Add ...

  7. python实战扫码下载_实例:用 Python 做一个扫码工具

    原标题:实例:用 Python 做一个扫码工具 来自公众号: 新建文件夹X 链接:https://blog.csdn.net/ZackSock/article/details/108610957Pyt ...

  8. hdfs 多个文件合并_hadoop学习笔记3 hadoop程序将本地文件夹中多个文件,合并为一个文件并上传到hdfs中--梦飞翔的地方(梦翔天空)...

    今天梦翔儿,成功实现hadoop编程,将本地文件夹中多个文件,合并为一个文件并上传到hdfs中 直接上代码:PutMerge.java import java.io.IOException; impo ...

  9. spring boot 自动跳转登录页面_徒手撸一个扫码登录示例工程

    徒手撸一个扫码登录示例工程 不知道是不是微信的原因,现在出现扫码登录的场景越来越多了,作为一个有追求.有理想新四好码农,当然得紧跟时代的潮流,得徒手撸一个以儆效尤 本篇示例工程,主要用到以下技术栈 q ...

  10. 我把3个镜头手机拍的照片发微信群,哥们说,现在手机摄像头越来越多,我有一个扫码就够了...

    为了测试一加8的超广角摄影效果,我在深圳福田区拍了一张图 我哥们看到这张图,感叹手机镜头畸变的同时,又讲出了无数普通手机用户的心声: 现在手机摄像头越来越多,我有一个扫码就够了 手机为什么会有越来越多 ...

最新文章

  1. 在Mac OS X 10.13.2中安装nltk 和numpy
  2. 【入门经典】在母版页中使用CSS
  3. SLAM-ch2-cmake中使用库
  4. Boosted Tree:一篇很有见识的文章
  5. 【REST】REST和JAX-RS相关知识介绍
  6. 图像二维离散傅里叶变换、幅度谱、相位谱
  7. 共筑计算新生态 共赢数字新时代
  8. Qt学习笔记-带TCP数据传输的局域网聊天软件
  9. php周日,PHP减去一周周日
  10. [Vue.js] 基础 -- Vue常用特性
  11. 【2017-3-17】视图,事务,备份还原,分离附加
  12. FPGA_电机控制(Verilog)
  13. Android WebView 下载没反应
  14. python有颜色进度条库_来看看Python炫酷的颜色输出与进度条打印
  15. Unity3D优化:分场景烘焙,综合场景加载
  16. Java抽象类与接口详解
  17. 少儿编程是智商税吗?不花钱让孩子赢在起跑线
  18. Linux中禁用命令历史记录
  19. 4G物联网卡的几点优势
  20. 英语12个月份的缩写

热门文章

  1. mysql连接失败问题
  2. C/C++编程:#pragma once用法总结
  3. 比特率,比特,字节的计算
  4. 春晚红包互动:中国云计算的成长史
  5. linux下命令行安装oracle 11g数据库
  6. php加密解密 hash,PHP 加解密总结之 hash
  7. SpringCloud Day05---服务网关(Gateway)
  8. Excel VBA自动填充公式
  9. 心中无码便是高清,用“脑补”除马赛克!
  10. centOS 手动部署 wekan