自定义excel导出模板并导出数据

    功能需求是用户自定义导出模板,可以自定义excel的表头,并且自定义导出的列。

    实现思路通过自动以注解扫描全局实体类,获取注解中标识的业务名称与类名存在数据库中,当用户上传模板时需要选择是哪一个业务名称的功能,将模板与实体类进行关联,导出时利用类的反射机制获取模板关联信息,下载模板并填充数据。

   具体实现:

  • 扫描实体类并插入数据库
@Component
@Slf4j
public class ExcelClassScanner implements CommandLineRunner {

    private final ExcelTemplateMapService excelTemplateMapService;

    public ExcelClassScanner(ExcelTemplateService excelTemplateService, ExcelTemplateMapService excelTemplateMapService) {
        this.excelTemplateMapService = excelTemplateMapService;
    }

    @Override
    public void run(String... args) throws Exception {
        log.debug("开始 -> excel模板,扫描全局获取业务名称与类名");
        initExcelTemplateMap();
        log.debug("完毕 -> excel模板扫描");
    }


    public void initExcelTemplateMap(){
        Set<ExcelTemplateMap> object = ClassScanner.getExcelTemplateMapClazz("com.***.***");//写自己项目的路径
        excelTemplateMapService.intiExcelTemplateMap(object);
        log.debug("共扫描到:" + object.size());
    }
}

项目启动执行, 每次执行会清空表重新插入。

  • 实体类
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_user")
@BusinessName("用户管理")
public class User extends BaseEntity {

    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    @TableId
    @Excel(name = "用户编号", cellType = Excel.ColumnType.NUMERIC, prompt = "用户编号")
    private Integer id;

    /**
     * 用户名
     */
    @Excel(name = "用户名")
    private String userName;

    /**
     * 密码
     */
    private String password;

    /**
     * 昵称
     */
    @Excel(name = "用户昵称")
    private String nickName;

    /**
     * 用户类型
     */
    private String type;

    /**
     * 手机号码
     */
    @Excel(name = "手机号码")
    private String phone;

    /**
     * 用户性别(0男 1女 2未知)
     */
    @Excel(name = "用户性别", convertExp = "0=男,1=女,2=未知")
    private String sex;

    /**
     * 头像路径
     */
    private String avatar;

    /**
     * 用户邮箱
     */
    @Excel(name = "用户邮箱")
    private String email;

    /**
     * 机构ID
     */
    private Integer deptId;
    /**
     * 机构名称
     */
    @Excel(name = "机构名称")
    private String deptName;
}

实体类中用到两个注解一个是@BusinessName用于标识业务名称,@Excel用于标识列名,这两个注解的value都必须与前端页面的名称一致,后面会根据列名匹配来填充数据。注解如下:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface BusinessName {
    String value() default "";
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel {
    /**
     * 导出到Excel中的名字.
     */
    public String name() default "";

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

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

    /**
     * 导出类型(0数字 1字符串)
     */
    public ColumnType cellType() default ColumnType.STRING;

    /**
     * 导出时在excel中每个列的高度 单位为字符
     */
    public double height() default 14;

    /**
     * 导出时在excel中每个列的宽 单位为字符
     */
    public double width() default 16;

    /**
     * 文字后缀,如% 90 变成90%
     */
    public String suffix() default "";

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

    /**
     * 提示信息
     */
    public String prompt() default "";

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

    /**
     * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
     */
    public boolean isExport() default true;

    /**
     * 另一个类中的属性名称,支持多级获取,以小数点隔开
     */
    public String targetAttr() 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;
        }
    }

    public enum ColumnType {
        NUMERIC(0), STRING(1);
        private final int value;

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

        public int value() {
            return this.value;
        }
    }
}
  • 模板上传
@PostMapping("/save")
    @ResponseBody
    public Result save(@RequestParam("className") String className,
                       @RequestParam("businessName") String businessName,
                       @RequestParam("templateStatus") String templateStatus,
                       @RequestParam("file") MultipartFile file) {

        if (file == null){
            return Result.error("请上传模板");
        }

        ExportTemplate exportTemplate = new ExportTemplate(className, businessName, templateStatus);
        //exportTemplateService.saveTemplate(exportTemplate, file);

        Class c = null;
        try {
            c = Class.forName(exportTemplate.getClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        ExcelUtil<Object> util = new ExcelUtil<Object>(c);
        List<String> list = util.getDifferentFormTemplate(file);
        if (list.size() > 0){
            return Result.error("模板中列:"+list.toString()+"不符合数据规范,请重新上传");
        }

        exportTemplateService.saveTemplate(exportTemplate, file);
        return Result.ok();
    }

 上传时对模板进行校验避免后面导出时列对应不上:

/**
     * 校验上传模板的列和类的属性的差异
     * @param file
     * @return List
     */
    public List<String> getDifferentFormTemplate(MultipartFile file){
        InputStream is = null;
        List<String> classCellList = new ArrayList<>();
        List<String> templateCellList = new ArrayList<>();
        try {
            is = file.getInputStream();
            Map<String,Object> map = readExcel(is);
            templateCellList = (List<String>) map.get("templateCellList");

            //获取类中注解@Excel的属性名称list
            createExcelField();
            fields.forEach(item -> {
                Excel excel = (Excel) item[1];
                classCellList.add(excel.name());
            });
        } catch (IOException e) {
            log.error("校验Excel模板异常{}", e.getMessage());
            throw new BaseException("校验Excel模板失败,请联系网站管理员!");
        }

        return listCompare(templateCellList, classCellList);
    }
  • 数据导出
 SystemExcelUtil<User> util = new SystemExcelUtil<User>(User.class);
 return util.exportTemplate(list);

在controller中调用,这里的list是要导出的数据。

  • 导出工具类
    /**
     * Excel sheet最大行数,默认65536
     */
    public static final int sheetSize = 65536;

    /**
     * 工作表名称
     */
    private String sheetName;

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

    /**
     * 工作薄对象
     */
    private Workbook wb;

    /**
     * 工作表对象
     */
    private Sheet sheet;

    /**
     * 样式列表
     */
    private Map<String, CellStyle> styles;

    /**
     * 导入导出数据列表
     */
    private List<T> list;

    /**
     * 注解列表
     */
    private List<Object[]> fields;

    /**
     * 实体对象
     */
    public Class<T> clazz;
     
    /**
     * 按照excel模板导出数据
     * @param filePath
     * @param list
     * @param filename
     * @return
     */
    public Result exportFormTemplate(String filePath, List<T> list, String filename){
        this.list = list;
        try {
        createExcelField();
        Map<String,Object> map = readExcel(readFtp(filePath));
        Workbook workbook = (Workbook) map.get("workbook");
        Integer rowNum = (Integer) map.get("rowNum");
        List<String> templateCellList = (List<String>) map.get("templateCellList");

        //克隆sheet
        Sheet cloneSheet = workbook.cloneSheet(0);
        //填充数据
        for (int i = 0; i < list.size(); i++){
            Integer index = i+1;
            Row nRow = cloneSheet.createRow(rowNum + index);

            T vo = (T) list.get(i);
            for (int j = 0; j < fields.size(); j++) {
                Field field = (Field) fields.get(j)[0];
                Excel excel = (Excel) fields.get(j)[1];
                // 设置实体类私有属性可访问
                field.setAccessible(true);
                if (templateCellList.contains(excel.name())){
                    Integer cellNum = templateCellList.indexOf(excel.name());
                    Cell cell = nRow.createCell(cellNum);
                    cell = this.addTemplateCell(excel, nRow, vo, field, cellNum);
                }
            }
        }

        FileOutputStream output = new FileOutputStream(getAbsoluteFile(filename));
        //删除模板sheet
        workbook.removeSheetAt(0);
        //重命名模板sheet
        workbook.setSheetName(0, sheetName != null ? sheetName : "sheet1");
        workbook.write(output);
        workbook.close();
        output.flush();
        output.close();
        return Result.ok(filename);
        } catch (Exception e) {
            log.error("导出Excel异常{}", e);
            throw new BaseException("导出Excel失败,请联系网站管理员!");
        }
    }

    /**
     * 从ftp服务器获取文件
     * @param ftpPath
     * @return inputStream
     */
    public InputStream readFtp(String ftpPath){
        int index = ftpPath.lastIndexOf("/");
        String path = ftpPath.substring(0, index);
        int pathBeg = path.indexOf(FTPUtil.hostname) + FTPUtil.hostname.length();
        path = path.substring(pathBeg);
        String filename = ftpPath.substring(index + 1, ftpPath.length());
        InputStream inputStream = null;
        try {
            FTPClient ftpClient=new FTPClient();
            ftpClient.connect(FTPUtil.hostname, FTPUtil.port);
            ftpClient.login(FTPUtil.username, FTPUtil.password);
            if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())){
                ftpClient.changeWorkingDirectory(path);
                inputStream = ftpClient.retrieveFileStream(filename);
            }
        } catch (IOException e) {
            log.error("获取ftp模板文件异常{}", e);
            throw new BaseException("导出Excel失败,请联系网站管理员!");
        }
        return  inputStream;
    }


    /**
     * 添加单元格
     */
    public Cell addTemplateCell(Excel attr, Row row, T vo, Field field, int column) {
        Cell cell = null;
        try {
            // 设置行高
            row.setHeight((short) (attr.height() * 20));
            // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列.
            if (attr.isExport()) {
                // 创建cell
                cell = row.createCell(column);
                //cell.setCellStyle(styles.get("data"));

                // 用于读取对象中的属性
                Object value = getTargetValue(vo, field, attr);
                String dateFormat = attr.dateFormat();
                String readConverterExp = attr.convertExp();
                if (StrUtil.isNotEmpty(dateFormat) && !StrUtil.isEmptyIfStr(value)) {
                    cell.setCellValue(com.dzj.drone.core.base.util.DateUtil.parseDateToStr(dateFormat, (Date) value));
                } else if (StrUtil.isNotEmpty(readConverterExp) && !StrUtil.isEmptyIfStr(value)) {
                    cell.setCellValue(convertByExp(String.valueOf(value), readConverterExp));
                } else {
                    // 设置列类型
                    setCellVo(value, attr, cell);
                }
            }
        } catch (Exception e) {
            log.error("导出Excel失败{}", e);
        }
        return cell;
    }

    /**
     * 读取上传的模板
     * @param is
     * @return rowNum 列名的行号,templateCellList 模板列集合,workbook 模板工作簿
     */
    public Map<String,Object> readExcel(InputStream is){
        //FileInputStream is = null;
        Workbook workbook = null;
        Map<String,Object> map = new HashMap<>();
        try {
            //is = new FileInputStream(file);
            workbook = WorkbookFactory.create(is);
            Sheet sheet = workbook.getSheetAt(0);

            //获取列最多的行,排除表明和合并的表名
            List<Integer> cellSizeList = new ArrayList();
            for (int rowNum = 0; rowNum <= sheet.getLastRowNum(); rowNum++) {
                Row row = sheet.getRow(rowNum);
                int cellSize = 0;
                for (int cellNum = 0; cellNum <= row.getLastCellNum(); cellNum++){
                    Cell cell = row.getCell(cellNum);
                    if (cell != null && cell.getCellType() != CellType.BLANK) {
                        cellSize++;
                    }
                };
                cellSizeList.add(cellSize);
            }
            Integer rowNum =cellSizeList.indexOf(Collections.max(cellSizeList));

            //获取模板中每一列的列名
            Row row = sheet.getRow(rowNum);
            List<String> templateCellList = new ArrayList<>();
            for (int cellNum = 0; cellNum <= row.getLastCellNum(); cellNum++){
                Cell cell = row.getCell(cellNum);
                if (cell != null && cell.getCellType() != CellType.BLANK) {
                    templateCellList.add(cell.toString());
                }
            };

            //模板表头中列的行号
            map.put("rowNum", rowNum);
            //模板中的列
            map.put("templateCellList", templateCellList);
            //模板的workbook
            map.put("workbook", workbook);
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return map;
    }

    /**
     * 得到所有定义字段
     */
    private void createExcelField() {
        this.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)) {
                putToField(field, field.getAnnotation(Excel.class));
            }

            // 多注解
            if (field.isAnnotationPresent(Excels.class)) {
                Excels attrs = field.getAnnotation(Excels.class);
                Excel[] excels = attrs.value();
                for (Excel excel : excels) {
                    putToField(field, excel);
                }
            }
        }
    }

    /**
     * 放到字段集合中
     */
    private void putToField(Field field, Excel attr) {
        if (attr != null && (attr.type() == Type.ALL || attr.type() == type) && attr.isExport()) {
            this.fields.add(new Object[]{field, attr});
        }
    }

    /**
     * 获取bean中的属性值
     *
     * @param vo    实体对象
     * @param field 字段
     * @param excel 注解
     * @return 最终的属性值
     * @throws Exception
     */
    private Object getTargetValue(T vo, Field field, Excel excel) throws Exception {
        Object o = field.get(vo);
        if (StrUtil.isNotEmpty(excel.targetAttr())) {
            String target = excel.targetAttr();
            if (target.indexOf(".") > -1) {
                String[] targets = target.split("[.]");
                for (String name : targets) {
                    o = getValue(o, name);
                }
            } else {
                o = getValue(o, target);
            }
        }
        return o;
    }

    /**
     * 解析导出值 0=男,1=女,2=未知
     *
     * @param propertyValue 参数值
     * @param converterExp  翻译注解
     * @return 解析后值
     * @throws Exception
     */
    public static String convertByExp(String propertyValue, String converterExp) throws Exception {
        try {
            String[] convertSource = converterExp.split(",");
            for (String item : convertSource) {
                String[] itemArray = item.split("=");
                if (itemArray[0].equals(propertyValue)) {
                    return itemArray[1];
                }
            }
        } catch (Exception e) {
            throw e;
        }
        return propertyValue;
    }

    /**
     * 设置单元格信息
     *
     * @param value 单元格值
     * @param attr  注解相关
     * @param cell  单元格信息
     */
    public void setCellVo(Object value, Excel attr, Cell cell) {
        if (ColumnType.STRING == attr.cellType()) {
            cell.setCellType(CellType.NUMERIC);
            cell.setCellValue(StrUtil.isEmptyIfStr(value) ? attr.defaultValue() : value + attr.suffix());
        } else if (ColumnType.NUMERIC == attr.cellType()) {
            cell.setCellType(CellType.NUMERIC);
            cell.setCellValue(Integer.parseInt(value + ""));
        }
    }

excel操作用的是poi

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值