深度解析:如何在若依系统中集成阿里云OSS实现高效文件存储

深度解析:如何在若依系统中集成阿里云OSS实现高效文件存储

Laughing
2024-03-26 / 2 评论 / 1,683 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2024年03月26日,已超过297天没有更新,若内容或图片失效,请留言反馈。

零、引言

随着信息化技术的快速发展,企业级应用对于海量文件存储的需求日益增长。而阿里云对象存储服务(OSS)以其高可用、高可靠、低成本的特点成为众多企业的首选解决方案。本文将以流行的开源后台管理系统——若依系统为例,详细阐述如何将其与阿里云OSS无缝集成,以实现文件资源的安全、高效存储。

壹、若依系统上传文件的现状

若依系统基于ElementUIel-upload组件,对于我们的业务来讲,目前存在两个需要改进的地方(1)文件选择后会自动上传,这个在前面的文章有过介绍若依系统上传图片压缩 - 香草物语 (xiangcaowuyu.net)(2)若依系统上传文件是上传到应用服务器的,我们需要实现的是上传到阿里云OSS,同时可以将OSS内容,通过内网下载到ECS,方便备份文件,减少OSS存储费用。

叁、开通并配置阿里云OSS

首先,您需要在阿里云官网注册并登录账号,然后开通OSS服务。在控制台中创建一个新的Bucket,为您的项目设定专属的存储空间,并根据业务需求设置合适的访问权限和地域属性。获取Bucket的相关信息,包括EndpointAccessKey IDAccessKey Secret,这是后续与OSS交互的重要凭证。

肆、集成阿里云OSS SDK

在若依系统的后端开发环境中,通过引入阿里云OSS SDK的依赖包:

在根目录的pom.xmlproperties配置阿里云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下载到本地

accessKeyIdaccessKeySecret是您访问阿里云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的集成,使得整个系统的文件存储和管理能力得到了显著提升。这一过程不仅展示了云存储服务的优势,也展现了若依系统良好的扩展性和兼容性,为企业级应用提供了更加灵活且高效的文件管理方案。

0

评论 (2)

取消
  1. 头像
    芽儿哟
    Windows 10 · Google Chrome

    AliyunFileUploadService类里的
    private OSS ossClient ;
    private AliyunConfig aliyunConfig;
    一直显示空怎么解决?

    回复
    1. 头像
      Laughing 作者
      MacOS · Google Chrome
      @ 芽儿哟

      config没有注入bean?

      回复