零、引言
随着信息化技术的快速发展,企业级应用对于海量文件存储的需求日益增长。而阿里云对象存储服务(OSS)以其高可用、高可靠、低成本的特点成为众多企业的首选解决方案。本文将以流行的开源后台管理系统——若依系统为例,详细阐述如何将其与阿里云OSS无缝集成,以实现文件资源的安全、高效存储。
壹、若依系统上传文件的现状
若依系统基于ElementUI
的el-upload
组件,对于我们的业务来讲,目前存在两个需要改进的地方(1)文件选择后会自动上传,这个在前面的文章有过介绍若依系统上传图片压缩 - 香草物语 (xiangcaowuyu.net)(2)若依系统上传文件是上传到应用服务器的,我们需要实现的是上传到阿里云OSS,同时可以将OSS内容,通过内网下载到ECS,方便备份文件,减少OSS存储费用。
叁、开通并配置阿里云OSS
首先,您需要在阿里云官网注册并登录账号,然后开通OSS服务。在控制台中创建一个新的Bucket
,为您的项目设定专属的存储空间,并根据业务需求设置合适的访问权限和地域属性。获取Bucket
的相关信息,包括Endpoint
、AccessKey ID
和 AccessKey Secret
,这是后续与OSS交互的重要凭证。
肆、集成阿里云OSS SDK
在若依系统的后端开发环境中,通过引入阿里云OSS SDK的依赖包:
在根目录的pom.xml
的properties
配置阿里云OSS的版本
<properties>
<aliyun-oss.version>3.17.4</aliyun-oss.version>
</properties>
在dependencyManagement
配置阿里云 OSS依赖
<!--阿里云-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun-oss.version}</version>
</dependency>
接着,在项目的配置文件(若依是在admin工程的的resources
文件夹中)中添加OSS相关的连接信息:
Yaml
# application.yml 示例
aliyun:
endpoint: 'your-endpoint'
endpointInternal: 'your-endpoint-internal'
accessKeyId: 'your-access-key-id'
accessKeySecret: 'your-access-key-secret'
bucketName: 'your-bucket-name'
urlPrefix: 'your-domain'
urlPrefixInternal: 'https://' + 'your-endpoint-internal'
解释一下上面几个配置的含义
endpoint
创建阿里云Bucket
时提供的外网地域节点,使用这个endpoint
实现文件的上传
endpointInternal
创建阿里云Bucket
时提供的内网地域节点,为了节约费用,我们ECS跟OSS买的是同一个地域的,这样通过内网下载OSS的文件是不收取费用的,把文件通过内网备份到ECS后,我们可以在空闲的时候,将备份的文件,通过ECS下载到本地
accessKeyId
、accessKeySecret
是您访问阿里云API的密钥,具有该账户完全的权限,这个可以在账户下的AccessKey管理查看
bucketName
这个是我们创建的Bucket名称
伍、配置参数
为了方便读取application.yml
的配置参数,我们创建一个配置类并完成OSS初始化
AliyunConfig.java
@Configuration
@ConfigurationProperties(prefix = "aliyun")
@Data
public class AliyunConfig {
/**
* 外网endpoint
*/
private String endpoint;
/**
* 内网endpoint
*/
private String endpointInternal;
/**
* key
*/
private String accessKeyId;
/**
* 密钥
*/
private String accessKeySecret;
/**
* 空间名称
*/
private String bucketName;
/**
* 外网Url前缀
*/
private String urlPrefix;
/**
* 内网Url前缀
*/
private String urlPrefixInternal;
@Bean
public OSS oSSClient() {
return new OSSClient(endpoint, accessKeyId, accessKeySecret);
}
}
陆、编写文件上传类
第三步:编写文件上传逻辑
在后端服务中创建一个专门处理文件上传的服务类或工具类,利用OSS SDK提供的API实现文件上传功能:
AliyunFileUploadService.java
@Component
@Slf4j
public class AliyunFileUploadService {
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
@Resource
private OSS ossClient;
@Resource
private AliyunConfig aliyunConfig;
/**
* 阿里云文件上传
*
* @param file 上传的文件
* @param ownerDirectory 目录
*/
public String upload(MultipartFile file, String ownerDirectory) throws InvalidExtensionException, IOException {
//文件新路径
String originalFilename = file.getOriginalFilename();
// 校验格式、大小等
boolean isLegal = false;
assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
String filePath = getFilePath(file, ownerDirectory);
// 上传到阿里云
ossClient.putObject(aliyunConfig.getBucketName(), filePath, new
ByteArrayInputStream(file.getBytes()));
//this.aliyunConfig.getUrlPrefix() + filePath 文件路径需要保持数据库
return aliyunConfig.getUrlPrefix() + filePath;
}
/**
* 生成文件路径
*
* @param file 文件
* @param ownerDirectory 自定义目录
* @return 生成的文件目录
*/
private String getFilePath(MultipartFile file, String ownerDirectory) {
String fileName;
String extension = getExtension(file);
if (!StringUtils.isEmpty(ownerDirectory)) {
fileName = ownerDirectory + "/" + IdUtils.fastUUID() + "." + extension;
} else {
fileName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
}
return fileName;
}
/**
* 查看文件列表
*
* @return 对象信息
*/
public List<OSSObjectSummary> list() {
// 设置最大个数。
final int maxKeys = 200;
// 列举文件。
ObjectListing objectListing = ossClient.listObjects(new ListObjectsRequest(aliyunConfig.getBucketName()).withMaxKeys(maxKeys));
List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
return sums;
}
/**
* 删除文件
*
* @param objectName 文件名
* @return 结果
*/
public boolean delete(String objectName) {
//如果文件路径是OSS的,截取后删除,否则不处理,直接返回成功
if (objectName != null && objectName.contains(aliyunConfig.getUrlPrefix())) {
objectName = objectName.replace(aliyunConfig.getUrlPrefix(), "");
ossClient.deleteObject(aliyunConfig.getBucketName(), objectName);
return true;
}
return true;
}
/**
* 下载文件下载文件
*
* @param objectName 数据库存储的文件路径
*/
public void exportOssFile(String objectName) throws IOException {
// ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
if (objectName != null && objectName.contains(aliyunConfig.getUrlPrefix())) {
objectName = objectName.replace(aliyunConfig.getUrlPrefix(), "");
// 创建OSSClient实例。
OSS ossClientLocal = new OSSClientBuilder().build(aliyunConfig.getEndpointInternal(), aliyunConfig.getAccessKeyId(), aliyunConfig.getAccessKeySecret());
try {
File file = getAbsoluteFile(objectName);
ossClientLocal.getObject(new GetObjectRequest(aliyunConfig.getBucketName(), objectName), file);
}catch (Exception exception){
throw new CustomException(exception.getMessage());
}finally {
if (ossClientLocal != null) {
ossClientLocal.shutdown();
}
}
}
}
/**
* 文件大小校验
*
* @param file 上传的文件
* @throws FileSizeLimitExceededException 如果超出最大大小
*/
public void assertAllowed(MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, InvalidExtensionException {
long size = file.getSize();
if (size > DEFAULT_MAX_SIZE) {
throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
}
String fileName = file.getOriginalFilename();
String extension = getExtension(file);
if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {
throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
fileName);
} else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {
throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
fileName);
} else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {
throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
fileName);
} else {
throw new InvalidExtensionException(allowedExtension, extension, fileName);
}
}
}
/**
* 获取文件名的后缀
*
* @param file 表单文件
* @return 后缀名
*/
public String getExtension(MultipartFile file) {
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
if (StringUtils.isEmpty(extension)) {
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
}
return extension;
}
/**
* 判断MIME类型是否是允许的MIME类型
*
* @param extension 扩展名
* @param allowedExtension 允许的扩展名
* @return true-允许;false-不允许
*/
public boolean isAllowedExtension(String extension, String[] allowedExtension) {
for (String str : allowedExtension) {
if (str.equalsIgnoreCase(extension)) {
return true;
}
}
return false;
}
/**
* 生成本地文件
*
* @param fileName 文件名
* @return 文件
* @throws IOException 异常
*/
private File getAbsoluteFile(String fileName) throws IOException {
String uploadDir = LeeFrameConfig.getProfile();
File desc = new File(uploadDir + File.separator + fileName);
if (!desc.getParentFile().exists()) {
desc.getParentFile().mkdirs();
}
if (!desc.exists()) {
desc.createNewFile();
}
return desc;
}
}
柒、Controller
public AjaxResult add(List<MultipartFile> imageFileList,
@RequestParam("form") String form) throws IOException, InvalidExtensionException {
}
Controller通过MultipartFile
接收前端传递的文件,然后调用服务层完成上传。
捌、前端交互
在若依系统的前端部分,当用户选择文件后,前端需将文件转换为二进制数据并通过Ajax或者其他HTTP请求方式发送给后端。后端接收到请求后,调用OSS服务进行文件上传并将返回的URL反馈至前端展示或保存至数据库。
具体可以参考若依系统上传图片压缩 - 香草物语 (xiangcaowuyu.net)
其他、安全与优化考量
为了增强安全性,可以考虑使用STS临时访问凭证进行上传操作,防止关键密钥泄露。另外,如果希望提高文件访问速度,可以为Bucket开启CDN加速,并根据实际场景调整缓存策略。
总结起来,通过上述步骤,我们成功实现了若依系统与阿里云OSS的集成,使得整个系统的文件存储和管理能力得到了显著提升。这一过程不仅展示了云存储服务的优势,也展现了若依系统良好的扩展性和兼容性,为企业级应用提供了更加灵活且高效的文件管理方案。
AliyunFileUploadService类里的
private OSS ossClient ;
private AliyunConfig aliyunConfig;
一直显示空怎么解决?
config没有注入bean?