上传 Excel 文件进行数据库比对

运行环境:

Spring Boot + MySQL + MyBatis

实现效果:

  1. 用户上传一个 Excel 文件(.xlsx);
  2. 程序读取 Excel 中的"身份证号码"列;
  3. 与数据库中某张表(例如 user_info 表)中的 id_card 字段进行比对;
  4. 返回匹配的数据列表。

一、依赖配置(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/

相关推荐
山峰哥12 小时前
SQL调优实战指南:从索引设计到高并发场景优化全链路解析
大数据·汇编·数据库·sql·编辑器·区块链
+VX:Fegn089512 小时前
计算机毕业设计|基于springboot + vue在线教育学习系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·学习·课程设计
KG_LLM图谱增强大模型12 小时前
企业级实用本体论及构建指南系列(1/4):Palantir 数据建模的哲学与实践
数据库·oracle·palantir
记得开心一点嘛13 小时前
使用ShardingSphere进行分库分表
数据库·mysql
CrazyClaz13 小时前
NewSQL数据库TiDB
数据库·tidb
lambo mercy13 小时前
python入门
前端·数据库·python
IT技术分享社区13 小时前
从删库到恢复:MySQL Binlog实战手册
数据库·mysql·程序员
小李云雾14 小时前
Python 多任务编程入门:进程的创建、同步与进程池使用
开发语言·数据库·python·oracle
AI题库14 小时前
PostgreSQL 18 从新手到大师:实战指南 - 2.6 PostgreSQL管理工具
数据库·postgresql