Java 通过POI快速导入带图片的excel并且图片不会丢失

这篇博客主要介绍了如何使用Java的POI库高效地导入带有图片的Excel文件,解决了图片丢失的问题。通过自定义注解将Excel数据映射到实体类,详细讲解了接口定义、实现方法以及关键的Excel读取工具类。在导入过程中,确保图片位置正确以避免读取失败,同时提供了自定义注解的实现,帮助进行数据转换。
摘要由CSDN通过智能技术生成
						## 通过POI快速导入带图片的excel并且图片不会丢失

导入带图片的excel,这里也是研究了很久,在之前的博客中也有说明过,在项目使用过程中,发现很多时候导入响应很慢,而且每次导入图片都会丢失几张,所以又花了点时间研究修改了下,具体如下:

这边在导入时,通过自定义注解,将excel读取到的数据转换到需要的实体类中,然后进行后续处理。
接下来看下面说明:

  1. excel模板如下:

在这里插入图片描述

  1. 导入poi依赖

		<dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>
  1. 接口如下

@PostMapping("/import")
    public CommonResult importData(@RequestParam("file") MultipartFile[] files){
        MultipartFile file = files[0];
        String fileName = file.getOriginalFilename();
        // 上传文件为空
        if (StringUtils.isEmpty(fileName)) {
            throw new ServiceException("没有导入文件");
        }
        // 上传文件名格式不正确
        if (fileName.lastIndexOf(".") != -1 && !".xlsx".equals(fileName.substring(fileName.lastIndexOf(".")))) {
            throw new ServiceException( "文件名格式不正确, 请使用后缀名为.xlsx的文件");
        }
        dataService.importData(file);
        return CommonResult.success();
    }

这里的CommonResult是自定义的返回类,ServiceException自定义异常,dataService是实现方法

  1. 实现方法如下:

@Override
    public void importData(MultipartFile file) {
        ExcelImportByPicture<DetailsDto> testImport = new ExcelImportByPicture(DetailsDto.class);
        List<DetailsDto> detailsDtos;
        try {
            detailsDtos= testImport.readExcelImageAndData(file, Constants.THREE);
        } catch (Exception e) {
            log.error("导入数据解析流转为DetailsDto失败:{}", e.getMessage());
            throw new ServiceException("导入数据文件解析失败!");
        }
        //TODO 封装实体数据和文件上传
        log.info("获取到的数据:{}", detailsDtos);
    }

这里的ExcelImportByPicture工具类就是数据读取和处理的工具类,先读取数据,再转换成这里要映射的实体类,返回给detailsDtos。就能看到最终读取的数据。
这里的Constants.THREE = 3,也就是标题及其它信息所占用的行,没有标题,只有抬头行,直接传入0就可。可根据自己实际情况修改。

  1. 请求参数类:

@Data
public class DetailsDto implements Serializable {

    /**
     * 序号
     */
    @Excel(name = "序号")
    private String no;

    /**
     * 案件编号
     */
    @Excel(name = "案件编号")
    private String num;

    /**
     * 道路名称
     */
    @Excel(name = "位置")
    private String address;

    /**
     * 具体点位
     */
    @Excel(name = "具体点位")
    private String point;

    /**
     * 存在问题
     */
    @Excel(name = "存在问题")
    private String problem;

    /**
     * 病害来源
     */
    @Excel(name = "问题来源")
    private String source;

    /**
     * 发现时间
     */
    @Excel(name = "发现时间")
    private Date findTime;

    /**
     * 修复时间
     */
    @Excel(name = "时间")
    private Date time;

    /**
     * 维修面积
     */
    @Excel(name = "大小")
    private Double area;

    /**
     * 维护前照片
     */
    @Excel(name = "照片1", getPicture = true)
    private PictureData beforeimg;

    /**
     * 维护后照片
     */
    @Excel(name = "照片2", getPicture = true)
    private PictureData afterimg;

    /**
     * 备注
     */
    @Excel(name = "备注")
    private String remark;

    private static final long serialVersionUID = 1L;
}

这里的@excel就是自定义的注解信息,name需要和模板中的抬头列名字一样,便于后面数据转换,getPicture表示为图片列。

  1. 自定义注解类

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel
{

    /**
     * 导出到Excel中的名字.
     */
    public String name() default "";

    /**
     * 日期格式, 如: yyyy-MM-dd
     */
    public String dateFormat() default "";

    /**
     * 读取图片 true=是,false=否)
     */
    public boolean getPicture() default false;
    

    /**
     * 当值为空时,字段的默认值
     */
    public String defaultValue() default "";

    /**
     * 设置只能选择不能输入的列内容.
     */
    public String[] combo() default {};

    /**
     * 自定义数据处理器参数
     */
    public String[] args() default {};

    /**
     * 字段类型(0:导出导入;1:仅导出;2:仅导入)
     */
    Type type() default Type.ALL;

    public enum Type
    {
        ALL(0), EXPORT(1), IMPORT(2);
        private final int value;

        Type(int value)
        {
            this.value = value;
        }

        public int value()
        {
            return this.value;
        }
    }
}

这里可以定义你需要的注解参数,然后在工具类进行具体业务实现。

  1. excel读取工具类:

public class ExcelImportByPicture<T> {

    private static final Logger log = LoggerFactory.getLogger(ExcelImportByPicture.class);

    /**
     * 导出类型(EXPORT:导出数据;IMPORT:导入模板)
     */
    private Excel.Type type;

    /**
     * 实体对象
     */
    public Class<T> clazz;

    public ExcelImportByPicture(Class<T> clazz) {
        this.clazz = clazz;
    }

    /**
     * @description: 获取excel上传中的图片及数据信息
     * @param: [file, titleNum]
     * @return: java.util.List<T>
     * @date: 2023/11/9 15:51
     * @version: 1.0
     **/
    public List<T> readExcelImageAndData(MultipartFile file, int titleNum) throws InstantiationException, IllegalAccessException {
        // 读取上传excel
        Workbook wb = readExcel(file);
        //Workbook wb = WorkbookFactory.create(file.getInputStream());
        if (wb == null) {
            return null;
        }
        // 获取当前页,这里如果有多页时可以获取更多的页,通过循环获取每页的数据
        // 获取总页数:wb.getNumberOfSheets(),获取某一个:wb.getSheet(sheetName)
        Sheet sheet = wb.getSheetAt(Constants.ZERO);
        //1:获取图片集合。行-列为key
        Map<String, PictureData> pictureDataMap = readExcelPicture(sheet);
        //2:获取excel中的数据(这里的数据不含图片信息)
        // 将图片信息传入,再通过实体字段属性将每个数据映射到字段上,包括获取到的图片信息
        return readExcelInfoAndPutClass(sheet, titleNum, pictureDataMap);
    }





    /**
     * @description: 将图片信息传入,再通过实体字段属性将每个数据映射到字段上,包括获取到的图片信息
     * @param: [sheet, titleNum, pictureDataMap]
     * @return: java.util.List<T>
     * @date: 2023/11/9 16:42
     * @version: 1.0
     **/
    private List<T> readExcelInfoAndPutClass(Sheet sheet, int titleNum, Map<String, PictureData> pictureDataMap) throws InstantiationException, IllegalAccessException {
        //存储实体list
        List<T> list = new ArrayList<>();
        //获取每个抬头及对应的实体字段进行映射到相应的下标上
        Map<Integer, Object[]> fieldsMap = getFieldsMap(sheet, titleNum);
        // 获取不为空的总行数
        int rowSize = sheet.getPhysicalNumberOfRows();
        // 遍历每一行,获取除了图片信息外的字段信息
        for (int rowNum = titleNum + 1; rowNum < rowSize; rowNum++) {
            Row row = sheet.getRow(rowNum);
            if (isRowEmpty(row)) {
                continue;
            }
            //建立所映射的实体对象
            T entity = null;
            for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet()) {
                Object val = row.getCell(entry.getKey());
                // 如果不存在实例则新建.
                entity = (entity == null ? clazz.newInstance() : entity);
                // 从map中得到对应列的field.
                Field field = (Field) entry.getValue()[0];
                Excel attr = (Excel) entry.getValue()[1];
                // 取得类型,并根据对象类型设置值.
                Class<?> fieldType = field.getType();
                //判断自定义属性并设置相关信息
                putValByCustomAttribute(fieldType, field, attr, rowNum, val, entry, pictureDataMap, entity, row);
            }
            list.add(entity);
        }
        return list;
    }

    /**
     * @description: 根据自定义属性设置相应的值
     * @param: [fieldType, field, attr, rowNum, val, entry, pictureDataMap, entity]
     * @return: void
     * @date: 2023/11/9 16:20
     * @version: 1.0
     */
    private void putValByCustomAttribute(Class<?> fieldType, Field field, Excel attr,
                                         int rowNum, Object val, Map.Entry<Integer, Object[]> entry,
                                         Map<String, PictureData> pictureDataMap, T entity, Row row) {
        //判断字段的类型来设置正确的值
        if (String.class == fieldType) {
            String s = Convert.toStr(val);
            if (StringUtils.endsWith(s, ".0")) {
                val = StringUtils.substringBefore(s, ".0");
            } else {
                String dateFormat = field.getAnnotation(Excel.class).dateFormat();
                if (StringUtils.isNotEmpty(dateFormat)) {
                    val = parseDateToStr(dateFormat, val);
                } else {
                    val = Convert.toStr(val);
                    if (ObjectUtils.isEmpty(val)) {
                        val = null;
                    }
                }
            }
        } else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) {
            val = Convert.toInt(val);
        } else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) {
            val = Convert.toLong(val);
        } else if (Double.TYPE == fieldType || Double.class == fieldType) {
            val = Convert.toDouble(val);
        } else if (Float.TYPE == fieldType || Float.class == fieldType) {
            val = Convert.toFloat(val);
        } else if (BigDecimal.class == fieldType) {
            val = Convert.toBigDecimal(val);
        } else if (Date.class == fieldType) {
            val = row.getCell(entry.getKey()).getDateCellValue();
        } else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) {
            val = Convert.toBool(val, false);
        }
        if (null != fieldType) {
            //自定义属性没有的话可以删除某一个属性判断
            String propertyName = field.getName();
            if (attr.getPicture()) {
                String rowAndCell = rowNum + "-" + entry.getKey();
                PictureData pictureData = pictureDataMap.get(rowAndCell);
                if (Objects.nonNull(pictureData)) {
                    val = pictureData;
                } else {
                    val = null;
                }
            }
            ReflectUtils.invokeSetter(entity, propertyName, val);
        }
    }


    /**
     * @description: 获取每个抬头及对应的实体字段进行映射到相应的下标上
     * @param: [sheet, titleNum]
     * @return: java.util.Map<java.lang.Integer, java.lang.Object [ ]>
     * @date: 2023/11/9 16:05
     * @version: 1.0
     **/
    private Map<Integer, Object[]> getFieldsMap(Sheet sheet, int titleNum) {
        //存储每列标题和每列的下标值
        Map<String, Integer> cellMap = new HashMap<>();
        //获取抬头行
        Row titleRow = sheet.getRow(titleNum);
        //获取标头行的总列数
        int columnSize = sheet.getRow(titleNum).getPhysicalNumberOfCells();
        // 遍历一行中每列值
        for (int cellNum = 0; cellNum < columnSize; cellNum++) {
            Cell cell = titleRow.getCell(cellNum);
            if (cell != null) {
                cellMap.put(cell.toString(), cellNum);
            }
        }
        // 有数据时才处理 得到类的所有field.
        List<Object[]> fields = this.getFields();
        Map<Integer, Object[]> fieldsMap = new HashMap<>();
        for (Object[] objects : fields) {
            Excel attr = (Excel) objects[1];
            Integer column = cellMap.get(attr.name());
            if (column != null) {
                fieldsMap.put(column, objects);
            }
        }
        return fieldsMap;
    }


    /**
     * @description: 获取图片集合
     * @param: [file, sheet]
     * @return: java.util.Map<java.lang.String, org.apache.poi.ss.usermodel.PictureData>
     * @date: 2023/11/7 17:30
     * @version: 1.0
     **/
    private Map<String, PictureData> readExcelPicture(Sheet sheet) {
        // 声明当前页图片的集合
        Map<String, PictureData> sheetImageMap;
        // 获取图片
        try {
            //2003版本的excel,用.xls结尾
            sheetImageMap = getPicturesHSS((HSSFSheet) sheet);
        } catch (Exception ex) {
            try {
                //2007版本的excel,用.xlsx结尾
                sheetImageMap = getPicturesXSS((XSSFSheet) sheet);
            } catch (Exception e) {
                log.error(ex.getMessage());
                throw new ServiceException("解析图片异常!");
            }
        }
        return sheetImageMap;
    }

    /**
     * 获取图片和位置 (xls)
     * @param sheet
     * @return
     * @throws IOException
     */
    private Map<String, PictureData> getPicturesHSS(HSSFSheet sheet) {
        Map<String, PictureData> map = new HashMap<String, PictureData>();
        List<HSSFShape> list = sheet.getDrawingPatriarch().getChildren();
        for (HSSFShape shape : list) {
            if (shape instanceof HSSFPicture) {
                HSSFPicture picture = (HSSFPicture) shape;
                HSSFClientAnchor cAnchor = (HSSFClientAnchor) picture.getAnchor();
                PictureData pdata = picture.getPictureData();
                // 行号-列号
                String key = cAnchor.getRow1() + "-" + cAnchor.getCol2();
                map.put(key, pdata);
            }
        }
        return map;
    }


    /**
     * 获取图片和位置 (xlsx)
     * @param sheet
     * @return
     * @throws IOException
     */
    private Map<String, PictureData> getPicturesXSS(XSSFSheet sheet) {
        Map<String, PictureData> sheetIndexPicMap = new HashMap<String, PictureData>();
        for (POIXMLDocumentPart dr : sheet.getRelations()) {
            if (dr instanceof XSSFDrawing) {
                XSSFDrawing drawing = (XSSFDrawing) dr;
                List<XSSFShape> shapes = drawing.getShapes();
                for (XSSFShape shape : shapes) {
                    XSSFPicture pic = (XSSFPicture) shape;
                    //解决图片空指针报错问题   2021-12-27
                    XSSFClientAnchor anchor = (XSSFClientAnchor) shape.getAnchor();
                    //下面这个处理时间较长
                    //XSSFClientAnchor anchor = pic.getPreferredSize();
                    //CTMarker marker = anchor.getFrom();
                    // 行号-列号,该取用方式列准确率不高
                    //String key = marker.getRow() + "-" + marker.getCol();
                    //行号-列号
                    String key = anchor.getRow1() + "-" + (anchor.getCol2());
                    sheetIndexPicMap.put(key, pic.getPictureData());
                }
            }
        }
        return sheetIndexPicMap;
    }


    /**
     * 读取excel
     * @param file
     * @return
     */
    private Workbook readExcel(MultipartFile file) {
        Workbook wb = null;
        ZipSecureFile.setMinInflateRatio(0);
        if (file == null) {
            return null;
        }
        InputStream is;
        try {
            is = file.getInputStream();
            //2003版本的excel,用.xls结尾
            //得到工作簿
            wb = new HSSFWorkbook(is);
        } catch (Exception ex) {
            log.error(ex.getMessage());
            try {
                //2007版本的excel,用.xlsx结尾
                is = file.getInputStream();
                //得到工作簿
                wb = new XSSFWorkbook(is);
            } catch (IOException e) {
                log.error(ex.getMessage());
            }
        }
        return wb;
    }

    /**
     * 获取字段注解信息
     */
    private List<Object[]> getFields() {
        List<Object[]> fields = new ArrayList<Object[]>();
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        for (Field field : tempFields) {
            // 单注解
            if (field.isAnnotationPresent(Excel.class)) {
                Excel attr = field.getAnnotation(Excel.class);
                if (attr != null && (attr.type() == Excel.Type.ALL || attr.type() == type)) {
                    field.setAccessible(true);
                    fields.add(new Object[]{field, attr});
                }
            }
            // 多注解
            if (field.isAnnotationPresent(Excels.class)) {
                Excels attrs = field.getAnnotation(Excels.class);
                Excel[] excels = (Excel[]) attrs.value();
                for (Excel attr : excels) {
                    if (attr != null && (attr.type() == Excel.Type.ALL || attr.type() == type)) {
                        field.setAccessible(true);
                        fields.add(new Object[]{field, attr});
                    }
                }
            }
        }
        return fields;
    }

    /**
     * 判断是否是空行
     * @param row 判断的行
     * @return
     */
    private boolean isRowEmpty(Row row) {
        if (row == null) {
            return true;
        }
        for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) {
            Cell cell = row.getCell(i);
            if (cell != null && cell.getCellType() != CellType.BLANK) {
                return false;
            }
        }
        return true;
    }

    /**
     * 格式化不同类型的日期对象
     * @param dateFormat 日期格式
     * @param val        被格式化的日期对象
     * @return 格式化后的日期字符
     */
    private String parseDateToStr(String dateFormat, Object val) {
        if (val == null) {
            return "";
        }
        String str;
        if (val instanceof Date) {
            str = new SimpleDateFormat(dateFormat).format((Date) val);
        } else if (val instanceof LocalDateTime) {
            str = new SimpleDateFormat(dateFormat).format(((LocalDateTime) val).atZone(ZoneId.systemDefault()).toInstant());
        } else if (val instanceof LocalDate) {
            LocalDate temporalAccessor = (LocalDate) val;
            LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
            ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
            str = new SimpleDateFormat(dateFormat).format(Date.from(zdt.toInstant()));
        } else {
            str = val.toString();
        }
        return str;
    }
/**
 * Excel注解集
 * 
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Excels
{
    Excel[] value();
}
package cn.goktech.module.utils;

import com.goktech.common.core.text.Convert;
import com.goktech.common.core.utils.DateUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.poi.ss.usermodel.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.*;
import java.util.Date;

/**
 * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
 * 
 * @author ruoyi
 */
@SuppressWarnings("rawtypes")
public class ReflectUtils
{
    private static final String SETTER_PREFIX = "set";

    private static final String GETTER_PREFIX = "get";

    private static final String CGLIB_CLASS_SEPARATOR = "$$";

    private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class);

    /**
     * 调用Getter方法.
     * 支持多级,如:对象名.对象名.方法
     */
    @SuppressWarnings("unchecked")
    public static <E> E invokeGetter(Object obj, String propertyName)
    {
        Object object = obj;
        for (String name : StringUtils.split(propertyName, "."))
        {
            String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
            object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
        }
        return (E) object;
    }

    /**
     * 调用Setter方法, 仅匹配方法名。
     * 支持多级,如:对象名.对象名.方法
     */
    public static <E> void invokeSetter(Object obj, String propertyName, E value)
    {
        Object object = obj;
        String[] names = StringUtils.split(propertyName, ".");
        for (int i = 0; i < names.length; i++)
        {
            if (i < names.length - 1)
            {
                String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
                object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
            }
            else
            {
                String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
                invokeMethodByName(object, setterMethodName, new Object[] { value });
            }
        }
    }

    /**
     * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
     */
    @SuppressWarnings("unchecked")
    public static <E> E getFieldValue(final Object obj, final String fieldName)
    {
        Field field = getAccessibleField(obj, fieldName);
        if (field == null)
        {
            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
            return null;
        }
        E result = null;
        try
        {
            result = (E) field.get(obj);
        }
        catch (IllegalAccessException e)
        {
            logger.error("不可能抛出的异常{}", e.getMessage());
        }
        return result;
    }

    /**
     * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
     */
    public static <E> void setFieldValue(final Object obj, final String fieldName, final E value)
    {
        Field field = getAccessibleField(obj, fieldName);
        if (field == null)
        {
            // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 ");
            return;
        }
        try
        {
            field.set(obj, value);
        }
        catch (IllegalAccessException e)
        {
            logger.error("不可能抛出的异常: {}", e.getMessage());
        }
    }

    /**
     * 直接调用对象方法, 无视private/protected修饰符.
     * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
     * 同时匹配方法名+参数类型,
     */
    @SuppressWarnings("unchecked")
    public static <E> E invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
            final Object[] args)
    {
        if (obj == null || methodName == null)
        {
            return null;
        }
        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
        if (method == null)
        {
            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
            return null;
        }
        try
        {
            return (E) method.invoke(obj, args);
        }
        catch (Exception e)
        {
            String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
            throw convertReflectionExceptionToUnchecked(msg, e);
        }
    }

    /**
     * 直接调用对象方法, 无视private/protected修饰符,
     * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
     * 只匹配函数名,如果有多个同名函数调用第一个。
     */
    @SuppressWarnings("unchecked")
    public static <E> E invokeMethodByName(final Object obj, final String methodName, final Object[] args)
    {
        Method method = getAccessibleMethodByName(obj, methodName, args.length);
        if (method == null)
        {
            // 如果为空不报错,直接返回空。
            logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");
            return null;
        }
        try
        {
            // 类型转换(将参数数据类型转换为目标方法参数类型)
            Class<?>[] cs = method.getParameterTypes();
            for (int i = 0; i < cs.length; i++)
            {
                if (args[i] != null && !args[i].getClass().equals(cs[i]))
                {
                    if (cs[i] == String.class)
                    {
                        args[i] = Convert.toStr(args[i]);
                        if (StringUtils.endsWith((String) args[i], ".0"))
                        {
                            args[i] = StringUtils.substringBefore((String) args[i], ".0");
                        }
                    }
                    else if (cs[i] == Integer.class)
                    {
                        args[i] = Convert.toInt(args[i]);
                    }
                    else if (cs[i] == Long.class)
                    {
                        args[i] = Convert.toLong(args[i]);
                    }
                    else if (cs[i] == Double.class)
                    {
                        args[i] = Convert.toDouble(args[i]);
                    }
                    else if (cs[i] == Float.class)
                    {
                        args[i] = Convert.toFloat(args[i]);
                    }
                    else if (cs[i] == Date.class)
                    {
                        if (args[i] instanceof String)
                        {
                            args[i] = DateUtils.parseDate(args[i]);
                        }
                        else
                        {
                            args[i] = DateUtil.getJavaDate((Double) args[i]);
                        }
                    }
                    else if (cs[i] == boolean.class || cs[i] == Boolean.class)
                    {
                        args[i] = Convert.toBool(args[i]);
                    }
                }
            }
            return (E) method.invoke(obj, args);
        }
        catch (Exception e)
        {
            String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
            throw convertReflectionExceptionToUnchecked(msg, e);
        }
    }

    /**
     * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
     * 如向上转型到Object仍无法找到, 返回null.
     */
    public static Field getAccessibleField(final Object obj, final String fieldName)
    {
        // 为空不报错。直接返回 null
        if (obj == null)
        {
            return null;
        }
        Validate.notBlank(fieldName, "fieldName can't be blank");
        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass())
        {
            try
            {
                Field field = superClass.getDeclaredField(fieldName);
                makeAccessible(field);
                return field;
            }
            catch (NoSuchFieldException e)
            {
                continue;
            }
        }
        return null;
    }

    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
     * 如向上转型到Object仍无法找到, 返回null.
     * 匹配函数名+参数类型。
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
     */
    public static Method getAccessibleMethod(final Object obj, final String methodName,
            final Class<?>... parameterTypes)
    {
        // 为空不报错。直接返回 null
        if (obj == null)
        {
            return null;
        }
        Validate.notBlank(methodName, "methodName can't be blank");
        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass())
        {
            try
            {
                Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
                makeAccessible(method);
                return method;
            }
            catch (NoSuchMethodException e)
            {
                continue;
            }
        }
        return null;
    }

    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
     * 如向上转型到Object仍无法找到, 返回null.
     * 只匹配函数名。
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
     */
    public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum)
    {
        // 为空不报错。直接返回 null
        if (obj == null)
        {
            return null;
        }
        Validate.notBlank(methodName, "methodName can't be blank");
        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass())
        {
            Method[] methods = searchType.getDeclaredMethods();
            for (Method method : methods)
            {
                if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum)
                {
                    makeAccessible(method);
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
     */
    public static void makeAccessible(Method method)
    {
        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
                && !method.isAccessible())
        {
            method.setAccessible(true);
        }
    }

    /**
     * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
     */
    public static void makeAccessible(Field field)
    {
        if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
                || Modifier.isFinal(field.getModifiers())) && !field.isAccessible())
        {
            field.setAccessible(true);
        }
    }

    /**
     * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
     * 如无法找到, 返回Object.class.
     */
    @SuppressWarnings("unchecked")
    public static <T> Class<T> getClassGenricType(final Class clazz)
    {
        return getClassGenricType(clazz, 0);
    }

    /**
     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
     * 如无法找到, 返回Object.class.
     */
    public static Class getClassGenricType(final Class clazz, final int index)
    {
        Type genType = clazz.getGenericSuperclass();

        if (!(genType instanceof ParameterizedType))
        {
            logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType");
            return Object.class;
        }

        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

        if (index >= params.length || index < 0)
        {
            logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
                    + params.length);
            return Object.class;
        }
        if (!(params[index] instanceof Class))
        {
            logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
            return Object.class;
        }

        return (Class) params[index];
    }

    public static Class<?> getUserClass(Object instance)
    {
        if (instance == null)
        {
            throw new RuntimeException("Instance must not be null");
        }
        Class clazz = instance.getClass();
        if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR))
        {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null && !Object.class.equals(superClass))
            {
                return superClass;
            }
        }
        return clazz;

    }

    /**
     * 将反射时的checked exception转换为unchecked exception.
     */
    public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e)
    {
        if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
                || e instanceof NoSuchMethodException)
        {
            return new IllegalArgumentException(msg, e);
        }
        else if (e instanceof InvocationTargetException)
        {
            return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException());
        }
        return new RuntimeException(msg, e);
    }
}

这里就结束了,通过上面的步骤就可以精确获取到每一个图片数据,当然要注意你表格中的图片位置放好了没有,有可能位置不对也会影响读取失败的。
有疑问可留言,看到了就回

有帮到你,给个三连哦!

在使用POI读取Excel时,可以通过 HSSFClientAnchor 类来获取 Excel图片的位置和大小信息,并通过 PictureData 类来获取图片的二进制数据,然后可以将图片保存到本地或者上传到服务器。 以下是一个示例代码: ```java FileInputStream inputStream = new FileInputStream("test.xls"); Workbook workbook = new HSSFWorkbook(inputStream); Sheet sheet = workbook.getSheetAt(0); // 获取所有图片并处理 List<PictureData> pictures = new ArrayList<>(); Map<Integer, PictureData> picturesMap = new HashMap<>(); List<HSSFShape> shapes = ((HSSFSheet) sheet).getDrawingPatriarch().getChildren(); for (HSSFShape shape : shapes) { if (shape instanceof HSSFPicture) { HSSFPicture picture = (HSSFPicture) shape; PictureData pictureData = picture.getPictureData(); pictures.add(pictureData); picturesMap.put(picture.getPictureIndex(), pictureData); } } // 处理单元格中的图片 for (Row row : sheet) { for (Cell cell : row) { if (cell.getCellTypeEnum() == CellType.BLANK) { continue; } if (cell.getCellTypeEnum() == CellType.STRING && StringUtils.isNotBlank(cell.getStringCellValue())) { RichTextString richTextString = cell.getRichStringCellValue(); for (int i = 0; i < richTextString.numFormattingRuns(); i++) { int startIndex = richTextString.getIndexOfFormattingRun(i); int endIndex = startIndex + richTextString.getLengthOfFormattingRun(i); HSSFFont font = (HSSFFont) richTextString.getFontOfFormattingRun(i); if (font.getUnderline() == Font.U_SINGLE && font.getColor() == HSSFColor.AUTOMATIC.index) { for (int j = startIndex; j < endIndex; j++) { HSSFRichTextString hssfRichTextString = (HSSFRichTextString) richTextString; int pictureIndex = hssfRichTextString.getIndexOfFormattingRun(i) / 3; PictureData pictureData = picturesMap.get(pictureIndex); if (pictureData != null) { String fileName = UUID.randomUUID().toString() + "." + pictureData.suggestFileExtension(); byte[] data = pictureData.getData(); // 处理图片数据 } } } } } } } ``` 在上面的代码中,首先获取 Excel 中的所有图片,然后遍历单元格,通过 RichTextString 获取单元格中的所有文本和图片,并通过 HSSFRichTextString.getIndexOfFormattingRun() 方法获取图片在文本中的位置,最后可以通过 HSSFClientAnchor 类获取图片Excel 中的位置和大小信息。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

焚目圣僧渡众生

你的 一角将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值