运行环境:
Spring Boot + MySQL + MyBatis
实现效果:
- 用户上传一个 Excel 文件(.xlsx);
- 程序读取 Excel 中的"身份证号码"列;
- 与数据库中某张表(例如 user_info 表)中的 id_card 字段进行比对;
- 返回匹配的数据列表。
一、依赖配置(pom.xml)
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Apache POI for Excel -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.4</version>
</dependency>
<!-- Lombok (optional) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
二、数据库表结构示例(MySQL)
CREATE TABLE user_info (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
id_card VARCHAR(18) UNIQUE
);
三、实体类
package com.example.demo.entity;
import lombok.Data;
@Data
public class UserInfo {
private Long id;
private String name;
private String idCard; // 身份证号码
// 可以添加其他字段
}
四、Mapper 接口UserInfoMapper.java
package com.example.demo.mapper;
import com.example.demo.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper {
@Select({
"<script>",
"SELECT * FROM user_info WHERE id_card IN",
"<foreach collection='idCards' item='idCard' open='(' separator=',' close=')'>",
"#{idCard}",
"</foreach>",
"</script>"
})
List<UserInfo> findByIdCards(@Param("idCards") List<String> idCards);
}
五、Service 层ExcelService.java
package com.example.demo.service;
import com.example.demo.entity.UserInfo;
import com.example.demo.mapper.UserInfoMapper;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
public class ExcelService {
@Autowired
private UserInfoMapper userInfoMapper;
public List<UserInfo> processExcel(MultipartFile file) throws IOException {
List<String> idCards = extractIdCardsFromExcel(file);
if (idCards.isEmpty()) {
return new ArrayList<>();
}
// 去重
Set<String> uniqueIdCards = new HashSet<>(idCards);
return userInfoMapper.findByIdCards(new ArrayList<>(uniqueIdCards));
}
private List<String> extractIdCardsFromExcel(MultipartFile file) throws IOException {
List<String> idCards = new ArrayList<>();
try (InputStream is = file.getInputStream();
Workbook workbook = new XSSFWorkbook(is)) {
Sheet sheet = workbook.getSheetAt(0); // 默认读第一个 sheet
Row headerRow = sheet.getRow(0);
// 查找"身份证号码"列的位置(支持中文列名)
int idCardColumnIndex = -1;
for (Cell cell : headerRow) {
if ("身份证号码".equals(cell.getStringCellValue())) {
idCardColumnIndex = cell.getColumnIndex();
break;
}
}
if (idCardColumnIndex == -1) {
throw new IllegalArgumentException("未找到"身份证号码"列");
}
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
Cell cell = row.getCell(idCardColumnIndex);
if (cell == null) continue;
String idCard = getCellValueAsString(cell).trim();
if (!idCard.isEmpty()) {
idCards.add(idCard);
}
}
}
return idCards;
}
private String getCellValueAsString(Cell cell) {
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
// 身份证可能是数字格式,但应作为字符串处理(避免科学计数法)
cell.setCellType(CellType.STRING);
return cell.getStringCellValue();
default:
return "";
}
}
}
六、ControllerExcelController.java
package com.example.demo.controller;
import com.example.demo.entity.UserInfo;
import com.example.demo.service.ExcelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
@Controller
public class ExcelController {
@Autowired
private ExcelService excelService;
// 首页:上传页面
@GetMapping("/upload")
public String uploadPage() {
return "upload";
}
// 处理上传
@PostMapping("/upload")
public String handleUpload(@RequestParam("file") MultipartFile file, Model model, RedirectAttributes redirectAttributes) {
if (file.isEmpty()) {
model.addAttribute("error", "请选择一个 Excel 文件");
return "upload";
}
try {
List<UserInfo> matchedUsers = excelService.processExcel(file);
model.addAttribute("results", matchedUsers);
model.addAttribute("count", matchedUsers.size());
} catch (IOException e) {
model.addAttribute("error", "文件读取失败:" + e.getMessage());
} catch (IllegalArgumentException e) {
model.addAttribute("error", "Excel 格式错误:" + e.getMessage());
}
return "upload";
}
@GetMapping("/download-template")
public ResponseEntity<Resource> downloadTemplate() {
try {
Resource resource = new ClassPathResource("template.xlsx");
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
// ✅ 安全的中文文件名处理
String filename = "身份证上传模板.xlsx";
String encodedFilename = URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + encodedFilename + "\"; filename*=UTF-8''" + encodedFilename)
.header(HttpHeaders.CONTENT_TYPE,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.body(resource);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
七、前端页面(upload.html)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>身份证比对系统</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.result { margin-top: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background-color: #f4f4f4; }
.error { color: red; }
</style>
</head>
<body>
<h2>上传 Excel 文件进行身份证比对</h2>
<form method="post" enctype="multipart/form-data" action="/upload">
<input type="file" name="file" accept=".xlsx" required />
<button type="submit">上传并比对</button>
</form>
<!-- 下载模板链接 -->
<a href="/download-template" class="download-template">📥 下载模板</a>
<!-- 错误信息 -->
<div th:if="${error}" class="result error" th:text="${error}"></div>
<!-- 匹配结果 -->
<div class="result" th:if="${results != null}">
<h3>匹配结果(共 <span th:text="${count}"></span> 条):</h3>
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>身份证号码</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${results}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.idCard}"></td>
</tr>
</tbody>
</table>
<!-- ✅ 只在有结果时显示清空按钮 -->
<div class="actions">
<button type="button" onclick="window.location.href='/upload'">清空记录</button>
</div>
</div>
</body>
</html>
把
template.xlsx放到src/main/resources/