本文主要记录自己在工作学习中遇到的坑和解决思路,仅供大家参考

目录

前言

一、钉盘是什么?

二、为什么要使用钉盘?

三、JSAPI鉴权

1.鉴权的时机

2.鉴权的时效

3.鉴权的代码

3.1.获取access_token

3.2.获取jsapi_ticket

3.3.计算签名参数

3.4.引入JS

3.5.JSAPI鉴权

四、钉盘空间

1.添加空间

2.空间授权

五、上传和预览

1.上传

2.预览

六、问题与解决

1.问题

2.解决思路

七、总结


前言

在工作中,经常会被要求提供附件的上传、下载和预览等功能,上传和下载实现起来较为简单,但是预览功能在尝试过多种实现方式后都不尽如人意。此时想到了网上是否有现成的第三方程序可以直接使用,鉴于一些不可描述的原因(就是领导不舍得花钱,就是抠~~~),最终选定了免费的钉钉云盘来实现预览功能。以下是我在实现过程中的一些心得和遇到的问题及其解决方式。


一、钉盘是什么?

钉盘,顾名思义,就是钉钉自带的云盘,可以给注册企业开辟免费100G云空间(想扩容得加钱)用于文件的存储。

二、为什么要使用钉盘?

钉钉提供了很多自带的API给广大的开发者,而调用这些API则可以实现我们的需求(主要原因还是免费)。

三、JSAPI鉴权

钉钉不管后端提供了各种API,前端也提供了大量的JSAPI供大家使用,比如我们即将使用的上传和预览,都是前端可以直接用JS调用,但是由于不同客户端的区分,部分JSAPI需要在使用之前先要鉴权(比如钉钉的获取登录用户信息就不用鉴权,上传和预览则需要),具体哪些JSAPI需要鉴权可参考:https://open.dingtalk.com/document/isvapp/jsapi-overview

鉴权的方式有很多种,这里我介绍一种我这次使用的鉴权方式

1.鉴权的时机

上面我们说了为什么要鉴权,但为什么鉴权还有时机呢?那是因为鉴权是针对某一个页面URL的,比如我们新增页面要用到上传功能,这时候我们需要在用上传的JSAPI前先对当前新增页面进行鉴权,成功后才能在新增页面使用上传的JSAPI。那么问题来了,我一个应用有N个需要用到此类JSAPI的页面,那每个页面都需要单独做鉴权吗?那岂不是很麻烦?

参考钉钉的开发者文档,加和钉钉开发人员你的沟通后,总结出鉴权只要在主页面鉴权一次,后续路由变动不影响鉴权的有效性。什么意思呢?举个例子,我通过登录页进入到首页后,只要在首页初始化时鉴权一次(比如对url:http://xxx.xxx.xxx.xxx:8080/index鉴权),后面通过变动路由实现跳转的页面将全部都有鉴权效果。

2.鉴权的时效

鉴权一次后,是不是只要URL不变,鉴权是不是一直都有效呢?当然不是,当你关闭浏览器后重新通过浏览器登录系统后,需要重新鉴权,所以建议在登录时跟着登录的方法或进入首页时,调用鉴权。

3.鉴权的代码

3.1.获取access_token

根据创建的H5微应用的AppKey和AppSecret获取,基操,做过钉钉应用开发的应该都会,新人可以参考:https://open.dingtalk.com/document/orgapp/obtain-orgapp-token#

3.2.获取jsapi_ticket

调用钉钉API获取jsapi_ticket,jsapi_ticket有2小时的时效性(不要在意这些,因为我们获取后马上就会用上,不可能会给他过期的机会)

public static String getJsapiTicket(String accessToken) throws Exception {DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/get_jsapi_ticket");OapiGetJsapiTicketRequest req = new OapiGetJsapiTicketRequest();req.setHttpMethod("GET");OapiGetJsapiTicketResponse rsp = client.execute(req, accessToken);System.out.println(rsp.getBody());return rsp.getTicket();}

3.3.计算签名参数

参数说明:

jsticket:通过3.2获取;

nonceStr:自定义标识,可以随意取;

timeStamp:时间戳,一般用当前时间即可,需要注意的是需要保存下来,后面一起传递到前端使用,前后端的时间戳要保持一致

url:需要鉴权的URL,上面提到的首页地址即可

public static String sign(String jsticket, String nonceStr, long timeStamp, String url) throws Exception {String plain = "jsapi_ticket=" + jsticket + "&noncestr=" + nonceStr + "&timestamp=" + String.valueOf(timeStamp)+ "&url=" + decodeUrl(url);MessageDigest sha1 = MessageDigest.getInstance("SHA-256");sha1.reset();sha1.update(plain.getBytes("UTF-8"));return byteToHex(sha1.digest());}// 字节数组转化成十六进制字符串private static String byteToHex(final byte[] hash) {Formatter formatter = new Formatter();for (byte b : hash) {formatter.format("%02x", b);}String result = formatter.toString();formatter.close();return result;}/*** 因为ios端上传递的url是encode过的,android是原始的url。开发者使用的也是原始url,* 所以需要把参数进行一般urlDecode** @param url* @return* @throws Exception*/private static String decodeUrl(String url) throws Exception {URL urler = new URL(url);StringBuilder urlBuffer = new StringBuilder();urlBuffer.append(urler.getProtocol());urlBuffer.append(":");if (urler.getAuthority() != null && urler.getAuthority().length() > 0) {urlBuffer.append("//");urlBuffer.append(urler.getAuthority());}if (urler.getPath() != null) {urlBuffer.append(urler.getPath());}if (urler.getQuery() != null) {urlBuffer.append('?');urlBuffer.append(URLDecoder.decode(urler.getQuery(), "utf-8"));}return urlBuffer.toString();}

3.4.引入JS

前端引入JS

<script src="https://g.alicdn.com/dingding/dingtalk-jsapi/2.10.3/dingtalk.open.js"></script>

3.5.JSAPI鉴权

获取上面获取的鉴权信息,填入后即可鉴权成功。

ding.config({agentId: XXXXXXXX, // 必填,微应用IDcorpId: 'dingXXXXXXXXXX', // 必填,企业IDtimeStamp: 123456789, // 必填,生成签名的时间戳nonceStr: 'ceshi', // 必填,自定义固定字符串。signature: '987654321', // 必填,签名type: 0, // 选填。0表示微应用的jsapi,1表示服务窗的jsapi;不填默认为0。该参数从dingtalk.js的0.8.3版本开始支持jsApiList: ['biz.cspace.preview'] // 必填,需要使用的jsapi列表,注意:不要带dd。})ding.error(function(err) {console.log('dd error: ' + JSON.stringify(err))}) // 该方法必须带上,用来捕获鉴权出现的异常信息,否则不方便排查出现的问题

四、钉盘空间

通过第三步的一顿操作我们终于把最复杂(恶心,yue~~~)的鉴权准备工作做好了,后续我们就可以调用上传和预览的JSAPI了,但在此之前,我们需要指定一个空间用来存放上传的附件,这里我们就需要在钉盘上添加一个特定的空间(可以理解为是一个文件夹),用于存储文件。

1.添加空间

这个比较简单,话不多说,直接上代码。

Client client = createClient();AddSpaceHeaders addSpaceHeaders = new AddSpaceHeaders();addSpaceHeaders.xAcsDingtalkAccessToken = accessToken;AddSpaceRequest.AddSpaceRequestOptionCapabilities optionCapabilities = new AddSpaceRequest.AddSpaceRequestOptionCapabilities().setCanSearch(true) // 是否支持搜索,默认否.setCanRename(true) // 是否支持重命名空间名称,默认否.setCanRecordRecentFile(true); // 是否支持被列入最近使用列表,默认否AddSpaceRequest.AddSpaceRequestOption option = new AddSpaceRequest.AddSpaceRequestOption().setName("测试") // 空间名称
//                .setQuota(1024L) // 空间大小,不指定则不限制.setCapabilities(optionCapabilities).setScene("ceshi") // 空间场景.setSceneId("001") // 空间场景Id,scene和sceneId两者一起确定一个空间,可以理解为联合主键.setOwnerType("USER"); // owner类型,填USER即可AddSpaceRequest addSpaceRequest = new AddSpaceRequest().setUnionId("123456789") // 钉钉用户自带的unionId,可以通过钉钉API获取.setOption(option);try {AddSpaceResponse addSpaceResponse = client.addSpaceWithOptions(addSpaceRequest, addSpaceHeaders, new RuntimeOptions());System.out.println(addSpaceResponse.getBody().getSpace()); // 获取空间的ID} catch (TeaException err) {if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {// err 中含有 code 和 message 属性,可帮助开发定位问题}} catch (Exception _err) {TeaException err = new TeaException(_err.getMessage(), _err);if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {// err 中含有 code 和 message 属性,可帮助开发定位问题}}

注意:新创建的空间是无法通过客户端(比如手机钉钉或钉钉PC端)看到的,可以理解为创建的文件夹是不可见的,但确实真实存在的。正好用于我们保存上传的附件,因为我们只是想让用户可以把附件上传上来,后续不能通过客户端查看。

2.空间授权

新创建的空间对于所有普通用户(非管理员)是没有权限操作的,企业的员工还无法上传和预览,我们需要对企业员工授权。

参数说明:

members.type:授权对象类型,ORG为企业;DEPT为部门;USER为用户,常用这仨

members.id:ORG对应企业corpId,DEPT对应部门deptId,USER对应用户unionId

roleId:OWNER为拥有者,MANAGER为管理者,EDITOR为编辑者,DOWNLOADER为下载者,READER为查看者,权限按顺利从高至低,没人只能保有一个roleId,即给用户添加了高级权限后,再添加低级权限也没用,反之则高权限覆盖低权限。如需要从高权限降至低权限则需要调用修改权限的API,参考:https://open.dingtalk.com/document/orgapp/modify-storage-permissions

String accessToken = accessToken;com.aliyun.dingtalkstorage_1_0.Client client = createClient();AddPermissionHeaders addPermissionHeaders = new AddPermissionHeaders();addPermissionHeaders.xAcsDingtalkAccessToken = accessToken;AddPermissionRequest.AddPermissionRequestOption option = new AddPermissionRequest.AddPermissionRequestOption();AddPermissionRequest.AddPermissionRequestMembers members0 = new AddPermissionRequest.AddPermissionRequestMembers().setType("ORG") // 授权对象类型.setId("dingXXXXXXXXXXXX").setCorpId("dingXXXXXXXXXXXX");AddPermissionRequest addPermissionRequest = new AddPermissionRequest().setUnionId("123456789") // 授权管理员的unionId.setRoleId("DOWNLOADER") // 赋予的权限.setMembers(java.util.Arrays.asList(members0)).setOption(option);try {AddPermissionResponse addPermissionResponse = client.addPermissionWithOptions("123123123", "0", addPermissionRequest, addPermissionHeaders, new RuntimeOptions());System.out.println(addPermissionResponse.getBody().getSuccess());} catch (TeaException err) {if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {// err 中含有 code 和 message 属性,可帮助开发定位问题System.out.println(err.code);System.out.println(err.message);}} catch (Exception _err) {TeaException err = new TeaException(_err.getMessage(), _err);if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {// err 中含有 code 和 message 属性,可帮助开发定位问题System.out.println(err.code);System.out.println(err.message);}}

五、上传和预览

OK,准备工作已经就绪,现在就可以上传和预览了,直接上代码

1.上传

参数说明:

compress:是否压缩,默认true

spaceId:上面创建的空间ID

folderId:空间中存放附件的文件夹ID,没有可以不传

types:上传的方式

dd.biz.util.uploadAttachment({image:{multiple:true,compress:false,max:9,spaceId: "12345",folderId:"123"},space:{corpId:"xxx3020",spaceId:"12345",folderId:"123",isCopy:1 , max:9},file:{spaceId:"12345",folderId:"123",max:1},types:["photo","camera","file","space"],//PC端支持["photo","file","space"]onSuccess : function(result) {//onSuccess将在文件上传成功之后调用/*{type:'', // 用户选择了哪种文件类型 ,image(图片)、file(手机文件)、space(钉盘文件)data: [{spaceId: "232323",fileId: "DzzzzzzNqZY",fileName: "审批流程.docx",fileSize: 1024,fileType: "docx"},{spaceId: "232323",fileId: "DzzzzzzNqZY",fileName: "审批流程1.pdf",fileSize: 1024,fileType: "pdf"},{spaceId: "232323",fileId: "DzzzzzzNqZY",fileName: "审批流程3.pptx",fileSize: 1024,fileType: "pptx"}]}*/},onFail : function(err) {}
});

2.预览

参数说明:都很好理解,友情提醒fileSize和fileType可以不填,不影响预览

dd.biz.cspace.preview({corpId: "dingf8b3xxxxx",spaceId: "13557022",fileId: "11452819",fileName: "钉盘快速入门",fileSize: 1024,fileType: "pdf",onSuccess : function(res) {},onFail : function(err) {}
});

六、问题与解决

到这里是不是小伙伴们觉得已经完成了?我只能说你们还是太年轻了,下面我将我遇到的问题和解决思路分享出来,有更好的解决方法可以分享出来讨论。

1.问题

为了每个用户都要可以上传附件,所以我们将会给每个企业用户设置钉盘空间的权限为EDITOR。那么问题来了,有了编辑者的权限后,所有用户都可以对空间内的附件进行修改,毕竟领导可不希望上传到服务器的附件今天显示的报销金额是1000,明天就变成了10000。这时候就会有小伙伴说了,不是新建的空间是客户端不可见的吗,我看不到我肯定也改不了啊。但事你们忽略了一个问题,我们还有预览功能。

预览里面居然可以编辑,而且问过钉钉开发人员后得知还不能隐藏,为什么就不能在预览的JSAPI调用参数里添加一个参数来控制是否允许在预览时进行修改呢?当我知道无法通过代码层面来解决这个问题时,我的内心是崩溃的,连我这么菜的人都能想到的需求为啥钉钉开发人员没有想到。

2.解决思路

崩溃归崩溃,但还是要实现甲方爸爸的需求(毕竟关系到了我的口袋)。既然钉钉无法实现我们的需求,我们就要换一个思路。经过翻开了大量钉钉API后我找到了一种办法:

现在的问题是,预览只需要用户有READER或者DOWNLOADER权限即可,可以用户能上传,就必须要有EDITOR的权限,那么我们是否可以将两者分开呢?基于这个思路,我将原来的A空间不变,还是赋予员工EDITOR权限。然后新增B钉盘空间,赋予员工DOWNLOADER权限。在用户上传附件至A空间后,立即触发移动文件的API,将刚上传的附件从A空间移动到B空间。之后就简单了,前端的预览功能统一访问B空间的文件。这样就做到了权限分离,上传和预览分开,顺利解决。上代码:

参数说明:

option.conflictStrategy:

auto_rename:自动重命名,默认值

overwrite:覆盖

return_dentry_if_exists:返回已存在

return_error_if_exists:报错

unionId:操作人的unionId,需要注意的是由于目标空间普通用户没有编辑权限,这里需要写死管理员的unionId

public void moveFile(@RequestParam(value = "file_id", required = false) String fileId) throws Exception {String accessToken = accessToken;com.aliyun.dingtalkstorage_1_0.Client client = createClient();MoveDentryHeaders moveDentryHeaders = new MoveDentryHeaders();moveDentryHeaders.xAcsDingtalkAccessToken = accessToken;MoveDentryRequest.MoveDentryRequestOption option = new             MoveDentryRequest.MoveDentryRequestOption().setConflictStrategy("AUTO_RENAME") // 文件和文件夹的名称冲突策略.setPresevePermissions(true); // 移动后,是否保留权限,默认否MoveDentryRequest moveDentryRequest = new MoveDentryRequest().setUnionId("1234567") // 操作人的钉钉unionId.setTargetSpaceId("目标空间ID") // 目标空间ID.setTargetFolderId("0") // 目标空间文件夹ID,根目录传0.setOption(option);try {MoveDentryResponse moveDentryResponse = client.moveDentryWithOptions("原空间ID", fileId, moveDentryRequest, moveDentryHeaders, new RuntimeOptions());System.out.println(moveDentryResponse.getBody().getAsync());} catch (TeaException err) {if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {// err 中含有 code 和 message 属性,可帮助开发定位问题}} catch (Exception _err) {TeaException err = new TeaException(_err.getMessage(), _err);if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {// err 中含有 code 和 message 属性,可帮助开发定位问题}}}

七、总结

至此顺利完成功能,虽然其中的原理不难,都是调用钉钉现成的API,但当第三方无法实现自身的需求时,变通思路是非常有必要的,特此记录作为学习笔记。如大家有更好的解决方式望留言分享。

利用钉钉云盘实现业务系统需要的附件上传、下载和预览相关推荐

  1. 如何在linux系统下使用百度云盘上传下载文件?

    学习目标: 如何在linux系统下使用百度云盘上传下载文件? 环境准备: linux操作系统 python开发运行环境 文件下载: 1.Linux系统安装工具 pip install requests ...

  2. 微信小程序云开发实现上传文件和预览下载文件

    微信小程序云开发实现上传文件和预览下载文件 一.前言 目前微信提供了一个接口 wx.chooseMessageFile 它能让用户从聊天记录里面选择一个或者多个文件,然后返回它的一些信息,列入文件的p ...

  3. 安装、进程-云计算学习笔记---hadoop的简介,以及安装,用命令实现对hdfs系统进行文件的上传下载-by小雨...

    本文是一篇关于安装.进程-的帖子 1.Hadoop简介 1.hadoop的生诞 l  Nutch和Lucene之父Doug Cutting在2006年成完Hadoop目项. l  Hadoop并非一个 ...

  4. 云计算学习笔记004---hadoop的简介,以及安装,用命令实现对hdfs系统进行文件的上传下载

    1.Hadoop简介 1.hadoop的诞生 l  Nutch和Lucene之父Doug Cutting在2006年完成Hadoop项目. l  Hadoop并不是一个单词,它来源于DougCutti ...

  5. WIN8/10 中科院 ARP系统显示和附件上传的解决办法

    相信科院体系的朋友们在出国开会的时候都会接触一个ARP系统. ARP系统有多烂这里就不吐槽了,这里只说问题和解决办法.(浪费了我太多时间来解决) 由于ARP系统的开发完全是基于IE8以前的IE浏览器, ...

  6. 在Linux系统(服务器)使用阿里云盘服务快速上传下载文件

    使用集群服务器的时候,尤其是当服务器有多个节点时有些复杂,连接集群我们一般用xshell,传输文件我们一般使用Xftp,一般对于单个节点服务器来说是方便的,使用Xftp还可以可视化本地和服务器端的文件 ...

  7. 阿里云盘小白羊版:支持满速上传下载的第三方阿里云盘客户端

    阿里云盘小白羊版是一款第三方的阿里云盘客户端,支持 Windows. macOS,扫码登录之后,可满速下载,满速上传,支持预览视频.图片,支持多并发上传.断点续传,支持 115 链.秒传链! 2021 ...

  8. 百度网盘海外版Dubox正式更名为TeraBox,上传下载不限速

    百度网盘相信大家都不陌生,在平时的工作与生活中用来存储与分享各类文件那是相当的方便.但是它也有令人恨得牙痒的地方,就是下载太龟速了! 去年,百度旗下公司Popln在海外推出一款网盘产--Dubox,也 ...

  9. Linux上的天翼云盘客户端,支持上传下载

    最近把手头的电信卡办理的业务疏理了一遍,退订了不必要的业务,并且薅了免费的天翼云盘,发现这个真是神器,不仅送了10T的免费空间,而且下载能到十几M,上传是满速(我的宽带比较挫,上传只有7M),相对应国 ...

最新文章

  1. VB.NET程序如何巧妙释放内存
  2. HTTP 中POST GET 区别
  3. 超低延迟实时流媒体传输技术
  4. 打印网页时背景图片的问题
  5. Jmeter文章索引贴
  6. 苹果Mac强大的采样器音源软件:Native Instruments Kontakt
  7. spring-boot-资源处理
  8. Wireshark入门:第一次亲密接触
  9. Mysql入门经典.pdf下载
  10. Ubuntu18.04设置系统默认音频设备:使用pavucontrol命令
  11. 澳大利亚IT解决方案提供商使用OpManager节省了数万美元的IT维护成本
  12. 学校网站建设的必要性
  13. 【实践】人体红外传感器
  14. 每天五分钟玩转K8S(二)
  15. 2019互联网岳麓峰会”区块链分会场—长沙率先推出区块链公共服务平台
  16. 智慧零售的分级战场,苏宁618的升降策略能否厚积薄发?
  17. 基于TextRank算法的单领域多文本摘要(英文摘要)
  18. 华为设备 配置成为FTP服务器/客户端
  19. [宋史学习] 宋太宗评价
  20. 为什么都有API网关?聊聊API网关的作用

热门文章

  1. Oracle基础教程文档~超级全
  2. 组件传参的终极版,事件车,父子传参的祖宗。
  3. 网络协议和Netty——第二章 Java原生网络编程学习笔记
  4. VBA简单实现两个Excel文件的比较方法
  5. Chevereto配合PicGo打造个人图床
  6. 免费又好用的屏幕录像/直播软件:Open Broadcaster Software
  7. 小区蔬菜配送的小程序
  8. 接收后台返回的文件流或 base64 后下载打印 pdf 功能
  9. DoDataExchange(CDataExchange *pDX);
  10. Python相关工具使用01_设置双击直接打开.ipynb文件