Java数据校验

OSS提供基于MD5和CRC64的数据校验,确保上传、下载和拷贝文件(Object)过程中的数据完整性。

注意事项

  • 本文以华东1(杭州)外网Endpoint为例。如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见OSS访问域名、数据中心、开放端口

  • 本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见Java配置访问凭证

  • 本文以OSS域名新建OSSClient为例。如果您希望通过自定义域名、STS等方式新建OSSClient,请参见新建OSSClient

MD5校验

如果上传文件时设置了Content-MD5,OSS会根据接收的内容计算MD5。OSS计算的MD5值和上传提供的MD5值不一致时,则返回InvalidDigest异常,从而保证数据的完整性。返回InvalidDigest异常后,您需要重新上传文件。

分片上传也支持MD5校验。在分片上传MultipartUpload请求中,meta是对于文件的设置,其中分片上传实现MD5的校验是在每个分片中实现的。主要是调用UploadPartRequest中的setMd5Digest,用以设置客户端计算该分片的本地MD5。

说明

putObject、getObject、appendObject、postObject、Multipart、uploadPart支持MD5校验。

  • 上传文件时进行MD5校验:

    import com.aliyun.oss.*;
    import com.aliyun.oss.common.auth.*;
    import com.aliyun.oss.common.utils.BinaryUtil;
    import com.aliyun.oss.model.ObjectMetadata;
    import java.io.ByteArrayInputStream;
    
    public class Demo {
        public static void main(String[] args) throws Throwable {
            // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "examplebucket";
            // 填写Object的完整路径。Object完整路径中不能包含Bucket名称。
            String objectName = "exampledir/object";
            // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
            String region = "cn-hangzhou";
    
            // 创建OSSClient实例。
            ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
            clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);        
            OSS ossClient = OSSClientBuilder.create()
            .endpoint(endpoint)
            .credentialsProvider(credentialsProvider)
            .clientConfiguration(clientBuilderConfiguration)
            .region(region)               
            .build();
    
            try {
                // 上传字符串。
                String content = "Hello OSS";
    
                ObjectMetadata meta = new ObjectMetadata();
                // 设置MD5校验。
                String md5 = BinaryUtil.toBase64String(BinaryUtil.calculateMd5(content.getBytes()));
                meta.setContentMD5(md5);
    
                ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()), meta);
    
            } catch (OSSException oe) {
                System.out.println("Caught an OSSException, which means your request made it to OSS, "
                        + "but was rejected with an error response for some reason.");
                System.out.println("Error Message:" + oe.getErrorMessage());
                System.out.println("Error Code:" + oe.getErrorCode());
                System.out.println("Request ID:" + oe.getRequestId());
                System.out.println("Host ID:" + oe.getHostId());
            } catch (ClientException ce) {
                System.out.println("Caught an ClientException, which means the client encountered "
                        + "a serious internal problem while trying to communicate with OSS, "
                        + "such as not being able to access the network.");
                System.out.println("Error Message:" + ce.getMessage());
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
        }
    }            
  • 分片上传文件时进行MD5校验:

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.common.auth.CredentialsProviderFactory;
    import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
    import com.aliyun.oss.common.utils.BinaryUtil;
    import com.aliyun.oss.model.CompleteMultipartUploadRequest;
    import com.aliyun.oss.model.CompleteMultipartUploadResult;
    import com.aliyun.oss.model.InitiateMultipartUploadRequest;
    import com.aliyun.oss.model.InitiateMultipartUploadResult;
    import com.aliyun.oss.model.PartETag;
    import com.aliyun.oss.model.UploadPartRequest;
    import com.aliyun.oss.model.UploadPartResult;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
            String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
            // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填写Bucket名称,例如examplebucket。  
            String bucketName = "examplebucket";
            // 填写Object的完整路径。Object完整路径中不能包含Bucket名称。
            String objectName = "exampledir/object";
            // 待上传本地文件路径。
            String localFile = "D:\\localpath\\examplefile.txt";
            // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
            String region = "cn-hangzhou";
    
            // 创建OSSClient实例。
            ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
            clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);        
            OSS ossClient = OSSClientBuilder.create()
            .endpoint(endpoint)
            .credentialsProvider(credentialsProvider)
            .clientConfiguration(clientBuilderConfiguration)
            .region(region)               
            .build();
            // 创建InitiateMultipartUploadRequest对象。
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);
            // 如果需要在初始化分片时设置文件存储类型,请参考以下示例代码
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // request.setObjectMetadata(metadata);
            // 初始化分片。
            InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
            // 返回uploadId,它是分片上传事件的唯一标识,您可以根据这个uploadId发起相关的操作,如取消分片上传、查询分片上传等。
            String uploadId = upresult.getUploadId();
            // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
            List<PartETag> partETags = new ArrayList<PartETag>();
            // 计算文件有多少个分片。
            final long partSize = 1 * 1024 * 1024L;   // 1MB
            final File sampleFile = new File(localFile);
            long fileLength = sampleFile.length();
            int partCount = (int) (fileLength / partSize);
            if (fileLength % partSize != 0) {
                partCount++;
            }
            // 遍历分片上传。
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                InputStream instream = new FileInputStream(sampleFile);
                InputStream instream1 = new FileInputStream(sampleFile);
             // 跳过已经上传的分片。
                instream.skip(startPos);
                instream1.skip(startPos);
                String md5;
                if(i==partCount-1){
             // 注意最后一个分片读取的是到文件尾部的数据,非一个分片的大小
                    md5 = md5(instream1,fileLength - startPos);
                }else{
                    md5 = md5(instream1,partSize);
                }
              // instream1.skip(n)
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setKey(objectName);
                uploadPartRequest.setUploadId(uploadId);
                uploadPartRequest.setInputStream(instream);
                uploadPartRequest.setMd5Digest(md5);
             // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。
                uploadPartRequest.setPartSize(curPartSize);
             // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出这个范围,OSS将返回InvalidArgument的错误码。
                uploadPartRequest.setPartNumber( i + 1);
             // 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。
                UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
             // System.out.println("server md5" +uploadPartResult.getETag());
             // 每次上传分片之后,OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
                partETags.add(uploadPartResult.getPartETag());
            }
            // 创建CompleteMultipartUploadRequest对象。
            // 在执行完成分片上传操作时,需要提供所有有效的partETags。OSS收到提交的partETags后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
            // 如果需要在完成文件上传的同时设置文件访问权限,请参考以下示例代码。
            // completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.PublicRead);
            // 完成上传。
            CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            // 关闭OSSClient。
            ossClient.shutdown();
        }
        public static String md5(InputStream in , long length1) throws Exception{
            byte[] bytes = new byte[(int) length1];
            long length_tmp = length1;
            int readSize = in.read(bytes, (int) 0, (int) length_tmp);
            return BinaryUtil.toBase64String(BinaryUtil.calculateMd5(bytes));
        }
    }

CRC64校验

上传、下载和拷贝文件时默认开启CRC数据校验,以确保数据的完整性。

说明
  • putObject、getObject、appendObject、uploadPart支持CRC64校验。上传时默认开启CRC校验,如果客户端计算的CRC值与服务端返回的CRC值不一致, 则会抛出InconsistentException异常。

  • 范围下载不支持CRC64校验。

  • CRC64校验会占用一定的CPU,对上传、下载速度均会有影响。

  • 下载文件时CRC64校验

    以下代码用于下载文件时进行CRC64数据完整性校验:

    import com.aliyun.oss.*;
    import com.aliyun.oss.common.auth.*;
    import com.aliyun.oss.common.utils.IOUtils;
    import com.aliyun.oss.internal.OSSHeaders;
    import com.aliyun.oss.internal.OSSUtils;
    import com.aliyun.oss.model.GetObjectRequest;
    import com.aliyun.oss.model.OSSObject;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    
    public class Demo {
        public static void main(String[] args) throws Throwable {
            // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "examplebucket";
            // 填写Object的完整路径。Object完整路径中不能包含Bucket名称。
            String objectName = "exampledir/object";
            // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
            String region = "cn-hangzhou";
    
            // 创建OSSClient实例。
            ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
            clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);        
            OSS ossClient = OSSClientBuilder.create()
            .endpoint(endpoint)
            .credentialsProvider(credentialsProvider)
            .clientConfiguration(clientBuilderConfiguration)
            .region(region)               
            .build();
    
            try {
                // 流式下载。
                GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, objectName);
                OSSObject ossObject = ossClient.getObject(bucketName, objectName);
    
                // 读取文件内容,只有读取文件内容之后才能获取clientCrc。
                System.out.println("Object content:");
                BufferedReader reader = new BufferedReader(new InputStreamReader(ossObject.getObjectContent()));
                while (true) {
                    String line = reader.readLine();
                    if (line == null) break;
    
                    System.out.println("\n" + line);
                }
                // 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
                reader.close();
    
                // 查看客户端是否开启了CRC校验,默认是开启状态。
                Boolean isCrcCheckEnabled = ((OSSClient)ossClient).getClientConfiguration().isCrcCheckEnabled();
                // 查看是否是范围下载请求。范围下载方式不支持CRC校验。
                Boolean isRangGetRequest = getObjectRequest.getHeaders().get(OSSHeaders.RANGE) != null;
    
                // 校验CRC,且只有读取文件内容之后才能获取clientCRC。
                if (isCrcCheckEnabled && !isRangGetRequest) {
                    Long clientCRC = IOUtils.getCRCValue(ossObject.getObjectContent());
                    OSSUtils.checkChecksum(clientCRC, ossObject.getServerCRC(), ossObject.getRequestId());
                }
            } catch (OSSException oe) {
                System.out.println("Caught an OSSException, which means your request made it to OSS, "
                        + "but was rejected with an error response for some reason.");
                System.out.println("Error Message:" + oe.getErrorMessage());
                System.out.println("Error Code:" + oe.getErrorCode());
                System.out.println("Request ID:" + oe.getRequestId());
                System.out.println("Host ID:" + oe.getHostId());
            } catch (ClientException ce) {
                System.out.println("Caught an ClientException, which means the client encountered "
                        + "a serious internal problem while trying to communicate with OSS, "
                        + "such as not being able to access the network.");
                System.out.println("Error Message:" + ce.getMessage());
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
        }
    }
  • 追加上传时CRC64校验

    以下代码用于追加上传时进行CRC64数据完整性校验:

    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.common.auth.*;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import com.aliyun.oss.model.*;
    import java.io.ByteArrayInputStream;
    
    public class Demo {
        public static void main(String[] args) throws Exception {
            // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "examplebucket";
            // 填写Object完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
            String objectName = "exampleobject.txt";
            // 填写第一次追加内容,例如Hello。
            String firstAppendContent = "Hello";
            // 填写第二次追加内容,例如World。
            String secondAppendContent = "World";        
            // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
            String region = "cn-hangzhou";
    
            // 创建OSSClient实例。
            ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
            clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);        
            OSS ossClient = OSSClientBuilder.create()
            .endpoint(endpoint)
            .credentialsProvider(credentialsProvider)
            .clientConfiguration(clientBuilderConfiguration)
            .region(region)               
            .build();
    
            try {
                // 第一次追加。
                AppendObjectRequest appendObjectRequest = new AppendObjectRequest(bucketName, objectName, new ByteArrayInputStream(firstAppendContent.getBytes()));
                appendObjectRequest.setPosition(0L);
                // 初始化CRC。初始化CRC之后,SDK内部默认会对上传结果进行CRC校验。
                appendObjectRequest.setInitCRC(0L);
                AppendObjectResult appendObjectResult = ossClient.appendObject(appendObjectRequest);
    
                // 第二次追加。
                appendObjectRequest = new AppendObjectRequest(bucketName, objectName, new ByteArrayInputStream(secondAppendContent.getBytes()));
                appendObjectRequest.setPosition(appendObjectResult.getNextPosition());
                // 初始化CRC设置为已上传数据的CRC。初始化CRC之后,SDK内部默认会对上传结果进行CRC校验。
                appendObjectRequest.setInitCRC(appendObjectResult.getClientCRC());
                ossClient.appendObject(appendObjectRequest);
            } catch (OSSException oe) {
                System.out.println("Caught an OSSException, which means your request made it to OSS, "
                        + "but was rejected with an error response for some reason.");
                System.out.println("Error Message:" + oe.getErrorMessage());
                System.out.println("Error Code:" + oe.getErrorCode());
                System.out.println("Request ID:" + oe.getRequestId());
                System.out.println("Host ID:" + oe.getHostId());
            } catch (ClientException ce) {
                System.out.println("Caught an ClientException, which means the client encountered "
                        + "a serious internal problem while trying to communicate with OSS, "
                        + "such as not being able to access the network.");
                System.out.println("Error Message:" + ce.getMessage());
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
        }
    }

相关文档

关于数据校验的完整示例代码,请参见GitHub示例