文件上传的三种存储方式

1、本地上传

  • 新建springboot项目,引入Thymeleaf、web相关依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • 在项目resource目录下新建templates文件夹并创建上传文件页面upload.html:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
    <form action="upload" method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="file">
    <input type="submit" value="提交">
</form>
</body>
</html>
  • 创建success.html页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>上传成功</title>
</head>
<body>
<div th:if="${fileName}">
    <h2 th:text="|${fileName} 上传成功,存储路径为 :${path}|"> </h2>
</div>
</body>
</html>
  • 再创建一个error.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>上传失败</title>
</head>
<body>
<div th:if="${fileName}">
    <h2 th:text="|${fileName} 上传失败,失败原因为 :${msg}|"> </h2>
</div>
</body>
</html>
  • 新建uploadController
@Controller
@RequestMapping("/")
public class UploadController {
    
    @Autowired
    private UploadService uploadService;

    @GetMapping("/upload")
    public String to_upload(){
        return "upload";
    }

    @PostMapping("/upload")
    public String upload(MultipartFile file, HttpServletRequest request, Model model){
        if (file!=null){
            Map map=uploadService.localUpload(file);
            model.addAllAttributes(map);
            return "success";
        }
        model.addAttribute("fileName",file.getOriginalFilename());
        model.addAttribute("msg","参数错误!");
        return "error";
    }
}
  • service
public interface UploadService {

    Map<String,Object> localUpload(MultipartFile file);

}
  • service实现类
@Service
public class UploadServiceImpl implements UploadService {

    @Override
    public Map<String, Object> localUpload(MultipartFile file) {
        Map<String, Object> resultMap=new HashMap<>();
        try {
            //原文件名加上时间戳作为上传后保存的文件名
            String fileName=System.currentTimeMillis()+file.getOriginalFilename();
            //指定目标文件名,此处将文件保存在项目的resource目录下,也可以保存在本地的其他目录下
            String destFileName=System.getProperty("user.dir")
                    +File.separator+"src"
                    +File.separator+"main"
                    +File.separator+"resources"
                    +File.separator+"upload"
                    +File.separator+fileName;
            //新建目标文件
            File destFile = new File(destFileName);
            //创建目标文件的存储路径
            destFile.getParentFile().mkdirs();
            //将前端接收的文件转化为目标文件
            file.transferTo(destFile);
            //将上传后的文件名和存储路径返回
            resultMap.put("fileName",fileName);
            resultMap.put("path",destFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return resultMap;
    }
}
  • 在项目的resource目录新建application.yml配置文件,添加配置信息
server:
  port: 8899    #端口
spring:
  thymeleaf:
    prefix: classpath:/templates/   #前端页面文件路径
    suffix: .html                   #前端页面文件后缀名

image.png

  • 选择一张名为test.jpg的图片后提交,

image.png

  • 上传成功

image.png

  • 在项目的resource目录下可以找到刚刚上传的文件

image.png

  • 再选择一个稍微大一点的文件进行上传,一个10MB的压缩包

image.png

上传失败
image.png

控制台显示错误信息

org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededException: The field file exceeds its maximum permitted size of 1048576 bytes.
    at org.apache.tomcat.util.http.fileupload.FileUploadBase$FileItemIteratorImpl$FileItemStreamImpl$1.raiseError(FileUploadBase.java:633) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.checkLimit(LimitedInputStream.java:76) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.read(LimitedInputStream.java:135) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at java.io.FilterInputStream.read(FilterInputStream.java:107) ~[na:1.8.0_201]
    at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:98) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:68) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:293) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.connector.Request.parseParts(Request.java:2855) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.connector.Request.parseParameters(Request.java:3194) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.connector.Request.getParameter(Request.java:1116) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:381) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:84) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:836) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1747) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_201]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_201]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.19.jar:9.0.19]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_201]

原因是上传的文件大小超过限制,因为默认的最大上传文件不能超过1MB,如果需要上传更大的文件,就需要在appliction.yml文件中添加以下配置:

servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

image.png

重新启动项目,再次上传

image.png

查看resource下的upload文件夹
image.png

没问题

2、上传文件到分布式文件系统FastDFS

先在服务器上搭建FastDFS分布式文件系统,具体步骤可以参考https://segmentfault.com/a/11...

  • 在前面的项目中引入FastDFS的相关依赖
<dependency>
    <groupId>com.github.tobato</groupId>
    <artifactId>fastdfs-client</artifactId>
    <version>1.26.7</version>
</dependency>
  • 在application.yml文件中配置FastDFS相关信息
fdfs:
  connect-timeout: 3000                #连接的超时时间
  so-timeout: 3000                     #读取的超时时间
  tracker-list: 192.168.0.170:22122   #tracker服务所在的ip地址和端口号
  • 在resource目录下新建file.properties文件,用于配置一些需要的参数,这里只需要配置storage的地址
file.storageHost=192.168.0.171:8888/

image.png

  • 读取file.properties中的配置需要引入相关的依赖
<!-- springboot默认解析yml文件,需要解析xml、properties等文件需要引入下面的依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
  • 新建config包,在config包下创建FileUploadConfig类
@Component
@ConfigurationProperties(prefix = "file")
@PropertySource("classpath:file.properties")
public class FileUploadConfig {
    private String storageHost;

    public String getStorageHost() {
        return storageHost;
    }

    public void setStorageHost(String storageHost) {
        this.storageHost = storageHost;
    }
    
}
  • 在UploadService添加fdfsUpload方法:
public interface UploadService {

    Map<String,Object> localUpload(MultipartFile file);

    Map<String,Object> fdfsUpload(MultipartFile file);
}
  • UploadServiceImpl实现:
@Autowired
FileUploadConfig fileUploadConfig;
@Autowired
private FastFileStorageClient fastFileStorageClient;

@Override
public Map<String, Object> fdfsUpload(MultipartFile file) {
    //获取上传文件的后缀名
    String fileName=file.getOriginalFilename();
    String[] fileNameArr=fileName.split("\\.");
    String suffix=fileNameArr[fileNameArr.length-1];
    //将文件上传到FastDFS
    StorePath storePath=null;
    try {
        storePath=fastFileStorageClient.uploadFile(file.getInputStream(),file.getSize(),suffix,null);
    } catch (IOException e) {
        e.printStackTrace();
    }

    //将文件名和存储路径返回,存储路径为storage的所在的服务器地址加上文件在服务器上的存储地址
    Map<String, Object> resultMap=new HashMap<>();
    resultMap.put("fileName",fileName);
    resultMap.put("path",fileUploadConfig.getStorageHost()+storePath.getFullPath());
    return resultMap;

}
  • UploadController
@PostMapping("/fdfsUpload")
public String fdfsUpload(MultipartFile file, Model model){
    if (file!=null){
        Map map=uploadService.fdfsUpload(file);
        model.addAllAttributes(map);
        return "success";
    }
    model.addAttribute("fileName",file.getOriginalFilename());
    model.addAttribute("msg","参数错误!");
    return "error";
}
  • 修改前端表单的action为fdfsUpload

image.png

  • 启动项目,仍然是上传test.jpg图片

image.png

  • 上传成功

image.png

  • 在浏览器中可以直接访问图片地址

image.png

3、上传到腾讯云对象存储COS

  • COS介绍

对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。COS 提供网页端管理界面、多种主流开发语言的 SDK、API 以及命令行和图形化工具,并且兼容 S3 的 API 接口,方便用户直接使用社区工具和插件。

image.png

  • 购买成功后进入对象存储控制台

image.png

  • 创建存储桶,自定义名称,设置访问权限为共有读私有写以便后续进行测试。记住请求域名,后面会用到。

image.png

  • 创建文件夹,命名为upload,此时就可以通过控制台上传文件了

image.png

  • 在项目中引入COS的相关依赖
<dependency>
    <groupId>com.qcloud</groupId>
    <artifactId>cos_api</artifactId>
    <version>5.6.18</version>
</dependency>
  • 在file.properties中添加COS相关配置

image.png

  • secretId和secretKey可以在密钥管理中查看

image.png
host即为前面说的请求域名,region和bucketName在请求域名中都有

  • 在FileUploadConfig类中添加属性

image.png

  • 在UploadService中新增CosUpload方法
public interface UploadService {

    Map<String,Object> localUpload(MultipartFile file);

    Map<String,Object> fdfsUpload(MultipartFile file);

    Map<String,Object> cosUpload(MultipartFile file);
}
  • 在UploadServiceImpl中实现cosUpload方法
    @Override
    public Map<String, Object> cosUpload(MultipartFile file) {
        // 指定要上传到的存储桶
        String bucketName = fileUploadConfig.getBucketName();
        //指定文件名
        String fileName=System.currentTimeMillis()+file.getOriginalFilename();
        // 指定要上传到 COS 上对象键
        String key = "upload/"+fileName;
        PutObjectRequest putObjectRequest = null;
        try {
            putObjectRequest = new PutObjectRequest(bucketName, key, file.getInputStream(),null);
        } catch (IOException e) {
            e.printStackTrace();
        }
        PutObjectResult putObjectResult = getCosClient().putObject(putObjectRequest);

        Map<String, Object> resultMap=new HashMap<>(2);
        resultMap.put("fileName",fileName);
        resultMap.put("path",fileUploadConfig.getHost()+key);
        return resultMap;
    }

    private COSClient getCosClient(){
        // 1 初始化用户身份信息(secretId, secretKey)。
        String secretId = fileUploadConfig.getSecretId();
        String secretKey = fileUploadConfig.getSecretKey();
        COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
        // 2 设置 bucket 的区域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
        // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。
        Region region = new Region(fileUploadConfig.getRegion());
        ClientConfig clientConfig = new ClientConfig(region);
        // 3 生成 cos 客户端。
        COSClient cosClient = new COSClient(cred, clientConfig);
        return cosClient;
    }
  • 修改表单action为cosUpload

image.png

  • 启动项目,上传文件

image.png

  • 在浏览器中打开图片地址,成功显示

image.png

4、结尾

整个项目结构如下
image.png

阅读 1.5k

推荐阅读