柏竹 柏竹
首页
后端
前端
  • 应用推荐
关于
友链
  • 分类
  • 标签
  • 归档

柏竹

奋斗柏竹
首页
后端
前端
  • 应用推荐
关于
友链
  • 分类
  • 标签
  • 归档
  • Java基础

  • JavaWeb

  • 拓展技术

  • 框架技术

  • 数据库

  • 数据结构

  • Spring

  • SpringMVC

  • SpringBoot

  • SpringClound

  • Ruoyi-Vue-Plus

    • ruoyi-vue-plus-基础功能
    • ruoyi-vue-plus-权限控制
    • ruoyi-vue-plus-表格操作
      • 表格操作
      • 应用流程
      • 字典枚举
        • 字典转换
        • 枚举转换
      • 单元格合并
      • 表格监听
    • ruoyi-vue-plus-缓存功能
    • ruoyi-vue-plus-日志功能
    • ruoyi-vue-plus-线程相关
    • ruoyi-vue-plus-OSS功能
    • ruoyi-vue-plus-代码生成功能
    • ruoyi-vue-plus-多数据源
    • ruoyi-vue-plus-任务调度
    • ruoyi-vue-plus-监控功能
    • ruoyi-vue-plus-国际化
    • ruoyi-vue-plus-XSS功能
    • ruoyi-vue-plus-防重幂&限流 功能
    • ruoyi-vue-plus-推送功能
    • ruoyi-vue-plus-序列化功能
    • ruoyi-vue-plus-数据加密
    • ruoyi-vue-plus-单元测试
    • ruoyi-vue-plus-前端插件
    • ruoyi-vue-plus-前端工具篇
    • ruoyi-vue-plus-部署篇
    • ruoyi-vue-plus-前端篇
    • ruoyi-vue-plus-后端工具篇
    • ruoyi-vue-plus-框架篇
    • ruoyi-vue-plus-问题解决
  • 后端
  • Ruoyi-Vue-Plus
柏竹
2023-11-14
目录

ruoyi-vue-plus-表格操作

# 表格操作

官方文档 : https://easyexcel.opensource.alibaba.com (opens new window)

涉及类

类 说明
ExcelUtil 通用工具类
CellMerge -> CellMergeStrategy 单元格合并注解
ExcelDictFormat -> ExcelDictConvert 字典类格式化注解
ExcelEnumFormat -> ExcelEnumConvert 枚举类格式化注解
ExcelBigNumberConvert 解决单元格整数转化 (防失真)
ExcelListener 表格监听
ExcelResult 表格响应对象

# 应用流程

  1. 获取数据集合
  2. 集合对象类 含有 @ExcelIgnoreUnannotated 和 @ExcelProperty标识列属性名称
  3. 获取导出的文件输出流
  4. 导入数据至输输出流
  5. 导出...

提示

  • 如果指定数据集合的类没有采用上方两个注解默认采用方法名称
  • 文件输出流导出一定要关闭 , 否则无法访问

简单应用

@Test
void test() {
    List<TestUser> userlist = new ArrayList<TestUser>() {{
        add(new TestUser("张三", 18, "0", new Date()));
        add(new TestUser("李四", 20, "1", new Date()));
        add(new TestUser("王五", 21, "0", new Date()));
    }};

    // 获取文件流 输出
    File file = new File("E:\\DOTO\\users.xlsx");
    // 一定要关闭流 (以下使用方式会自动关闭)
    try(BufferedOutputStream outputStream = FileUtil.getOutputStream(file)) {
        ExcelUtil.exportExcel(userlist,"users", TestUser.class, outputStream);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

TestUser类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ExcelIgnoreUnannotated
public class TestUser {
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "年龄")
    private Integer age;
    @ExcelProperty(value = "性别")
    private String sex;
    @ExcelProperty(value = "生日")
    private Date birthday;
}

# 字典枚举

字典枚举可以理解成表格中单元格的下拉框 , 指定单元格仅能选择下拉框中的内容 , 通过下拉框指定的常量数据进行锁定用户输入的值 , 避免输入下拉框以外的值 . 对于这些问题 , ruoyi 框架有以下两种解决方案

  • 字典转换
  • 枚举转换

主要原理是 根据类实现 Converter接口的 convertToJavaData()/convertToExcelData() 方法转化 , 以下采用若依是实现方案 , 以SysUser使用为例

# 字典转换

Bean读取到字典数据

@ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "sys_user_sex")
private String sex;

@ExcelDictFormat注解的意图 , 标识指定字典的key , 通过key拿到字典响应的字典数据

字符串枚举读取字典数据

@ExcelProperty(value = "用户性别", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "0=男,1=女,2=未知", separator = ",")
private String sex;

字典格式化注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelDictFormat {

    /**
     * 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
     */
    String dictType() default "";

    /**
     * 读取内容转表达式 (如: 0=男,1=女,2=未知)
     */
    String readConverterExp() default "";

    /**
     * 分隔符,读取字符串组内容
     */
    String separator() default StringUtils.SEPARATOR;

}

**ExcelDictConvert通用字典格式转换实现类 **

点击展开
@Slf4j
public class ExcelDictConvert implements Converter<Object> {

    @Override
    public Class<Object> supportJavaTypeKey() {
        return Object.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return null;
    }
    
    /**
     * Excel 转 Java
     * @param cellData            Excel 单元格数据
     * @param contentProperty     当前属性信息
     * @param globalConfiguration 全局配置
     * @return 转化结果对象
     */
    @Override
    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        // 获取注解中的key
        ExcelDictFormat anno = getAnnotation(contentProperty.getField());
        String type = anno.dictType();
        String label = cellData.getStringValue();
        String value;
        // 是否写有标识的key 
        if (StringUtils.isBlank(type)) {
            // 没有key , 采用 readConverterExp , 文本形式解析枚举 , 以 "," 分割符 , 例如 "(如: 0=男,1=女,2=未知)"
            value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator());
        } else {
            // 有key , 通过Bean获取
            value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator());
        }
        // 转化对应的属性类型
        return Convert.convert(contentProperty.getField().getType(), value);
    }

    /**
     * Java 转 Excel 
     * @param object              属性值
     * @param contentProperty     当前属性信息
     * @param globalConfiguration 全局配置
     * @return 转化结果 , WriteCellData类封装
     */
    @Override
    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        if (ObjectUtil.isNull(object)) {
            return new WriteCellData<>("");
        }
        ExcelDictFormat anno = getAnnotation(contentProperty.getField());
        String type = anno.dictType();
        String value = Convert.toStr(object);
        String label;
        if (StringUtils.isBlank(type)) {
            label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator());
        } else {
            label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator());
        }
        return new WriteCellData<>(label);
    }
	
    /**
     * 获取注解信息
     */
    private ExcelDictFormat getAnnotation(Field field) {
        return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class);
    }
}

# 枚举转换

采用status属性做示例

@ExcelProperty(value = "用户类型", index = 1, converter = ExcelEnumConvert.class)
@ExcelEnumFormat(enumClass = UserStatus.class, textField = "info")
private String userStatus;

UserStatus 枚举 只有两个属性 code 枚举常量值 , info 可查阅的值

枚举转化注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelEnumFormat {

    /**
     * 字典枚举类型
     */
    Class<? extends Enum<?>> enumClass();

    /**
     * 字典枚举类中对应的code属性名称,默认为code
     */
    String codeField() default "code";

    /**
     * 字典枚举类中对应的text属性名称,默认为text
     */
    String textField() default "text";
}

ExcelEnumConvert通用枚举格式转换实现类

点击展开
@Slf4j
public class ExcelEnumConvert implements Converter<Object> {

    @Override
    public Class<Object> supportJavaTypeKey() {
        return Object.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return null;
    }

    /**
     * Excel 转 Java
     * @param cellData            Excel 单元格数据
     * @param contentProperty     当前属性信息
     * @param globalConfiguration 全局配置
     * @return 转化结果对象
     */
    @Override
    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        cellData.checkEmpty();
        // Excel中填入的是枚举中指定的描述
        Object textValue = null;
        // 判断单元格类型
        switch (cellData.getType()) {
            case STRING:
            case DIRECT_STRING:
            case RICH_TEXT_STRING:
                textValue = cellData.getStringValue();
                break;
            case NUMBER:
                textValue = cellData.getNumberValue();
                break;
            case BOOLEAN:
                textValue = cellData.getBooleanValue();
                break;
            default:
                throw new IllegalArgumentException("单元格类型异常!");
        }
        // 如果是空值
        if (ObjectUtil.isNull(textValue)) {
            return null;
        }
        // 通过反射读取枚举信息
        Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
        // 从Java输出至Excel是code转text
        // 因此从Excel转Java应该将text与code对调
        Map<Object, Object> enumTextToCodeMap = new HashMap<>();
        enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key));
        // 应该从text值 -> code中查找
        Object codeValue = enumTextToCodeMap.get(textValue);
        return Convert.convert(contentProperty.getField().getType(), codeValue);
    }

    /**
     * Java 转 Excel
     * @param object              属性值
     * @param contentProperty     当前属性信息
     * @param globalConfiguration 全局配置
     * @return 转化结果 , WriteCellData类封装
     */
    @Override
    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        if (ObjectUtil.isNull(object)) {
            return new WriteCellData<>("");
        }
        // 通过反射读取枚举信息
        Map<Object, String> enumValueMap = beforeConvert(contentProperty);
        String value = Convert.toStr(enumValueMap.get(object), "");
        return new WriteCellData<>(value);
    }

    /**
     * 获取枚举信息
     */
    private Map<Object, String> beforeConvert(ExcelContentProperty contentProperty) {
        ExcelEnumFormat anno = getAnnotation(contentProperty.getField());
        Map<Object, String> enumValueMap = new HashMap<>();
        Enum<?>[] enumConstants = anno.enumClass().getEnumConstants();
        for (Enum<?> enumConstant : enumConstants) {
            Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
            String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField());
            enumValueMap.put(codeValue, textValue);
        }
        return enumValueMap;
    }
    
    private ExcelEnumFormat getAnnotation(Field field) {
        return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class);
    }
}

# 单元格合并

意图 : 导出对象时将列中的相同字段值进行合并

应用

  1. 对导出的对象指定字段属性加上 @CellMerge注解
  2. 在工具类上的方法含有 布尔值merge参数 设为true实现

提示

CellMerge注解中 有 index 属性 , 该属性用于控制对第几列进行合并 (一般情况不用管)

# 表格监听

实现表监听 , 继承 AnalysisEventListener类 和 直接实现 ExcelListener接口 , 监听表格的目的

  1. 创建监听类 , 并且继承AnalysisEventListener类 和 直接实现 ExcelListener接口
  2. 根据以下解析器执行时段的方法进行处理逻辑
  3. 执行流读取表格 , ExcelUtil类的 *importExcel()*方法实现监听解析

默认解析监听器

点击展开
@Slf4j
@NoArgsConstructor
public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements ExcelListener<T> {

    /**
     * 直接实现 ExcelListener , 虽然 AnalysisEventListener 也实现了 AnalysisEventListener
     */

    /**
     * 是否Validator检验,默认为是
     */
    private Boolean isValidate = Boolean.TRUE;

    /**
     * excel 表头数据
     */
    private Map<Integer, String> headMap;

    /**
     * 导入回执
     */
    private ExcelResult<T> excelResult;

    public DefaultExcelListener(boolean isValidate) {
        this.excelResult = new DefaultExcelResult<>();
        this.isValidate = isValidate;
    }

    /**
     * 处理异常
     *
     * @param exception ExcelDataConvertException
     * @param context   Excel 上下文
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        String errMsg = null;

        // 转化解析异常
        if (exception instanceof ExcelDataConvertException) {
            // 如果是某一个单元格的转换异常 能获取到具体行号
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            Integer rowIndex = excelDataConvertException.getRowIndex();
            Integer columnIndex = excelDataConvertException.getColumnIndex();
            errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常<br/>",
                rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
            if (log.isDebugEnabled()) {
                log.error(errMsg);
            }
        }

        // 约束异常
        if (exception instanceof ConstraintViolationException) {
            ConstraintViolationException constraintViolationException = (ConstraintViolationException) exception;
            Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
            String constraintViolationsMsg = StreamUtils.join(constraintViolations, ConstraintViolation::getMessage, ", ");
            errMsg = StrUtil.format("第{}行数据校验异常: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg);
            if (log.isDebugEnabled()) {
                log.error(errMsg);
            }
        }
        excelResult.getErrorList().add(errMsg);
        throw new ExcelAnalysisException(errMsg);
    }

    /**
     * 解析表头数据
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        this.headMap = headMap;
        log.debug("解析到一条表头数据: {}", JsonUtils.toJsonString(headMap));
    }

    /**
     * 解析行数据
     */
    @Override
    public void invoke(T data, AnalysisContext context) {
        // 对象校验 , 注解校验
        if (isValidate) {
            ValidatorUtils.validate(data);
        }
        // 添加至结果集
        excelResult.getList().add(data);
    }

    /**
     * 解析完执行
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.debug("所有数据解析完成!");
    }

    /**
     * 读取监听到的数据
     */
    @Override
    public ExcelResult<T> getExcelResult() {
        return excelResult;
    }

}
#ruoyi-vue-plus

← ruoyi-vue-plus-权限控制 ruoyi-vue-plus-缓存功能→

最近更新
01
HTTPS自动续签
10-21
02
博客搭建-简化版(脚本)
10-20
03
ruoyi-vue-plus-部署篇
07-13
更多文章>
Theme by Vdoing | Copyright © 2019-2024 | 桂ICP备2022009417号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式