Springboot | 如何上传文件
文章目录
- 1. 核心上传逻辑:FileUploadController
- 2. 使文件系统中的文件可通过 HTTP 访问:WebConfig
- 3. 安全性配置:WebSecurityConfig
- 4. 前端实现(这里用的是Angular)
在许多应用程序开发中,我们经常需要实现文件上传功能,例如允许用户上传头像或文档到服务器。虽然 Spring Boot 默认可以从 src/main/resources/static
等目录提供静态文件,但对于用户上传的内容,通常更灵活的做法是将它们保存在服务器文件系统的特定位置,而不是项目资源目录内。这样做的好处包括更精细的权限控制、便于独立管理和备份用户数据等。
那么,如何将文件存储在自定义的文件系统路径下,并且还能通过 HTTP 访问这些文件呢?
下面我们将结合您提供的 Java 代码(Spring Boot Controller 和配置)以及前端(HTML 和 TypeScript)代码,来详细讲解这一过程。
1. 核心上传逻辑:FileUploadController
首先,我们需要一个 Spring Boot Controller 来处理文件上传的 HTTP 请求。您提供的 FileUploadController
就是用于这个目的:
@RestController
@RequestMapping("/file")
public class FileUploadController {@AutowiredIUserService userService; // 用于操作用户数据,如更新头像路径@Autowiredprivate JwtUtil jwtUtil; // 用于解析 JWT Token,获取用户信息(虽然在提供的uploadFile方法中未完全体现解析逻辑)@PostMapping("/avatar/upload")public String uploadFile(@RequestParam("file") MultipartFile file, @RequestHeader("Authorization") String token) {if (file.isEmpty()) {return "文件为空";}// 获取项目运行目录,并在其下创建 uploads 目录来存放上传文件String uploadDir = System.getProperty("user.dir") + File.separator + "uploads" + File.separator;File directory = new File(uploadDir);if (!directory.exists()) {directory.mkdirs(); // 如果目录不存在,则创建}try {String originalFilename = file.getOriginalFilename();String extension = "";int lastDotIndex = originalFilename.lastIndexOf(".");if (lastDotIndex != -1) {extension = originalFilename.substring(lastDotIndex + 1); // 获取文件扩展名}// 生成新的文件名,这里使用了 MD5 哈希(尽管代码中使用的 username 未在此方法内获取)// 一个更好的实践是从 Token 中解析出用户标识来生成独一无二的文件名// 注意:提供的代码片段中 username 变量未定义,此处需要从 token 或其他途径获取String newFileName = HashUtil.calculateMD5(username + file.getOriginalFilename()) + "." + extension;String newFilePath = uploadDir + newFileName;// 保存文件到指定路径file.transferTo(new File(newFilePath));// TODO: 这里需要完善根据用户 ID 或其他标识更新用户头像路径的逻辑// 例如:String userId = jwtUtil.extractUserId(token);// userService.updateUserAvatar(userId, newFileName);// 尝试删除用户旧的头像文件(如果存在且文件名已记录)// 注意:此处的 user 对象未在本方法中初始化,需要从 token 或其他途径获取用户实体File file1 = new File(uploadDir + user.getAvatar());if (file1.exists() && !file1.isDirectory()) {file1.delete(); // 删除旧文件}return "文件上传成功: " + newFileName; // 通常返回新文件的相对路径或文件名} catch (IOException e) {e.printStackTrace();return "文件上传失败";} catch (Exception e) {e.printStackTrace();return "文件处理失败";}}// 这个 GET 端点 /avatar/load/{fileName} 用于通过特定的 API 路径下载文件// 但更常见的做法是结合 WebConfig 配置,直接通过静态资源路径访问@GetMapping("/avatar/load/{fileName}")public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {try {// 构建文件在文件系统中的完整路径File file = new File(System.getProperty("user.dir") + "/uploads/" + fileName);if (!file.exists()) {return ResponseEntity.notFound().build(); // 文件不存在返回 404}// 将文件封装成 Resource 返回Resource resource = new FileSystemResource(file);// 设置响应头,告知浏览器是附件下载return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"").body(resource);} catch (Exception e) {e.printStackTrace();return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); // 发生错误返回 500}}
}
在这个控制器中,uploadFile
方法接收上传的文件 (MultipartFile
) 和授权信息 (Authorization
header)。它计算出文件应该保存的服务器路径(在项目运行目录下的 uploads
文件夹),并创建一个基于 MD5 散列的新文件名以避免冲突(虽然代码中用于散列的 username
变量需要从 Token 中获取)。文件通过 transferTo
方法保存到服务器文件系统。成功后,通常会更新用户的头像信息到数据库,并可能删除旧的头像文件。
downloadFile
方法提供了一个通过 API 路径 (/file/avatar/load/{fileName}
) 获取特定文件的方式,并将其作为资源返回,强制浏览器进行下载。
2. 使文件系统中的文件可通过 HTTP 访问:WebConfig
仅仅将文件保存在文件系统还不够,我们需要让这些文件能够通过 HTTP URL 被浏览器访问到。这就是 WebConfig
中 addResourceHandlers
方法的作用:
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {// 将所有以 /uploads/ 开头的 URL 请求映射到文件系统中的 uploads 目录registry.addResourceHandler("/uploads/**").addResourceLocations("file:" + System.getProperty("user.dir") + "/uploads/");}
}
通过这段配置,我们将 /uploads/**
这个 URL 路径映射到了服务器文件系统中的 项目运行目录/uploads/
路径。这意味着,如果你的 Spring Boot 应用运行在 http://localhost:8080
上,并且上传了一个名为 abcdef123.png
的文件到 项目运行目录/uploads/
目录下,那么你可以直接通过 http://localhost:8080/uploads/abcdef123.png
这个 URL 在浏览器中访问到它(例如显示为图片)。
这种方式比通过 downloadFile
API 端点获取文件更常见,尤其是在需要在网页中直接显示图片时。
3. 安全性配置:WebSecurityConfig
由于一些原因,导致直接访问/file/upload/
会发生403
错误,所以,我们可以这样做
@Configuration
public class WebSecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/**").permitAll() // 允许所有用户访问 (包括上传和下载接口).anyRequest().authenticated()).csrf(csrf -> csrf.disable()); // 禁用 CSRF 防护 (根据需要配置)return http.build();}
}
当然,在生产环境中,应该根据实际需求调整这里的安全规则,例如只允许认证用户访问上传接口 (/file/avatar/upload
),或者对 /uploads/**
路径下的文件访问进行更细粒度的控制(尽管 addResourceHandlers
本身不提供复杂的访问控制)。
4. 前端实现(这里用的是Angular)
在前端,使用 HTML 提供了文件选择输入框和一个触发上传的按钮:
<div class="mb-4 flex flex-col" style="height: auto;"><input type="file" (change)="onFileSelected($event)" class="mb-2 text-base" accept=".png, .jpeg, .jpg, .gif" style="font-size: 0.9rem"/><span (click)="onUpload()" class="blue-text-hover-underline w-fit text-base" style="font-size: 0.9rem">上传</span>
</div>
accept=".png, .jpeg, .jpg, .gif"
可以考虑不加上,不加上这个就会只出现支持所有文件上传
下图是加上accept=".png, .jpeg, .jpg, .gif"
的
下图是不加上accept=".png, .jpeg, .jpg, .gif"
的
对应的 TypeScript 代码处理文件选择和上传请求:
selectedFile: File | null = null; // 用于存储选中的文件onFileSelected(event: Event): void {const input = event.target as HTMLInputElement;if (input.files && input.files.length > 0) {this.selectedFile = input.files[0]; // 将选中的文件存储到变量中}
}onUpload(): void {if (this.selectedFile) {const formData = new FormData();formData.append('file', this.selectedFile); // 将文件添加到 FormData 中,key 为 'file',与后端 @RequestParam("file") 对应// 发送 POST 请求到后端上传接口// 注意:此处的请求并未携带后端期望的 Authorization headerthis.http.post('http://localhost:8080/file/avatar/upload', formData, { responseType: 'text' }).subscribe({next: (response: string) => {console.log('上传成功:', response);this.loadUserInfo(); // 上传成功后,重新加载用户信息(可能包含新的头像 URL)},error: (error) => console.error('上传失败:', error),});} else {console.warn('未选择文件');}
}
前端通过 onFileSelected
方法获取用户选择的文件,并将其保存在 selectedFile
变量中。当用户点击“上传”按钮时,onUpload
方法会创建一个 FormData
对象,将选中的文件添加进去,然后使用 HTTP 客户端 (this.http
) 发送 POST 请求到后端的 /file/avatar/upload
接口。请求成功后,通常会刷新用户界面以显示新的头像。
相关文章:

Springboot | 如何上传文件
文章目录 1. 核心上传逻辑:FileUploadController2. 使文件系统中的文件可通过 HTTP 访问:WebConfig3. 安全性配置:WebSecurityConfig4. 前端实现(这里用的是Angular) 在许多应用程序开发中,我们经常需要实现…...
axios结合AbortController取消文件上传
<template><div><input type"file" multiple change"handleFileUpload" /><button click"cancelUpload" :disabled"!isUploading">取消上传</button><div>总进度:{{ totalProgress }}…...

spring中的@Async注解详解
一、核心功能与作用 Async 是Spring框架提供的异步方法执行注解,用于将方法标记为异步任务,使其在独立线程中执行,从而提升应用的响应速度和吞吐量。其主要作用包括: 非阻塞调用:主线程调用被标记方法后立即返回&…...

MyBatis 报错:Column count doesn‘t match value count at row 1 详解与解决
本文适用于使用 MyBatis MySQL 开发中出现 “Column count doesnt match value count at row 1” 报错的朋友,尤其是在批量插入或更新数据时,遇到 XML 映射文件中 insert 标签报错的问题。 一、遇到的问题: 二、错误原因分析 列数与值数量不…...
第四天——贪心算法——种花
1. 题目 有一个花坛,其中0 表示该位置是空的,可以种花。1 表示该位置已经有花,不能种花。 规则:新种的花不能种在相邻的位置(即如果某个位置已经种了花,它的左右两个相邻位置不能再种花)。给定…...

【人工智能】自然语言编程革命:腾讯云CodeBuddy实战5步搭建客户管理系统,效率飙升90%
CodeBuddy 导读一、产品介绍1.1 **什么是腾讯云代码助手?**1.2 插件安装1.2.1 IDE版本要求1.2.2 注意事项1.2.4 插件安装1.2.4.1 环境安装1.2.4.2 安装腾讯云AI代码助手** 1.2.5 功能介绍1.2.5.1 Craft(智能代码生成)1.2.5.2 Chat(…...

麦肯锡110页PPT企业组织效能提升调研与诊断分析指南
“战略清晰、团队拼命、资源充足,但业绩就是卡在瓶颈期上不去……”这是许多中国企业面临的真实困境。表面看似健康的企业,往往隐藏着“组织亚健康”问题——跨部门扯皮、人才流失、决策迟缓、市场反应滞后……麦肯锡最新研究揭示:组织健康度…...

【MySQL】第二弹——MySQL表的增删改查(CRUD)初阶
文章目录 🎓一. CRUD🎓二. 新增(Create)🎓三. 查询(Rertieve)📖1. 全列查询📖2. 指定列查询📖3. 查询带有表达式📖4. 起别名查询(as )📖 5. 去重查询(distinct)📖6. 排序…...
内存、磁盘、CPU区别,Hadoop/Spark与哪个联系密切
1. 内存、磁盘、CPU的区别和作用 1.1 内存(Memory) 作用: 内存是计算机的短期存储器,用于存储正在运行的程序和数据。它的访问速度非常快,比磁盘快几个数量级。在分布式计算中,内存用于缓存中间结果、存储…...
hz2新建Keyword页面
新建一个single-keywords.php即可,需要筛选项再建taxonomy-knowledge-category.php 参考:https://www.tkwlkj.com/customize-wordpress-category-pages.html WordPress中使用了ACF创建了自定义产品分类products,现在想实现自定义产品分类下的…...

离散制造企业WMS+MES+QMS+条码管理系统高保真原型全解析
在离散型制造企业的生产过程中,库存管理混乱、生产进度不透明、质检流程繁琐等问题常常成为制约企业发展的瓶颈。为了帮助企业实现全流程数字化管控,我们精心打造了一款基于离散型制造企业(涵盖单件生产、批量生产、混合生产模式)…...
【并发编程基石】CAS无锁算法详解:原理、实现与应用场景
一、什么是CAS? CAS(Compare-And-Swap) 是现代并发编程的核心算法之一,它通过处理器指令级的原子操作实现线程安全,无需传统锁机制。其核心逻辑可以用一个公式表示: CAS(V, E, N) {if (V E) { // 比较当…...
(自用)Java学习-5.8(总结,springboot)
一、MySQL 数据库 表关系 一对一、一对多、多对多关系设计外键约束与级联操作 DML 操作 INSERT INTO table VALUES(...) DELETE FROM table WHERE... UPDATE table SET colval WHERE...DQL 查询 基础查询:SELECT * FROM table WHERE...聚合函数:COUNT()…...
GOOSE 协议中MAC配置
在 GOOSE(Generic Object Oriented Substation Event)协议中,主站(Publisher)发送的 MAC 地址不需要与从站(Listener)的 MAC 地址一致,其通信机制与 MAC 地址的匹配逻辑取决于 GOOSE…...
机器学习之决策树与决策森林:机器学习中的强大工具
机器学习之决策树与决策森林:机器学习中的强大工具 摘要:本文深入探讨决策树和决策森林在机器学习中的应用优势及其适用场景。决策树凭借其易于配置、原生处理多种数据类型、鲁棒性及可解释性等特点,在小数据集和表格数据处理方面表现卓越。…...
【Redis】谈谈Redis的设计
Redis(Remote Dictionary Service)是一个高性能的内存键值数据库,其设计核心是速度、简单性和灵活性。以下从架构、数据结构、持久化、网络模型等方面解析 Redis 的设计实现原理: 1. 核心设计思想 内存优先:数据主要存…...
【C++】流(Stream)详解:标准流、文件流和字符串流
【C】流(Stream)详解:标准流、文件流和字符串流 在C编程中,流(Stream)是一个非常重要的概念,它为我们提供了统一的数据输入输出接口。本文将详细介绍C中的三种主要流类型:标准流、文件流和字符串流。 一、标准流(Standard Strea…...

基于 Spring Boot 瑞吉外卖系统开发(十三)
基于 Spring Boot 瑞吉外卖系统开发(十三) 查询套餐 在查询套餐信息时包含套餐的分类名,分类名称在category表中,因此这里需要进行两表关联查询。 自定义SQL如下: select s.* ,c.name as category_name from setmeal…...

POSE识别 神经网络
Pose 识别模型介绍 Pose 识别是计算机视觉领域的一个重要研究方向,其目标是从图像或视频中检测出人体的关键点位置,从而估计出人体的姿态。这项技术在许多领域都有广泛的应用,如动作捕捉、人机交互、体育分析、安防监控等。 Pose 识别模型的…...
CSS3 基础知识、原理及与CSS的区别
CSS3 基础知识、原理及与CSS的区别 CSS3 基础知识 CSS3 是 Cascading Style Sheets 的第3个版本,是CSS技术的升级版本,于1999年开始制订,2001年5月23日W3C完成了CSS3的工作草案。 CSS3 主要模块 选择器:更强大的元素选择方式盒…...
电能质量扰动信号信号通过hilbert变换得到瞬时频率
利用Hilbert变换从电能质量扰动信号中提取瞬时频率、瞬时幅值、Hilbert谱和边际谱的详细步骤及MATLAB代码实现。该流程适用于电压暂降、暂升、谐波、闪变等扰动分析。 1. Hilbert变换与特征提取流程 1.1 基本步骤 信号预处理:滤波去噪(如小波去噪&…...
Linux工作台文件操作命令全流程解析(高级篇之awk精讲)
全文目录 1 工具介绍2 核心优势3 命令格式3.1 命令格式说明3.2 组成部分详解3.2.1 选项3.2.2 模式3.2.3 动作3.2.4 输入文件 4 使用说明4.1 常用示例4.2 awk 编程解析4.2.1 基础说明4.2.2 编程进阶 4.3 温馨提示 5 内置变量6 参考文献 写在前面 前面一篇《Linux工作台文件操作命…...

力扣119题:杨辉三角II(滚动数组)
小学生一枚,自学信奥中,没参加培训机构,所以命名不规范、代码不优美是在所难免的,欢迎指正。 标签: 杨辉三角、滚动数组 语言: C 题目: 给定一个非负索引 rowIndex,返回「杨辉三角…...
c++:算法(Algorithms)
目录 常用 STL 算法 1️⃣ std::sort(排序) 2️⃣ std::find(查找等于某值的元素) 3️⃣ std::count(统计出现次数) 4️⃣ std::next(获取迭代器的下一个位置) 5️⃣ .erase(…...

大疆无人机(全系列,包括mini)拉流至电脑,实现直播
参考视频 【保姆级教程】大疆无人机rtmp推流直播教程_哔哩哔哩_bilibili VLC使用教程: VLC工具使用指南-CSDN博客 目录 实现效果: 电脑端 编辑 编辑 无人机端 VLC拉流 分析 实现效果: (实验机型:大疆mini4kRC-N2遥控器、大…...

uniapp-商城-54-后台 新增商品(页面布局)
后台页面中还存在商品信息的添加和修改等。接下来我们逐步进行分析和展开。包含页面布局和数据库逻辑等等。 1、整体效果 样式效果如下,依然采用了表单形式来完成和商家信息差不多,但在商品属性上多做了一些弹窗等界面,样式和功能点表多。 …...
深入浅出MySQL 8.0:新特性与最佳实践
MySQL作为开源关系型数据库的佼佼者,近年来持续更新迭代,尤其是在8.0版本中引入了一系列令人兴奋的新特性。本文将介绍一些MySQL 8.0的关键新功能,并提供最佳实践,旨在帮助开发人员和DBA更好地利用这一强大的数据库管理系统。 一…...
JIT+Opcache如何配置才能达到性能最优
首先打开php.ini文件,进行配置 1、OPcache配置 ; 启用OPcache opcache.enable1; CLI环境下启用OPcache(按需配置) opcache.enable_cli0; 预加载脚本(PHP 7.4,加速常用类) ; opcache.preload/path/to/prel…...
(2)python开发经验
文章目录 1 pyside6加载ui文件2 使用pyinstaller打包 更多精彩内容👉内容导航 👈👉Qt开发 👈👉python开发 👈 1 pyside6加载ui文件 方法1: 直接加载ui文件 from PySide6.QtWidgets import QAp…...

WebpackVite总结篇与进阶
模块化 Webpack Webpack 入口entry 分离app和第三方库入口 这是什么? 这是告诉 webpack 我们想要配置 2 个单独的入口点(例如上面的示例)。 为什么? 这样你就可以在 vendor.js 中存入未做修改的必要 library 或文件࿰…...