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 读文件并返回。
代码整体流程(控制层 + 落盘)
- 接收请求(/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" 和错误信息