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

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

目录

前言

一、钉盘是什么?

二、为什么要使用钉盘?

三、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, // 必填,微应用ID
            corpId: 'dingXXXXXXXXXX', // 必填,企业ID
            timeStamp: 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,但当第三方无法实现自身的需求时,变通思路是非常有必要的,特此记录作为学习笔记。如大家有更好的解决方式望留言分享。

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
概述 本文介绍基于hadoop的网络云盘上传下载系统的设计与实现。该系统是一个基于web的云存储系统,用户可以通过web界面上传下载文件。系统采用hadoop分布式文件系统作为底层存储,实现了大规模文件的存储和管理。同时,系统支持多用户和权限控制,确保数据的安全和隐私。 设计 系统的设计分为三个部分:前端、后端和存储。前端主要负责用户界面的设计和实现,后端负责业务逻辑的处理,存储部分则采用hadoop分布式文件系统。 前端设计 前端采用HTML、CSS和JavaScript实现,使用Bootstrap框架进行页面布局和样式设计。用户可以通过web界面上传下载文件,同时也可以创建、删除和管理文件夹。 后端设计 后端采用Java语言实现,使用Spring框架进行开发。系统采用MVC架构,将业务逻辑分为三个部分:控制器、服务和数据访问层。 控制器:负责接收用户请求,解析参数,并调用相应的服务处理请求。 服务:负责处理具体的业务逻辑,包括文件上传下载、删除和权限控制等。 数据访问层:负责访问底层数据存储,即hadoop分布式文件系统。 存储设计 系统采用hadoop分布式文件系统作为底层存储,实现了大规模文件的存储和管理。hadoop分布式文件系统将文件分成多个块存储在不同的节点上,可以实现数据的冗余备份和高可用性。 实现 系统实现分为三个部分:hadoop环境搭建、后端服务开发和前端页面开发。 hadoop环境搭建 系统采用hadoop 2.7.7版本,需要先搭建hadoop环境。具体步骤如下: 1. 安装Java环境。 2. 下载hadoop 2.7.7版本,并解压到指定目录。 3. 配置hadoop环境变量。 4. 修改hadoop配置文件,包括core-site.xml、hdfs-site.xml和mapred-site.xml。 5. 格式化hadoop文件系统。 6. 启动hadoop集群。 后端服务开发 后端采用Java语言实现,使用Spring框架进行开发。具体步骤如下: 1. 创建Spring Boot项目。 2. 添加相关依赖,包括Spring Web、Spring Data JPA、Hadoop Common、Hadoop HDFS和Hadoop Client等。 3. 编写控制器、服务和数据访问层。 4. 配置hadoop文件系统的连接信息。 5. 部署并启动服务。 前端页面开发 前端采用HTML、CSS和JavaScript实现,使用Bootstrap框架进行页面布局和样式设计。具体步骤如下: 1. 创建HTML页面。 2. 使用Bootstrap框架设计页面布局和样式。 3. 使用JavaScript编写与后端服务交互的代码。 4. 部署前端页面到web服务器。 总结 本文介绍了基于hadoop的网络云盘上传下载系统的设计和实现。该系统采用hadoop分布式文件系统作为底层存储,实现了大规模文件的存储和管理。同时,系统支持多用户和权限控制,确保数据的安全和隐私。该系统可以作为企业或个人云存储的解决方案,实现了可扩展、高可用和安全的云存储服务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值