将文件转存到钉钉的钉盘中

最近做项目遇到了一个需求,就是在自己的系统上提交审批,会走钉钉的审批流,并且把上传的文件显示在钉钉的审批流附件中。这里牵扯到的就是文件转存到钉盘的知识了,注意是转存哦,并不是直接上传到钉盘中。这个需求用了半天的时间解决掉了,下面是记录本次解决的步骤,旨在方便后期再遇到此类问题时可以套用,也给遇到此类问题的开发者们提供一个解决的思路。

1、文档链接:保存文件到自定义或审批钉盘空间 - 钉钉开放平台

首先可以根据上面的钉钉开发文档链接,了解钉钉转存文件所需要的参数。也可以通过下面附的截图来粗略的看一下:

2、参数解析:

 2.1)所需参数中access_token是钉钉验证权限的token,只需要根据创建的应用中的appkey和appsecret请求一下gettoken接口,就会返回access_token,请求地址如下图:

2.2)agent_id是创建应用中的AgentId,在应用的详情页面也是存在的,直接拿过来即可。

2.3)code 为免登授权码,需要注意的是钉钉返回的code有效期为5分钟,并且使用一次过后立即失效!我们这边实现方式是在选择文件上传时,前端都会去调用免登授权码接口获取code,然后将code传递给后端

2.4) media_id 这个参数需要调用钉钉的单步文件上传接口来获取

单步文件上传 - 钉钉开放平台

fileSize获取比较简单,主要是Post请求体中file的路径这一块儿,我一直不知道该怎么写。后来是通过了比较笨的方式,通过multipartFile转file,然后转存到本地后再把路径附上去,来获取media_id。

2.5) space_id可以通过以下方法获取钉盘id(企业只有一个spaceId,这个值通常是固定的)


    public Long getSpaceId() {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/processinstance/cspace/info");
        OapiProcessinstanceCspaceInfoRequest req = new OapiProcessinstanceCspaceInfoRequest();
// 用户的钉钉id
        req.setUserId("xxxxxxxxx");
        OapiProcessinstanceCspaceInfoResponse rsp = null;
        try {
            rsp = client.execute(req, getAccessToken());
        } catch (ApiException e) {
            logger.error("获取钉盘Id失败!原因:{}", e.getErrMsg());
        }
        if (Objects.isNull(rsp)) {
            return null;
        }
        return rsp.getResult().getSpaceId();
    }

2.6)folder_id 我们是审批附件,所以传0。(如果是自定义钉盘,不需要传)

2.7)name的话就是文件名称,比较容易获取,这里也不多赘述

2.8)overwrite 是指遇到同名文件时是否覆盖,布尔类型,默认的话是false,这个参数可以根据具体的业务需求来选择

3、具体实现方式(伪代码):

    @Resource
    private IDingDingService dingDingService;


/**
     * 
     * @param multipartFile 上传的文件
     * @param code 前端调用钉钉免登授权接口获取的code
     * @return
     */
    @PostMapping("/upload")
    public AjaxResult uploadFile(@RequestParam("file") MultipartFile multipartFile, @RequestParam(value = "code") String code) {
        String originalFilename = multipartFile.getOriginalFilename();
        Map<String, Object> resultMap = Maps.newHashMap();
        // 获取钉盘id的方法在参数介绍中已经写明了,可以向上找找看
        /* 上面说过,钉盘id一个企业只有一个,那么这里为什么不写死呢? 
            因为2点:
                1、写死试过,没成功
                2、钉钉推荐每次转存文件前,都先获取一次spaceId
            所以没办法,只能跟着文档走喽~~
        */
        Long spaceId = dingDingService.getSpaceId();
        if (Objects.isNull(spaceId)) {
            return AjaxResult.error("获取钉盘ID失败!");
        }
        
         // 获取钉钉返回的mediaId
        String mediaId = dingDingService.getMediaId(multipartFile);
        if (StringUtils.isBlank(mediaId)) {
            return AjaxResult.error("获取文件钉盘mediaId出错!");
        }

/* 文件转存钉盘后返回的spaceId,fileName,fileId,fileType,fileSize等信息,
这些信息在文件设置到钉钉审批流附件中需要用到*/
        String dentry = dingDingService.saveFileToDingSpace(code, mediaId, originalFilename);
        if (Objects.isNull(dentry)) {
            return AjaxResult.error("转存审批钉盘失败!");
        }
// 将返回的json字符串转换,并封装到map后返回
        JSONObject jsonObject = JSON.parseObject(dentry);
        resultMap.put("file_id", jsonObject.get("id"));
        resultMap.put("file_Name", jsonObject.get("name"));
        resultMap.put("file_size", jsonObject.get("size"));
        resultMap.put("file_type", jsonObject.get("type"));
        resultMap.put("space_id", spaceId);
    } 
/**
     * 获取钉盘mediaId
     *
     * @param multipartFile 上传的文件
     * @return
     */
    public String getMediaId(MultipartFile multipartFile) {
        // 将multipartFile转为file
        File file = transferToFile(multipartFile);
        // 这里我把获取accessToken的方法封装了一下
        String accessToken = getAccessToken();
        OapiFileUploadSingleRequest request = new OapiFileUploadSingleRequest();
        // 获取文件大小
        request.setFileSize(multipartFile.getSize());
        // 应用的AgentId
        request.setAgentId(SpringUtils.getBean(AppConfig.class).getAgentId());
        DingTalkClient client = null;
        try {
            client = new DefaultDingTalkClient("https://oapi.dingtalk.com/file/upload/single?" + WebUtils.buildQuery(request.getTextParams(), "utf-8"));
        } catch (IOException e) {
            logger.error("创建钉钉单步文件上传路径失败!");
        }
        // 必须重新new一个请求
        request = new OapiFileUploadSingleRequest();
        // 这里文件的路径使用的是转成file之后文件存储的路径
        request.setFile(new FileItem(file.getPath()));
        OapiFileUploadSingleResponse response = null;
        try {
            response = client.execute(request, accessToken);
        } catch (ApiException e) {
            logger.error("调用钉钉单步文件上传接口,获取mediaId失败!");
            return null;
        }
        if (Objects.isNull(response)) {
            return null;
        }
        return response.getMediaId();
    }

// multipartFile 转 file的方法
public static File transferToFile(MultipartFile multipartFile) {
//        选择用缓冲区来实现这个转换即使用java 创建的临时文件 使用 MultipartFile.transferto()方法 。
        File file = null;
        try {
            String originalFilename = multipartFile.getOriginalFilename();
            String[] filename = originalFilename.split("\\.");
            /* ReviewConfig.getUploadPath()是我写在配置文件中的路径,
            这里要注意路径必须是存在的,因为项目发布在linux中,
            所以这里我写的是 /home/uploadPath/upload,可以根据自己的需求进行替换,
            生成的文件会保存在指定的路径下 */
            file=File.createTempFile(filename[0] + System.currentTimeMillis(), "." + filename[1], new File(ReviewConfig.getUploadPath())); 
            multipartFile.transferTo(file);
            file.deleteOnExit();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }

/* 封装的获取access_token的方法,
如果不想频繁的获取token,可以考虑放到redis中做缓存 */
public String getAccessToken() {
        DefaultDingTalkClient client = new DefaultDingTalkClient(UrlConstant.URL_GET_TOKEN);
        OapiGettokenRequest request = new OapiGettokenRequest();
        OapiGettokenResponse response;
    /* 应用中的appkey和appsercet 这里我写在了配置文件中,大家也可以写死,毕竟一个H5微应用
        的appkey和appsercet是固定的
 */
        request.setAppkey(appconfig.getAppKey());
        request.setAppsecret(appconfig.getAppSecret());
        // 请求方式是get
        request.setHttpMethod("GET");
        try {
            response = client.execute(request);
        } catch (ApiException e) {
            logger.debug(e.getErrCode(), e.getErrMsg());
            return null;
        }
        return accessToken.toString();
    }
/**
     * 转存文件到审批钉盘
     *
     * @param code 钉钉免登授权码
     * @param mediaId 获取到钉钉返回的media_id
     * @return
     */
    @Override
    public String saveFileToDingSpace(String code, String mediaId, String fileName) {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/cspace/add");
        OapiCspaceAddRequest req = new OapiCspaceAddRequest();
        // AgentId同上,写在配置文件中的
        req.setAgentId(SpringUtils.getBean(AppConfig.class).getAgentId());
        req.setCode(code);
        req.setMediaId(mediaId);
        // 前面获取到的spaceId
        req.setSpaceId("40xxxxxx");
        req.setName(fileName);
        // 遇到同名文件是否覆盖,我这里根据业务需求,选择了false
        req.setOverwrite(false);
        req.setHttpMethod("GET");
        OapiCspaceAddResponse rsp = null;
        try {
            rsp = client.execute(req, getAccessToken());
        } catch (ApiException e) {
            logger.error("文件转存钉盘失败!原因:{}", e.getErrMsg());
            return null;
        }
        if (Objects.isNull(rsp)) {
            return null;
        }
        // 将返回信息中的json串取出并返回
        return rsp.getDentry();
    }

那么至此,文件转存到钉盘的功能就完结啦。我们这边的需求实际上是在上传图片的时候,先把文件转存到钉盘一份, 获取到spaceId、fileSize、fileName,fileId,fileType这几个参数(因为文件设置到钉钉审批流附件时需要这5个参数),获取完成后,再上传到自己的minio服务器上,这样在钉钉审批流查看附件时,看到的是钉盘中的附件,而在我们的系统中查看附件时,是minio上的附件。

写的比较简单粗暴啊,建议大家先熟悉钉钉开放文档后再结合本文查看,效果更优。有不清楚的可以评论或者私信问我,如果对你有帮助,帮忙点个赞,谢啦~~

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值