共计 9940 个字符,预计需要花费 25 分钟才能阅读完成。
接下来讲讲当咱们遇到以下的 excel 文档,要求采纳这种模板形式导出
若每个单元格都设置款式的形式,则会很繁琐
那么咱们能不能读取该模板,获取外面的款式,导出的时候使用到呢?
一、应用模板打印
要想应用模板的形式读取款式,首先就获取到该模板,咱们的思路是
- 制作模版文件(模版文件的门路)
- 导入(加载)模版文件,从而失去一个工作簿
- 读取工作表
- 读取行
- 读取单元格
- 读取单元格款式
- 设置单元格内容
- 其余单元格应用读取款式
接下来让咱们代码实际实现思路,从获取模板开始 ….
@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 刷脸登录实战开发(报表相干视频)