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的请求拦截
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;@Overridepublic 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 读文件并返回。
代码整体流程(控制层 + 落盘)
- 接收请求(/addPerson)取到 FaceForm(里有 fileBase64, extName, personId)。
- 校验 fileBase64 为空就直接报错返回。
- 开关检测apiConfiguration.isUsed() 控制是否启用人脸识别。
- 调用识别并保存文件(newPerson())
把 Base64 发给第三方人脸 API(faceApi.newperson(...))
成功返回后从响应里拿到 FaceId
用 FaceId + "." + extName 作为文件名,拼成:savePath = faceLocalPath + faceId + "." + extName比如:D:/community/upload/face/ab12cd34.jpg
用 Base64Util.decoderBase64File(fileBase64, savePath) 把 Base64 解码写入磁盘
生成可访问 URL + 更新库
构造 fileName = faceId + "." + extName
组装 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:准备文件读取
- 从 session 获取当前登录用户。
- 组合文件路径 basePath = excel + fileName。
- 用 Apache POI 打开 .xls 文件:
- POIFSFileSystem → 处理 Excel 二进制流。
- HSSFWorkbook → 代表 Excel 工作簿对象。
步骤 2:把 Excel 转成二维数组
- 取第一个工作表:wb.getSheetAt(0)。
- 获取总行数和总列数。
- 设定 跳过表头(这里 headRow = 2,意思是前两行是表头,不读)。
- 创建二维数组 data[r - headRow][c] 用于存储表格内容。
- 循环每一行、每一列:
用 DataFormatter 把单元格值统一转成字符串
如果读取失败:
先置为空字符串
如果是第一列(可能是数字 ID),尝试用 getNumericCellValue() 转成整数,再转字符串
步骤 3:解析数据并入库
- 遍历 data 数组的每一行:
- 创建一个新的 Person 对象
- 固定字段初始化:personId=0、state=1、faceUrl=""
- 取出小区名称 communityName,去 communityService 查询对应的小区 ID:
- 如果不存在,立即返回错误 "Excel文件第X行小区名称不存在!"
- 填充其他字段(楼栋名、房号、姓名、性别、手机号、居住性质、备注等)。
- 设置 creater 为当前登录用户。
- 调用 personService.save(single) 把这条数据插入数据库。
步骤 4:返回结果
如果所有数据都成功导入,返回:
{"status": "success","data": "数据导入完成!"
}
如果中途发现错误(比如小区不存在),提前返回 "status": "fail" 和错误信息