前言

在开发Web应用时,文件存储和管理是一个常见需求。123云盘提供了开放的API接口,允许开发者将文件上传到其云存储平台。本文将详细介绍如何在Spring Boot项目中对接123云盘API,实现文件上传并获取文件直链的功能。

本文将详细介绍如何在SpringBoot项目中对接123云盘开放平台API,实现文件上传和获取文件直链的功能。我们将基于123云盘的官方文档和提供的Java代码示例,逐步讲解实现过程。

一、准备工作

1. 注册123云盘开发者账号

首先需要访问123云盘开放平台注册开发者账号,获取client_idclient_secret

2. 添加项目依赖

在SpringBoot项目中,我们需要添加OkHttp依赖用于HTTP请求:

xml

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.3</version>
</dependency>

二、核心代码实现

1. 配置属性类

首先创建一个配置类来存储123云盘的客户端凭证:

java

@Data
@Component
@ConfigurationProperties(prefix = "yunpan")
public class YunpanProperties {
    private String clientId;
    private String clientSecret;
}

application.yml中配置:

yaml

yunpan:
  client_id: xxxxxxxxxxxx
  client_secret: xxxxxxxxxxxx

2. AccessToken服务

123云盘的API需要通过AccessToken进行鉴权,我们需要实现一个服务来获取和管理AccessToken:

java

public interface AccessTokenService {
    String getAccessToken() throws IOException;
}

@Component
public class AccessTokenServiceImpl implements AccessTokenService {

    @Autowired
    private YunpanProperties yunpanProperties;

    private String accessToken;
    private LocalDateTime expiredAt;

    public synchronized String getAccessToken() throws IOException {
        // 如果token不存在或已过期,则刷新token
        if (expiredAt == null || LocalDateTime.now().isAfter(expiredAt)) {
            refreshAccessToken();
        }
        return accessToken;
    }

    private void refreshAccessToken() throws IOException {
        OkHttpClient client = new OkHttpClient();
        // 构建请求体
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("clientID", yunpanProperties.getClientId());
        jsonObject.put("clientSecret", yunpanProperties.getClientSecret());
        RequestBody body = RequestBody.create(jsonObject.toString(), MediaType.get("application/json"));

        // 构建请求
        Request request = new Request.Builder()
                .url("https://open-api.123pan.com/api/v1/access_token")
                .post(body)
                .addHeader("Platform", "open_platform")
                .addHeader("Content-Type", "application/json")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("获取AccessToken失败: " + response);
            }
            
            String jsonResponse = response.body().string();
            JSONObject data = JSON.parseObject(jsonResponse).getJSONObject("data");
            this.accessToken = data.getString("accessToken");
            String expiredAtStr = data.getString("expiredAt");
            this.expiredAt = OffsetDateTime.parse(expiredAtStr).toLocalDateTime();
            System.out.println("✅ 刷新 accessToken 成功: " + this.accessToken);
        }
    }
}

3. 123云盘操作类

这是核心的操作类,封装了与123云盘API交互的所有方法:

java

@Slf4j
@Component
public class YunpanOperator {

    @Autowired
    private AccessTokenService accessTokenService;

    /**
     * 计算文件的MD5值
     * @param file 要计算的文件
     * @return MD5字符串
     */
    private String getFileMD5(MultipartFile file) throws NoSuchAlgorithmException, IOException {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] digest = md5.digest(file.getBytes());
        BigInteger bigInt = new BigInteger(1, digest);
        StringBuilder md5Str = new StringBuilder(bigInt.toString(16));
        // 补齐32位
        while (md5Str.length() < 32) {
            md5Str.insert(0, "0");
        }
        return md5Str.toString();
    }

    /**
     * 获取上传域名
     * @return 上传域名
     */
    private String getDomain() throws IOException {
        OkHttpClient client = new OkHttpClient().newBuilder().build();
        Request request = new Request.Builder()
                .url("https://open-api.123pan.com/upload/v2/file/domain")
                .get()
                .addHeader("Authorization", accessTokenService.getAccessToken())
                .addHeader("Platform", "open_platform")
                .build();
        
        try (Response response = client.newCall(request).execute()) {
            String jsonResponse = response.body().string();
            log.info("获取上传域名响应: {}", jsonResponse);
            return JSONObject.parseObject(jsonResponse).getJSONArray("data").getString(0);
        }
    }

    /**
     * 单步上传文件到123云盘
     * @param file 要上传的文件
     * @return 文件ID
     */
    public String uploadSingleFile(MultipartFile file) throws IOException, NoSuchAlgorithmException {
        // 生成随机文件名,避免重名
        String originalFilename = file.getOriginalFilename();
        String extensionName = originalFilename.substring(originalFilename.lastIndexOf("."));
        String filename = UUID.randomUUID().toString() + extensionName;
        
        // 上传参数
        String parentFileID = "0"; // 上传到根目录
        String etag = getFileMD5(file);
        String size = String.valueOf(file.getSize());

        // 构建多部分请求体
        RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
                .addFormDataPart("file", filename,
                        RequestBody.create(MediaType.parse("application/octet-stream"),
                                file.getBytes()))
                .addFormDataPart("parentFileID", parentFileID)
                .addFormDataPart("filename", filename)
                .addFormDataPart("etag", etag)
                .addFormDataPart("size", size)
                .build();

        // 构建请求
        Request request = new Request.Builder()
                .url(getDomain() + "/upload/v2/file/single/create")
                .method("POST", body)
                .addHeader("Authorization", "Bearer " + accessTokenService.getAccessToken())
                .addHeader("Platform", "open_platform")
                .build();

        // 执行请求并处理响应
        try (Response response = client.newCall(request).execute()) {
            String json = response.body().string();
            log.info("文件上传响应: {}", json);
            return JSON.parseObject(json).getJSONObject("data").getString("fileID");
        }
    }

    /**
     * 获取文件直链
     * @param fileID 文件ID
     * @return 文件直链URL
     */
    public String getDirectLink(String fileID) throws IOException {
        OkHttpClient client = new OkHttpClient().newBuilder().build();
        Request request = new Request.Builder()
                .url("https://open-api.123pan.com/api/v1/direct-link/url?fileID=" + fileID)
                .get()
                .addHeader("Content-Type", "application/json")
                .addHeader("Platform", "open_platform")
                .addHeader("Authorization", "Bearer " + accessTokenService.getAccessToken())
                .build();

        try (Response response = client.newCall(request).execute()) {
            String jsonResponse = response.body().string();
            log.info("获取直链响应: {}", jsonResponse);
            return JSON.parseObject(jsonResponse).getJSONObject("data").getString("url");
        }
    }
}

4. 上传服务接口与实现

定义上传服务接口和实现:

java

public interface UploadService {
    String upload(MultipartFile file) throws Exception;
}

@Service
public class YunpanUploadServiceImpl implements UploadService {
    @Autowired
    private YunpanOperator yunpanOperator;
    
    @Override
    public String upload(MultipartFile file) throws Exception {
        // 1. 上传文件到123云盘
        String fileID = yunpanOperator.uploadSingleFile(file);
        // 2. 获取文件直链
        return yunpanOperator.getDirectLink(fileID);
    }
}

5. 控制器

最后创建控制器处理文件上传请求:

java

@RestController
@RequestMapping("/upload")
@Slf4j
public class UploadController {
    
    @Resource(name = "yunpanUploadServiceImpl")
    private UploadService uploadService;

    @PostMapping
    public Result upload(MultipartFile file) {
        log.info("上传文件:{}", file.getOriginalFilename());
        try {
            String url = uploadService.upload(file);
            return Result.success(url);
        } catch (Exception e) {
            log.error("上传失败", e);
            return Result.error("上传失败: " + e.getMessage());
        }
    }
}

三、实现流程解析

  1. 获取AccessToken

    • 使用client_id和client_secret调用/api/v1/access_token接口获取AccessToken

    • AccessToken有有效期,需要在过期前刷新

  2. 获取上传域名

    • 调用/upload/v2/file/domain接口获取可用的上传域名

    • 123云盘可能提供多个域名用于负载均衡

  3. 文件上传

    • 计算文件的MD5值

    • 构建multipart/form-data请求,包含文件流和元数据

    • 调用/upload/v2/file/single/create接口上传文件

    • 如果文件MD5已存在,会触发秒传机制

  4. 获取直链

    • 使用文件ID调用/api/v1/direct-link/url接口获取文件直链

    • 直链可用于直接下载文件

四、注意事项

  1. 文件命名限制

    • 文件名要小于256个字符

    • 不能包含以下字符:"\/:*?|><

    • 不能全部是空格

  2. 文件大小限制

    • 单步上传接口限制文件大小为1GB

    • 大文件需要使用分片上传接口

  3. 重名处理

    • 可以通过duplicate参数设置重名处理策略

    • 1: 保留两者(新文件自动添加后缀)

    • 2: 覆盖原文件

  4. 错误处理

    • 所有API调用都需要进行错误处理

    • 检查HTTP响应状态码和返回的JSON中的code字段

  5. 性能优化

    • 可以复用OkHttpClient实例

    • 对大文件可以考虑使用分片上传

五、扩展功能

  1. 文件管理

    • 可以实现文件列表查询、删除、移动等功能

    • 对应API:/api/v2/file/list/api/v1/file/delete

  2. 目录管理

    • 可以创建、删除目录

    • 对应API:/api/v1/dir/create/api/v1/dir/delete

  3. 分片上传

    • 对于大文件,可以实现分片上传

    • 流程:初始化上传 -> 上传分片 -> 完成上传

通过以上实现,我们成功在SpringBoot项目中集成了123云盘的文件上传功能,并能够获取文件直链供用户下载。这种方案适用于需要将文件存储到第三方云服务的应用场景。