You need to enable JavaScript to run this app.
导航
上传回调(Java SDK)
最近更新时间:2025.02.27 19:41:05首次发布时间:2023.08.03 16:36:18
我的收藏
有用
有用
无用
无用

上传回调是指客户端在请求时携带回调(Callback)参数,服务端在上传完成后,发送同步的 POST 回调请求到 CallBack 中指定的第三方应用服务器,在服务器确认接受并返回结果后,才将所有结果返回给客户端。
关于上传回调的详细介绍,请参见上传回调

示例代码

从 2.6.0 版本开始,Java SDK 支持在 putObject 和 completeMultipartUpload 接口设置上传回调参数。

普通上传实现上传回调

import com.volcengine.tos.TOSV2;
import com.volcengine.tos.TOSV2ClientBuilder;
import com.volcengine.tos.TosClientException;
import com.volcengine.tos.TosServerException;
import com.volcengine.tos.model.object.PutObjectInput;
import com.volcengine.tos.model.object.PutObjectOutput;

import java.io.ByteArrayInputStream;

public class PutObjectWithCallbackExample {
    public static void main(String[] args) {
        String endpoint = "your endpoint";
        String region = "your region";
        String accessKey = System.getenv("TOS_ACCESS_KEY");
        String secretKey = System.getenv("TOS_SECRET_KEY");

        String bucketName = "bucket-example";
        String objectKey = "example_dir/example_object.txt";

        // 上传回调参数
        String callback = "your callback param";
        // 上传回调自定义变量
        String callbackVar = "your callback custom variable";

        TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey);

        try{
            // 上传的数据内容,以 String 的形式
            String data = "1234567890abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+<>?,./   :'1234567890abcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+<>?,./   :'";
            // 也可以以 byte 数组的形式上传
            byte[] dataInBytes = data.getBytes();
            // 统一封装成 ByteArrayInputStream
            ByteArrayInputStream stream = new ByteArrayInputStream(dataInBytes);
            PutObjectInput putObjectInput = new PutObjectInput()
                    .setBucket(bucketName).setKey(objectKey).setContent(stream)
                    .setCallback(Base64.getEncoder().encodeToString(callback.getBytes(StandardCharsets.UTF_8)))
                    .setCallbackVar(Base64.getEncoder().encodeToString(callbackVar.getBytes(StandardCharsets.UTF_8)));
            PutObjectOutput output = tos.putObject(putObjectInput);
            System.out.println("putObject succeed, object's etag is " + output.getEtag());
            System.out.println("putObject succeed, object's crc64 is " + output.getHashCrc64ecma());
        } catch (TosClientException e) {
            // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送
            System.out.println("putObject failed");
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
        } catch (TosServerException e) {
            // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息
            System.out.println("putObject failed");
            System.out.println("StatusCode: " + e.getStatusCode());
            System.out.println("Code: " + e.getCode());
            System.out.println("Message: " + e.getMessage());
            System.out.println("RequestID: " + e.getRequestID());
        } catch (Throwable t) {
            // 作为兜底捕获其他异常,一般不会执行到这里
            System.out.println("putObject failed");
            System.out.println("unexpected exception, message: " + t.getMessage());
        }
    }
}

分片上传实现上传回调

import com.volcengine.tos.TOSV2;
import com.volcengine.tos.TOSV2ClientBuilder;
import com.volcengine.tos.TosClientException;
import com.volcengine.tos.TosServerException;
import com.volcengine.tos.model.object.CompleteMultipartUploadV2Input;
import com.volcengine.tos.model.object.CompleteMultipartUploadV2Output;
import com.volcengine.tos.model.object.UploadedPartV2;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

public class CompleteMultipartWithCallbackExample {
    public static void main(String[] args) {
        String endpoint = "your endpoint";
        String region = "your region";
        String accessKey = System.getenv("TOS_ACCESS_KEY");
        String secretKey = System.getenv("TOS_SECRET_KEY");

        String bucketName = "bucket-example";
        String objectKey = "example_dir/example_object.txt";

        // 指定的需要合并的分片上传任务的uploadID,
        // 需保证该 uploadId 已通过初始化分片上传接口 createMultipartUpload 调用返回。
        // 否则,对于不存在的 uploadId 会返回 404 not found。
        String uploadId = "the specific uploadId";

        // 上传回调参数
        String callback = "your callback param";
        // 上传回调自定义变量
        String callbackVar = "your callback custom variable";

        // 已上传分片列表
        List<UploadedPartV2> uploadedParts = new ArrayList<>();

        TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey);

        try {
            CompleteMultipartUploadV2Input complete = new CompleteMultipartUploadV2Input().setBucket(bucketName)
                    .setKey(objectKey).setUploadID(uploadId)
                    .setCallback(Base64.getEncoder().encodeToString(callback.getBytes(StandardCharsets.UTF_8)))
                    .setCallbackVar(Base64.getEncoder().encodeToString(callbackVar.getBytes(StandardCharsets.UTF_8)));
            complete.setUploadedParts(uploadedParts);

            CompleteMultipartUploadV2Output completedOutput = tos.completeMultipartUpload(complete);
            System.out.printf("completeMultipartUpload succeed, etag is %s, crc64 value is %s, location is %s.\n",
                    completedOutput.getEtag(), completedOutput.getHashCrc64ecma(), completedOutput.getLocation());
        } catch (TosClientException e) {
            // 操作失败,捕获客户端异常,一般情况是请求参数错误,此时请求并未发送
            System.out.println("putObject failed");
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
        } catch (TosServerException e) {
            // 操作失败,捕获服务端异常,可以获取到从服务端返回的详细错误信息
            System.out.println("putObject failed");
            System.out.println("StatusCode: " + e.getStatusCode());
            System.out.println("Code: " + e.getCode());
            System.out.println("Message: " + e.getMessage());
            System.out.println("RequestID: " + e.getRequestID());
        } catch (Throwable t) {
            // 作为兜底捕获其他异常,一般不会执行到这里
            System.out.println("putObject failed");
            System.out.println("unexpected exception, message: " + t.getMessage());
        }
    }
}

验证回调签名的示例代码

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

public class CallbackHandler {
    private static final String AUTHORIZATION = "Authorization";
    private static final String XTOS_PUBLIC_KEY_URL = "x-tos-pub-key-url";

    public void callbackHandler(HttpServletRequest req, HttpServletResponse resp) {
        byte[] sign = getSignature(req);
        if (sign == null) {
            System.out.println("get callback signature error");
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        byte[] publicKey = getPublicKey(req);
        if (publicKey == null) {
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        String stringToSign;
        try {
            stringToSign = getStringToSign(req);
        } catch (IOException | NoSuchAlgorithmException e) {
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        try {
            verifySignature(publicKey, stringToSign, sign);
            resp.setStatus(HttpServletResponse.SC_OK);
            resp.getWriter().write("{\"Status\":\"OK\"}");
        } catch (Exception e) {
            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }

    public byte[] getSignature(HttpServletRequest req) {
        String authorization = req.getHeader(AUTHORIZATION);
        if (authorization == null || authorization.isEmpty()) {
            return null;
        }
        return Base64.getDecoder().decode(authorization);
    }

    public byte[] getPublicKey(HttpServletRequest req) {
        byte[] bytePublicKey = null;
        String publicKeyURLBase64 = req.getHeader(XTOS_PUBLIC_KEY_URL);
        if (publicKeyURLBase64 == null || publicKeyURLBase64.isEmpty()) {
            return bytePublicKey;
        }
        byte[] publicKeyURLBytes = Base64.getDecoder().decode(publicKeyURLBase64);

        try {
            URL url = new URL(new String(publicKeyURLBytes));
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            if (connection.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
                throw new IOException("Get public key failed");
            }
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int nRead;
            byte[] data = new byte[16384];
            while ((nRead = connection.getInputStream().read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
            buffer.flush();
            bytePublicKey = buffer.toByteArray();
            connection.disconnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytePublicKey;
    }

    public String getStringToSign(HttpServletRequest req) throws IOException, NoSuchAlgorithmException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        byte[] buff = new byte[16384];

        int nRead;
        while ((nRead = req.getInputStream().read(buff, 0, buff.length)) != -1) {
            buffer.write(buff, 0, nRead);
        }
        buffer.flush();
        byte[] bodyContent = buffer.toByteArray();
        String stringToSign = calcStringToSign(req, bodyContent);
        System.out.println(stringToSign);
        System.out.println(new String(bodyContent, StandardCharsets.UTF_8));
        return stringToSign;
    }

    public String calcStringToSign(HttpServletRequest req, byte[] body) {
        StringBuilder buf = new StringBuilder();
        // 处理路径:使用已解码的路径(url_decode(path))
        String path = req.getRequestURI();
        buf.append(path);

        // 处理查询参数:获取原始查询字符串并解析
        String queryString = req.getQueryString();
        Map<String, List<String>> sortedParams = new TreeMap<>();
        if (queryString != null && !queryString.isEmpty()) {
            String[] pairs = queryString.split("&");
            for (String pair : pairs) {
                int idx = pair.indexOf("=");
                String key = (idx > 0) ? pair.substring(0, idx) : pair;
                String value = (idx > 0 && pair.length() > idx + 1) ? pair.substring(idx + 1) : "";
                // URL解码键和值
                try {
                    key = URLDecoder.decode(key, StandardCharsets.UTF_8.name());
                    value = URLDecoder.decode(value, StandardCharsets.UTF_8.name());
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e);
                }
                sortedParams.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
            }
        }

        // 拼接排序后的查询参数
        if (!sortedParams.isEmpty()) {
            buf.append('?');
            boolean firstKey = true;
            for (Map.Entry<String, List<String>> entry : sortedParams.entrySet()) {
                String key = entry.getKey();
                List<String> values = entry.getValue();
                // 对每个值进行排序(根据需求决定是否需要)
                Collections.sort(values);
                for (String value : values) {
                    if (!firstKey) {
                        buf.append('&');
                    }
                    buf.append(key).append('=').append(value);
                    firstKey = false;
                }
            }
        }

        buf.append('\n');
        // 使用UTF-8编码转换body
        buf.append(new String(body, StandardCharsets.UTF_8));
        return buf.toString();
    }

    public PublicKey loadPublicKey(byte[] publicKeyPEMBytes) throws Exception {
        String publicKeyPEM = new String(publicKeyPEMBytes, StandardCharsets.UTF_8);

        // 去掉头尾标识
        String publicKeyPEMFormatted = publicKeyPEM
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s+", ""); // 去掉换行和空格

        // Base64解码
        byte[] encoded = Base64.getDecoder().decode(publicKeyPEMFormatted);

        // 生成公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
        return keyFactory.generatePublic(keySpec);
    }

    public void verifySignature(byte[] publicKey, String stringToSign, byte[] sign) throws Exception {
        PublicKey pubKey = loadPublicKey(publicKey);

        Signature verifier = Signature.getInstance("MD5withRSA");
        verifier.initVerify(pubKey);
        verifier.update(stringToSign.getBytes(StandardCharsets.UTF_8));

        if (!verifier.verify(sign)) {
            throw new SignatureException("Invalid signature");
        }
    }
}