一、文件微服务
1.1 文件微服务
文件上传功能,这类比较通用的业务,可以抽取为一个 文件微服务 。在这个文件微服务中需要有如下功能:
- 处理文件上传。
- 上传文件的查询。
- 文件的删除。
- 垃圾文件的处理。
在开发时需要将这些附件上传保存到存储服务器(阿里云、七牛云、FastDFS、MinIO等)并将这些附件记录、可访问的文件地址存储到数据库中;因为在产品详情页查看时需要使用到。要能够通过产品id查询到对应的附件。
说明了这个文件微服务需要记录下来,是由哪些业务上传过来的数据;而且附件的数量也比较庞大。所以需要有对应的数据库来记录业务与附件的关系。
1.2 文件上传技术分析
- 文件存储:存储具体的文件;可以采用阿里云OSS存储。
- 文件记录:直接记录上传的文件信息到数据库表,baoxian-file数据库是专门存放文件、附件的数据库;其中有一个附件的表 tab_file在这个表中存储附件的信息即可。
上传的资料很有可能比较大(700MB, 1G, 3G);那么将这些大文件发送到 后端微服务的时候,很有可能因为网络不稳定而上传失败或者要重试多次,影响体验。可以将这些资料大文件分割为多个1M大小的文件多次上传到后端微服务,提高上传的成功率。
简单文件上传时序图:
大文件分片上传时序图:
上传资料大文件的时候;需要对文件至少3次以上的处理;分别处理:分片初始化、n次分片上传、分片合并;所以也将对应3个上传接口。
二、对象存储
2.1 对象存储介绍
对象存储服务(Object Storage Service)是一种数据存储和管理模型,用于存储和组织非结构化数据(文件:文本、图片、音频、视频),通常以对象(Object)的形式存储数据。每个对象通常包括数据本身、元数据(描述数据的信息),以及一个唯一的标识符。总的来说;就是存文件的。也一般会称OSS为存储服务器。
- 方式一:可以存储到服务器所在硬盘。
- 方式二:可以自己搭建存储服务器;比如:MinIO、FastDFS都是可自行搭建的分布式文件存储服务器。
- 方式三:可以使用第三方,自己不用搭;直接用就行。比如:阿里云OSS(https://oss.console.aliyun.com/bucket)、华为云OSS、七牛云等。
2.2 简单文件上传
使用Java SDK发起OSS请求,需要配置访问凭证。具体如下:- # 测试之前需要先设置系统环境变量;打开CMD 执行如下命令:
- set OSS_ACCESS_KEY_ID=你自己在阿里云上的AccessKey
- set OSS_ACCESS_KEY_SECRET=你自己在阿里云上的AccessKeySecret
- # 永久生效
- setx OSS_ACCESS_KEY_ID "%OSS_ACCESS_KEY_ID%"
- setx OSS_ACCESS_KEY_SECRET "%OSS_ACCESS_KEY_SECRET%"
- # 设置之后;可以通过如下命令查看是否设置成功. IDEA中要生效的话,可以重启IDEA
- echo %OSS_ACCESS_KEY_ID%
- echo %OSS_ACCESS_KEY_SECRET%
复制代码 引入maven依赖- <dependency>
- <groupId>org.springframework.boot</groupId>
- spring-boot-starter-test</artifactId>
- </dependency>
复制代码 简单上传代码:- package com.itheima.sfbx.file;
- import com.aliyun.oss.ClientException;
- import com.aliyun.oss.OSS;
- import com.aliyun.oss.OSSClientBuilder;
- import com.aliyun.oss.OSSException;
- import com.aliyun.oss.common.auth.CredentialsProviderFactory;
- import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
- import com.aliyun.oss.model.PutObjectRequest;
- import com.aliyun.oss.model.PutObjectResult;
- import org.junit.Test;
- import java.io.ByteArrayInputStream;
- import java.io.File;
- public class AliOSSUploadTest {
- /**
- * 简单上传
- */
- @Test
- public void testSimpleUpload() throws Exception {
- // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
- String endpoint = "https://oss-cn-beijing.aliyuncs.com";
- // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
- EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
- // 填写Bucket名称,例如examplebucket。
- String bucketName = "baoxian-oss";
- // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
- String objectName = "upload/pic/logo.png";
- // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
- // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
- String filePath= "E:\\pic\\logo.png";
- // 创建OSSClient实例。
- OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
- try {
- // 创建PutObjectRequest对象。
- PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));
- // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
- // ObjectMetadata metadata = new ObjectMetadata();
- // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
- // metadata.setObjectAcl(CannedAccessControlList.Private);
- // putObjectRequest.setMetadata(metadata);
- // 上传文件。
- PutObjectResult result = ossClient.putObject(putObjectRequest);
- System.out.println(result);
- } 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();
- }
- }
- }
- }
复制代码 上传成功:
2.3 分片文件上传
大文件上传面临网络中断风险和传输时间过长的挑战。分片上传通过将文件分割为多个小分片并发传输,提供断点续传能力和传输性能优化,有效应对网络不稳定环境下的文件传输需求。
在上传大文件(超过5 GB)到OSS的过程中,如果出现网络中断、程序异常退出等问题导致文件上传失败,您需要使用分片上传的方式上传大文件。分片上传通过将待上传的大文件分成多个较小的碎片(Part),充分利用网络带宽和服务器资源并行上传多个Part,加快上传完成时间,并在Part上传完成之后调用CompleteMultipartUpload接口将这些Part组合成一个完整的Object。
使用场景:
- 大文件加速上传。当文件大小超过5 GB时,使用分片上传可实现并行上传多个Part以加快上传速度。
- 网络环境较差。网络环境较差时,建议使用分片上传。当出现上传失败的时候,您仅需重传失败的Part。
- 暂停和恢复上传:分片上传任务没有过期时间。您可以随时暂停和恢复分片上传,直到完成或取消分片上传。
- 文件大小不确定。可以在需要上传的文件大小还不确定的情况下开始上传,这种场景在视频监控等行业应用中比较常见。
分片文件上传流程说明:
- 将待上传文件按照一定大小进行分片。
- 使用InitiateMultipartUpload接口初始化一个分片上传任务。
- 使用UploadPart接口上传分片。
文件切分成Part之后,文件顺序是通过上传过程中指定的partNumber来确定,所以您可以并发上传这些碎片。并发数并非越多越快,请结合自身网络状况和设备负载综合考虑。如果您希望终止上传任务,可调用AbortMultipartUpload接口,成功上传的Part会一并删除。
- 使用CompleteMultipartUpload接口将Part组合成一个Object。
使用限制:
单个文件的大小:不超过48.8TB。
分片数量:1~10,000个。
单个分片大小:最小值为100KB,最大值为5GB。最后一个分片的大小允许小于100KB。
分片上传代码:- /**
- * 分片上传
- */
- @Test
- public void testMultiPartUpload() throws Exception {
- // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
- String endpoint = "https://oss-cn-beijing.aliyuncs.com";
- // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
- EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
- // 填写Bucket名称,例如examplebucket。
- String bucketName = "baoxian-oss";
- // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
- String objectName = "upload/file/"+ UUID.randomUUID()+".pdf";
- // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
- // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
- String filePath= "E:\\file\\Java.pdf";
- // 创建OSSClient实例。
- OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
- try {
- //1、初始化上传
- InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName, objectName);
- InitiateMultipartUploadResult initiateMultipartUploadResult = ossClient.initiateMultipartUpload(initiateMultipartUploadRequest);
- String uploadId = initiateMultipartUploadResult.getUploadId();
- System.out.println("分片上传初始化完成;uploadId:" + uploadId);
- //2、分片上传文件
- //2.1、读取要上传的文件
- File file = new File(filePath);
- //2.2、计算要分片上传的次数;每个分片大小设置为1M,计算要上传多少次
- //单个分片文件大小;1MB
- long partSize = 1024 * 1024;
- //文件总长度
- long fileLength = file.length();
- //分为几个分片
- int partCount = (int) (fileLength / partSize);
- if (fileLength % partSize != 0) {
- partCount++;
- }
- System.out.println("总共分为:" + partCount + "个分片");
- //2.3、循环上传分片
- //记录每次每个分片上传之后的eTag
- List<PartETag> partETags = new ArrayList<PartETag>();
- for (int i = 0; i < partCount; i++) {
- //当前分片的文件起始位置
- long startPos = i * partSize;
- //创建分片请求对象
- UploadPartRequest uploadPartRequest = new UploadPartRequest();
- //桶名称
- uploadPartRequest.setBucketName(bucketName);
- //上传的文件名
- uploadPartRequest.setKey(objectName);
- uploadPartRequest.setUploadId(uploadId);
- //分片号
- uploadPartRequest.setPartNumber(i + 1);
- //设置当前分片文件内容
- FileInputStream fileInputStream = new FileInputStream(file);
- fileInputStream.skip(startPos);
- uploadPartRequest.setInputStream(fileInputStream);
- //当前这个分片的大小;但是最后一个分片大小可能是不到1M的;所以需要处理
- long currentPartSize = (i+1==partCount)?(fileLength-startPos):partSize;
- uploadPartRequest.setPartSize(currentPartSize);
- //上传分片
- UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
- System.out.println("Part#" + uploadPartRequest.getPartNumber() + " ETag:" + uploadPartResult.getETag());
- //记录 etag
- partETags.add(uploadPartResult.getPartETag());
- }
- //3、完成分片上传;合并(阿里云端合并)
- CompleteMultipartUploadRequest completeMultipartUploadRequest
- = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
- ossClient.completeMultipartUpload(completeMultipartUploadRequest);
- System.out.println("分片上传完成!");
- } 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();
- }
- }
- }
复制代码 上传成功:
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |