Java:基于SpringBoot的微服务文件上传下载组件,支持本地+OSS上传和后期可扩展性

编写上传下载组件,优势:

①通过spring.factories注册,便于被多个微服务共同依赖

②oss上传使用s3 sdk,可接入其他oss服务商

③可以接入新的上传模式,通过mode一键切换

上传工具类

放在common或其他方便被所有微服务共同依赖的模块

位置:ruoyi-common/src/main/java/com/summer/upload

统一接口

统一对外接口,负责整合所有上传方式、根据mode自动切换上传方式

java 复制代码
package com.summer.upload;

import com.ruoyi.common.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.InputStream;

/**
 * 文件上传下载组件(集成各上传工具类)
 *
 * @author summer
 */
@Service
public class UploadComponent {

    @Value("${upload.mode}")
    private Integer mode;

    @Autowired(required = false)
    private UploadUtil uploadUtil;

    @Autowired(required = false)
    private OssUtil ossUtil;


    /**
     * 基础上传接口
     *
     * @param filePath 示例:cat.jpg、pet/cat.jpg
     */
    public String upload(String filePath, InputStream inputStream) {
        try (InputStream fileStream = inputStream) {
            // 1.本地上传
            if (mode == 1) {
                return uploadUtil.upload(filePath, fileStream);
            }

            // 2.oss上传
            if (mode == 2) {
                return ossUtil.upload(filePath, fileStream);
            }

            return "";
        } catch (Exception e) {
            throw new ServiceException("上传失败:" + e.getMessage());
        }
    }

    /**
     * 基础下载接口
     *
     * @param filePath 示例:cat.jpg、pet/cat.jpg
     */
    public InputStream download(String filePath) {
        try {

            // 1.本地文件下载
            if (mode == 1) {
                return uploadUtil.download(filePath);
            }

            // 2.oss文件下载
            if (mode == 2) {
                return ossUtil.download(filePath);
            }

            return null;
        } catch (Exception e) {
            throw new ServiceException("下载失败:" + e.getMessage());
        }
    }
}

本地上传

java 复制代码
package com.summer.upload;

import cn.hutool.core.io.FileUtil;
import com.ruoyi.common.exception.ServiceException;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;

import java.io.File;
import java.io.InputStream;

/**
 * 本地上传工具类
 *
 * @author summer
 */
@Setter
public class UploadUtil {

    @Value("${upload.local.dir}")
    private String dir;

    @Value("${upload.local.link}")
    private Boolean link;

    @Value("${upload.local.domain}")
    private String domain;

    @Value("${upload.local.bucket}")
    private String bucket;


    /**
     * 上传文件
     *
     * @param filePath 示例:cat.jpg、pet/cat.jpg
     */
    public String upload(String filePath, InputStream inputStream) {
        // 确保目录存在并保存
        String localFilePath = FileUtil.file(dir, filePath).getAbsolutePath();
        FileUtil.mkParentDirs(localFilePath);
        FileUtil.writeFromStream(inputStream, localFilePath);
        // 文件返回模式(http链接模式需配置文件服务)
        String url = domain + "/" + bucket + "/" + filePath;
        return link ? url : filePath;
    }

    /**
     * 下载文件
     *
     * @param filePath 示例:cat.jpg、pet/cat.jpg
     */
    public InputStream download(String filePath) {
        // 获取本地文件
        File file = new File(dir + "/" + filePath);
        if (!file.exists()) {
            throw new ServiceException("文件不存在:" + filePath);
        }
        return FileUtil.getInputStream(file);
    }
}

oss上传

添加依赖

xml 复制代码
<!-- oss客户端 -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.676</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-sts</artifactId>
<version>1.12.676</version>
</dependency>

代码

java 复制代码
package com.summer.upload;

import cn.hutool.core.io.FileUtil;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.ruoyi.common.exception.ServiceException;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.PostConstruct;
import java.io.InputStream;

/**
 * oss上传工具类
 *
 * @author summer
 */
@Setter
public class OssUtil {

    @Value("${upload.oss.accessKeyId}")
    private String accessKeyId;

    @Value("${upload.oss.accessKeySecret}")
    private String accessKeySecret;

    @Value("${upload.oss.endPoint}")
    private String endPoint;

    @Value("${upload.oss.domain}")
    private String domain;

    @Value("${upload.oss.bucket}")
    private String bucket;

    @Value("${upload.oss.link}")
    private Boolean link;

    private AmazonS3 oss;


    @PostConstruct
    private void init() {
        AWSCredentials credentials = new BasicAWSCredentials(accessKeyId, accessKeySecret);
        ClientConfiguration clientConfig = new ClientConfiguration();
        clientConfig.setProtocol(Protocol.HTTP);
        oss = AmazonS3ClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withClientConfiguration(clientConfig)
                .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, Regions.DEFAULT_REGION.toString()))
                .enablePathStyleAccess()
                .build();
    }

    /**
     * 上传文件
     *
     * @param filePath 示例:cat.jpg、pet/cat.jpg
     */
    public String upload(String filePath, InputStream inputStream) {
        try {
            // 识别文件类型
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(FileUtil.getMimeType(filePath));
            // 上传文件
            oss.putObject(bucket, filePath, inputStream, metadata);
            String url = domain + "/" + bucket + "/" + filePath;
            return link ? url : filePath;
        } catch (Exception e) {
            throw new ServiceException("OSS上传失败:" + e.getMessage());
        }
    }

    /**
     * 下载文件
     *
     * @param filePath 示例:cat.jpg、pet/cat.jpg
     */
    public InputStream download(String filePath) {
        try {
            return oss.getObject(bucket, filePath).getObjectContent();
        } catch (Exception e) {
            throw new ServiceException("OSS下载失败:" + e.getMessage());
        }
    }
}

自动配置类

用于将上传工具类注册到Bean容器,在服务器注入配置以及使用

上传工具不在某个具体微服务的启动类扫描范围内,所以需要进行一些配置才能注入到Bean容器

Configuration

位置:ruoyi-common/src/main/java/com/summer/upload/

java 复制代码
package com.summer.upload;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 文件上传工具自动配置
 *
 * @author summer
 */
@Configuration
public class UploadAutoConfiguration {

    /**
     * 统一上传组件
     */
    @Bean
    public UploadComponent uploadComponent() {
        return new UploadComponent();
    }

    /**
     * 本地上传工具配置
     */
    @Bean
    @ConditionalOnProperty(name = "upload.mode", havingValue = "1")
    public UploadUtil localUploadUtil() {
        return new UploadUtil();
    }

    /**
     * OSS上传工具配置
     */
    @Bean
    @ConditionalOnProperty(name = "upload.mode", havingValue = "2")
    public OssUtil ossUtil() {
        return new OssUtil();
    }
} 

spring.factories

进入common模块资源目录:ruoyi-common/src/main/resources/

springboot2,使用 spring.factories:

资源目录下创建文件:META-INF/spring.factories

ini 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.summer.upload.UploadAutoConfiguration

springboot3,使用新机制:

资源目录下创建文件:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

复制代码
com.summer.upload.UploadAutoConfiguration

微服务调用

yml

假设admin服务需要调用

在application.yml中添加配置

yml 复制代码
# 上传配置
upload:
    # 1-本地上传,2-oss上传
    mode: 2
    local:
        dir: upload
        link: true
        domain: https://xxx
        bucket: test
    oss:
        accessKeyId: xxx
        accessKeySecret: xxx
        endPoint: http://xxx
        domain: https://xxx
        bucket: test
        link: true

service

service调用上传组件,并扩充更多重载方法、入库逻辑、文件名逻辑等自定义逻辑

java 复制代码
package com.ruoyi.web.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import com.ruoyi.common.exception.ServiceException;
import com.summer.upload.UploadComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;

/**
 * @author summer
 */
@Service
public class FileService {

    @Autowired
    private UploadComponent uploadComponent;


    /**
     * 拓展上传方法 - http文件入参
     */
    public String upload(MultipartFile file) {
        try {
            return this.upload(file.getOriginalFilename(), file.getInputStream());
        } catch (Exception e) {
            throw new ServiceException("上传失败:" + e.getMessage());
        }
    }


    /**
     * 上传文件名替换规则
     *
     * @param filePath 示例:cat.jpg、pet/cat.jpg
     */
    private String buildFileName(String filePath) {
        // 去除开头多余斜杠
        filePath = filePath.replaceAll("^/+", "");
        // 将文件名替换为UUID避免特殊字符
        String oldName = FileUtil.getName(filePath);
        String newName = oldName.replaceAll(FileUtil.mainName(oldName), IdUtil.simpleUUID());
        return filePath.replaceAll(oldName, newName);
    }

    /**
     * 基础上传接口
     *
     * @param filePath 示例:cat.jpg、pet/cat.jpg
     */
    public String upload(String filePath, InputStream inputStream) {
        // 重构文件名
        filePath = buildFileName(filePath);
        return uploadComponent.upload(filePath, inputStream);
    }

    /**
     * 基础下载接口
     *
     * @param filePath 示例:cat.jpg、pet/cat.jpg
     */
    public InputStream download(String filePath) {
        return uploadComponent.download(filePath);
    }
}

controller

java 复制代码
package com.ruoyi.web.controller.common;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import com.ruoyi.common.core.domain.common.R;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.service.FileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author summer
 */
@RestController
@RequestMapping("/file")
@Api(tags = "文件上传")
public class FileController {

    @Autowired
    private FileService fileService;


    /**
     * 上传通用文件 - 单个
     */
    @ApiOperation("上传通用文件")
    @PostMapping("/upload")
    public R<String> upload(MultipartFile file) {
        return R.OK(fileService.upload(file));
    }

    /**
     * 查看或下载文件
     */
    @ApiOperation("查看或下载文件")
    @GetMapping("/view/**")
    public void viewFile(@RequestParam(defaultValue = "false") Boolean download, HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 相对路径获取文件
        String filePath = request.getRequestURI().substring("/file/view/".length());
        InputStream inputStream = fileService.download(filePath);
        // 补充文件类型
        String fileName = FileUtil.getName(filePath);
        String mimeType = FileUtil.getMimeType(fileName);
        response.setContentType(mimeType);
        // 下载模式
        if (download) {
            response.setHeader("Content-Disposition", "attachment;fileName=" + fileName);
        }
        IoUtil.copy(inputStream, response.getOutputStream());
    }
}
相关推荐
WuWuII1 分钟前
gateway
java·gateway
浩宇软件开发7 分钟前
Android开发,实现一个简约又好看的登录页
android·java·android studio·android开发
南客先生14 分钟前
多级缓存架构设计与实践经验
java·面试·多级缓存·缓存架构
anqi2717 分钟前
如何在 IntelliJ IDEA 中编写 Speak 程序
java·大数据·开发语言·spark·intellij-idea
m0_7401546723 分钟前
maven相关概念深入介绍
java·maven
fanTuanye36 分钟前
Spring-全面详解(学习总结)
java·spring·ssm框架
Best_Liu~37 分钟前
TransactionTemplate 与@Transactional 注解的使用
java·开发语言·spring boot·后端
胡斌附体1 小时前
idea启动springboot方式及web调用
java·spring boot·intellij-idea
她和夏天一样热2 小时前
【Java面试题04】MySQL 篇
java·mysql·adb