Box浅度接触-Java实现Box文件上传下载

背景

Box(https://www.box.com/home)是定义为内容云,在我有限认知里面,感觉应该和云存储系统没啥区别。近日,有幸和Box做了一次浅度接触,颇为缠绵,记录在这里供有需要的朋友参考。

需求很简单,合作伙伴会使用Box存储一些文件用于我们之间共享协作,我们要能够通过编程方式实现Box上资料的文件上传和下载。

云存储产品之前接触过一些,起初不以为然,但体验过程中就遇到了一些周折。在国内云存储已经和阳光、空气和水一样无处不在的大环境下,恐怕很少有必须使用国外的云存储产品,因为在使用过程中,几乎找不到有价值的参考资料,于是,在这里做个总结,以供有需求的朋友能够对基于Box的存储服务能够快速的入门。为了简单直观,文中使用了大量的截图,并且在过程中引用了大量外部链接供您参考。

Box相关资料

Box官网:https://www.box.com/home

Box开发者网站(文档和支持):https://developer.box.com/get-started/

Box开发者Console:https://app.box.com/developers/services

Box Java SDK github:https://github.com/box/box-java-sdk

如何通过Service Account访问Box文件夹:https://support.box.com/hc/en-us/community/posts/1500000023641-Explain-How-to-Access-Files-via-a-Service-Account

前序工作

首先注册账号,申请一个免费的个人账号体验一下,这里没什么可说的。

选择free的版本就好了

输入全名和邮箱以及密码和确认密码,之后通过邮箱激活。一切轻松搞定

然后就是signin

成功登录后

Box会在你的存储空间的根目录下预存一个文件Get Started with Box.pdf,没时间去看,有兴趣的可以了解一下,应该包含特性的介绍已经你能在这里做什么的说明。但我要做的通过编程去实现文件上传下载,虽然简单到不行,但是这种文档应该也不会提供此类内容。

接下来的部分就是看如何通过编程接口来实现文件上传下载了。按习惯,应该找开发者文档之类的东西。去研究一下这个地址吧。https://developer.box.com/get-started/

看到这里,不得不叹服于Box产品文档的强大API接口,甚至还专门讲解了如何通过Postman去调用API,但别着急去看那么强大的文档,向下翻翻

还是这个GetStart页面拖到底部,发现有各种开发语言的SDK可用,好事啊

有支持各种编程语言的SDK可以直接用。妥妥滴。

作为主营Java业务的程序员,直接去看看JavaSDK吧 https://github.com/box/box-java-sdk

开发者Console地址:https://app.box.com/developers/services

这里是中文,如果有必要,可以在Box“我的账户”下设置语言,如果看到截图中有中文又有英文的,那我就是在这里可以切换语言设置了。因为开发者文档中很多术语翻译过来在中文环境下难以对应。

 

进入正题

目标:协作方会提供一个文件夹,用于存储文件,我方需要通过编程实现Box的文件上传下载,这一切只需要在服务端完成。

1、创建应用

首先需要在开发者Console创建一个应用。

1.1 选择应用类型

Box允许创建3中类型的应用:

  • 自定义应用程序;
  • 受限访问应用程序;
  • Box自定义技能。

对于每种类型的应用不在这里详细说明了,如果想了解详情,请查看相关的说明文档。

根据我们的需求背景,选择自定义应用类型;

1.2 选择认证方式

对于自定义应用程序类型的应用,Box提供了3种认证方式:

  • 使用JWT的服务器端验证;
  • OAuth2.0的用户验证;
  • 基于App Token的服务器端认证。

这里最好切换成英文的,因为翻译成中文后对照开发者文档(只提供了英文和日文版)看起来有点糊涂。这里根据需求,选择使用JWT的服务端认证。然后在下方输入应用名称即可。

2、获取用于认证的秘钥对

在应用的配置(Configuration)选项卡页中“添加和管理公钥”(Add and Manage Public Keys)直接使用“生成公私秘钥对”(Generate a Public/Private Keypair)下载一个配置文件xxx_config.json

这里需要一个前置条件,首先要求账户必须启动 two factor 认证。

2.1 插曲:two factor认证

这里需要返回到“我的账户”进行two factor认证设置。

在“我的账户”下的“账户设置”中设置“2-Step Verification”

点击“Set up”

这里又出现了两个选择,选择何种方式做两步校验:

  • 基于App的认证方式;
  • 基于SMS短信的验证。

这里选择基于App的认证,这也是推荐的认证方式,因为在国内短信服务经常无效,这个坑我已经踩过,希望你不要再踩了。老老实实的下载一个认证的手机App,我选择了微软认证器(Microsoft Authenticator),当然你也可以选择其他认证器,iPhone用户在AppStore搜索“认证器”就能找到 Microsoft Authenticator,下载安装。

用Microsoft Authenticator扫描二维码,会在Microsoft Authenticator上获取一个30秒消失用于的一次性验证的数字码,在网页的二维码下面Step2中输入这个验证码。

到这里,two factor 认证就算完成了。

在回到开发者Console继续完成密钥对的生成工作。再次点击“生成公/私密钥对”(Generate a Public/Private Keypair),这是页面跳出要求输入二步验证的页面,再次打开微软认证器的手机App,输入认证码,拼手速,因为认证码30秒刷新一次。

认证通过,再次点击“Generate a Public/Private Keypair

下载xxx_config.json配置文件

这个配置文件就可以放在你的工程中了,但你以为这样就完了?并没有

3、申请授权

在开发者Console的应用配置中给应用做一下授权,默认只有读权限,这里给个写权限。

管理权限因为需求用不上,这里不给了。然后保存设置即可。

再切换到“Authorization”选项卡,要通过应用访问Box资源需要提交授权申请,然后由管理员批准授权方可通过应用使用编程方式访问Box资源。

提交授权申请

切换到管理员Console

在“Apps”->"Custom Apps"下可以看到授权申请,这里批准授权即可

授权应用

授权后,授权状态变为Enabled

再次切换到开发者Console,查看应用的授权状态,已经变为Enbaled,证明授权已经通过。

现在可以通过程序调用了。

这里,还需要交代一点,通过应用访问Box,相当于在Box中新建了一个自动创建的服务账户(Service Account),关于这一点的详细说明如下:

https://developer.box.com/guides/authentication/user-types/service-account/

需要说明的是,服务账户(Service Account)在Box中相当于一个托管的账户,不能用于登录Box系统,只能通过API调用Box的服务。服务账户默认只能访问本服务账户下的文件夹和文件,不能访问到其他的文件夹和文件。如果要访问其他的文件夹,需要主账号在自己的文件夹上添加合作者。添加合作者的方式就是添加这个Service Account的email为合作者,关于这个问题参考

https://support.box.com/hc/en-us/community/posts/1500000023641-Explain-How-to-Access-Files-via-a-Service-Account

这个问题的答复。

4、添加合作者

如上,我们首先要找到这个Service Account的email,然后为主账号下的文件夹添加合作者。

Service Account的email在开发者Console中的应用General Settings中查看 Service Account Info中查看

类似 AutomationUser_xxx_xxxxx@boxdevedition.com的email地址就是这个Service Account的email,“美们,复制它!”。

返回“我的账户”,选择要分享给Service Account的文件夹,这里以/books这个文件夹为例,点击进入books文件夹

点击右侧的Sharing->Collaborators->“Invite People”

在Invite People中添加之前复制的Service Account的email

添加成功

到了这里,就可以通过编程调用Box接口获取共享文件夹的访问权限,之后遍历文件夹,上传文件,下载文件就可以实现了。

5、编码

注意,之前生成并下载的密钥对配置文件放到工程的resource目录中,以便在编译后可以在classpath中可访问。

写个工具类,实现了Box文件夹遍历、文件上传、文件下载的功能,供参考


import com.box.sdk.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Box 工具类
 *
 */
public class BoxUtils {

    private final static Logger logger = LoggerFactory.getLogger(BoxUtils.class);


    /**
     * 上传文件到指定的Box文件夹
     *
     * @param api            BoxAPIConnection 不能为空
     * @param boxFolderId    文件夹ID,不能为空
     * @param localFile      本地文件对象,不能为空
     * @param targetFileName 目标文件名
     * @param targetFileDesc 目标文件描述
     * @param listener       进度监听器
     * @return BoxFile.Info
     * @throws FileNotFoundException 打开文件输入流文件不存在会抛出异常
     */
    public static BoxFile.Info uploadFile(BoxAPIConnection api, String boxFolderId, File localFile, String targetFileName, String targetFileDesc, ProgressListener listener) throws FileNotFoundException {
        if (api == null) {
            logger.error("BoxAPIConnection should not be null");
        }
        if (boxFolderId == null || boxFolderId.trim().equals("")) {
            logger.error("boxFolderId should not be empty");
        }
        BoxFolder folder = new BoxFolder(api, boxFolderId);
        if (localFile.isFile() && localFile.exists()) {
            long localFileSize = localFile.length();
            targetFileName = targetFileName == null || targetFileName.equals("") ? localFile.getName() : targetFileName;
            folder.canUpload(targetFileName, localFileSize);
            FileInputStream localFin = new FileInputStream(localFile);
            FileUploadParams params = new FileUploadParams();
            params.setContent(localFin);
            params.setName(targetFileName);
            params.setDescription(targetFileDesc);
            params.setProgressListener(listener);
            return folder.uploadFile(params);
        } else {
            logger.error("localFilePath is not exist or is not file");
            return null;
        }
    }

    /**
     * 上传文件到指定的Box文件夹
     *
     * @param api            BoxAPIConnection 不能为空
     * @param boxFolderId    文件夹ID,不能为空
     * @param localFilePath  本地文件路径,不能为空
     * @param targetFileName 目标文件名
     * @param targetFileDesc 目标文件描述
     * @param listener       进度监听器
     * @return BoxFile.Info
     * @throws FileNotFoundException 打开文件输入流文件不存在会抛出异常
     */
    public static BoxFile.Info uploadFile(BoxAPIConnection api, String boxFolderId, String localFilePath, String targetFileName, String targetFileDesc, ProgressListener listener) throws FileNotFoundException {
        if (localFilePath == null || localFilePath.trim().equals("")) {
            logger.error("localFilePath can't be empty");
        }
        File localFile = new File(localFilePath);
        return uploadFile(api, boxFolderId, localFile, targetFileName, targetFileDesc, listener);
    }

    /**
     * 上传文件到指定的Box文件夹
     *
     * @param api           BoxAPIConnection 不能为空
     * @param boxFolderId   Box 文件夹ID,不能为空
     * @param localFilePath 本地文件路径,不能为空
     * @return BoxFile.Info
     * @throws FileNotFoundException 打开文件输入流文件不存在会抛出异常
     */
    public static BoxFile.Info uploadFile(BoxAPIConnection api, String boxFolderId, String localFilePath) throws FileNotFoundException {
        return uploadFile(api, boxFolderId, localFilePath, null, null, null);
    }


    /**
     * 上传文件到指定的Box文件夹
     *
     * @param api         BoxAPIConnection 不能为空
     * @param boxFolderId Box 文件夹ID,不能为空
     * @param localFile   本地文件路径,不能为空
     * @return BoxFile.Info
     * @throws FileNotFoundException 打开文件输入流文件不存在会抛出异常
     */
    public static BoxFile.Info uploadFile(BoxAPIConnection api, String boxFolderId, File localFile) throws FileNotFoundException {
        return uploadFile(api, boxFolderId, localFile, null, null, null);
    }


    /**
     * 下载文件
     * 输出流会在方法中被关闭 localOutputStream.close();
     *
     * @param api               BoxAPIConnection 不能为空
     * @param boxFileId         Box 文件ID,不能为空
     * @param localOutputStream 下载文件的目标输出流,不能为空
     * @param listener          进度监听器
     * @throws IOException
     */
    public static void downloadFile(BoxAPIConnection api, String boxFileId, OutputStream localOutputStream, ProgressListener listener) throws IOException {
        BoxFile file = new BoxFile(api, boxFileId);
        if (listener != null) {
            file.download(localOutputStream, listener);
        } else {
            file.download(localOutputStream);
        }
        localOutputStream.close();
    }

    /**
     * 下载文件
     * 输出流会在方法中被关闭 localOutputStream.close();
     *
     * @param api               BoxAPIConnection 不能为空
     * @param boxFileId         Box 文件ID,不能为空
     * @param localOutputStream 下载文件的目标输出流,不能为空
     * @throws IOException
     */
    public static void downloadFile(BoxAPIConnection api, String boxFileId, OutputStream localOutputStream) throws IOException {
        downloadFile(api, boxFileId, localOutputStream, null);
    }

    /**
     * 下载文件
     *
     * @param api           BoxAPIConnection 不能为空
     * @param boxFileId     Box 文件ID,不能为空
     * @param localFilePath 下载文件的本地文件路径,不能为空
     * @param listener      进度监听器
     * @throws IOException
     */
    public static void downloadFile(BoxAPIConnection api, String boxFileId, String localFilePath, ProgressListener listener) throws IOException {
        FileOutputStream localOutputStream = new FileOutputStream(new File(localFilePath));
        downloadFile(api, boxFileId, localOutputStream, listener);
    }

    /**
     * 下载文件
     *
     * @param api           BoxAPIConnection 不能为空
     * @param boxFileId     Box 文件ID,不能为空
     * @param localFilePath 下载文件的本地文件路径,不能为空
     * @throws IOException
     */
    public static void downloadFile(BoxAPIConnection api, String boxFileId, String localFilePath) throws IOException {
        FileOutputStream localOutputStream = new FileOutputStream(new File(localFilePath));
        downloadFile(api, boxFileId, localOutputStream);
    }

    /**
     * 下载文件到指定本地目录,文件名同box文件名
     *
     * @param api       BoxAPIConnection 不能为空
     * @param boxFileId Box 文件ID,不能为空
     * @param localDir  下载文件的本地目录,不能为空
     * @param listener  进度监听器
     * @throws IOException
     */
    public static void downloadFileToDir(BoxAPIConnection api, String boxFileId, String localDir, ProgressListener listener) throws IOException {
        BoxFile boxFile = new BoxFile(api, boxFileId);
        BoxFile.Info info = boxFile.getInfo();
        String fileName = info.getName();
        String locaFilePath = null;
        if (localDir.endsWith("/") || localDir.endsWith("\\")) {
            locaFilePath = localDir + fileName;
        } else {
            locaFilePath = localDir + File.separator + fileName;
        }
        downloadFile(api, boxFileId, locaFilePath, listener);
    }

    /**
     * 下载文件到指定本地目录,文件名同box文件名
     *
     * @param api       BoxAPIConnection 不能为空
     * @param boxFileId Box 文件ID,不能为空
     * @param localDir  下载文件的本地目录,不能为空
     * @throws IOException
     */
    public static void downloadFileToDir(BoxAPIConnection api, String boxFileId, String localDir) throws IOException {
        downloadFileToDir(api, boxFileId, localDir, null);
    }


    /**
     * 遍历指定文件夹中的所有文件及文件夹
     *
     * @param api      BoxAPIConnection 不能为空
     * @param folderId Box 文件夹ID,如果为空,或者是"/"代表遍历根目录;否则遍历指定文件夹
     * @return
     */
    public static List<BoxItem.Info> listFiles(BoxAPIConnection api, String folderId) {
        BoxFolder folder = null;
        if (folderId == null || folderId.trim().equals("") || folderId.trim().equals("/")) {
            folder = BoxFolder.getRootFolder(api);
        } else {
            folder = new BoxFolder(api, folderId);
        }
        List<BoxItem.Info> list = new ArrayList<BoxItem.Info>();
        for (BoxItem.Info itemInfo : folder) {
            list.add(itemInfo);
        }
        return list;
    }

}

写个demo

import com.box.sdk.*;

import java.io.*;
import java.util.List;

public class Demo {

    private final static String FOLDER_ID = "xxx";

    private static BoxConfig getBoxConfig() throws IOException {
        InputStream inputStream = Demo.class.getClassLoader().getResourceAsStream("config.json");
        Reader reader = new InputStreamReader(inputStream);
        BoxConfig config = BoxConfig.readFrom(reader);
        return config;
    }

    private static BoxDeveloperEditionAPIConnection getApi() throws IOException {
        BoxConfig boxConfig = getBoxConfig();
        BoxDeveloperEditionAPIConnection api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig);
        return api;
    }

    public static void main(String[] a) throws IOException {
        BoxAPIConnection api = getApi();

        //遍历root目录
        List<BoxItem.Info> files = BoxUtils.listFiles(api,"/");
        for (BoxItem.Info file:files){
            System.out.println(file.getID()+"::"+file.getName());
        }

        //创建文件夹
        createRootFolder(api,"books");

        //上传文件
        BoxUtils.uploadFile(api,FOLDER_ID,"/your/local/file.txt");

        //下载文件
        BoxUtils.downloadFile(api,"boxFileId","/your/local/file.txt");

        //下载文件到文件夹
        BoxUtils.downloadFileToDir(api,"boxFileId","/your/local/folder");

    }

    public static void createRootFolder(BoxAPIConnection api,String folderName){
        BoxFolder rootFolder = BoxFolder.getRootFolder(api);
        BoxFolder.Info folderInfo = rootFolder.createFolder(folderName);
        System.out.println(folderInfo.getName());
    }

}

以上,基本还原看了Box从注册、配置、开发编码的整个过程。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值