SpringBoot实现文件上传

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的请求拦截

java 复制代码
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. 浏览器请求

java 复制代码
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 拼到路径末尾,得到:

java 复制代码
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、人脸图片识别并上传

java 复制代码
@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表的导入和导出

java 复制代码
/**
     * 数据的导出功能实现
     * @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

java 复制代码
@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 文件

java 复制代码
@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 导入数据到数据库

java 复制代码
@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:返回结果

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

javascript 复制代码
{
  "status": "success",
  "data": "数据导入完成!"
}

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

相关推荐
uzong2 小时前
认知破局:在信息茧房时代重构后端工程师的思维思维
后端
Lisonseekpan2 小时前
MVCC的底层实现原理是什么?
java·数据库·后端·mysql
灰原喜欢柯南3 小时前
实战:MyBatis 中 db.properties 的正确配置与最佳实践
java·数据库·mybatis
牛马程序员‍3 小时前
Day116 若依融合mqtt
java·mqtt·若依·mqttx
David爱编程4 小时前
Java中main 方法为何必须是static?
java·后端
追梦人物4 小时前
Uniswap 手续费和协议费机制剖析
前端·后端·区块链
小沈同学呀4 小时前
阿里巴巴高级Java工程师面试算法真题解析:LRU Cache实现
java·算法·面试
程序员Forlan4 小时前
SpringBoot查询方式全解析
java·spring boot·后端
我今晚不熬夜5 小时前
使用单调栈解决力扣第42题--接雨水
java·数据结构·算法·leetcode