Springboot | 如何上传文件

news2025/5/14 19:23:51

文章目录

    • 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 {

    @Autowired
    IUserService userService; // 用于操作用户数据,如更新头像路径

    @Autowired
    private 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 被浏览器访问到。这就是 WebConfigaddResourceHandlers 方法的作用:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public 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 {

    @Bean
    public 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"的

下图是不加上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 header
        this.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 接口。请求成功后,通常会刷新用户界面以显示新的头像。

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

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

相关文章

spring中的@Async注解详解

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

MyBatis 报错:Column count doesn‘t match value count at row 1 详解与解决

本文适用于使用 MyBatis MySQL 开发中出现 “Column count doesnt match value count at row 1” 报错的朋友&#xff0c;尤其是在批量插入或更新数据时&#xff0c;遇到 XML 映射文件中 insert 标签报错的问题。 一、遇到的问题&#xff1a; 二、错误原因分析 列数与值数量不…

【人工智能】自然语言编程革命:腾讯云CodeBuddy实战5步搭建客户管理系统,效率飙升90%

CodeBuddy 导读一、产品介绍1.1 **什么是腾讯云代码助手&#xff1f;**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&#xff08;智能代码生成&#xff09;1.2.5.2 Chat&#xff08…

麦肯锡110页PPT企业组织效能提升调研与诊断分析指南

“战略清晰、团队拼命、资源充足&#xff0c;但业绩就是卡在瓶颈期上不去……”这是许多中国企业面临的真实困境。表面看似健康的企业&#xff0c;往往隐藏着“组织亚健康”问题——跨部门扯皮、人才流失、决策迟缓、市场反应滞后……麦肯锡最新研究揭示&#xff1a;组织健康度…

【MySQL】第二弹——MySQL表的增删改查(CRUD)初阶

文章目录 &#x1f393;一. CRUD&#x1f393;二. 新增(Create)&#x1f393;三. 查询(Rertieve)&#x1f4d6;1. 全列查询&#x1f4d6;2. 指定列查询&#x1f4d6;3. 查询带有表达式&#x1f4d6;4. 起别名查询(as )&#x1f4d6; 5. 去重查询(distinct)&#x1f4d6;6. 排序…

离散制造企业WMS+MES+QMS+条码管理系统高保真原型全解析

在离散型制造企业的生产过程中&#xff0c;库存管理混乱、生产进度不透明、质检流程繁琐等问题常常成为制约企业发展的瓶颈。为了帮助企业实现全流程数字化管控&#xff0c;我们精心打造了一款基于离散型制造企业&#xff08;涵盖单件生产、批量生产、混合生产模式&#xff09;…

基于 Spring Boot 瑞吉外卖系统开发(十三)

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

POSE识别 神经网络

Pose 识别模型介绍 Pose 识别是计算机视觉领域的一个重要研究方向&#xff0c;其目标是从图像或视频中检测出人体的关键点位置&#xff0c;从而估计出人体的姿态。这项技术在许多领域都有广泛的应用&#xff0c;如动作捕捉、人机交互、体育分析、安防监控等。 Pose 识别模型的…

力扣119题:杨辉三角II(滚动数组)

小学生一枚&#xff0c;自学信奥中&#xff0c;没参加培训机构&#xff0c;所以命名不规范、代码不优美是在所难免的&#xff0c;欢迎指正。 标签&#xff1a; 杨辉三角、滚动数组 语言&#xff1a; C 题目&#xff1a; 给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角…

大疆无人机(全系列,包括mini)拉流至电脑,实现直播

参考视频 【保姆级教程】大疆无人机rtmp推流直播教程_哔哩哔哩_bilibili VLC使用教程&#xff1a; VLC工具使用指南-CSDN博客 目录 实现效果&#xff1a; 电脑端 ​编辑 ​编辑 无人机端 VLC拉流 分析 实现效果&#xff1a; (实验机型&#xff1a;大疆mini4kRC-N2遥控器、大…

uniapp-商城-54-后台 新增商品(页面布局)

后台页面中还存在商品信息的添加和修改等。接下来我们逐步进行分析和展开。包含页面布局和数据库逻辑等等。 1、整体效果 样式效果如下&#xff0c;依然采用了表单形式来完成和商家信息差不多&#xff0c;但在商品属性上多做了一些弹窗等界面&#xff0c;样式和功能点表多。 …

WebpackVite总结篇与进阶

模块化 Webpack Webpack 入口entry 分离app和第三方库入口 这是什么&#xff1f; 这是告诉 webpack 我们想要配置 2 个单独的入口点&#xff08;例如上面的示例&#xff09;。 为什么&#xff1f; 这样你就可以在 vendor.js 中存入未做修改的必要 library 或文件&#xff0…

【python】基础知识点100问

以下是Python基础语法知识的30条要点整理,涵盖数据类型、函数、控制结构等核心内容,结合最新资料归纳总结: 基础30问 一、函数特性 函数多返回值 支持用逗号分隔返回多个值,自动打包为元组,接收时可解包到多个变量 def func(): return 1, "a" x, y = func()匿…

SpringBoot--springboot简述及快速入门

spring Boot是spring提供的一个子项目&#xff0c;用于快速构建spring应用程序 传统方式&#xff1a; 在众多子项目中&#xff0c;spring framework项目为核心子项目&#xff0c;提供了核心的功能&#xff0c;其他的子项目都需要依赖于spring framework&#xff0c;在我们实际…

vscode_python远程调试_pathMappings配置说明

1.使用说明 vscode python 远程调试pathMappings 配置 launch.json "pathMappings": [{"localRoot": "本地代码目录","remoteRoot": "远程代码目录" # 注意不是运行目录, 是远程代码的目录}],2.测试验证 测试目的: 远程代…

遨游5G-A防爆手机:赋能工业通信更快、更安全

在工业数字化转型与5G-A商用进程加速的双重驱动下&#xff0c;中国防爆手机市场正迎来历史性发展机遇。作为“危、急、特”场景通信解决方案服务商&#xff0c;遨游通讯深刻洞察到&#xff1a;当5G-A网络以超高速率、海量连接和毫秒级时延重塑行业生态时&#xff0c;防爆手机这…

Profibus DP主站与Modbus RTU/TCP网关与海仕达变频器轻松实现数据交互

Profibus DP主站与Modbus RTU/TCP网关与海仕达变频器轻松实现数据交互 Profibus DP主站转Modbus RTU/TCP&#xff08;XD-MDPBm20&#xff09;网关在Profibus总线侧实现主站功能&#xff0c;在Modbus串口侧实现从站功能。可将ProfibusDP协议的设备&#xff08;如&#xff1a;海…

「华为」人形机器人赛道投资首秀!

温馨提示&#xff1a;运营团队2025年最新原创报告&#xff08;共210页&#xff09; —— 正文&#xff1a; 近日&#xff0c;【华为】完成具身智能赛道投资首秀&#xff0c;继续加码人形机器人赛道布局。 2025年3月31日&#xff0c;具身智能机器人头部创企【千寻智能&#x…

格雷希尔G10和G15系列自动化快速密封连接器,适用于哪些管件的密封,以及它们相关的特性有哪些?

格雷希尔G10和G15系列快速密封连接器&#xff0c;用于自动化和半自动化过程中的外部或内部密封&#xff0c;通过使用气压驱动来挤压内部的密封圈&#xff0c;创造一个适用于各种管件的无泄漏密封连接&#xff0c;连接器内部的弹性密封圈可以提供其他产品不能提供的卓越密封性能…

专栏特辑丨悬镜浅谈开源风险治理之SBOM与SCA

随着容器、微服务等新技术日新月异&#xff0c;开源软件成为业界主流形态&#xff0c;软件行业快速发展。但同时&#xff0c;软件供应链也越来越趋于复杂化和多样化&#xff0c;软件供应链安全风险不断加剧。 软件供应链安全主要包括软件开发生命周期和软件生存运营周期&#x…