我所知道报表之POI使用模板打印、自定义工具类、工具类整理

28640

接下来讲讲当我们遇到以下的excel文档,要求采用这种模板方式导出

image.png

若每个单元格都设置样式的方式,则会很繁琐

那么我们能不能读取该模板,获取里面的样式,导出的时候运用到呢?

一、使用模板打印


要想使用模板的方式读取样式,首先就获取到该模板,我们的思路是

  1. 制作模版文件(模版文件的路径)
  2. 导入(加载)模版文件,从而得到一个工作簿
  3. 读取工作表
  4. 读取行
  5. 读取单元格
  6. 读取单元格样式
  7. 设置单元格内容
  8. 其他单元格使用读取样式

接下来让我们代码实践完成思路,从获取模板开始....

@RequestMapping(value = "/export/{month}", method = RequestMethod.GET)
public void export(@PathVariable(name = "month") String month) throws Exception {
    //1.构造数据
    List<User> list = userService.findByReport(companyId,month+"%");
    
    //2.加载模板流数据
    Resource resource = new ClassPathResource("excel-template/hr-demo.xlsx");
    FileInputStream fis = new FileInputStream(resource.getFile());
    
    //3.根据文件流,加载指定的工作簿
    XSSFWorkbook wb = new XSSFWorkbook(fis);
    
    //4.读取工作表
    Sheet sheet = wb.getSheetAt(0);
    
    //5.抽取公共的样式
    Row styleRow = sheet.getRow(2);//第三行
    CellStyle [] styles = new CellStyle[styleRow.getLastCellNum()];
    for(int i=0;i<styleRow.getLastCellNum();i++) {
        styles[i] = styleRow.getCell(i).getCellStyle();
    }
}

这时我们已经将Excel文档里的第三行单元格样式保存到CelStyle数组里

若我们想使用的话,只需要根据对应数组下标for循环读取即可

@RequestMapping(value = "/export/{month}", method = RequestMethod.GET)
public void export(@PathVariable(name = "month") String month) throws Exception {
    
    //6.构建cell单元格
    Cell cell = null;
    int titleInext = 2;
    for (user report : list) {
        styleRow = sheet.createRow(titleInext++);
        //编号
        cell = dataRow.createCell(0);
        cell.setCellValue(report.getUserId());
        cell.setCellStyle(styles[0]);
        //姓名
        cell = dataRow.createCell(1);
        cell.setCellValue(report.getUsername());
        cell.setCellStyle(styles[1]);

        //手机
        cell = dataRow.createCell(2);
        cell.setCellValue(report.getMobile());
        cell.setCellStyle(styles[2]);
    } 
    //7.输出文件下载
    String fileName = URLEncoder.encode(month+"人员信息.xlsx", "UTF-8");
    response.setContentType("application/octet-stream");
    response.setHeader("content-disposition", "attachment;filename=" + new
String(fileName.getBytes("ISO8859-1")));
    response.setHeader("filename", fileName);
    workbook.write(response.getOutputStream());   
}

再导出的话,我们就可以拥有对应的样式了

二、自定义工具类


使用模板打印时,我们将模板里的第三行单元格样式存储到数组中

那么我们采用一个for循环的操作进行赋值

for (user report : list) {
    styleRow = sheet.createRow(titleInext++);
    for(int j=0;j<styles.length;j++){
        cell = dataRow.createCell(j);//创建单元格
        cell.setCellStyle(styles[0]);//赋值单元格样式
    }
}

但是我们怎么知道这个第一个单元格赋值ID、第二个单元格赋值名称呢?

我们对于单元格的位置和属性,并且动态的使用对象属性赋值需要思考

我们可以不可以使用注解的方式进行标注呢?

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelAttribute {
    /** 对应的列名称 */
    String name() default "";
    /** 列序号 */
    int sort();
    /** 字段类型对应的格式 */
    String format() default "";
}

当我们需要使用的时候则在User类使用注解方式来定位赋值的位置

class User{
    
    @ExcelAttribute(sort = 0)
    private String name;
    
    @ExcelAttribute(sort = 1)
    private String tel;

    //省略其他关键性代码.....
}

接下来我们体会体会使用注解的方式获取值

//1.构造数据
List<User> list = userService.findByReport(companyId,month+"%");

//2.加载模板流数据
Resource resource = new ClassPathResource("excel-template/hr-demo.xlsx");
FileInputStream fis = new FileInputStream(resource.getFile());

//3.获取User类的Class
Class clazz = User.class;

//3.获取自己声明User的各种字段
//包括public,protected,private
Field fields[] = clazz.getDeclaredFields();

//4.根据文件流,加载指定的工作簿
XSSFWorkbook wb = new XSSFWorkbook(fis);
    
//5.读取工作表
Sheet sheet = wb.getSheetAt(0);

//6.抽取公共的样式
Row styleRow = sheet.getRow(2);//第三行
CellStyle [] styles = new CellStyle[styleRow.getLastCellNum()];
for(int i=0;i<styleRow.getLastCellNum();i++) {
    styles[i] = styleRow.getCell(i).getCellStyle();
}
//7.写入单元格表
int titleInext = 2;
for (user report : list) {
    Row row = sheet.createRow(titleInext++);
    for(int i=0;i<styles.length;i++) {
        Cell cell = row.createCell(i);
        cell.setCellStyle(styles[i]);
        //获取自己声明User的各种字段
        for (Field field : fields) {
            //如果ExcelAttribute注解存在于此字段上
            if(field.isAnnotationPresent(ExcelAttribute.class)){
                //setAccessible = true意味着允许客户端拥有超级权限
                //比如Java对象序列化 或者 其他持久化机制等通常禁止的机制
                field.setAccessible(true);
                //返回字段上的ExcelAttribute 注解
                ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);
                //将字段上的排序序号进行匹配赋值
                if(i == ea.sort()) {
                    cell.setCellValue(field.get(t).toString());
                }
            }
        }
    }
}

这样的情况我们只需要标注对应的字段位置样式即可,更加简洁代码

三、导入工具类


同理,根据导出的思路,我们也可以将导入的方式也变成一个工具类

一样使用一个注解的方式来标注对应的单元格与字段位置

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelAttribute {
    /** 对应的列名称 */
    String name() default "";
    /** 列序号 */
    int sort();
    /** 字段类型对应的格式 */
    String format() default "";
}
class User{
    
    @ExcelAttribute(sort = 0)
    private String name;
    
    @ExcelAttribute(sort = 1)
    private String tel;

    //省略其他关键性代码.....
}

我们的目的是将单元格里的信息与字段进行匹配返回实体类集合

那么对于单元格里的属性处理、以及实体类的字段类型我们要进行转换

/**
 * 格式转为String
 * @param cell
 * @return
 */
public String getValue(Cell cell) {
    if (cell == null) {
        return "";
    }
    switch (cell.getCellType()) {
        case STRING:
            return cell.getRichStringCellValue().getString().trim();
        case NUMERIC:
            if (DateUtil.isCellDateFormatted(cell)) {
                Date dt = DateUtil.getJavaDate(cell.getNumericCellValue());
                return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(dt);
            } else {
                // 防止数值变成科学计数法
                String strCell = "";
                Double num = cell.getNumericCellValue();
                BigDecimal bd = new BigDecimal(num.toString());
                if (bd != null) {
                    strCell = bd.toPlainString();
                }
                // 去除 浮点型 自动加的 .0
                if (strCell.endsWith(".0")) {
                    strCell = strCell.substring(0, strCell.indexOf("."));
                }
                return strCell;
            }
        case BOOLEAN:
            return String.valueOf(cell.getBooleanCellValue());
        default:
            return "";
    }
}
/**
 * 类型转换 将cell 单元格格式转为 字段类型
 */
private Object covertAttrType(Field field, Cell cell) throws Exception {
    String fieldType = field.getType().getSimpleName();
    if ("String".equals(fieldType)) {
        return getValue(cell);
    }else if ("Date".equals(fieldType)) {
        return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(getValue(cell)) ;
    }else if ("int".equals(fieldType) || "Integer".equals(fieldType)) {
        return Integer.parseInt(getValue(cell));
    }else if ("double".equals(fieldType) || "Double".equals(fieldType)) {
        return Double.parseDouble(getValue(cell));
    }else {
        return null;
    }
    //其他类型待添加......
}

接下来我们根据导出的思路,编写导入的方式将对应的单元格绑定实体类

//1.组装User的集合
List<User> list = new ArrayList<User>();

//2.反射实体类
User entity = null;

//3.导入的文件InputStream流
XSSFWorkbook workbook = new XSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);

//4.获取User类的Class
Class clazz = User.class;

//5.获取自己声明User的各种字段
//包括public,protected,private
Field fields[] = clazz.getDeclaredFields();

//6.定义初始行数与单元格
int beginRow = 1;//从第几行开始读取
int beginCell = 1;//从第几单元格开始读取

Row row = null;

//7.循环读取行数
try {
    for (int rowNum = beginRow; rowNum <= sheet.getLastRowNum(); rowNum++) {
        //获取行
        row = sheet.getRow(rowNum);
        //实例化对象
        entity = (User)clazz.newInstance();
        //循环读取每行的单元格数
        for (int j = beginCell; j < row.getLastCellNum(); j++) {
            //获取单元格
            Cell cell = row.getCell(j);
            for (Field field : fields) {
                if(field.isAnnotationPresent(ExcelAttribute.class)){
                    field.setAccessible(true);
                    ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);
                    if(j == ea.sort()) {
                        //使用covertAttrType进行类型转换
                        field.set(entity, covertAttrType(field, cell));
                    }
                }
            }
        }
        list.add(entity);
    }
} catch (Exception e) {
    e.printStackTrace();
}

这样我们就可以使用工具类,将导入的excel文档读取单元格信息与实体类绑定

四、工具类整理


根据我们的思路提供整合导入工具类、导出工具类方便使用

//导入工具类
public class ExcelImportUtil<T> {
 
    private Class clazz;
    private  Field fields[];
 
    public ExcelImportUtil(Class clazz) {
        this.clazz = clazz;
        fields = clazz.getDeclaredFields();
    }
 
    /**
     * 基于注解读取excel
     */
    public List<T> readExcel(InputStream is, int rowIndex,int cellIndex) {
        List<T> list = new ArrayList<T>();
        T entity = null;
        try {
            XSSFWorkbook workbook = new XSSFWorkbook(is);
            Sheet sheet = workbook.getSheetAt(0);
            System.out.println(sheet.getLastRowNum());
            for (int rowNum = rowIndex; rowNum <= sheet.getLastRowNum(); rowNum++) {
                Row row = sheet.getRow(rowNum);
                entity = (T) clazz.newInstance();
                System.out.println(row.getLastCellNum());
                for (int j = cellIndex; j < row.getLastCellNum(); j++) {
                    Cell cell = row.getCell(j);
                    for (Field field : fields) {
                        if(field.isAnnotationPresent(ExcelAttribute.class)){
                            field.setAccessible(true);
                            ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);
                            if(j == ea.sort()) {
                                field.set(entity, covertAttrType(field, cell));
                            }
                        }
                    }
                }
                list.add(entity);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }
 

    /**
     * 类型转换 将cell 单元格格式转为 字段类型
     */
    private Object covertAttrType(Field field, Cell cell) throws Exception {
        String fieldType = field.getType().getSimpleName();
        if ("String".equals(fieldType)) {
            return getValue(cell);
        }else if ("Date".equals(fieldType)) {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(getValue(cell)) ;
        }else if ("int".equals(fieldType) || "Integer".equals(fieldType)) {
            return Integer.parseInt(getValue(cell));
        }else if ("double".equals(fieldType) || "Double".equals(fieldType)) {
            return Double.parseDouble(getValue(cell));
        }else {
            return null;
        }
    }
 
 
    /**
     * 格式转为String
     * @param cell
     * @return
     */
    public String getValue(Cell cell) {
        if (cell == null) {
            return "";
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getRichStringCellValue().getString().trim();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    Date dt = DateUtil.getJavaDate(cell.getNumericCellValue());
                    return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(dt);
                } else {
                    // 防止数值变成科学计数法
                    String strCell = "";
                    Double num = cell.getNumericCellValue();
                    BigDecimal bd = new BigDecimal(num.toString());
                    if (bd != null) {
                        strCell = bd.toPlainString();
                    }
                    // 去除 浮点型 自动加的 .0
                    if (strCell.endsWith(".0")) {
                        strCell = strCell.substring(0, strCell.indexOf("."));
                    }
                    return strCell;
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            default:
                return "";
        }
    }
}
@Getter
@Setter
//导出工具类
public class ExcelExportUtil<T> {

    private int rowIndex;
    private int styleIndex;
    private String templatePath;
    private Class clazz;
    private  Field fields[];

    public ExcelExportUtil(Class clazz,int rowIndex,int styleIndex) {
        this.clazz = clazz;
        this.rowIndex = rowIndex;
        this.styleIndex = styleIndex;
        fields = clazz.getDeclaredFields();
    }

    /**
     * 基于注解导出
     */
    public void export(HttpServletResponse response,InputStream is, List<T> objs,String fileName) throws Exception {

        XSSFWorkbook workbook = new XSSFWorkbook(is);
        Sheet sheet = workbook.getSheetAt(0);

        CellStyle[] styles = getTemplateStyles(sheet.getRow(styleIndex));

        AtomicInteger datasAi = new AtomicInteger(rowIndex);
        for (T t : objs) {
            Row row = sheet.createRow(datasAi.getAndIncrement());
            for(int i=0;i<styles.length;i++) {
                Cell cell = row.createCell(i);
                cell.setCellStyle(styles[i]);
                for (Field field : fields) {
                    if(field.isAnnotationPresent(ExcelAttribute.class)){
                        field.setAccessible(true);
                        ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);
                        if(i == ea.sort()) {
                            cell.setCellValue(field.get(t).toString());
                        }
                    }
                }
            }
        }
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setContentType("application/octet-stream");
        response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1")));
        response.setHeader("filename", fileName);
        workbook.write(response.getOutputStream());
    }

    public CellStyle[] getTemplateStyles(Row row) {
        CellStyle [] styles = new CellStyle[row.getLastCellNum()];
        for(int i=0;i<row.getLastCellNum();i++) {
            styles[i] = row.getCell(i).getCellStyle();
        }
        return styles;
    }
}

参考资料


黑马程序员:基于SaaS平台的iHRM刷脸登录实战开发(报表相关视频)

阅读 753

心有多大,舞台就有多大

95 声望
20 粉丝
0 条评论
你知道吗?

心有多大,舞台就有多大

95 声望
20 粉丝
宣传栏