使用DropZone+SpringBoot实现图片的上传和浏览

news2024/9/21 22:28:51

经常在项目中需要使用上传文件功能,找了不少前端上传组件,都不是很好用,今天尝试了一下DropZone,发现不错,顺便记录一下使用过程,方便后续查阅。在做开发的时候,经常需要调研一些技术,因此前后端都需要用到,为方便开发,这里采用传统的开发方式,没有做前后端分离,方便调试。前端采用HTML+Bootstrap+jQuery,后端采用SpringBoot2.6.3。

总体

新建一个SpringBoot程序,目录结构如下:
在这里插入图片描述
files:存放程序运行过程中的生成文件
logs:日志目录
src:源代码目录
uploads:上传文件目录
在这里插入图片描述
前端第三方文件放在/src/main/resources/static目录下,主要有bootstrap、dropzone和jquery,模版文件放在/src/main/resources/templates目录下,在这里注意调试时,经常需要修改HTML页面,需要快速将HTML页面编译到到运行目录/target/classes,这里使用快捷键CTRL+F9即可,前提是IDEA的自动构建项目要勾上
在这里插入图片描述

前端

首先从DropZone下载相关的js文件和css文件,或者直接让一些大模型给一个示例,我这里采用腾讯元宝,稍微修改一下,基本可以用,但是不少地方还是需要自己定制。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Upload</title>
    <link rel="stylesheet" href="./bootstrap/css/bootstrap.min.css">
    <script src="./bootstrap/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="./dropzone/dropzone.min.css">
    <script src="./dropzone/dropzone.min.js"></script>
    <script src="./jquery/jquery.min.js"></script>
    <style>
        .dropzone { padding: 3px 0 0 0; margin: 0 0 8px 0; text-align: center; overflow: hidden; width: 120px;
            min-height: 30px; height: 36px; border: #ccc solid 1px; }
        .dz-processing, .dz-button { display: none; }
        #fileList { display: flex; flex-wrap: wrap; gap: 10px; }
        .file-item { background-color: #f9f9f9; padding: 5px; border: 1px solid #ccc; border-radius: 0; cursor: pointer; }
    </style>
</head>
<body>
    <div class="container">
        <div style="padding:10px 0;">参看:https://www.dropzone.dev/</div>
        <div class="dropzone primary" id="my-dropzone">上传文件</div>
        <div id="message"></div>
        <div id="fileList"></div>
    </div>

<script>
    Dropzone.options.myDropzone = {
        url: '/upload',
        paramName: 'file',
        maxFiles:10,//一次性上传的文件数量上限
        maxFilesize: 20, //MB
        acceptedFiles: ".jpg,.gif,.png", //上传的类型
        parallelUploads: 3,
        dictMaxFilesExceeded: "您最多只能上传10个文件!",
        dictResponseError: '文件上传失败!',
        dictInvalidFileType: "你不能上传该类型文件,文件类型只能是*.jpg,*.gif,*.png。",
        dictFallbackMessage:"浏览器不受支持",
        dictFileTooBig:"文件过大上传文件最大支持.",
        previewTemplate: '<div></div>',
        showPreviewOnDrop: false,
        showPreviewOnUpload: false,
        init: function() {
            this.on('success', function(file, response) {
                $('#message').html('<p class="text-success">' + response.msg + '</p>');
                let json = JSON.parse(file.xhr.response);
                let fileName = '<div class="file-item" οnclick="showImage(\'' + json.data + '\')">' + json.data + '</div>';
                $('#fileList').prepend(fileName);
            });
            this.on('error', function(file, response) {
                $('#message').html('<p class="text-danger">Failed to upload file.</p>');
            });

            // 自定义文件显示方式
            this.on('addedfile', function(file) {
                console.log(file);
            });

            this.on('removedfile', function(file) {
                console.log(file);
                $('#fileList .file-item[data-dz-id="' + file.id + '"]').remove();
            });
        }
    };

    function showImage(url) {
        window.open('image/' + url, '_blank')
    }

    $(document).ready(function() {
        $.get('/images', function(res) {
            let array = res.data;
            let count = array.length;
            for (let i = 0; i < count; i ++) {
                let item = '<div class="file-item" οnclick="showImage(\'' + array[i] + '\')">' + array[i] + '</div>';
                $('#fileList').append(item);
            }
        })
    });
</script>
</body>
</html>

后端

后端主要实现三个接口,上传接口、获取图片列表接口、显示单个图片接口,分别对应/upload,/images,/image/{filename}

package org.example.imgtool.controller;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.example.imgtool.ImgtoolApplication;
import org.example.imgtool.utils.FileUtil;
import org.example.imgtool.utils.PathUtil;
import org.example.imgtool.utils.SnowflakeGenerator;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;

@Slf4j
@RestController
public class FileUploadController {

    private static final String UPLOAD_DIR = "uploads/";

    @GetMapping("/images")
    public ResponseEntity<JSONObject> getImages() {
        String imagesPath = PathUtil.getAppPath(ImgtoolApplication.class) + "files/images.txt";
        List<String> list = FileUtil.readFileToList(imagesPath);
        JSONObject json = new JSONObject(true);
        json.put("code", HttpStatus.OK.value());
        json.put("data", list);
        json.put("msg", "获取数据成功");
        return new ResponseEntity<>(json, HttpStatus.OK);
    }

    @GetMapping(value = "/image/{filename}", produces = MediaType.IMAGE_JPEG_VALUE)
    public ResponseEntity<byte[]> getRemoteImage(@PathVariable String filename) throws IOException {
        String imagePath = PathUtil.getAppPath(ImgtoolApplication.class) + "uploads/" + filename;
        byte[] bytes = FileUtil.getFileByteArray(imagePath);
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.IMAGE_JPEG));
        File file = new File(imagePath);
        if (file.exists()) {
            return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }
    }

    @PostMapping("/upload")
    public ResponseEntity<JSONObject> uploadFile(@RequestParam("file") MultipartFile file) {
        try {
            // 检查上传目录是否存在,如果不存在则创建
            Path uploadPath = Paths.get(UPLOAD_DIR);
            if (!Files.exists(uploadPath)) {
                Files.createDirectories(uploadPath);
            }

            // 保存文件到上传目录
            String fileName = file.getOriginalFilename();
            int index = fileName.lastIndexOf(".");
            String extension = fileName.substring(index);
            String newFileName = String.format("%d%s", SnowflakeGenerator.generatorId(), extension);
            log.debug(newFileName);
            Path filePath = uploadPath.resolve(newFileName);
            Files.copy(file.getInputStream(), filePath);
            // 文件名写入文件
            String imagesPath = PathUtil.getAppPath(ImgtoolApplication.class) + "files/images.txt";
            File f = new File(imagesPath);
            List<String> list = null;
            if (f.exists()) {
                list = FileUtil.readFileToList(imagesPath);
            } else {
                list = Collections.emptyList();
            }
            list.add(0, newFileName);
            FileUtil.writeFile(imagesPath, list);

            JSONObject json = new JSONObject(true);
            json.put("code", HttpStatus.OK.value());
            json.put("data", newFileName);
            json.put("msg", "上传成功" + newFileName);
            return new ResponseEntity<>(json, HttpStatus.OK);
        } catch (IOException e) {
            log.error("Upload image fail : {}", e.getMessage());
            JSONObject json = new JSONObject(true);
            json.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
            json.put("msg", "上传失败");
            return new ResponseEntity<>(json, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

配置文件如下,注意这里spring.thymeleaf.cache一定要配置为false,这样前面提到的CTRL+F9就可以实时调试,方便前端调试代码。

spring.thymeleaf.cache=false
spring.thymeleaf.check-template=true
spring.thymeleaf.check-template-location=true
spring.thymeleaf.content-type=text/html
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.excluded-view-names=
spring.thymeleaf.mode=HTML5
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
server.port=8080
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB

效果

最后实现效果如下
在这里插入图片描述

源代码

转到https://download.csdn.net/download/Angushine/89672489下载即可。

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

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

相关文章

web渗透:SQL注入漏洞的基础知识

目录 SQL注入漏洞的定义和原理 SQL注入的类型和攻击方法 SQL注入的防御措施 示例代码 深入研究 SQL注入漏洞的常见攻击场景有哪些&#xff1f; 如何有效防范SQL注入攻击&#xff1f; SQL注入与跨站脚本攻击&#xff08;XSS&#xff09;之间有什么区别&#xff1f; 主要…

Together规则引擎

Together规则引擎是一个基于Web的应用程序&#xff0c;用于构建可解释和可跟踪的业务规则。使用简单的图形用户界面&#xff0c;业务和IT用户可以协作指定需求和规则逻辑。这些完全指定的规则可以直接自动化。Together规则引擎基于规则模型和符号(DMN)开放标准&#xff0c;确保…

【Algorithm】三步问题

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 1.三步问题1.题目连接2.算法原理讲解&&代码实现 2.最小花费爬楼梯1.题目连接2.算法原理讲解&&代码实现 3.解码方法1.题目连接2.算法原理讲解&&代码实现 1.三步问题 1.题目连…

储能bms-下电延时方案分享

下电延时功能在很多bms方案中是很常见的&#xff0c;主要是用来存储SOC,故障码&#xff0c;以及电池的一些重要信息&#xff0c;下图展示的是一种实现方案&#xff0c;VCC5V_A_ENIN在硬件上直接连接到三极管的后端&#xff0c;在ACC信号给过来之后&#xff0c;跳过三极管&#…

我在高职教STM32——ADC电压采集与光敏电阻(2)

大家好,我是老耿,高职青椒一枚,一直从事单片机、嵌入式、物联网等课程的教学。对于高职的学生层次,同行应该都懂的,老师在课堂上教学几乎是没什么成就感的。正是如此,才有了借助头条平台寻求认同感和成就感的想法。在这里,我准备陆续把自己花了很多心思设计的教学课件分…

中小型工厂企业数字化ERP管理系统(源码+功能方案)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本&#xff0c;并实时掌握各环节的运营状况。 在采购管理方面&#xff0c;系统能够处理采购订单、供应商管理和采购入库等流程&#xff…

里程牌!EGFR抑制剂+双抗联合方案获批,显著延长肺癌患者生存期

前言&#xff1a; EGFR是抗肿瘤领域最经典的靶点&#xff0c;以其为靶点的小分子抑制剂、单抗、双抗和ADC研究层出不穷&#xff0c;但耐药性接踵而来。本文就EGFR结构、耐药机制和药物研发情况进行综述&#xff0c;以期设计出更优秀的药物&#xff0c;使更多患者获益。 EGFR的…

大数据-96 Spark 集群 SparkSQL Scala编写SQL操作SparkSQL的数据源:JSON、CSV、JDBC、Hive

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

活动|华院计算主办数智人系列沙龙,探讨人工智能如何赋能内容营销

在科技日新月异的今天&#xff0c;人工智能正以前所未有的速度重塑着我们的世界&#xff0c;它不仅深刻改变了传统行业的运作模式&#xff0c;更在电商零售、客户服务、保险金融等多个领域开辟了全新的生态与可能。其中&#xff0c;数智人作为大语言模型、音视频生成等人工智能…

【开端】开发团队如何应对突发的技术故障和危机

开发团队如何应对突发的技术故障和危机&#xff1f; 在数字化时代&#xff0c;软件服务的稳定性至关重要。然而&#xff0c;即便是像网易云音乐这样的大型平台&#xff0c;也难免遇到突发的技术故障。8月19日下午&#xff0c;网易云音乐疑似出现服务器故障&#xff0c;网页端出…

选择护眼台灯的标准是什么?2024值得入手的护眼台灯推荐

2022年3月1日起&#xff0c;正式实施的《儿童青少年学习用品近视防控卫生要求》&#xff08;GB 40070-2021&#xff09;规定了与近视防控相关的读写作业台灯卫生要求。要求从照度、均匀度、显色指数、色温、防蓝光等方面去完善护眼台灯&#xff0c;可见国家多这方面多么的重视&…

黑神话悟空爆火,有人靠它赚翻了!

黑神话悟空&#xff0c;这个游戏最近爆火&#xff0c;相信很多人都知道。 这样的热点事件&#xff0c;对于大多数人来说&#xff0c;那就是图个热闹&#xff0c;吃个瓜&#xff1b; 但对于那些混在互联网副业圈里的&#xff0c;那闻到的都是钱味。 热点事件&#xff0c;意味…

2025长江流域跨境电商展:Temu在丹麦的惊人崛起,跨境电商的新风向标

Temu在丹麦的惊人崛起&#xff1a;跨境电商的新风向标 在全球化电商竞争日益激烈的今天&#xff0c;一个新兴的电商平台能够在短短时间内超越行业巨头亚马逊&#xff0c;成为丹麦消费者的首选&#xff0c;无疑是一个值得关注的现象。拼多多海外分支Temu正是这样一个平台&#…

支付宝开放平台-开发者社区——AI 日报「8 月 26 日」

1 国产机器人黑马首次登场&#xff0c;打螺丝堪比擎天柱&#xff01;国家队全栈自主研发 新智元丨阅读原文 浙江人形机器人创新中心研发的领航者2号 NAVIA1&#xff0c; 在2024 世界机器人大会上首次亮相&#xff0c;展示了其类人外观和高智能作业能力。这款1.65 米高、60公斤…

蓝牙耳机什么价位的性价比高?2024百元性价比品牌机型推荐

随着科技的不断进步&#xff0c;蓝牙耳机已成为现代人日常生活中不可或缺的配件之一&#xff0c;市场上的蓝牙耳机品牌和型号繁多&#xff0c;价格也从几十元到几千元不等&#xff0c;使得消费者在选择时往往感到眼花缭乱&#xff0c;那么蓝牙耳机什么价位的性价比高&#xff1…

安科瑞AEW100电力改造智能电力仪表,体积小巧

AEW100电力改造用智能电力仪表主要用于计量低压网络的三相有功电能&#xff0c;具有RS485通讯和470MHz无线通讯功能&#xff0c;方便用户进行用电监测、集抄和管理。 功能&#xff1a; AEW100电力改造用智能电力仪表主要用于计量低压网络的三相有功电能&#xff0c;具有RS485…

ssm动漫展示系统-计算机毕业设计源码12113

目 录 摘要 1 绪论 1.1 研究背景 1.2 研究意义 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据新增流程 3.2.2 数据删除流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.5本章小结 3 系统总体设…

如何根据不同的场景选择合适的报表格式?一文详细解答

在处理数据的过程中&#xff0c;许多人常感困扰于报表格式选择的多样性&#xff0c;这源于面对纷繁复杂的数据集时&#xff0c;难以迅速锁定最适合的呈现方式。这种迷茫感源于报表设计的灵活性&#xff0c;每种格式都针对特定情境和数据特性精心打造。 不必为选择何种报表格式…

Dooring智图,一款开箱即用的图片海报编辑器

嗨, 大家好, 我是徐小夕. 之前一直在社区分享零代码&低代码的技术实践&#xff0c;也陆陆续续设计并开发了多款可视化搭建产品&#xff0c;比如&#xff1a; Nocode/Doc&#xff0c;可视化 零代码打造下一代文件编辑器爆肝1000小时, Dooring零代码搭建平台3.5正式上线可视化…

产品小白学习及求职的3个误区,看看自己中招了没?

产品经理是互联网行业中颇有“钱”途的岗位&#xff0c;学习的人也最多&#xff0c;很多小白在学习产品的过程中或多或少的会踩坑&#xff0c;进入误区&#xff0c;小编本文就总结了小白学习产品的3大误区&#xff0c;快来看看自己中招了没吧。 1、画出漂亮的高保真原型就能当产…