【SpringBoot】SpringBoot:实现文件上传和下载功能

news2024/11/23 15:16:15

文章目录

      • 引言
      • 项目初始化
        • 添加依赖
      • 配置文件存储位置
      • 实现文件上传功能
        • 创建文件上传控制器
        • 创建上传页面
      • 实现文件下载功能
        • 创建文件下载控制器
      • 安全性和最佳实践
        • 文件大小限制
        • 文件类型验证
        • 文件名和路径验证
        • 文件下载时的安全性
      • 测试与部署
        • 示例:编写单元测试
      • 部署
      • 结论

在这里插入图片描述

引言

文件上传和下载是Web应用程序中常见的需求。在现代应用中,用户需要上传各种类型的文件,如图片、文档、视频等,或者下载生成的报告和数据文件。SpringBoot通过其强大的生态系统和简化的配置,能够高效地实现文件上传和下载功能。本文将详细介绍如何使用SpringBoot实现这一功能,并讨论相关的安全性和最佳实践。

项目初始化

首先,我们需要创建一个SpringBoot项目。可以通过Spring Initializr快速生成一个项目,添加所需的依赖项。

添加依赖

pom.xml中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>

配置文件存储位置

为了方便管理上传的文件,我们需要在项目中配置文件存储的位置。可以在application.properties文件中进行配置:

file.upload-dir=/tmp/uploads

这将文件上传目录设置为/tmp/uploads,你也可以根据需要更改为其他路径。

实现文件上传功能

创建文件上传控制器

创建一个控制器类,用于处理文件上传请求。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.io.File;
import java.io.IOException;

@RestController
public class FileUploadController {

    @Value("${file.upload-dir}")
    private String uploadDir;

    @PostMapping("/upload")
    public String handleFileUpload(@RequestParam("file") MultipartFile file,
                                   RedirectAttributes redirectAttributes) {
        if (file.isEmpty()) {
            return "Please select a file to upload.";
        }

        try {
            File dest = new File(uploadDir + File.separator + file.getOriginalFilename());
            file.transferTo(dest);
            return "You successfully uploaded " + file.getOriginalFilename() + "!";
        } catch (IOException e) {
            e.printStackTrace();
            return "Failed to upload " + file.getOriginalFilename() + "!";
        }
    }
}
创建上传页面

使用Thymeleaf创建一个简单的文件上传页面。在src/main/resources/templates目录下创建一个upload.html文件:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>File Upload</title>
</head>
<body>
<h1>Upload a File</h1>
<form method="POST" enctype="multipart/form-data" action="/upload">
    <input type="file" name="file"/>
    <input type="submit" value="Upload"/>
</form>
</body>
</html>

实现文件下载功能

创建文件下载控制器

创建一个控制器类,用于处理文件下载请求。

import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
public class FileDownloadController {

    @Value("${file.upload-dir}")
    private String uploadDir;

    @GetMapping("/download/{filename}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
        try {
            Path filePath = Paths.get(uploadDir).resolve(filename).normalize();
            Resource resource = new UrlResource(filePath.toUri());

            if (resource.exists()) {
                return ResponseEntity.ok()
                        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                        .body(resource);
            } else {
                return ResponseEntity.notFound().build();
            }
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseEntity.notFound().build();
        }
    }
}

安全性和最佳实践

文件大小限制

为了防止用户上传过大的文件,可以在application.properties中设置文件大小限制:

spring.servlet.multipart.max-file-size=2MB
spring.servlet.multipart.max-request-size=2MB
文件类型验证

为了防止上传恶意文件,可以在上传控制器中添加文件类型验证:

import org.springframework.web.bind.annotation.RequestMapping;

@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file,
                               RedirectAttributes redirectAttributes) {
    if (file.isEmpty()) {
        return "Please select a file to upload.";
    }

    String contentType = file.getContentType();
    if (!isValidContentType(contentType)) {
        return "Invalid file type. Only PNG, JPEG, and PDF are allowed.";
    }

    try {
        File dest = new File(uploadDir + File.separator + file.getOriginalFilename());
        file.transferTo(dest);
        return "You successfully uploaded " + file.getOriginalFilename() + "!";
    } catch (IOException e) {
        e.printStackTrace();
        return "Failed to upload " + file.getOriginalFilename() + "!";
    }
}

private boolean isValidContentType(String contentType) {
    return contentType.equals("image/png") ||
           contentType.equals("image/jpeg") ||
           contentType.equals("application/pdf");
}
文件名和路径验证

为了防止路径遍历攻击,需要验证上传文件的文件名和路径:

import org.springframework.web.util.UriUtils;

@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file,
                               RedirectAttributes redirectAttributes) {
    if (file.isEmpty()) {
        return "Please select a file to upload.";
    }

    String fileName = UriUtils.encodePath(file.getOriginalFilename(), "UTF-8");
    Path destinationPath = Paths.get(uploadDir).resolve(fileName).normalize();

    if (!destinationPath.startsWith(Paths.get(uploadDir))) {
        return "Invalid file path.";
    }

    try {
        file.transferTo(destinationPath.toFile());
        return "You successfully uploaded " + file.getOriginalFilename() + "!";
    } catch (IOException e) {
        e.printStackTrace();
        return "Failed to upload " + file.getOriginalFilename() + "!";
    }
}
文件下载时的安全性

在处理文件下载请求时,也需要注意路径遍历攻击,并对文件路径进行验证:

@GetMapping("/download/{filename}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
    try {
        String encodedFileName = UriUtils.encodePath(filename, "UTF-8");
        Path filePath = Paths.get(uploadDir).resolve(encodedFileName).normalize();

        if (!filePath.startsWith(Paths.get(uploadDir))) {
            return ResponseEntity.badRequest().body(null);
        }

        Resource resource = new UrlResource(filePath.toUri());
        if (resource.exists()) {
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                    .body(resource);
        } else {
            return ResponseEntity.notFound().build();
        }
    } catch (IOException e) {
        e.printStackTrace();
        return ResponseEntity.notFound().build();
    }
}

测试与部署

在完成文件上传和下载功能的开发后,应该进行充分的测试,确保所有功能都能正常工作。可以使用JUnit和MockMVC进行单元测试和集成测试。

示例:编写单元测试
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class FileUploadTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testFileUpload() throws Exception {
        mockMvc.perform(multipart("/upload")
                .file("file", "test content".getBytes()))
                .andExpect(status().isOk());
    }
}

通过这种方式,可以确保应用的各个部分在开发过程中得到充分的测试,减少上线后的问题。

部署

SpringBoot应用可以打包成可执行的JAR文件,方便部署。通过mvn package命令,可以生成一个包含所有依赖的JAR文件。

mvn package
java -jar target/demo-0.0.1-SNAPSHOT.jar

这种打包方式使得SpringBoot应用的部署变得非常简单,不再需要复杂的服务器配置。

结论

通过本文的介绍,我们了解了如何使用SpringBoot实现文件上传和下载

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

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

相关文章

C++的异常捕获

目录 C语言的异常处理方式 C的异常处理方式 异常的抛出与捕获 抛出与捕获原则 自定义异常体系 异常安全 异常规范 异常的优缺点 优点 缺点 C语言的异常处理方式 1、终止程序 常见形式&#xff1a;assert 缺陷&#xff1a;太过强硬&#xff0c;如果发生内存错误&#…

文心一言 VS 讯飞星火 VS chatgpt (282)-- 算法导论20.4 3题

三、在 CONNECTED-COMPONENTS 作用于一个有 k 个连通分量的无向图 G(V&#xff0c;E) 的过程中&#xff0c;FIND-SET 需要调用多少次&#xff1f; UNION 需要调用多少次&#xff1f;用 |V| 、 |E| 和 k 来表示你的答案。如果要写代码&#xff0c;请用go语言。 文心一言&#x…

【Java】过滤器/拦截器

文章目录 两者区别request链路全过程 在实际开发中&#xff0c;过滤器和拦截器都是经常使用的技术&#xff0c;但一被提及到其区别时&#xff0c;整个人就愣住了&#xff0c;好像没有认真地对两者进行区别和总结&#xff0c;这两者之间也确实很容易混淆&#xff0c;因此结合了很…

C++ 54 之 继承中同名的静态成员处理

#include <iostream> using namespace std;// 父类 class Base07{ public:static int m_a; // 静态成员&#xff0c;类内定义static void fun(){cout << "Base中的fun"<< endl;}static void fun(int a){cout << "Base中的fun(int a)&qu…

53. QT插件开发--插件(动态库so)的调用与加载

1. 说明 在使用QT进行插件库的开发之后,还需要将这个插件库程序生成的so动态链接库加载到主程序框架中进行使用,才能达到主程序的模块化开发的效果。在前一篇文章插件创建中介绍了如何在QT中开发插件库,并提供外部接口调用。本篇博客的主要作用是模拟在主程序框架中加载动态…

EasyRecovery2024数据恢复神器#电脑必备良品

EasyRecovery数据恢复软件&#xff0c;让你的数据重见天日&#xff01; 大家好&#xff01;今天我要给大家种草一个非常实用的软件——EasyRecovery数据恢复软件&#xff01;你是不是也曾经遇到过不小心删除了重要的文件&#xff0c;或者电脑突然崩溃导致数据丢失的尴尬情况呢&…

element-ui input输入框和多行文字输入框字体不一样

页面中未作样式修改&#xff0c;但是在项目中使用element-ui input输入框和多行文字输入框字体不一样&#xff0c;如下图所示&#xff1a; 这是因为字体不一致引起的&#xff0c;如果想要为Element UI的输入框设置特定的字体&#xff0c;你可以在你的样式表中添加以下CSS代码…

快速UDP网络连接之QUIC协议介绍

文章目录 一、QUIC协议历史1.1 问题&#xff1a;QUIC为什么在应用层实现1.2 QUIC协议相关术语1.3 QUIC和TCP对比1.4 QUIC报文格式1.4.1 QUIC报文格式-Stream帧11.4.2 QUIC报文格式-Stream帧2 二、QUIC的特点2.1 连接建立低时延&#xff0c;2.2 多路复用流复用-HTTP1.1流复用-HT…

SpringBoot整合H2数据库并将其打包成jar包、转换成exe文件二(补充)

SpringBoot整合H2数据库并将其打包成jar包、转换成exe文件二&#xff08;补充&#xff09; 如果你想在cmd命令窗口内看到程序运行&#xff0c;即点开弹出运行窗口&#xff0c;关闭时exe自动关闭。 需要再launch4j上进行如下操作&#xff1a; 这样转换好的exe就可以有控制台了…

springboot + Vue前后端项目(第十六记)

项目实战第十六记 写在前面1 第一个bug1.1 完整的Role.vue 2 第二个bug2.1 修改路由router下面的index.js 总结写在最后 写在前面 发现bug&#xff0c;修复bug 1 第一个bug 分配菜单时未加入父id&#xff0c;导致分配菜单失效 <!-- :check-strictly"true" 默…

人工智能对零售业的影响

机器人、人工智能相关领域 news/events &#xff08;专栏目录&#xff09; 本文目录 一、人工智能如何改变零售格局二、利用人工智能实现购物体验自动化三、利用人工智能改善库存管理四、通过人工智能解决方案增强客户服务五、利用人工智能分析消费者行为六、利用 AI 打造个性化…

C++前期概念(重)

目录 命名空间 命名空间定义 1. 正常的命名空间定义 2. 命名空间可以嵌套 3.头文件中的合并 命名空间使用 命名空间的使用有三种方式&#xff1a; 1:加命名空间名称及作用域限定符&#xff08;::&#xff09; 2:用using将命名空间中某个成员引入 3:使用using namespa…

TCP协议报头详解

目录 前言 TCP特点 TCP报头 1.源端口和目的端口 2.序号 3.确认号 4.数据偏移 5.保留 6.控制位 ① 紧急URG&#xff08;URGent&#xff09; ② 确认ACK&#xff08;ACKnowledgment&#xff09; ③ 推送PSH&#xff08;PuSH&#xff09; ④复位RST&#xff08;ReSeT&…

【数据结构】初识集合深入剖析顺序表(Arraylist)

【数据结构】初识集合&深入剖析顺序表&#xff08;Arraylist&#xff09; 集合体系结构集合的遍历迭代器增强for遍历lambda表达式 List接口中的增删查改List的5种遍历ArrayList详解ArrayList的创建ArrayList的增删查改ArrayList的遍历ArrayList的底层原理 &#x1f680;所属…

UnityAPI学习之 播放游戏音频的类(AudioSource)

播放游戏音频的类&#xff08;AudioSource&#xff09; using System.Collections; using System.Collections.Generic; using UnityEngine;public class NO17AudioSource : MonoBehaviour {private AudioSource audioSource;//音频组件public AudioClip clip;//音频文件public…

预编译、函数变量提升

函数声明会覆盖变量的声明&#xff0c;也就是会提升到最前面。 形参传进来相当于变量声明&#xff0c;所以当有函数声明时&#xff0c;会被覆盖。

情绪管理:大我则定,小我则乱(王阳明)

学了很多知识&#xff0c;却还是感物易动&#xff1f;如何让心回归中正&#xff1f;王阳明一言以蔽之&#xff1a; —— 大我&#xff0c;大我则定&#xff0c;小我则乱 保持心静的方法&#xff1a;有大爱&#xff0c;为大局着想

Spark-Shuffle阶段优化-Bypass机制详解

Spark概述 Spark-Shuffle阶段优化-Bypass机制详解 Spark的Bypass机制是一种特定情况下的优化策略&#xff0c;目的是减少Shuffle过程中不必要的排序开销&#xff0c;从而提升性能。当Shuffle分区数较少且数据量不大时&#xff0c;Bypass机制可以显著加快Shuffle速度。 1.什么…

使用 Nginx 和 SSL 访问 Python Flask 应用的教程

在本教程中&#xff0c;我们将介绍如何使用 Nginx 和 SSL 来访问 Python Flask 应用。通过这种方式&#xff0c;你可以在提高安全性的同时&#xff0c;也能利用 Nginx 的反向代理功能来优化应用的性能和稳定性。 环境准备 在开始之前&#xff0c;请确保你的系统已经安装了以…

准备离职了 电脑怎么清理?离职最干净的电脑清理办法

准备离职了 电脑怎么清理&#xff1f;离职最干净的电脑清理办法 人在江湖身不由己&#xff0c;离职这个事情&#xff0c;所有人都要面对。无论是出于个人发展、工作环境、薪资待遇还是其他原因&#xff0c;离职都是人生和职业道路上的一种常态。离职是一个残酷的事实&#xff…