文章目录

  • 前言
  • 技术栈
  • 项目目录
  • 前端实现
    • index.html
    • script.js
  • 后端实现
    • MultipartFile 介绍
    • 配置文件
    • 实体类
    • Controller
    • AjaxResult
    • Mapper
    • Service
    • 拦截器
  • 测试结果展示

前言

最近有一个需求需要实现图片上传,因此,本人找到了一个可以快速实现该功能的插件mini-upload-form。在此记录分享一下使用过程。

mini-upload-form的Github跳转
SpringBoot简单优雅实现图片上传功能(超详细)
将程序从github拉下后,前端页面index.html可简单修改后直接使用,我们的精力主要放在后端实现。

技术栈

项目目录

SpringBoot简单优雅实现图片上传功能(超详细)

需将mini-upload-from中assets文件下的资源引入自己项目中。除外还需引入bootstrap和jquery。

前端实现

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Mini Ajax File Upload Form</title>
    <!-- The main CSS file -->
    <link href="mini-upload-form/css/style.css" rel="stylesheet"/>
    <link href="bootstrap/css/bootstrap.css" rel="stylesheet"/>
</head>
<body>
<div class="row">
    <div class="col align-self-start">
        <div><h4>上传照片:</h4></div>
        <div class="d-flex flex-row flex-wrap mb-5" id="upload-img-container">
            <p>暂无</p>
        </div>
    </div>
</div>
<div class="row">
    <div class="col align-self-start">
        <form id="upload" method="post" action="/file/upload" enctype="multipart/form-data">
            <div id="note" class="text-white"></div>
            <div id="drop">
                拖到此区域
                <a>浏览本地</a>
                <input id="uploadFile" type="file" name="file" multiple/>
            </div>
            <ul>
                <!-- The file uploads will be shown here -->
            </ul>
        </form>
        <div class="col align-self-start">
        </div>
        <div class="row">
            <div class="col align-self-start">
                <div class="text-center">
                    <a type="button" id="submit-file-btn" class="text-center btn btn-info text-white">提交</a>
                </div>
            </div>
        </div>
    </div>
</div>
<script src="js/jquery-1.11.3.js"></script>
<script src="bootstrap/js/bootstrap.js"></script>
<script src="mini-upload-form/js/jquery.knob.js"></script>
<!-- jQuery File Upload Dependencies -->
<script src="mini-upload-form/js/jquery.ui.widget.js"></script>
<script src="mini-upload-form/js/jquery.iframe-transport.js"></script>
<script src="mini-upload-form/js/jquery.fileupload.js"></script>
<!-- Our main JS file -->
<script src="mini-upload-form/js/script.js"></script>
</body>
</html>

script.js

本文还实现了上传成功后回写到前端页面,所以需修改script.js添加提交成功后回调函数。

 // Automatically upload the file once it is added to the queue
            var jqXHR = data.submit().success(function (result) {
                var status = result['code'];
                var fileUri = result['fileUri'];
                var fileId = result['fileId'];
                var originFileName = result['originFileName'];
                if (status!='200'){
                    data.context.addClass('error');
                    numError++;
                }else{
                    images.push(fileId)
                    localStorage.setItem('cd_files', JSON.stringify(images))
                    numSuccess++;
                    $("#upload-img-container>p:nth-child(1)").remove()
                    const imageHTML =  "<div style='width:100px;height:100px;margin:.5rem;text-align:center'>"+
                        "<image class='rounded' style='width:100%;height:100%' src="+staticServer + fileUri +" alt=>"+
                        "<p class='text-center text-muted ' style='word-wrap: break-word;'>"+originFileName+"</p></div>";
                    $("#upload-img-container").append(imageHTML)
                }
                updateNote();
            });
 // Update the note
    function updateNote() {
        if($('#note').length)
            $('#note').html('<span>' + numSuccess + '</span> 个成功上传.<br /><span id="note-error">' + numError + '</span> 个上传失败.' + (numError > 0 ? ' <a href="#" id="btn-retry"> (Retry all)</a>' : ''));
    }

后端实现

MultipartFile 介绍

MultipartFile是SpringMVC提供简化上传操作的工具类。

在不使用框架之前,都是使用原生的HttpServletRequest来接收上传的数据,文件是以二进制流传递到后端的,然后需要我们自己转换为File类。使用了MultipartFile工具类之后,我们对文件上传的操作就简便许多了。
MultipartFile接口方法

package org.springframework.web.multipart;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;
public interface MultipartFile extends InputStreamSource {
	//getName() 返回参数的名称
    String getName();
	//获取源文件的昵称
    @Nullable
    String getOriginalFilename();
	//getContentType() 返回文件的内容类型
    @Nullable
    String getContentType();
	//isEmpty() 判断是否为空,或者上传的文件是否有内容
    boolean isEmpty();
	//getSize() 返回文件大小 以字节为单位
    long getSize();
	//getBytes() 将文件内容转化成一个byte[] 返回
    byte[] getBytes() throws IOException;
	//getInputStream() 返回InputStream读取文件的内容
    InputStream getInputStream() throws IOException;
    default Resource getResource() {
        return new MultipartFileResource(this);
    }
	//transferTo(File dest) 用来把 MultipartFile 转换换成 File
    void transferTo(File var1) throws IOException, IllegalStateException;
    default void transferTo(Path dest) throws IOException, IllegalStateException {
        FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
    }
}

配置文件

# server
server.port=8010
# datasource
spring.datasource.url=jdbc:mysql://xxxx:3306/upload?characterEncoding=utf-8&useSSL=false
spring.datasource.username=***
spring.datasource.password=****
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# upload size setting
spring.servlet.multipart.max-file-size=30MB
spring.servlet.multipart.max-request-size=30MB       
file.upload.dir=D://upload
file.visit.path=upload

实体类

图片存储实体类SysFile

@Data
@Builder
@TableName("sys_file")
public class SysFile {
    private String fileId;
    private String mediaType;
    private String fileFormat;
    private String filePath;
    private String originalName;
    private Long fileSize;
    private Integer imageWidth;
    private Integer imageHeight;
    private String createUserId;
    private Date createDate;
    private String updateUserId;
    private Date updateDate;
}

Controller

@RestController
@RequestMapping("/file")
public class FileUploadController {
    @Autowired
    private LocalStorageService localStorageService;
    @RequestMapping("/upload")
    public AjaxResult upload(@RequestParam("file") MultipartFile file) throws Exception {
        try {
            if (file.isEmpty()) {
                throw new RuntimeException("上传文件不能为空");
            }
            return localStorageService.upload(file);
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("文件上传失败");
        }
    }
}

AjaxResult


public class AjaxResult extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;
    /**
     * 状态码
     */
    public static final String CODE_TAG = "code";
    /**
     * 返回内容
     */
    public static final String MSG_TAG = "msg";
    /**
     * 数据对象
     */
    public static final String DATA_TAG = "data";
    /**
     * 状态类型
     */
    public enum Type {
        /**
         * 成功
         */
        SUCCESS(200),
        /**
         * 警告
         */
        WARN(301),
        /**
         * 错误
         */
        ERROR(500);
        private final int value;
        Type(int value) {
            this.value = value;
        }
        public int value() {
            return this.value;
        }
    }
    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult() {
    }
    /**
     * 初始化一个新创建的AjaxResult对象
     *
     * @param type 状态类型
     * @param msg  返回内容
     */
    public AjaxResult(Type type, String msg) {
        super.put(CODE_TAG, type.value);
        super.put(MSG_TAG, msg);
    }
    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param type 状态类型
     * @param msg  返回内容
     * @param data 数据对象
     */
    public AjaxResult(Type type, String msg, Object data) {
        super.put(CODE_TAG, type.value);
        super.put(MSG_TAG, msg);
        if (!ObjectUtils.isEmpty(data)) {
            super.put(DATA_TAG, data);
        }
    }
    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult success() {
        return AjaxResult.success("操作成功");
    }
    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(Object data) {
        return AjaxResult.success("操作成功", data);
    }
    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg) {
        return AjaxResult.success(msg, null);
    }
    /**
     * 返回成功消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data) {
        return new AjaxResult(Type.SUCCESS, msg, data);
    }
    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult warn(String msg)
    {
        return AjaxResult.warn(msg, null);
    }
    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult warn(String msg, Object data)
    {
        return new AjaxResult(Type.WARN, msg, data);
    }
    /**
     * 返回错误消息
     *
     * @return
     */
    public static AjaxResult error()
    {
        return AjaxResult.error("操作失败");
    }
    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(String msg)
    {
        return AjaxResult.error(msg, null);
    }
    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data)
    {
        return new AjaxResult(Type.ERROR, msg, data);
    }
    /**
     * 方便链式调用
     *
     * @param key   键
     * @param value 值
     * @return 数据对象
     */
    @Override
    public AjaxResult put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}

Mapper

SysFileMapper

@Mapper
public interface SysFileMapper extends BaseMapper<SysFile> {
}

Service

SysFileService

public interface SysFileService extends IService<SysFile> {
}
@Service
public class sysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> implements SysFileService {
}

LocalStorageService

@Slf4j
@Service("localStorageService")
public class LocalStorageService {
    @Autowired
    private SysFileService sysFileService;
    @Value("${file.upload.dir}")
    private String uploadDir;
    @Value("${file.visit.path:upload}")
    private String visitPath;
    private static final String[] ALLOW_FILE_TYPES = { "jpg", "jpeg", "png"};
    public AjaxResult upload(MultipartFile file) throws Exception {
        String contentType =  file.getContentType().toLowerCase();
        boolean allowType = false;
        for (String allowFileType : ALLOW_FILE_TYPES) {
            if(contentType.contains(allowFileType)) {
                allowType = true;
                break;
            }
        }
        if (!allowType) {
            return AjaxResult.error("请上传正确的文件格式");
        }
        String fileId = UUID.randomUUID().toString();
        String subFolder = hashBy256(fileId);
        File destFolder = new File(uploadDir + File.separator + subFolder);
        if (!destFolder.exists()) {
            destFolder.mkdirs();
        }
        String originalFilename = file.getOriginalFilename();
        String fileExt = getFileType(originalFilename);
        String destFileName = fileId + "." + fileExt;
        File destFile = new File(uploadDir + File.separator + subFolder +  File.separator + destFileName);
        log.info("file saved in: {}", destFile.getAbsoluteFile());
        file.transferTo(destFile);
        String filePath = visitPath + File.separator + subFolder + File.separator + destFileName;
        String fileUri = transferPathAsImageUri(filePath);
        Long fileSize = file.getSize();
        SysFile sysFile = SysFile.builder()
                .fileId(fileId)
                .filePath(filePath)
                .fileFormat(fileExt)
                .fileSize(fileSize)
                .mediaType(contentType)
                .originalName(originalFilename)
                .build();
        sysFileService.save(sysFile);
        return AjaxResult.success()
                .put("fileId", fileId)
                .put("filePath", filePath)
                .put("originFileName", originalFilename)
                .put("fileName", destFileName)
                .put("fileUri", fileUri);
    }
    private String getFileType(String originalFilename){
        int extPos = originalFilename.lastIndexOf(".");
        if (extPos != -1) {
            return originalFilename.substring(extPos + 1);
        }
        throw new RuntimeException("未知类型");
    }
    public static String hashBy256(String str) {
        String s = Integer.toHexString(Math.abs(str.hashCode()) % 256).toUpperCase();
        if (s.length() == 1) {
            s = "0" + s;
        }
        return s;
    }
    public static String transferPathAsImageUri(String fileServerPath) {
        if (!StringUtils.hasLength(fileServerPath)) {
            return null;
        }
        return "/" + fileServerPath.replaceAll("\\\\", "/");
    }
}

拦截器

ResourceConfig,该拦截器用于上传后回写到界面

@Configuration
public class ResourceConfig implements WebMvcConfigurer {
    @Value("${file.visit.path:upload}")
    private String visitPath;
    @Value("${file.upload.dir}")
    private String resourceDir;
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/" + visitPath + "/**").addResourceLocations("file:" + resourceDir + "/");
    }
}

测试结果展示

前端界面
SpringBoot简单优雅实现图片上传功能(超详细)

到此,完整的图片上传功能就结束了,希望本文对您有所帮助。

发表回复