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

news2024/11/19 23:12:37

文章目录

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


前言

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

mini-upload-form的Github跳转
在这里插入图片描述
将程序从github拉下后,前端页面index.html可简单修改后直接使用,我们的精力主要放在后端实现。

技术栈

  • jdk1.8.0_162
  • maven-3.3.9
  • mybatis-plus

项目目录

在这里插入图片描述

需将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("文件上传失败");
        }
    }
}

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 + "/");
    }
}

测试结果展示

前端界面
在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/80733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CAS:2374782-03-1,NOTA-FAPI-4化学试剂供应

试剂描述 NOTA-FAPI-4是FAPI-4的类似物和成纤维细胞活化蛋白&#xff08;FAP&#xff09;抑制剂。NOTA-FAPI-4可作为PET示踪剂用于检测与成纤维细胞活化蛋白相关的紊乱。 试剂基本信息 1、名称&#xff1a;NOTA-FAPI-4 2、CAS编号&#xff1a;2374782-03-1 3、分子式&#x…

Seata模式

爬虫组件分析目录概述需求&#xff1a;设计思路实现思路分析1.一、AT 模式参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,challenge Survi…

_4LeetCode代码随想录算法训练营第四天-C++

_4LeetCode代码随想录算法训练营第四天-C 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II 24.两两交换链表中的节点 整体思路 不是简单地交换值&#xff0c;而是交换指针地指向。 终止条件为&#xff1a; cur->next ! nul…

偏微分方程重要的前置知识

现在觉得很dog 开学期末考试正好美赛。无法评论&#xff0c;无法评论。乐淘淘&#xff0c;乐淘淘。期末考试不要延迟&#xff0c;求求了或者不安排在下学期第一周也可以。。。。反正求求了&#xff0c;美赛机会难得当然&#xff0c;如果是偏微分方程的问题的话&#xff0c;其实…

springboot连接Oracle的注意点(数据库信息配置、主键精度问题、OJDBC jar包、Oracle主键自增问题)

开篇废话&#xff1a;&#xff08;前段时间因为太忙没有坚持写博客&#xff0c;导致很久没有更新&#xff0c;今天终于忙里偷闲写上一篇&#xff09; 最近做了一个项目&#xff0c;数据库用的是Oracle&#xff0c;由于之前一直用的是MySQL&#xff0c;所以在一些细节配置上不是…

详细教你用NPS搭建内网穿透服务

文章目录 前言一、NPS概述 NPS的原理 二、NPS服务器搭建 1、下载软件2、云服务器配置 2.1、防火墙配置2.2、用WinSCP远程上传服务文件2.3、使用SSH终端安装启动2.4、修改配置文件 三、客户端连接总结 前言 相信大家外出旅游或者出差都是背着轻薄本&#xff0c;如果空闲之余想…

【Dubbo3高级特性】「实战开发」适配日志框架并支持运行时动态切换使用的日志框架开发实战

日志框架适配及运行时管理 本节内容主要是针对于如何在Dubbo中适配日志框架并支持运行时动态切换使用的日志框架&#xff0c;首先前提是需要进行启动我们Dubbo服务的Qos服务&#xff0c;它主要用于作为我们的操作对应的日志切换的功能实现机制 特性说明 日志框架适配&#x…

MATLB|基于matpower优化调度的风力模型预测

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

T6300A 网络综合测试仪 以太网数据 千兆以太网测试仪

一款功能强大、便携式、方便使用、价格便宜的高性价比手持式以太网测试仪是企业中网络管理和维护人员的刚需仪器。好的以太网测试仪可以帮助工作人员迅速解决网络不通、网速慢、丢包、延迟等问题。 当今以太网测试仪市场参差不齐&#xff0c;说的功能一个比一个强&#xff0c;…

【轻松掌握C语言】动态内存管理

目录 一、为什么存在动态内存分配 二、动态内存函数 1、malloc函数 (1)函数的用途 (2)函数的使用 2、free函数 (1)函数的用途 (2)函数的使用 3、calloc函数 (1)函数的用途 (2)函数的使用 4、realloc函数 (1)函数的用途 (2)函数的使用 三、常见的动态内存错误 1、对NULL指针的解…

【小f的刷题笔记】(JS)链表 - 单链表的倒数第 k 个节点 LeetCode19 单链表的中点 LeetCode876

【链表】 一、单链表的倒数第 k 个节点&#xff1a; ✔ 要求&#xff1a;只遍历一遍&#xff0c;链表有多长未知 LeetCode19 链接&#xff1a; 19.删除链表的倒数第N个结点 题目&#xff1a; 思路&#xff1a; 因为没有给头结点&#xff0c;我们就先定义一个哑结点&#…

从对称加密和非对称加密讲解HTTP到HTTPS的发展思路

一、传统的HTTP协议 传统的http在进行网络数据传输时&#xff0c;数据信息都是明文的&#xff0c;因此就很容易出现数据在网络的传输过程&#xff08;中间路由过程&#xff09;数据被监听或者窃取、替换的危险。因此http是一种不安全的传输协议。 那么就需要对数据进行加密。…

网络编程与通信原理

总感觉这个概念&#xff0c;和研发有点脱节&#xff1b; 一、基础概念 不同设备之间通过网络进行数据传输&#xff0c;并且基于通用的网络协议作为多种设备的兼容标准&#xff0c;称为网络通信&#xff1b; 以C/S架构来看&#xff0c;在一次请求当中&#xff0c;客户端和服务端…

物联网开发笔记(59)- 使用Micropython开发ESP32开发板之控制合宙4g Air724U模块

一、目的 这一节我们学习如何使用我们的ESP32开发板来控制合宙4g Air724U模块。 二、环境 ESP32 合宙4g Air724U模块 Thonny IDE 几根杜邦线 接线方法&#xff1a; 注意连接方式&#xff1a; ESP32的RX2----->4G模块的TX ESP32的TX2----->4G模块的RX 三、介绍 1&…

JSP ssh机房学生上机管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 JSP SSH机房学生上机管理系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采 用B/S模式开发。开发环境为TOMCA…

SpringBoot 注解方式快速整合Mybatis

序言&#xff1a;使用MyBatis3提供的注解可以逐步取代XML&#xff0c;例如使用Select注解直接编写SQL完成数据查询&#xff0c;使用SelectProvider高级注解还可以编写动态SQL&#xff0c;以应对复杂的业务需求。 一. 基础注解 MyBatis 主要提供了以下CRUD注解&#xff1a; Se…

Spring Cloud Alibaba Nacos Config - - - >@RefreshScope动态获取刷新后的配置内容

初学者不知道有没有这个疑惑&#xff1a;我明明已经在 SpringBoot 应用的 bootstrap.yml 配置文件中&#xff0c;通过 spring.cloud.nacos.config.refresh-enabledtrue 开启配置文件动态刷新了&#xff0c;为什么在 Controller 控制类中使用 Value 注解无法获取到配置文件修改后…

猿如意中的【取色器】效率工具详情介绍

目录 一、工具名称 二、下载安装渠道 2.1 什么是猿如意&#xff1f; 2.2 如何下载猿如意&#xff1f; 2.3 如何在猿如意中下载取色器&#xff1f; 三、取色器介绍 四、软件安装过程 五、软件界面 六、取色器功能特点介绍 七、取色器使用/体验感受 一、工具名称…

Typescript学习(第三弹)

泛型 定义 不预先确定的数据类型&#xff0c;具体的类型在使用的时候才确定&#xff0c;把泛型理解为代表类型的参数 泛型函数 泛型函数类型 泛型接口 引用泛型接口要指定一个类型&#xff0c;否则会报错 或者在泛型接口里指定一个默认类型 泛型类 泛型放在类的后面这样…

项目上线后我是如何通过慢查询和索引让系统快起来的

1、前言 最近对mysql的操作比较多一些&#xff0c;主要是项目上线以后&#xff0c;难免会有一些数据上的问题。开始的时候还主要由后端来处理&#xff0c;后面数据问题确实比较多&#xff0c;于是我就找后端要来服务器的账号密码&#xff0c;连上数据库顺便来看看数据的问题。…