一、文件微服务
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>
<artifactId>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();
}
}
}
上传成功:


浙公网安备 33010602011771号