文章目录

  • 一、新建SpringBoot项目
  • 1.pom.xml
  • 2.application.yml
  • 3.配置Swagger3
  • 4.统一返回类Result
  • 5.统一异常响应ErrorCode
  • 二、上传文件到后端
  • 1.Controller
  • 2.Service
  • 3.Swagger3测试
  • 三、上传文件到云服务器
  • 1.前期准备
  • 2.SpringBoot集成
  • 2.1 配置SpringBoot
  • 2.2 七牛云工具类
  • 2.3 controller和service
  • 2.4 测试
  • 3.扩展



在SpringBoot中,接收、存储图片和返回图片到前端,这种需求很常见,而对于一个后端程序员来说,每次写demo都需要搭建前端代码(vue麻烦,html不实用),出错了抓瞎,… …。我也是疲于搭建繁琐的前端代码,就有了本文的纯后端代码。

本文对于接口测试部分,使用swagger3,无需搭建前端代码!使用最简单的方式实现文件的上传(纯后端,完全不用编写前端代码),大道至简。

我也在实际的项目当中使用到七牛云服务器存储图片,在这里我记录存储到不同的位置:把文件存储到服务器后端和七牛云服务器

提供所有代码,所有细节都有提到,若其中有疑问,请留言哦~

一、新建SpringBoot项目

【点击我查看如何快速搭建SpringBoot项目】。还需进行一些必要的配置。

1.pom.xml

使用到web依赖、swagger3测试、lombok。

<!-- web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--swagger3-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

2.application.yml

server:
  port: 8082

spring:
  servlet:
    multipart:
      # 文件传输尺寸设置
      max-file-size: 1024MB
      max-request-size: 100240MB

config:
  swagger3:
    flag: true

3.配置Swagger3

Swagger3是现在的最新版本。swagger3支持swagger2的所有注解,就安装swagger2的使用也没什么问题。

swagger2不能支持多文件上传测试,只能支持单文件上传,如果是多文件的话,就会出现多文件部分null的情况。所以这里使用swagger3进行文件上传测试(事实上,swagger3更强大)

配置了它,后端开发人员真的是非常的爽,不需要写任何一点前端的代码就是实现测试。

注意配置扫描包,它是你的controller包所在位置

@Configuration
@EnableOpenApi
public class Swagger3Config {

    @Value("${config.swagger3.flag}")
    private boolean flag;

    @Bean
    public Docket docket(Environment environment){
        return new Docket(DocumentationType.OAS_30) // 指定3.0版本
                .groupName("文件上传")
                .apiInfo(apiInfo())
                .enable(flag)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.pdh.file.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo(){

        return new ApiInfo("基于SpringBoot上传文件",
                "基于SpringBoot,实现上传文件到本地电脑和上传文件到云服务器(以七牛云服务器为例)",
                "v1.0",
                "",
                new Contact("彭德华","","pdhcool@163.com"),
                "Apache 2.0",
                "http://www.apache.org/licenses/LICENSE-2.0",
                new ArrayList());
    }
}

以下两点非必需,可以不写,正真使用也只是在开发的时候,会统一规范一下整个项目的一些响应、返回等参数。

当然,如果不写的话,就可以把Controller、Service的返回值更改为最简单的String字符串,直接返回一句话(仅是测试才能这么写~)。

4.统一返回类Result

便于前后端处理数据(使用非常广泛)。当然,也可以不用,直接返回一个String字符串。

@Data
@AllArgsConstructor
public class Result {

    private boolean success;

    private int code;

    private String msg;

    private Object data;

    // 成功和失败 方法
    public static Result success(Object data){
        return new Result(true,200,"success",data);
    }
    
    public static Result fail(int code, String msg){
        return new Result(false,code,msg,null);
    }
}

5.统一异常响应ErrorCode

public enum ErrorCode {
    PARAMS_NULL(10002,"参数为空"),
    UPLOAD_FAIL(20001,"上传失败")
    ;

    private int code;
    private String msg;

    ErrorCode(int code, String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

二、上传文件到后端

从接收参数的controller处开始编写代码。

我直接把 单文件上传 + 多文件上传 统一写在一起,可以根据自己的需求选择对应的服务接口。

1.Controller

注意,这里使用swagger3进行接口测试,接口使用 @RequestPart() 注解接收参数。如果使用 RequestParam() 注解接收参数,swagger-ui界面默认传递string字符串【点击我可查看 @RequestPart()和RequestParam()注解区别】。

package com.pdh.controller1;

import com.pdh.entity.Result;
import com.pdh.service.UploadService;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

/**
 * @Author: 彭_德华
 * @Date: 2021-10-20 16:38
 */
@RestController
public class UploadController {

    @Autowired
    private UploadService uploadService;

    @Operation(summary = "单个/多个 文件上传到本地")
    @PostMapping(value = "/uploadFileToLocal" ,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Result uploadFileToLocal(@RequestPart("files") MultipartFile[] files){
        return uploadService.uploadFileToLocal(files);
    }
}

2.Service

真实写项目的时候,文件存储路径处理这个问题还会继续优化。

package com.pdh.service;

import com.pdh.common.ErrorCode;
import com.pdh.entity.Result;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @Author: 彭_德华
 * @Date: 2021-10-21 15:05
 */
@Service
public class UploadService {

   /**
     * 两个存储路径可选
     * path1:存储在本机的指定位置
     * path2:存储在当前项目所在的目录下
     */
    private String path1 = "D:\\TempFile\\file\\";
    private String path2 = System.getProperty("user.dir") + "\\src\\main\\resources\\static\\uploadFile\\";

    public Result uploadFileToLocal(MultipartFile[] files) {
        if (files.length == 0) {
            return Result.fail(ErrorCode.PARAMS_NULL.getCode(), ErrorCode.PARAMS_NULL.getMsg());
        }

        List<String> fileNameList = new ArrayList<>();
        for (MultipartFile file : files) {
            String fileName = storeFile(file);
            fileNameList.add(fileName);
        }

        return Result.success(fileNameList);
    }
    
    /**
     * 存储文件到本地
     * @param file
     * @return 文件名
     */
    private String storeFile(MultipartFile file){
        String originalFilename = file.getOriginalFilename();

        String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1); // +1是为了除去 . (点)
        String fileName = UUID.randomUUID().toString().replace("-", "") + "." + suffix;

        File elem = new File(path2 + fileName);
        if (!elem.getParentFile().exists()) {
            elem.getParentFile().mkdirs();
        }

        try {
            file.transferTo(elem);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return fileName;
    }

}

到这里就简单的实现了一个上传接收文件的操作,启动项目。

3.Swagger3测试

访问 http://localhost:8082/swagger-ui/index.html (端口在application.yml中设置),就会有如下页面(这里页面我该了一下,仅仅是截图不一):

springboot文件服务器 springboot将文件传到服务器上_springboot文件服务器

之后就是傻瓜式使用测试就可以了(注意,只能上传文件哦,不能是文件夹)。

接口测试成功之后,就可以看到正确的返回值,同时,可在对应的位置看到刚刚上传的文件(我把它放在了当前项目所在位置)。


三、上传文件到云服务器

图片上传有很多的方式,当然这个是根据业务的需求,很多人都喜欢把图片的url上传到数据库中,用实体类来对图片的高度、宽度、名称、url进行封装,我觉得如果你部署的那台服务器是有网络的环境下建议用七牛云上传,七牛云上传把图片保存到七牛云空间,那个url地址是不会发生变化的,不会应为你项目的迁移或者服务器地址发生变化而受影响。

使用七牛云的优势:

  • 速度快。
  • 不占用服务器带宽。
  • 使得服务端更加轻盈。
  • … …

1.前期准备

注册七牛云账户【点击我跳转到七牛云】,之后进行实名认证。步骤很简单,之后需要注册一个云空间。(根据需求是否充值)

springboot文件服务器 springboot将文件传到服务器上_云存储_02


springboot文件服务器 springboot将文件传到服务器上_服务器_03

2.SpringBoot集成

打开七牛云开发者中心,进入SDK模块,打开Java SDK,就能解决绝大多数问题(上传、加密、访问、删除… …)。

我使用的模块是:https://developer.qiniu.com/kodo/1239/java#server-upload。当然还有其他模块,会使用是关键。

2.1 配置SpringBoot

依赖

<!-- 七牛云 云服务器包 -->
<dependency>
    <groupId>com.qiniu</groupId>
    <artifactId>qiniu-java-sdk</artifactId>
    <version>[7.7.0, 7.7.99]</version>
</dependency>

配置信息application(七牛云->个人中心->密钥管理)

# qiniu
qiniu:
  url: -
  accessKey: -
  accessSecretKey: -
  bucket: - # 新建的空间名

2.2 七牛云工具类

这里使用的是字节流上传文件

package com.pdh.file.utils;

import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

/**
 * @Author: 彭_德华
 * @Date: 2021-09-25 15:41
 * 七牛云工具类
 */
@Slf4j
@Component
public class QiniuUtils {

    // 读取 application.yml 配置文件中的信息
    @Value("${qiniu.url}")
    private String url;

    @Value("${qiniu.accessKey}")
    private String accessKey;

    @Value("${qiniu.accessSecretKey}")
    private String accessSecretKey;

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

    /**
     * 图片上传
     *
     * @param file     对象
     * @param fileName 文件名
     * @return
     */
    public boolean uploadSingleFile(MultipartFile file, String fileName) {
        //构造一个带指定 Region 对象的配置类
        Configuration cfg = new Configuration(Region.huabei());
        //...其他参数参考类注释
        UploadManager uploadManager = new UploadManager(cfg);
        //默认不指定key的情况下,以文件内容的hash值作为文件名
        try {
            byte[] fileOfBytes = file.getBytes();
            Auth auth = Auth.create(accessKey, accessSecretKey);
            String upToken = auth.uploadToken(bucket);
            Response response = uploadManager.put(fileOfBytes, fileName, upToken);

            // 直接打印日志记录
            log.info(response.bodyString());

            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return false;
    }
}

2.3 controller和service

controller

@Operation(summary = "多个/单个 文件上传到七牛云")
@PostMapping(value = "/uploadMultiFileToQiniu" ,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Result uploadMultiFileToQiniu(@RequestPart("files") MultipartFile[] files){
    return uploadService.uploadFileToQiniu(files);
}

service

写法参考官方的SDK文档,这里使用字节流传输。

public Result uploadFileToQiniu(MultipartFile[] files) {
    if (files.length == 0) {
        return Result.fail(ErrorCode.PARAMS_NULL.getCode(), ErrorCode.PARAMS_NULL.getMsg());
    }

    List<String> fileNameList = new ArrayList<>();
    for(MultipartFile file:files) {
        String fileName = uploadToQiniu(file);
        fileNameList.add(fileName);
    }

    return Result.success(fileNameList);
}

/**
     * 存储文件到七牛云
     * @param file
     * @return 文件名
     */
private String uploadToQiniu(MultipartFile file) {
    String originalFilename = file.getOriginalFilename();
    String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
    String fileName = System.currentTimeMillis() + "." + suffix;

    boolean flag = qiniuUtils.uploadSingleFile(file, fileName);
    if(flag){
        return fileName;
    }
    return null;
}

2.4 测试

打开swagger3,进行文件上传测试。上传成功后,可通过 域名+文件名 外链访问到该文件(图片直接查看,其他格式文件会默认下载!)

在Java控制台可查看输出的日志:

2021-10-25 09:28:32.296 INFO 25096 — [nio-8082-exec-3] com.pdh.file.utils.QiniuUtils : {“hash”:“ltw111KNc1BPuhHlAmNf7TyFdlM6”,“key”:“1635125310706.zip”}
2021-10-25 09:28:32.486 INFO 25096 — [nio-8082-exec-3] com.pdh.file.utils.QiniuUtils : {“hash”:“Fr3rN0Y1cBuhyfb5rXFlWs8X1rKp”,“key”:“1635125312297.md”}
2021-10-25 09:28:32.683 INFO 25096 — [nio-8082-exec-3] com.pdh.file.utils.QiniuUtils : {“hash”:“Fkb_zCB7un-O0vu9b45GmLUJ0zK-”,“key”:“1635125312486.png”}

与下面的文件名对比,都完全对应上!

springboot文件服务器 springboot将文件传到服务器上_spring boot_04

3.扩展

我只实现了文件的上传操作,而且实现的非常粗糙(无安全处理、访问数据格式等)。对于一些更多的需求和数据处理,需要做出更多的代码迭代【点击我跳转到七牛云开发者中心】。开发过程中不会就去查询官方提供的开发者文档,真的香~

springboot文件服务器 springboot将文件传到服务器上_spring boot_05

像七牛云这一类的云服务器还很很多,以后使用谁也不一定,但是可以肯定的是:任何工具使用之前,都需要查看官方给出的开发者文档,这会使得我们使用起来事半功倍,还能发现许多问题之处并及时解决