1、背景
实习工作中,有需求表单通过用户导入Excel将数据批量导入到表单输入框中,因为子表单导入的需要有多个,所以使用poi+注解+反射实现了通用的上传解析方法,使用时只需要在实体类中标注相应的注解,调用转换方法即可完成导入
2、Maven依赖导入
依赖主要是使用poi包和poi-ooxml包去操作Excel表格中的数据,注意poi与poi-ooxml要保持一致
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
3、代码实现
3.1、注解类
首先定义两个注解类,注解类的功能主要是用于定义一些Excel转换的时候需要用到的一些信息
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 表单子表导出使用的注解,主要用于标注一些信息
*/
@Retention(RetentionPolicy.RUNTIME)//注解的生命周期 该注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Target(ElementType.TYPE)
public @interface ExcelForm {
String name();
//导出几个sheet默认导出一个
int limit() default 1;
//从第几个sheet开始,默认从第一页开始
int page() default 0;
//当前Excel表头数据从第几行开始,不设置默认是0
int headStart() default 0;
}
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 ExcelFormField {
/**
* 导出到Excel中的名字.表头名字
*/
String name();
/**
* 日期格式, 如: yyyy-MM-dd
*/
String dateFormat() default "";
/**
* 读取内容转表达式 (如: 0=男,1=女,2=未知)
*/
String readConverterExp() default "";
/**
* 导出时在excel中每个列的高度 单位为字符
*/
double height() default 14;
/**
* 导出时在excel中每个列的宽 单位为字符
*/
double width() default 20;
/**
* 文字后缀,如% 90 变成90%
*/
String suffix() default "";
/**
* 当值为空时,字段的默认值
*/
String defaultValue() default "";
boolean required() default true;
}
3.2、工具类实现
import com.pingan.smartcity.base.exception.BaseBusinessException;
import com.pingan.smartcity.gov.gra.common.utils.FileUtil;
import com.pingan.smartcity.gov.gra.common.utils.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.*;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static org.apache.poi.ss.usermodel.CellType.BLANK;
/**
* 表单填写子表导入Excel操作
*/
public class FormExcelUtils {
private final static String excel2003L = ".xls"; //2003- 版本的excel
private final static String excel2007U = ".xlsx"; //2007版本
/**
* 根据上传的文件,转换为对应的实体类集合
*
* @param in 文件流
* @param fileName 上传文件的名称
* @param templateClass 需要转换的实体类Class
* @param tableAnalysis 表名解析规则,表格每页第一个单位格需要标注表名,用于比较用户上传是否正确,为空默认为原始内容
* @param fieldAnalysis 表头部分解析,为null默认是表格原始内容
* @param <T> 实体类类型
* @return 转换后的实体类集合
* @throws Exception
*/
public static <T> List<T> getListByExcel(InputStream in,
String fileName,
Class<T> templateClass,
Function<String, String> tableAnalysis,
Function<String, String> fieldAnalysis) throws Exception {
//创建Excel工作薄
Workbook work = getWorkbook(in, fileName);
if (null == work) {
throw new Exception("创建Excel工作薄为空!");
}
//获取模板类的
ExcelForm excelForm = templateClass.getAnnotation(ExcelForm.class);
int limit = excelForm.page() + excelForm.limit();
if (work.getNumberOfSheets() < limit) {
throw new BaseBusinessException("配置错误,上传文件的页数小于需要扫描的页数");
}
Sheet sheet = null; //页数
Row row = null; //行数
Cell cell = null; //列数
//渲染好后的返回集合
List<T> lists = new ArrayList<>();
//遍历Excel中所有的sheet
for (int i = excelForm.page(); i < limit; i++) {
sheet = work.getSheetAt(i);
if (sheet == null) {
continue;
}
Cell oneCell = sheet.getRow(0).getCell(0);
String tableName = analysisRule(oneCell, tableAnalysis);
if (!excelForm.name().equals(tableName)) {
throw new BaseBusinessException("配置解析错误,请检查是否上传正确模板");
}
//遍历当前sheet中的所有行
for (int j = excelForm.headStart(); j <= sheet.getLastRowNum(); j++) {
//创建一个需要转换的实例
row = sheet.getRow(j);
if (isRowEmpty(row)) {
continue;
}
T template = templateClass.newInstance();
//遍历所有的列
for (int y = row.getFirstCellNum(); y < row.getLastCellNum(); y++) {
cell = row.getCell(y);
String value = getValue(cell);
//获取对应的表头列数据
Row headRow = sheet.getRow(excelForm.headStart() - 1);
Cell headCell = headRow.getCell(y);
String headValue = analysisRule(headCell, fieldAnalysis);
setFieldValueByFieldName(template, headValue, value);
}
lists.add(template);
}
}
return lists;
}
/**
* 获取工作页
*
* @param inStr,fileName
* @return
* @throws Exception
* @Description:根据文件后缀,自适应上传文件的版本
*/
public static Workbook getWorkbook(InputStream inStr, String fileName) throws Exception {
Workbook wb = null;
String fileType = fileName.substring(fileName.lastIndexOf("."));
if (excel2003L.equals(fileType)) {
wb = new HSSFWorkbook(inStr); //2003-
} else if (excel2007U.equals(fileType)) {
wb = new XSSFWorkbook(inStr); //2007+
} else {
throw new Exception("解析的文件格式有误!");
}
return wb;
}
/**
* 从Excel表单位中数据读取出来然后设置到对象中
*
* @param cell
*/
//解决excel类型问题,获得数值
public static String getValue(Cell cell) {
String value = "";
if (null == cell) {
return value;
}
CellType cellType = cell.getCellType();
switch (cellType) {
//数值型
case NUMERIC:
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
//如果是date类型则 ,获取该cell的date值
Date date = DateUtil.getJavaDate(cell.getNumericCellValue());
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
value = format.format(date);
;
} else {// 纯数字
BigDecimal big = new BigDecimal(cell.getNumericCellValue());
value = big.toString();
//解决1234.0 去掉后面的.0
if (null != value && !"".equals(value.trim())) {
String[] item = value.split("[.]");
if (1 < item.length && "0".equals(item[1])) {
value = item[0];
}
}
}
break;
//字符串类型
case STRING:
value = cell.getStringCellValue();
break;
// 公式类型
case FORMULA:
//读公式计算值
value = String.valueOf(cell.getNumericCellValue());
if (value.equals("NaN")) {// 如果获取的数据值为非法值,则转换为获取字符串
value = cell.getStringCellValue();
}
break;
// 布尔类型
case BOOLEAN:
value = " " + cell.getBooleanCellValue();
break;
default:
value = cell.getStringCellValue();
}
if ("null".endsWith(value.trim())) {
value = "";
}
return value;
}
/**
* 用反射方法,获取列名,比较,为属性赋值
*
* @param object 实体类
* @param fieldName 列标题
* @param val 当前行当前列的值
*/
public static void setFieldValueByFieldName(Object object, String fieldName, Object val) throws Exception {
//通过反射获取所有实体类所有定义的方法,object为数据库实体类
Field[] fields = object.getClass().getDeclaredFields();
//遍历fields
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
//读取注释,如@Excel(name = "科目")
ExcelFormField annotation = field.getAnnotation(ExcelFormField.class);
//因为private类型,所有要设置允许
field.setAccessible(true);
//annotation.name() 注解里的属性,如果annotation没写注解,读取原始名字如subject
if (annotation == null) {
try {
if (fieldName.equals(field.getName())) {
if (field.getType() == Integer.class) {
field.set(object, Integer.valueOf(val.toString()));
} else if (field.getType() == Long.class) {
field.set(object, Long.valueOf(val.toString()));
} else if (field.getType() == BigDecimal.class) {
field.set(object, NumberUtils.createBigDecimal(val.toString()));
} else {
field.set(object, val);
}
return;
}
} catch (Exception e) {
throw new BaseBusinessException("上传文件解析出错,<" + field.getName() + ">字段数据类型转换错误,请检查模板数据");
}
} else {//设置了注解,并且表格里的Excel字段值和注解的name值相同,则为相应字段赋值
if (fieldName.equals(annotation.name())) {
if (StringUtils.isBlank(val.toString()) && annotation.required()) {
throw new BaseBusinessException("上传文件解析出错,<" + fieldName + ">为必填项,请填写后再上传");
}
try {
//进行类型判断,因为实体类中变量类型不同。
if (field.getType() == Integer.class) {
field.set(object, Integer.valueOf(val.toString()));
} else if (field.getType() == Long.class) {
field.set(object, Long.valueOf(val.toString()));
} else if (field.getType() == Date.class) {
Date date = DateUtil.parseYYYYMMDDDate(val.toString());
field.set(object, date);
} else if (field.getType() == BigDecimal.class) {
field.set(object, new BigDecimal(StringUtils.isBlank(val.toString()) ? "0" : val.toString()));
} else {
field.set(object, val);
}
return;
} catch (Exception e) {
throw new BaseBusinessException("上传文件类型解析错误,<" + fieldName + ">字段类型转换有误,请检查");
}
}
}
}
}
//判断row是否为空
public static boolean isRowEmpty(Row row) {
if (null == row) {
return true;
}
int firstCellNum = row.getFirstCellNum(); //第一个列位置
int lastCellNum = row.getLastCellNum(); //最后一列位置
int nullCellNum = 0; //空列数量
for (int c = firstCellNum; c < lastCellNum; c++) {
Cell cell = row.getCell(c);
if (null == cell || BLANK == cell.getCellType()) {
nullCellNum++;
}
}
//所有列都为空
if (nullCellNum == (lastCellNum - firstCellNum)) {
return true;
}
return false;
}
/**
* 字段解析规则,主要用于表名和表头字段的解析
*
* @param cell Excel原始单位信息
* @param function 解析规则,如果为空返回原单位原始的值
* @return 解析后的数据
*/
public static String analysisRule(Cell cell, Function<String, String> function) {
String value = getValue(cell);
if (function == null) {
return value;
}
return function.apply(value);
}
}
3.3、在实体类中标注注解信息
@ExcelForm(name = "测试",headStart = 4)
public class Test {
@ExcelFormField(name = "表头字段")
private String a;
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
}
4、使用
使用时只需要将用户上传的文件传入,再传入需要转成实体类的Class,传入表名解析规则和表头字段解析规则,注意:Excel模板需要第一行第一列填入表格名称,主要用于匹对用户传入的模板是否正确,解析规则可以自己定义。Excel数据表表头的上传规则可以自己定义。两个解析规则可以传入null,如果为null则表示规则使用Excel的原始数据