SpringBoot实现文件上传

发布于:2025-08-11 ⋅ 阅读:(15) ⋅ 点赞:(0)

1、yml设置

1.对于文件进行设置

spring:
  servlet:
    multipart:
      # 设置文件最大大小
      max-file-size: 100MB
      # 设置请求最大大小
      max-request-size: 100MB

2.设置文件存储路径

这里使用本地文件路径模拟文件服务器

# 文件上传位置
upload-path:
  url: http://localhost:8080/
  face: D:/community/upload/face/
  file: D:/community/upload/file/
(1) upload-path

自定义配置前缀(不是 Spring Boot 自带的),通过 @ConfigurationProperties 或 @Value 注解注入到 Java 类中。

(2) url: http://localhost:8080/
  • 表示文件上传后,对外访问的基础 URL
  • 例如,保存的文件本地路径是 D:/community/upload/face/abc.jpg,访问 URL 就可以是:
http://localhost:8080/face/abc.jpg
  • 注意:要能访问这个 URL,需要在 WebMvcConfigurer 中配置静态资源映射,把 D:/community/upload/face/ 暴露成 /face/ 路径。
(3) face: D:/community/upload/face/
  • 专门存储人脸图片的本地磁盘目录。
  • 当上传人脸图片时,项目会把文件保存到这个目录。
  • 比如:
    • 上传一张jpg
    • 保存到D:/community/upload/face/test.jpg
(4) file: D:/community/upload/file/
  • 专门存储其他类型文件(非人脸图片,比如文档、压缩包等)的本地磁盘目录。
  • 用法和 face 类似,只是分了不同文件夹管理。

2、配置拦截器,将file的请求拦截

package com.qcby.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMVCConfiguration implements WebMvcConfigurer {
    @Value("${upload-path.face}")
    private String face;
    @Value("${upload-path.file}")
    private String file;
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/community/upload/face/**")
                .addResourceLocations("file:"+face);
        registry.addResourceHandler("/community/upload/file/**")
                .addResourceLocations("file:"+file);
    }
}

将 URL 请求路径(/community/upload/face/**)和磁盘路径(D:/community/upload/face/)绑定,让浏览器可以直接访问磁盘上的文件。

当用户访问 http://localhost:8080/community/upload/face/abc.jpg

Spring Boot 会去 D:/community/upload/face/abc.jpg 找这个文件,并直接返回给浏览器

前端访问http://localhost:8080/community/upload/face/123.jpg时,Springboot会映射到file:D:/community/upload/face/123.jpg,协议的切换和localhost:8080的变动是如何实现的

①Spring MVC 的资源处理机制

这是Spring MVC 提供的 静态资源处理机制 在工作

Spring Boot(底层是 Spring MVC)在启动时,会扫描配置的静态资源处理器(ResourceHttpRequestHandler),这个处理器有两个关键点:

1. 匹配 URL 路径模式(/community/upload/face/**)

当浏览器访问的 URL 路径部分(不包括协议、域名、端口)符合这个模式时,这个处理器会接管请求。

http://localhost:8080/community/upload/face/123.jpg → 匹配路径 /community/upload/face/123.jpg。

2. 映射到物理资源位置(file:D:/community/upload/face/)

file: 协议告诉 Spring MVC:资源在本地文件系统,不是在 classpath 里。

把 URL 路径中的匹配部分去掉,拼到物理路径后面:

D:/community/upload/face/ + 123.jpg

然后读取文件并通过 HTTP 响应流返回给浏览器。

②“localhost:8080” 没有被换成文件路径

很多人会以为 localhost:8080 被替换成 file: 路径,其实不是。

实际过程是这样的:

1. 浏览器请求

GET http://localhost:8080/community/upload/face/123.jpg

2. Tomcat 接收请求

    Spring Boot 内置 Tomcat 监听 8080 端口,接收到 /community/upload/face/123.jpg 这个路径的请求。

    3.Spring MVC 匹配处理器

    它发现 /community/upload/face/** 的规则被命中,就交给 ResourceHttpRequestHandler。

    4.处理器读取本地文件

    根据 file:D:/community/upload/face/ 这个配置,把 URL 里剩下的部分 123.jpg 拼到路径末尾,得到:

    D:/community/upload/face/123.jpg

    打开文件,把二进制内容写到 HTTP 响应中。

    5.浏览器显示图片

    对浏览器来说,它仍然是收到了 http://localhost:8080/... 的响应,只不过内容是 JPG 图片。

    ③协议和端口没有变化

    浏览器看到的始终是 http://localhost:8080/...,HTTP 协议和端口 8080 没变。

    只是在服务端内部,Spring MVC 把这个 HTTP 请求映射到了本地文件系统,并读取了文件内容作为 HTTP 响应。

    换句话说:

    对客户端 → 它是在访问 HTTP URL

    对服务端 → 它是在读硬盘文件并通过 HTTP 返回

    3、人脸图片识别并上传

    @Autowired
    private ICommunityService communityService;
    @Autowired
    private IPersonService personService;
    @Value("${upload-path.url}")
    private String uploadUrlPath;
    @Value("${upload-path.face}")
    private String faceLocalPath;
    @Autowired
    private ApiConfiguration apiConfiguration;
    /**
     * 录入居民人脸信息
     * @return
     */
    @PostMapping("/addPerson")
    public Result addPersonImg(@RequestBody FaceForm faceForm){
        System.out.println("进入到addPerson");
        System.out.println(faceForm.getExtName());
        Person person = personService.getById(faceForm.getPersonId());
        // 严谨性判断,参数是否为空
        if(faceForm.getFileBase64()== null || faceForm.getFileBase64().equals("")){
            return Result.error("请上传人脸图片");
        }
        // 判断是否是人脸,如果不是,直接返回
        if(apiConfiguration.isUsed()){
            String faceId = newPerson(faceForm,person.getUserName());
            if(faceId == null){
                return Result.error("人脸识别失败");
            }else{
                String fileName = faceId + "." + faceForm.getExtName();
                String faceUrl = uploadUrlPath + faceLocalPath.substring(3) + fileName;
                person.setFaceUrl(faceUrl);
                person.setState(2);
                personService.updateById(person);
                return Result.ok();
            }
        }else{
            return Result.error("人脸识别开启失败");
        }
    }
    
    /**
     * 创建人脸信息,调用人脸识别API,进行人脸比对
     * @param faceForm
     * @param userName
     * @return
     */
    private String newPerson(FaceForm faceForm, String userName) {
        String faceId = null;
        String fileBase64 = faceForm.getFileBase64();
        String extName = faceForm.getExtName();
        String personId = faceForm.getPersonId()+"";
        String savePath = faceLocalPath;
        if(fileBase64 != null && !fileBase64.equals("")){
            FaceApi faceApi = new FaceApi();
            RootResp newperson = faceApi.newperson(apiConfiguration, personId, userName, fileBase64);
            if(newperson.getRet()==0){
                JSONObject jsonObject = JSON.parseObject(newperson.getData().toString());
                faceId = jsonObject.getString("FaceId");
                if(faceId!=null){
                    savePath = savePath + faceId + "."+extName;
                    try {
                        Base64Util.decoderBase64File(fileBase64,savePath);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            } else{
                return faceId;
            }
        }
        return faceId;
    }

    1.配置如何参与

    @Value("${upload-path.url}") private String uploadUrlPath;来自 yml 的“对外基础 URL”,例如 http://localhost:8080/。

    @Value("${upload-path.face}") private String faceLocalPath;来自 yml 的“人脸图片本地目录”,例如 D:/community/upload/face/。

    同时你在 WebMVCConfiguration 里把:

    "/community/upload/face/**" ↔ "file:D:/community/upload/face/"

    做了静态资源映射。这意味着浏览器访问http://localhost:8080/community/upload/face/xxx.jpg 时,Spring 会从 D:/community/upload/face/xxx.jpg 读文件并返回。

    代码整体流程(控制层 + 落盘)

    1. 接收请求(/addPerson)取到 FaceForm(里有 fileBase64, extName, personId)。
    2. 校验 fileBase64 为空就直接报错返回。
    3. 开关检测apiConfiguration.isUsed() 控制是否启用人脸识别。
    4. 调用识别并保存文件(newPerson())
      1. 把 Base64 发给第三方人脸 API(faceApi.newperson(...))

      2. 成功返回后从响应里拿到 FaceId

      3. 用 FaceId + "." + extName 作为文件名,拼成:savePath = faceLocalPath + faceId + "." + extName比如:D:/community/upload/face/ab12cd34.jpg

      4. 用 Base64Util.decoderBase64File(fileBase64, savePath) 把 Base64 解码写入磁盘

    5. 生成可访问 URL + 更新库

      1. 构造 fileName = faceId + "." + extName

      2. 组装 faceUrl,然后 person.setFaceUrl(faceUrl),并 updateById

    4、EXCEL表的导入和导出

    /**
         * 数据的导出功能实现
         * @param personListForm
         * @return
         */
        @GetMapping("/exportExcel")
        public Result exportExcel(PersonListForm personListForm){
           //1.获取满足条件的数据
            PageVO pageVO = personService.personList(personListForm);
           //2.只需要满足条件的数据即可
            List list = pageVO.getList();
           //3.处理数据放入到EXcel表格当中
            String path = excel;
            path=ExcelUtil.ExpPersonInfo(list, path);
            return Result.ok().put("data", path);
        }
    
        /**
         * 文件上传
         * @param file
         * @return
         * @throws Exception
         */
        @PostMapping("/excelUpload")
        public Result excelUpload(@RequestParam("uploadExcel") MultipartFile file) throws Exception {
            if(file.getOriginalFilename().equals("")){
                return Result.error("没有选中要上传的文件");
            }else {
                String picName = UUID.randomUUID().toString();
                String oriName = file.getOriginalFilename();
                String extName = oriName.substring(oriName.lastIndexOf("."));
                String newFileName = picName + extName;
                File targetFile = new File(excel, newFileName);
                // 保存文件
                file.transferTo(targetFile);
                return Result.ok().put("data",newFileName);
            }
        }
    
    
    
        /**
         * 实现数据库新增表中数据
         * 数据导入操作
         * @param fileName
         * @param session
         * @return
         */
        @PostMapping("/parsefile/{fileName}")
        public Result parsefile(@PathVariable("fileName") String fileName,HttpSession session){
            User user = (User) session.getAttribute("user");
            //POIFSFileSystem 是 Apache POI 库中的核心类
            //专门用于处理 OLE 2 Compound Document Format(微软复合文档格式)
            //是 Java 开发中处理传统 Microsoft Office 文档(如 .xls, .doc, .ppt)的基础组件
            POIFSFileSystem fs = null;
            //HSSFWorkbook 是 Apache POI 库中处理 Microsoft Excel 格式(.xls 文件)的核心类
            // 作为 Horrible SpreadSheet Format 的缩写,它提供了完整的 API 来创建、读取和修改传统 Excel
            // 二进制文件。
            HSSFWorkbook wb = null;
            try {
                String basePath = excel + fileName;
                fs = new POIFSFileSystem(new FileInputStream(basePath));
                wb = new HSSFWorkbook(fs);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //这段代码的核心功能是从Excel中提取数据到二维数组,特别处理了第一列的数值转换。
            HSSFSheet sheet = wb.getSheetAt(0);// 获取工作簿中的第一个工作表
            Object[][] data = null;// 声明二维数组用于存储数据
            int r = sheet.getLastRowNum()+1;// 获取总行数(索引从0开始)
            int c = sheet.getRow(0).getLastCellNum();// 获取第一行的列数
            int headRow = 2;// 表头行数(跳过前2行)
            data = new Object[r - headRow][c];// 创建二维数组(行数=总行数-表头行)
            for (int i = headRow; i < r; i++) {// 从第3行开始(跳过表头)
                HSSFRow row = sheet.getRow(i); // 获取当前行
                for (int j = 0; j < c; j++) {// 遍历每列
                    HSSFCell cell = null;
                    try {
                        cell = row.getCell(j); // 获取单元格
                        try {
                            cell = row.getCell(j);
                            DataFormatter dataFormater = new DataFormatter();//使用DataFormatter将单元格值统一转为字符串
                            String a = dataFormater.formatCellValue(cell); // 格式化单元格值为字符串
                            data[i - headRow][j] = a;// 存储到数组
                        } catch (Exception e) {
                            // 异常处理:当单元格读取失败时
                            data[i-headRow][j] = ""; // 先设置为空字符串
                            // 特殊处理第一列(索引0)
                            if(j==0){
                                try {
                                    double d = cell.getNumericCellValue();// 尝试获取数值
                                    data[i - headRow][j] = (int)d + "";// 转换为整数后存储为字符串
                                }catch(Exception ex){
                                    data[i-headRow][j] = "";// 如果转换失败,保持为空
                                }
                            }
                        }
                    } catch (Exception e) {
                        System.out.println("i="+i+";j="+j+":"+e.getMessage());
                    }
                }
            }
    
            int row = data.length;
            int col = 0;
            String errinfo = "";
            headRow = 3;
            //String[] stitle={"ID","小区名称","所属楼栋","房号","姓名","性别","手机号码","居住性质","状态","备注"};
            errinfo = "";
            for (int i = 0; i < row; i++) {
                Person single = new Person();
                single.setPersonId(0);
                single.setState(1);
                single.setFaceUrl("");
                try {
                    col=1;
                    String communityName = data[i][col++].toString();
                    QueryWrapper<Community> queryWrapper = new QueryWrapper<>();
                    queryWrapper.eq("community_name", communityName);
                    Community community = this.communityService.getOne(queryWrapper);
                    if( community == null){
                        errinfo += "Excel文件第" + (i + headRow) + "行小区名称不存在!";
                        return Result.ok().put("status", "fail").put("data", errinfo);
                    }
                    single.setCommunityId(community.getCommunityId());
                    single.setTermName(data[i][col++].toString());
                    single.setHouseNo(data[i][col++].toString());
                    single.setUserName(data[i][col++].toString());
                    single.setSex(data[i][col++].toString());
                    single.setMobile(data[i][col++].toString());
                    single.setPersonType(data[i][col++].toString());
                    single.setRemark(data[i][col++].toString());
                    single.setCreater(user.getUsername());
                    this.personService.save(single);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return Result.ok().put("status", "success").put("data","数据导入完成!");
        }

    1. 导出数据到 Excel

    @GetMapping("/exportExcel")
    public Result exportExcel(PersonListForm personListForm)

    执行流程:

    1.获取数据

    调用 personService.personList(personListForm) 按条件分页查询数据,得到 PageVO。

    从 PageVO 中取出 list(这就是符合条件的人员数据)。

    2.生成 Excel 文件

    ExcelUtil.ExpPersonInfo(list, path) 将数据写入 Excel,并返回 Excel 文件的保存路径。

    3.返回结果

    Result.ok().put("data", path) 把文件路径返回给前端。前端可以用这个路径下载 Excel 文件。

    2. 上传 Excel 文件

    @PostMapping("/excelUpload")
    public Result excelUpload(@RequestParam("uploadExcel") MultipartFile file)

    执行流程:

    1.检查文件是否选择

    如果 file.getOriginalFilename() 是空字符串,就直接返回 "没有选中要上传的文件"。

    2.生成新文件名

    用 UUID.randomUUID().toString() 生成一个不重复的文件名。获取原文件后缀(比如 .xls / .xlsx),拼接成新的文件名。

    3.保存文件到服务器

    File targetFile = new File(excel, newFileName) 创建目标文件对象(excel 是存放目录)。file.transferTo(targetFile) 把上传的文件保存到指定目录。

    4.返回结果

    把新文件名返回给前端,用于后续解析。

    3. 从 Excel 导入数据到数据库

    @PostMapping("/parsefile/{fileName}")
    public Result parsefile(@PathVariable("fileName") String fileName,HttpSession session)

    执行流程:

    步骤 1:准备文件读取
    1. 从 session 获取当前登录用户。
    2. 组合文件路径 basePath = excel + fileName。
    3. 用 Apache POI 打开 .xls 文件:
      1. POIFSFileSystem → 处理 Excel 二进制流。
      2. HSSFWorkbook → 代表 Excel 工作簿对象。
    步骤 2:把 Excel 转成二维数组
    1. 取第一个工作表:wb.getSheetAt(0)。
    2. 获取总行数和总列数。
    3. 设定 跳过表头(这里 headRow = 2,意思是前两行是表头,不读)。
    4. 创建二维数组 data[r - headRow][c] 用于存储表格内容。
    5. 循环每一行、每一列:
      1. 用 DataFormatter 把单元格值统一转成字符串

      2. 如果读取失败:

        1. 先置为空字符串

        2. 如果是第一列(可能是数字 ID),尝试用 getNumericCellValue() 转成整数,再转字符串

    步骤 3:解析数据并入库
    1. 历 data 数组的每一行:
      1. 创建一个新的 Person 对象
      2. 固定字段初始化:personId=0、state=1、faceUrl=""
    2. 取出小区名称 communityName,去 communityService 查询对应的小区 ID:
      1. 如果不存在,立即返回错误 "Excel文件第X行小区名称不存在!"
    3. 填充其他字段(楼栋名、房号、姓名、性别、手机号、居住性质、备注等)。
    4. 设置 creater 为当前登录用户。
    5. 调用 personService.save(single) 把这条数据插入数据库。
    步骤 4:返回结果

    如果所有数据都成功导入,返回:

    {
      "status": "success",
      "data": "数据导入完成!"
    }

    如果中途发现错误(比如小区不存在),提前返回 "status": "fail" 和错误信息


    网站公告

    今日签到

    点亮在社区的每一天
    去签到