SpringBoot + EasyExcel 实现导入Excel并支持Excel中图片也能导入

在一个项目中,特别是互联网项目,基础数据的录入是相当头疼的一件事;比如,定义产品、产品规格、产品规格颜色;想像一下,用户先创建一个产品,再为这个产品创建N个规格,再为N个规格创建不同的颜色;理论上是没有任何问题的,但实际上用户将非常痛苦;那么EasyExcel无疑是一个不错的选择;

那么,新的问题又来了,Excel纯文本内容导入,但相关产品图片却需要后期一个一个补进去,如果能连图片一并导入岂不是更好;EasyExcel仅支持文本读取,无法对EXCEL中的图片进行处理(把图片提取出来存到服务 器的指定位置);

本文我们将来讨论并实现它们。

实现 SpringBoot + EasyExcel导入功能

1.项目目录

2.pom文件

html 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo1</name>
    <description>demo1</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- EasyExcel -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!-- SpringDoc OpenAPI (Swagger) -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.3.0</version>
        </dependency>

        <!-- Validation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3.项目配置

html 复制代码
spring:
  application:
    name: demo1
  servlet:
    multipart:
      max-file-size: 1MB
      max-request-size: 1MB
springdoc:
  api-docs:
    path: /api-docs
  swagger-ui:
    path: /swagger-ui.html
    operations-sorter: alpha
    tags-sorter: alpha
  default-consumes-media-type: application/json

server:
  port: 9999

4.实现代码

4.1 Excel映射类(用来与Excel对应)

java 复制代码
package com.example.demo.entity;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class UserExcel {
    @ExcelProperty("姓名")
    private String name;

    @ExcelProperty("年龄")
    private Integer age;

    @ExcelProperty("邮箱")
    private String email;

    @ExcelProperty("部门")
    private String department;

}

4.2 服务类(直接向数据库中写入)

接口

java 复制代码
package com.example.demo.service;

import com.example.demo.entity.UserExcel;

import java.util.List;

public interface ExcelDataService {

    void save(List<UserExcel> dataList);
    List<UserExcel> findAll();
}

实现

java 复制代码
package com.example.demo.service.impl;

import com.example.demo.entity.UserExcel;
import com.example.demo.service.ExcelDataService;
import org.springframework.stereotype.Service;

import java.util.List;
@Service
public class ExcelDataServiceImpl implements ExcelDataService {
    @Override
    public void save(List<UserExcel> dataList) {
        System.out.println("服务层保存数据: " + dataList);

    }

    @Override
    public List<UserExcel> findAll() {
        return List.of();
    }
}

4.3 监听类(监听可以做来源数据进行过滤等操作)

java 复制代码
package com.example.demo.entity;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.example.demo.service.ExcelDataService;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

/**
 * Excel监听器
 */
@Slf4j
public class UserExcelListener implements ReadListener<UserExcel> {
    /**
     * 每隔100条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;

    private final ExcelDataService excelDataService;
    /**
     * 缓存的数据
     */
    private List<UserExcel> cachedDataList = new ArrayList<>(BATCH_COUNT);

    public UserExcelListener(ExcelDataService excelDataService) {
        this.excelDataService = excelDataService;
    }

    @Override
    public void invoke(UserExcel userExcel, AnalysisContext analysisContext) {
        System.out.println("读取到数据: " + userExcel);
        log.info("读取到数据: {}", userExcel);
        /**
         * 缓存的数据 进行清洗
         */
        if (cachedDataList.size()==0){
            cachedDataList.add(userExcel);
        }else {
            //校验代码
            cachedDataList.add(userExcel);
        }

    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("读取完成");
        excelDataService.save(cachedDataList);
        System.out.println("读取完成");
        //全部完成后把数据做提交处理
    }
}

4.4 控制层

java 复制代码
package com.example.demo.controller;

import com.alibaba.excel.EasyExcel;
import com.example.demo.entity.UserExcel;
import com.example.demo.entity.UserExcelListener;
import com.example.demo.service.ExcelDataService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@RestController
@RequestMapping("/api/excel")
@Tag(name = "Excel文件处理", description = "Excel文件上传和下载相关接口")
public class UserController {
    @Autowired
    private ExcelDataService excelDataService;
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "上传Excel文件", description = "上传Excel文件并解析其中的数据")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "文件上传成功", 
            content = @Content(mediaType = "application/json",
            schema = @Schema(implementation = String.class))),
        @ApiResponse(responseCode = "400", description = "无效的请求")
    })
    public ResponseEntity<String> uploadExcel(
            @Parameter(description = "Excel文件", required = true,
                content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE,
                schema = @Schema(type = "string", format = "binary")))
            @RequestPart("file") MultipartFile file) {
        try {
            if (file.isEmpty()){
                return ResponseEntity.badRequest().body("文件不能为空");
            }
            String originalFilename = file.getOriginalFilename();
            if (originalFilename == null ||
                    (!originalFilename.toLowerCase().endsWith(".xlsx") &&
                            !originalFilename.toLowerCase().endsWith(".xls"))) {
                return ResponseEntity.badRequest().body("文件格式不正确,请上传.xlsx或.xls文件");
            }
            EasyExcel.read(file.getInputStream(), UserExcel.class, new UserExcelListener((ExcelDataService) excelDataService)).sheet().doRead();

            return ResponseEntity.ok("文件上传成功");

        }catch (Exception e){
            log.error("上传失败", e);
            return ResponseEntity.status(500).body("上传失败: " + e.getMessage());
        }
    }

}

效果

改造升级支持导入EXCEL中的图片

这个是重点,同时融合了前面EasyExcel的使用,本身EasyExcel不支持行内图片的读存,所以需要引用POI依赖进行额外处理。

1.准备资料

1.1 EXCEL数据文件

1.2 配置项目

html 复制代码
server:
  port: 9999

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 24568096
    driver-class-name: com.mysql.cj.jdbc.Driver
  sql:
    init:
      mode: always
      schema-locations: classpath:schema.sql
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      id-type: auto

# 应用自定义配置
app:
  # 图片根目录(相对项目根目录或绝对路径均可)。生产可改为绝对路径。
  file-storage:
    base-dir: /users/images
  import:
    # 邮箱唯一性检查,重复则跳过
    email-unique: true

springdoc:
  swagger-ui:
    path: /swagger-ui.html
  api-docs:
    enabled: true

1.3 POM依赖

html 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.11</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>employee-import</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>employee-import</name>
    <description>Excel 导入员工信息(内嵌图片落地为文件路径)</description>

    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <easyexcel.version>3.3.2</easyexcel.version>
        <springdoc.version>2.1.0</springdoc.version>
    </properties>

    <dependencies>
        <!-- Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Validation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

        <!-- EasyExcel(会自动引入兼容的 POI 版本) -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>${easyexcel.version}</version>
        </dependency>

        <!-- commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.15.1</version>
        </dependency>

        <!-- Lombok(可选) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- OpenAPI/Swagger UI -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

1.4 数据库表

sql 复制代码
CREATE TABLE IF NOT EXISTS employee (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(150) NOT NULL,
    department VARCHAR(100) NULL,
    photo_path VARCHAR(500) NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

1.5 mybatisPlus配置类

java 复制代码
/**
 * MyBatis-Plus 基础配置
 */
@Configuration
@MapperScan("com.example.employeeimport.mapper")
public class MyBatisPlusConfig {

    /**
     * 分页插件(虽本项目未分页使用,但保留基础配置便于扩展)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

2.工具类

2.1 Excel图片处理类

java 复制代码
/**
 * Excel 图片读取工具
 * 专门用于读取 Excel 中的内嵌图片
 */
public class ExcelImageReader {

    /**
     * 读取 Excel 文件中的所有图片
     * 返回 Map: key=行号(从0开始),value=图片字节数组列表
     */
    public static Map<Integer, List<byte[]>> readImages(InputStream inputStream) throws IOException {
        Map<Integer, List<byte[]>> result = new HashMap<>();
        
        try (Workbook workbook = WorkbookFactory.create(inputStream)) {
            // 读取第一个sheet
            workbook.getSheetAt(0);
            
            if (workbook instanceof XSSFWorkbook) {
                XSSFWorkbook xssfWorkbook = (XSSFWorkbook) workbook;
                XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0);
                
                // 获取所有的图片
                XSSFDrawing drawing = xssfSheet.getDrawingPatriarch();
                if (drawing != null) {
                    List<XSSFShape> shapes = drawing.getShapes();
                    for (XSSFShape shape : shapes) {
                        if (shape instanceof XSSFPicture) {
                            XSSFPicture picture = (XSSFPicture) shape;
                            XSSFClientAnchor anchor = (XSSFClientAnchor) picture.getAnchor();
                            
                            // 获取图片所在的行号
                            int rowIndex = anchor.getRow1();
                            
                            // 获取图片数据
                            XSSFPictureData pictureData = picture.getPictureData();
                            byte[] imageBytes = pictureData.getData();
                            
                            // 存储到结果map中
                            result.computeIfAbsent(rowIndex, k -> new ArrayList<>()).add(imageBytes);
                            
                            System.out.println("找到图片:行号=" + rowIndex + ",大小=" + imageBytes.length + " bytes");
                        }
                    }
                }
            }
        }
        
        return result;
    }

    /**
     * 获取指定行的第一张图片
     */
    public static byte[] getImageByRow(Map<Integer, List<byte[]>> imageMap, int rowIndex) {
        List<byte[]> images = imageMap.get(rowIndex);
        if (images != null && !images.isEmpty()) {
            return images.get(0);
        }
        return null;
    }
}

2.2 文件存储类

java 复制代码
/**
 * 文件存储工具:用于保存图片到按日期分目录,并返回相对路径
 */
public class FileStorageUtil {

    /**
     * 根据图片字节流简单判断扩展名(仅支持 jpg/png/gif,默认 jpg)
     */
    public static String detectImageExt(byte[] bytes) {
        if (bytes == null || bytes.length < 4) return "jpg";
        int b0 = bytes[0] & 0xFF;
        int b1 = bytes[1] & 0xFF;
        int b2 = bytes[2] & 0xFF;
        int b3 = bytes[3] & 0xFF;
        // PNG: 89 50 4E 47
        if (b0 == 0x89 && b1 == 0x50 && b2 == 0x4E && b3 == 0x47) return "png";
        // JPG: FF D8
        if (b0 == 0xFF && b1 == 0xD8) return "jpg";
        // GIF: 47 49 46
        if (b0 == 0x47 && b1 == 0x49 && b2 == 0x46) return "gif";
        return "jpg";
    }

    /**
     * 将字节保存到以日期分组的目录中,返回相对路径(yyyy/MM/dd/filename.ext)
     * baseDir 可以是绝对路径或相对路径;若目录不存在会自动创建
     */
    public static String saveBytesToDateDir(String baseDir, String filename, byte[] bytes) throws IOException {
        LocalDate now = LocalDate.now();
        String datePath = now.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
        File dir = new File(baseDir, datePath);
        if (!dir.exists() && !dir.mkdirs()) {
            throw new IOException("创建目录失败:" + dir.getAbsolutePath());
        }
        File target = new File(dir, filename);
        try (FileOutputStream fos = new FileOutputStream(target)) {
            fos.write(bytes);
        }
        // 返回相对 baseDir 的路径,用于入库
        return datePath + "/" + filename;
    }
}

3.类

3.1 实体类

java 复制代码
/**
 * 员工实体
 * 数据库仅存储图片的文件相对路径(含文件名和扩展名)
 */
@Data
@TableName("employee")
public class Employee {
    /** 主键ID(自增) */
    @TableId(type = IdType.AUTO)
    private Long id;

    /** 姓名 */
    private String name;

    /** 年龄 */
    private Integer age;

    /** 邮箱 */
    private String email;

    /** 部门 */
    private String department;

    /** 员工图片相对路径(例如:yyyy/MM/dd/uuid.png) */
    private String photoPath;
}

3.2 中间类

java 复制代码
@Data
@ExcelIgnoreUnannotated
public class EmployeeExcelDTO {

    /** 姓名(表头:姓名) */
    @ExcelProperty("姓名")
    private String name;

    /** 年龄(表头:年龄) */
    @ExcelProperty("年龄")
    private Integer age;

    /** 邮箱(表头:邮箱) */
    @ExcelProperty("邮箱")
    private String email;

    /** 部门(表头:部门) */
    @ExcelProperty("部门")
    private String department;
}

3.3 MyBtais Plus Mapper

java 复制代码
/**
 * 员工表 Mapper(使用 MyBatis-Plus BaseMapper)
 */
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}

4.服务层

4.1 接口

java 复制代码
public interface EmployeeService {

    /**
     * 从 Excel 导入员工数据(含内嵌图片),并将图片保存到本地目录,仅将相对路径保存到数据库
     *
     * @param file Excel 文件(multipart)
     * @return 失败的错误信息列表(空列表表示全部成功)
     */
    List<String> importFromExcel(MultipartFile file);
}

4.2 接口实现

注意这里的监听类在实现层处理的,而非再做一个监听类

java 复制代码
/**
 * 员工导入服务实现
 */
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {

    /** 图片根目录(相对或绝对),例如 ./uploads/images */
    @Value("${app.file-storage.base-dir:./uploads/images}")
    private String baseDir;

    /** 邮箱是否唯一(重复处理:跳过) */
    @Value("${app.import.email-unique:true}")
    private boolean emailUnique;

    @Resource
    private EmployeeMapper employeeMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<String> importFromExcel(@NotNull MultipartFile file) {
        List<String> errors = new ArrayList<>();
        
        // 第一步:读取所有图片(key=行号,value=图片字节数组列表)
        Map<Integer, List<byte[]>> imageMap = new HashMap<>();
        try (InputStream imageIs = file.getInputStream()) {
            imageMap = ExcelImageReader.readImages(imageIs);
            System.out.println("共读取到 " + imageMap.size() + " 行有图片");
        } catch (Exception e) {
            errors.add("读取图片失败:" + e.getMessage());
            return errors;
        }
        
        // 第二步:读取数据并匹配图片
        try (InputStream is = file.getInputStream()) {
            // 使用 EasyExcel 逐行读取数据
            Map<Integer, List<byte[]>> finalImageMap = imageMap;
            ReadListener<EmployeeExcelDTO> listener = new ReadListener<>() {
                @Override
                public void invoke(EmployeeExcelDTO data, com.alibaba.excel.context.AnalysisContext context) {
                    try {
                        // 基础校验
                        if (data == null) {
                            errors.add("空行");
                            return;
                        }
                        if (data.getName() == null || data.getName().isBlank()) {
                            errors.add("姓名不能为空,行:" + (context.readRowHolder().getRowIndex() + 1));
                            return;
                        }
                        if (data.getAge() == null || data.getAge() < 0 || data.getAge() > 120) {
                            errors.add("年龄不合法,行:" + (context.readRowHolder().getRowIndex() + 1));
                            return;
                        }
                        if (data.getEmail() == null || !data.getEmail().matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$")) {
                            errors.add("邮箱不合法,行:" + (context.readRowHolder().getRowIndex() + 1));
                            return;
                        }
                        if (emailUnique) {
                            Long count = employeeMapper.selectCount(new QueryWrapper<Employee>().eq("email", data.getEmail()));
                            if (count != null && count > 0) {
                                errors.add("邮箱已存在(跳过):" + data.getEmail());
                                return;
                            }
                        }

                        // 处理图片:从 imageMap 中获取当前行的图片
                        String relativePath = null;
                        int currentRow = context.readRowHolder().getRowIndex();
                        byte[] photoBytes = ExcelImageReader.getImageByRow(finalImageMap, currentRow);
                        
                        if (photoBytes != null && photoBytes.length > 0) {
                            String ext = FileStorageUtil.detectImageExt(photoBytes);
                            String filename = UUID.randomUUID().toString().replaceAll("-", "") + "." + ext;
                            relativePath = FileStorageUtil.saveBytesToDateDir(baseDir, filename, photoBytes);
                            System.out.println("保存图片成功:" + relativePath + ",员工:" + data.getName());
                        } else {
                            System.out.println("行 " + (currentRow + 1) + " 的员工 " + data.getName() + " 没有检测到图片");
                        }

                        // 入库 也可以使用一个LIST 批量插入 总之可以在这里实现相关业务逻辑的处理;比如主从表的关联
                        Employee emp = new Employee();
                        emp.setName(data.getName());
                        emp.setAge(data.getAge());
                        emp.setEmail(data.getEmail());
                        emp.setDepartment(Objects.toString(data.getDepartment(), null));
                        emp.setPhotoPath(relativePath);
                        employeeMapper.insert(emp);
                    } catch (Exception e) {
                        errors.add("行处理失败:" + (context.readRowHolder().getRowIndex() + 1) + ",原因:" + e.getMessage());
                    }
                }

                @Override
                public void doAfterAllAnalysed(com.alibaba.excel.context.AnalysisContext context) {
                    // 读取完成回调  一般作为从数据的批量插入
                }
            };

            EasyExcel.read(is, EmployeeExcelDTO.class, listener).sheet().doRead();
        } catch (Exception e) {
            errors.add("读取 Excel 失败:" + e.getMessage());
        }
        return errors;
    }
}

5.控制层

java 复制代码
@RestController
@RequestMapping("/employees")
@Validated
@Tag(name = "员工导入")
public class EmployeeImportController {

    @Resource
    private EmployeeService employeeService;

    /**
     * 导入 Excel(multipart/form-data,字段名 file)
     * Excel 模板表头顺序:姓名、年龄、邮箱、部门、相片(相片为内嵌图片)
     */
    @PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Operation(summary = "导入员工 Excel(内嵌图片将保存为文件路径)")
    public Map<String, Object> importExcel(@RequestPart("file") MultipartFile file) {
        List<String> errors = employeeService.importFromExcel(file);
        Map<String, Object> resp = new HashMap<>();
        resp.put("success", errors.isEmpty());
        resp.put("errors", errors);
        return resp;
    }
}

6.效果

6.1 Swagger

6.2 数据库

6.3 文件

相关推荐
m5655bj8 小时前
如何使用 Python 转换 Excel 工作表到 PDF 文档
开发语言·c#·excel
Eiceblue18 小时前
使用 Java 将 Excel 工作表转换为 CSV 格式
java·intellij-idea·excel·myeclipse
Bianca42719 小时前
Excel正则表达式.获取字符
正则表达式·excel
办公解码器1 天前
Excel怎么在下拉菜单中选择计算方式?
excel
梦里不知身是客111 天前
kettle的mysql 根据条件,导出到不同的excel中
数据库·mysql·excel
J.xx1 天前
在线excel数据导入导出框架
excel
办公解码器2 天前
Excel怎么批量快速修改批注?
excel
我是小邵2 天前
主流数据分析工具全景对比:Excel / Python / R / Power BI / Tableau / Qlik / Snowflake
python·数据分析·excel
乘风!2 天前
前端Jquery,后端Java实现预览Word、Excel、PPT,pdf等文档
pdf·word·excel·jquery