七牛云对象存储服务——实现Spring Boot多文件(图片)上传接口

目录

  • 1 需求说明
  • 2 存储
    • 2.1 七牛云
      • 2.1.1 账号申请
      • 2.1.2 获取 AccessKey/SecretKey
      • 2.1.3 开启对象存储服务
      • 2.1.4 获取测试域名
      • 2.1.5 汇总
  • 3 接口代码
    • 3.1 pom.xml
    • 3.2 配置文件 application.yml
    • 3.3 Application
    • 3.4 工具类
    • 3.5 登陆验证
    • 3.6 fastjson
    • 3.7 文件上传大小设置
    • 3.8 VO
    • 3.9 Entity
    • 3.10 持久层代码
    • 3.11 服务层代码
    • 3.12 控制层代码
  • 4 测试
    • 4.1 没有登录时
    • 4.2 单文件且为中文名时
    • 4.3 多文件上传
    • 4.4 查看数据的信息
    • 4.5 查看七牛控制台信息


1 需求说明

本次我们使用 Spring Boot 实现一个图片上传的接口,POST方式请求接口,Header中携带上传者的用户标识(uid) ,图片上传完毕后返回上传图片的信息(地址、宽度、高度)。JDK版本为 8 。


2 存储

图片上传一般会需要一个文件服务器,用来专门存储上传的文件,但有时如果需要对存储的文件实现更快速的上传和下载功能,并且又对可靠性、扩展性有很高的要求,在前期不打算投入过多情况下,可以考虑使用第三方提供的云服务,快速实现这个需求。

本次我们使用 七牛云提供的存储服务来实现这个需求,其控制台比较直观,接口开发测试方便,关于收费可以参考其官网 产品价格。

2.1 七牛云

2.1.1 账号申请

点击首页的 注册有礼,进入主页页面,填写邮箱、账号密码、手机号、短信验证,用户类型(个人用户或企业用户)、现居省份和城市,点击议下一步进行注册。

邮箱收到激活连接后点击激活,激活后登陆,此时需要实名认证,上传身份证的正面和背面图片完成实名认证。

2.1.2 获取 AccessKey/SecretKey

点击页头右侧的 【管理控制台】-> 点击右上角的头像 -> 密钥管理。如下图
七牛云对象存储服务——实现Spring Boot多文件(图片)上传接口_第1张图片

2.1.3 开启对象存储服务

鼠标滑到左侧边栏,点击【对象存储】,如果提示是否开通,选择开通。第一次使用时需要新建存储空间,填写存储空间名称(这个就是后面的 bucket 名),存储区域可以默认的 【华南】,访问控制可以选择【公开空间】,这个后期可以改为私有空间,,点击【确定创建】。
七牛云对象存储服务——实现Spring Boot多文件(图片)上传接口_第2张图片

2.1.4 获取测试域名

七牛云会默认生成一个域名,此域名可用于开发测试,测试域名的生命周期为30日,过期后系统会自动回收。

点击 【融合CDN】 -> 域名管理 ,可以看到此域名的基本信息,更多七牛测试域名的使用规范可查看官方的文档:七牛测试域名使用规范。
七牛云对象存储服务——实现Spring Boot多文件(图片)上传接口_第3张图片

2.1.5 汇总

至此我们已经设置好了七牛云的服务信息,这个也是我们调用七牛云服务时需要用到的常量参数,因此我们创建一个常量类,代码如下:

package yore.utils;

/**
 * 七牛云服务的常量类
 * Created by yore on 2019/12/7 17:52
 */
public interface QiniuConstant {
    /** 七牛AK */
    String ACCESS_KEY = "VXu6olBiuH********************AWQhHvVO6b";
    /** 七牛SK */
    String SECRET_KEY = "0Cjg_UCtpQ********************ZjWIPoI5zh";
    /** 七牛存储空间名 */
    String BUCKET = "yoreyuan";
    /** 七牛默认生成的域名 */
    String DOMAIN = "http://q24******.bkt.clouddn.com";
}

3 接口代码

使用 IntelliJ IDEA 创建一个Maven 项目,引入 Spring Boot 需要的依赖。

3.1 pom.xml

在我们引入spring-boot-starter-web 依赖时,会默认加入 jackson-databind 作为 JSON处理器,这里我们统一使用 fastjson 处理项目的 JSON 数据。


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.0.2.RELEASEversion>
    parent>
    <groupId>yore.springbootgroupId>
    <artifactId>qiniu-uploadartifactId>
    <version>1.0-SNAPSHOTversion>
    <packaging>jarpackaging>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <java.version>1.8java.version>
        <maven.compiler.source>1.8maven.compiler.source>
        <maven.compiler.target>1.8maven.compiler.target>
        <spring-boot.version>2.0.2.RELEASEspring-boot.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.20version>
        dependency>

        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>

        
        <dependency>
            <groupId>com.qiniugroupId>
            <artifactId>qiniu-java-sdkartifactId>
            <version>7.2.23version>
        dependency>
        <dependency>
            <groupId>com.squareup.okhttp3groupId>
            <artifactId>okhttpartifactId>
            <version>3.14.2version>
            <scope>compilescope>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.51version>
        dependency>
        <dependency>
            <groupId>com.qiniugroupId>
            <artifactId>happy-dns-javaartifactId>
            <version>0.1.6version>
            <scope>testscope>
        dependency>
    dependencies>

    <dependencyManagement>
        <dependencies>
            
            <dependency>
                
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-dependenciesartifactId>
                <version>${spring-boot.version}version>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>

        plugins>
    build>
project>

3.2 配置文件 application.yml

#端口号
server:
  port: 8081
  tomcat:
    uri-encoding: UTF-8

##datasource
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    sql-script-encoding: UTF-8
    type: com.alibaba.druid.pool.DruidDataSource
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    database: mysql
  messages:
    encoding: UTF-8
  http:
    encoding:
      force-response: true


###############
#My Configuration
###############
my:
  qiniu:
    image:
      namespace: yore/image/   # 文件上传的命名空间
multipart:
  maxFileSize: 10Mb
  maxRequestSize: 10Mb

3.3 Application

package yore;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Created by yore on 2019/12/7 15:55
 */
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3.4 工具类

  • 常量类:QiniuConstant.java 见 2.1.5
  • 枚举类:MsgEnum.java
package yore.utils;

/**
 * Created by yore on 2019/11/2 16:28
 */
public enum MsgEnum {
    SUCCESS_1(200, "文件上传成功"),
    OK_1(400, "请先登录"),
    OK_2(401, "文件非图片"),
    OK_3(402, "图片上传失败"),
    ;

    private Integer code;
    private String message;

    MsgEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }
    public String getMessage() {
        return message;
    }
}
  • 上传文件工具类:QiniuUpload.java
package yore.utils;

import com.alibaba.fastjson.JSON;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

/**
 * 上传图片到服务器
 *
 * Created by yore on 2019/12/7 17:57
 */
public class QiniuUpload {

    public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");

    //密钥配置
    private static Auth auth = Auth.create(QiniuConstant.ACCESS_KEY, QiniuConstant.SECRET_KEY);
    // 配置对象,设置为华南存储区域
    private static Configuration cfg = new Configuration(Zone.huanan());
    private static UploadManager uploadManager = new UploadManager(cfg);

    //简单上传,使用默认策略,只需要设置上传的空间名就可以了
    public static String getUpToken(){
        return auth.uploadToken(QiniuConstant.BUCKET);
    }

    /**
     * 将文件上传到 七牛云
     *
     * @auther: yore
     * @param file Multipart对象
     * @param nameSpace 上传的文件的命名空间
     * @param fileName 新的文件名(为了不重复、去除掉文件中的中文)
     * @return String 上传的文件路径
     * @date: 2019/12/8 4:23 PM
     */
    public static String updateFile(MultipartFile file, String nameSpace, String fileName) throws IOException {
        InputStream inputStream=file.getInputStream();
        ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
        byte[] buff = new byte[600]; //buff用于存放循环读取的临时数据
        int rc = 0;
        while ((rc = inputStream.read(buff, 0, 100)) > 0) {
            swapStream.write(buff, 0, rc);
        }

        // 给图片命名空间加上时间
        nameSpace = nameSpace.endsWith("/")? nameSpace: nameSpace+"/";
        nameSpace = nameSpace + sdf.format(new Date()) + "/";

        byte[] uploadBytes  = swapStream.toByteArray();
        try {
            Response response = uploadManager.put(uploadBytes, nameSpace + fileName, getUpToken());
            //解析上传成功的结果
            DefaultPutRet putRet;
            putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
            return QiniuConstant.DOMAIN + "/"+ putRet.key;
        } catch (QiniuException ex) {
            Response r = ex.response;
            System.err.println(r.toString());
            try {
                System.err.println(r.bodyString());
            } catch (QiniuException ex2) {
            }
        }
        return null;
    }

    /**
     * 获取一个随机的新文件名
     *
     * @auther: yore
     * @param fileName 旧文件名
     * @return  String 新文件名
     * @date: 2019/12/8 4:27 PM
     */
    public static String getNewFileName(String fileName){
        String newName = UUID.randomUUID().toString();
        // uuid + 文件后缀
        newName = newName + fileName.substring(fileName.lastIndexOf("."), fileName.length());
        return newName;
    }
}

3.5 登陆验证

需要认证的接口方法的注解类 UserLoginToken

package yore.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 需要登录才能进行操作的注解 UserLoginToken
 *
 * Created by yore on 2019/11/9 10:24 AM
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}

package yore.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import yore.annotation.UserLoginToken;
import yore.utils.MsgEnum;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * 通过拦截器判断用户是否登录,
 * 添加注解 @UserLoginToken 生效,其它不验证
 *
 * Created by yore on 2019/12/1 11:40 AM
 */
@Slf4j
public class AuthenticationInterceptor  implements HandlerInterceptor {

    /**
     * 预处理回调方法,实现处理器的预处理。
     * 主要拦截验证 head 中是否携带 uid
     *
     * @param request HttpServletRequest
     * @param response HttpServletResponse
     * @param handler 响应的处理器
     * @return boolean  返回值为true表示继续流程(如调用下一个拦截器或处理器)或者接着执行postHandle()和afterCompletion();
     *                  false表示流程中断,不会继续调用其他的拦截器或处理器,中断执行。
     * @throws Exception Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从 http 请求头中取出 uid
        String uid = request.getHeader("uid");

        // 如果不是映射到方法直接通过
        if(!(handler instanceof HandlerMethod)){
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Method method = handlerMethod.getMethod();

        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) {

                // 执行认证
                if (StringUtils.isEmpty(uid)) {
                    log.error(MsgEnum.OK_1.getMessage());
                    throw new RuntimeException(MsgEnum.OK_1.getMessage());
                }

                //TODO 验证用户 uid 是否合法

                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // noop
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // noop
    }

}
package yore.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import yore.interceptor.AuthenticationInterceptor;

/**
 * Created by yore on 2019/11/10 13:10
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                // 拦截所有请求,通过判断是否有 Tocken 认证的注解 决定是否需要登录
                .addPathPatterns("/**");
    }

    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }

}

3.6 fastjson

package yore.config;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

import java.nio.charset.Charset;

/**
 * Created by yore on 2019/12/8 19:46
 */
@Configuration
public class MyFastJsonConfig {

    @Bean
    public HttpMessageConverters /*FastJsonHttpMessageConverter*/ fastJsonHttpMessageConverters(){
        //创建FastJson的消息转换器
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        config.setCharset(Charset.forName("UTF-8"));
        /**
         * JOSN 中输入类名
         * 是否输出value 为null 的数据
         * 生成的 JSON格式化
         * 空集合输出[] 而非 null
         * 空字符串输出 "" 而非 null
         */
        config.setSerializerFeatures(
                SerializerFeature.WriteClassName,
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.PrettyFormat,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.WriteNullStringAsEmpty
        );
        converter.setFastJsonConfig(config);
        HttpMessageConverter<?> con = converter;
        return new HttpMessageConverters(con);
    }
}

3.7 文件上传大小设置

package yore.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.MultipartConfigElement;

/**
 * Created by yore on 2019/12/7 21:18
 */
@Configuration
public class MulterFile {

    @Value("${multipart.maxFileSize}")
    private String maxFileSize;
    @Value("${multipart.maxRequestSize}")
    private String maxRequestSize;

    /**
     * 文件上传配置
     * @return MultipartConfigElement
     */
    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        //文件最大
        factory.setMaxFileSize(maxFileSize); //KB,MB
        // 设置总上传数据总大小
        factory.setMaxRequestSize(maxRequestSize);
        return factory.createMultipartConfig();
    }
}

3.8 VO

package yore.VO;

import lombok.Data;

/**
 * 与前端交互的数据对象。
 *
 * Created by yore on 2019/12/7 10:57
 */
@Data
public class FileVO implements java.io.Serializable {
    private static final long serialVersionUID = 1682073645232877600L;

    /** 响应状态 */
    private Integer code;
    /** 状态信息 */
    private String msg;

    /** 资源链接 */
    private String url ;
    /** 图片宽度 */
    private Integer width;
    /** 图片高度 */
    private Integer height;

}
package yore.VO;

import lombok.Data;

/**
 *
 * Created by yore on 2018-11-30 21:11
 */
@Data
public class  ResultVO<T> implements java.io.Serializable{
    private static final long serialVersionUID = 4530709418613174547L;

    // 响应状态
    private Integer code;

    // 状态信息
    private String msg;

    // 响应时间戳
    private Long t;

    // 数据体
    private T data;

    public ResultVO() {
        this.t = System.currentTimeMillis();
    }
}

3.9 Entity

package yore.entity;

import lombok.Data;

import javax.persistence.*;

/**
 * 保存到数据库中的实体对象
 *
 * Created by yore on 2019/12/7 10:57
 */
@Data
@Entity(name = "file_image")
public class FileImageEntity implements java.io.Serializable {
    private static final long serialVersionUID = 5519954279503376340L;

    /** 主键,使用自增方式 */
    @Id
    @Column(name = "id", nullable = false)
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id ;

    @Column(name = "uid", length = 12, nullable = false)
    private Long uid ;

    /** 资源链接 */
    @Column
    private String url ;

    /** 图片宽度 */
    private Integer width;

    /** 图片高度 */
    private Integer height;

}

3.10 持久层代码

使用 JPA 方式持久化数据

package yore.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import yore.entity.FileImageEntity;

/**
 * Created by yore on 2019-12-7 13:20
 */
@Repository
public interface FileImageRepository extends JpaRepository<FileImageEntity, Long> {

}

3.11 服务层代码

服务层接口类

package yore.service;

import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import yore.VO.FileVO;
import yore.entity.FileImageEntity;

import java.util.List;

/**
 * 文件的服务层接口
 *
 * Created by yore on 2019/12/7 18:18
 */
public interface FileService {

    /**
     * 单文件上传
     *
     * @auther: yore
     * @param file 文件对象
     * @return FileVO
     * @date: 2019/12/7 7:16 PM
     */
    FileVO imageload(MultipartFile file);

    /**
     * 多文件上传
     *
     * @auther: yore
     * @param multiValueMap 文件Map对象
     * @return List
     * @date: 2019/12/7 7:19 PM
     */
    List<FileVO> imagesload(MultiValueMap<String, MultipartFile> multiValueMap);


    /**
     * 保存图片实体对象信息
     *
     * @auther: yore
     * @param fileImageEntity
     * @date: 2019/12/7 1:56 PM
     */
    void saveFileInfo(FileImageEntity fileImageEntity);

}

服务层接口实现类

package yore.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import yore.VO.FileVO;
import yore.entity.FileImageEntity;
import yore.repository.FileImageRepository;
import yore.service.FileService;
import yore.utils.MsgEnum;
import yore.utils.QiniuUpload;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by yore on 2019/12/7 18:20
 */
@Slf4j
@Service
public class FileServiceImpl implements FileService {

    @Value("${my.qiniu.image.namespace}")
    private String imageNameSpace;

    @Autowired
    private FileImageRepository fileImageRepository;

    @Override
    public FileVO imageload(MultipartFile file) {
        FileVO fileVO = new FileVO();
        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            if(bufferedImage == null){
                // 文件非图片
                fileVO.setCode(MsgEnum.OK_2.getCode());
                fileVO.setMsg(MsgEnum.OK_2.getMessage() + ":" + file.getName());
                return fileVO;
            }
            fileVO.setWidth(bufferedImage.getWidth());
            fileVO.setHeight(bufferedImage.getHeight());
            bufferedImage = null;

            String url = QiniuUpload.updateFile(file, imageNameSpace, QiniuUpload.getNewFileName(file.getOriginalFilename()));

            fileVO.setUrl(url);
            fileVO.setCode(MsgEnum.SUCCESS_1.getCode());
            fileVO.setMsg(MsgEnum.SUCCESS_1.getMessage());

        } catch (IOException e) {
            log.error(e.getMessage());
            fileVO.setCode(MsgEnum.OK_3.getCode());
            fileVO.setMsg(MsgEnum.OK_3.getMessage() + ":" + file.getName());
        }
        return fileVO;
    }

    @Override
    public List<FileVO> imagesload(MultiValueMap<String, MultipartFile> multiValueMap) {
        List<FileVO> list = new ArrayList<>();
        multiValueMap.forEach((k, v) -> {
            v.forEach(f -> {
                list.add(imageload(f));
            });
        });
        return list;
    }

    @Override
    public void saveFileInfo(FileImageEntity fileImageEntity) {
        if(!StringUtils.isEmpty(fileImageEntity.getUrl())){
            fileImageRepository.save(fileImageEntity);
        }
    }
}

3.12 控制层代码

package yore.controller;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import yore.VO.FileVO;
import yore.VO.ResultVO;
import yore.annotation.UserLoginToken;
import yore.entity.FileImageEntity;
import yore.service.FileService;
import yore.utils.MsgEnum;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * Created by yore on 2019/12/7 18:09
 */
@RestController
@RequestMapping("/file")
public class FileController {

    @Autowired
    private FileService fileService;

    /**
     * 上传图片的接口
     * 
     *     注意:
     *     1、((MultipartHttpServletRequest) request).getFileMap() 会根据 key 只保留单个 MultipartFile
     *     2、 如果确定 key 的值,推荐使用 ((MultipartHttpServletRequest) request).getFiles("file")
     *     3、保留所有的 MultipartFile 使用 ((MultipartHttpServletRequest) request).getMultiFileMap()
     *
     * 
* @auther: yore * @param request HttpServletRequest * @return ResultVO> 上传图片的状态及信息 * @throws Exception */
@UserLoginToken @PostMapping(value = "/upload") public ResultVO<List<FileVO>> upload(HttpServletRequest request) throws Exception { //List files = ((MultipartHttpServletRequest) request).getFiles("file"); //Map fileMap = ((MultipartHttpServletRequest) request).getFileMap(); MultiValueMap<String, MultipartFile> multiValueMap = ((MultipartHttpServletRequest) request).getMultiFileMap(); System.out.println(multiValueMap); ResultVO<List<FileVO>> resultVO = new ResultVO<>(); List<FileVO> listFileVO = fileService.imagesload(multiValueMap); Long uid = Long.parseLong(request.getHeader("uid")); listFileVO.forEach(fileVO -> { FileImageEntity fileImageEntity = new FileImageEntity(); BeanUtils.copyProperties(fileVO, fileImageEntity); fileImageEntity.setUid(uid); fileService.saveFileInfo(fileImageEntity); }); resultVO.setCode(MsgEnum.SUCCESS_1.getCode()); resultVO.setMsg(MsgEnum.SUCCESS_1.getMessage()); resultVO.setData(listFileVO); return resultVO; } }

4 测试

使用 Postman 测试文件上传接口,POST 方式请求接口 http://localhost:8081/file/upload

4.1 没有登录时

,当 headers 中没有 uid 参数时,会返回如下信息,从返回的 JSON 信息可以看到我们注册的 FastJosn 已经生效,返回的 JSON 中包含JOSN 中输入类名"@type",同时也有日期"timestamp"信息且其格式为yyyy-MM-dd HH:mm:ss

{
	"@type": "java.util.LinkedHashMap",
	"timestamp": "2019-12-07 21:49:51",
	"status": 500,
	"error": "Internal Server Error",
	"message": "请先登录",
	"path": "/file/upload"
}

4.2 单文件且为中文名时

在 postman 工具中,选择 Body -> form-data,KEY类型选择为 File,选择一张中文的图片,请求接口,返回如下
七牛云对象存储服务——实现Spring Boot多文件(图片)上传接口_第4张图片

4.3 多文件上传

本次上传多个文件,其中一份文件为文本,请求结果如下,会发现非图片的文件提示了错误信息,其它为图片的已经成功上传,并且获取到了图片的宽和高等信息。
七牛云对象存储服务——实现Spring Boot多文件(图片)上传接口_第5张图片
然后我们在浏览器上访问图片返回的 url 地址,可以查看的真实的图片。

4.4 查看数据的信息

查看数据库,可以看到图片信息也已经保存。

mysql> use test;
-- id 为主键,uid 用户标识,width 图片宽,height 图片高度,单位像素。
mysql> select * from file_image;
+----+--------+-----+-------------------------------------------------------------------------------------------------+-------+
| id | height | uid | url                                                                                             | width |
+----+--------+-----+-------------------------------------------------------------------------------------------------+-------+
|  7 |    610 | 325 | http://q24zo797k.bkt.clouddn.com/yore/image/2019/12/07/22e8566a-0e4b-451f-a206-99c1bcf8800a.png |   602 |
|  6 |    534 | 325 | http://q24zo797k.bkt.clouddn.com/yore/image/2019/12/07/dc6a8f02-3669-4b2f-af1e-4159c912afea.png |   660 |
+----+--------+-----+-------------------------------------------------------------------------------------------------+-------+
2 rows in set (0.00 sec)

4.5 查看七牛控制台信息

在控制台可以看到上传的文件信息,如果是图片可以很方便的查看到图片的信息。
七牛云对象存储服务——实现Spring Boot多文件(图片)上传接口_第6张图片


源码 qiniu-upload

你可能感兴趣的:(Spring)