原因:
最近开始的新工作涉及到excel的导入导出,而且发现其他几个小伙伴的任务都有涉及到。感觉大家都在做重复工作。难道每一种模板的excel都要复制代码,然后改一改。所以想着封装一个工具类,提供给大家使用。
目录解释
效果
上传
使用postman模拟文件上传,具体数据如下:
上传文件后将数据解析放在集合中。
重写SaveDataInterface接口后实现数据保存功能。
下载
请求:localhost:8080/downLoadExcel
内容如下:
部分代码
部分代码
- 上传下载入口
@Controller
public class PoiExcelController {
@Autowired
PoiExcelService poiExcelService;
@ResponseBody
@RequestMapping(value = "/fileUpload",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public List<Teacher> fileUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception {
//excel列和即将封装到的实体中属性对应关系
Map map = new HashMap();
map.put("0", "id");
map.put("1", "age");
map.put("2", "flag");
map.put("3", "name");
map.put("4", "birthday");
map.put("5", "money");
//重写保存方法,用于存储数据
SaveDataInterface<Teacher> saveDataInterface = new SaveDataInterface<Teacher>(){
@Override
public <T> int save(T t) {
if(t instanceof Teacher){
Teacher teacher = (Teacher) t;
return poiExcelService.saveUserInfo(teacher);
}
return 0;
}
};
//具体解析excel方法
List<Teacher> list = ExcelParseUtils.parseExcelToList(file, map, false,saveDataInterface, Teacher.class);
return list;
}
@RequestMapping(value = "downLoadExcel", method = RequestMethod.GET)
public void downLoadExcel(HttpServletResponse response) throws Exception {
List<Teacher> list = poiExcelService.getUserInfo();
//下载excel第一行各列名称及对应实体属性字段
Map<String, HeaderConfig> title = new HashMap<String, HeaderConfig>();
title.put("0", new HeaderConfig("编号", "id"));
title.put("1", new HeaderConfig("年龄", "age"));
title.put("2", new HeaderConfig("标识", "flag"));
title.put("3", new HeaderConfig("姓名", "name"));
title.put("4", new HeaderConfig("生日", "birthday"));
title.put("5", new HeaderConfig("余额", "money"));
//下载后文件名
String fileName = "teacher.xlsx";
//将实体转成excel并输出给前端
ExcelParseUtils.parseListToExcel(list, title, fileName, response);
}
}
- 上传解析
/**
* 将上传的文件解析成集合对象
* 该方法只支持简单数据boolean, String, int, double,long,java.util.Date
* 如果excel获取的类型和对象不一致,则认为对象中类型为字符串,将获取的值全部转换成String类型进行赋值。
*
* @param file 传文件流
* @param map excel中列对应实体类中的属性,以0开头
* @param dateFormat 如果有时间类型,时间类型格式化成什么样的字符串
* @param flag 标识excle表格第一行是否需要解析成对象。默认不需要
* @param saveDataInterface 钩子对象,如果需要保存数据,请实现接口,并重写方法(主要避免返回的list重新遍历存值)
* @param clazz 将数据封装的目标对象类型
* @param <T> 实体类的类型
* @return
* @throws Exception
*/
public static <T> List<T> parseExcelToList(MultipartFile file, Map<String, String> map, DateFormat dateFormat, boolean flag, SaveDataInterface saveDataInterface, Class<T> clazz) throws Exception {
//文件扩展名校验及是否是office文件校验
checkExtensionAndType(file);
List<T> list = new ArrayList<T>();
try {
//正确的文件类型 自动判断2003或者2007
Workbook workbook = PoiUtils.getWorkbookAuto(file);
//默认只有一个sheet
Sheet sheet = workbook.getSheetAt(0);
//获得sheet有多少行
int rows = sheet.getPhysicalNumberOfRows();
//读第一个sheet
for (int i = 0; i < rows; i++){
//flag为ture标识第一行需要解析,false为不需要解析
if(!flag && i == 0){
continue;
}
Row row = sheet.getRow(i);
//通过反射获取无产构造器
Constructor<T> constructor = clazz.getConstructor();
//创建对应的对象
T t = constructor.newInstance();
//遍历每行中的单元
for (int j = 0; j < row.getLastCellNum(); j++){
// 列,每一列在map中对应着一个对象的属性
Cell cell = row.getCell(j);
if (cell != null){
//处理单元格数据
parseExcelCell(map, dateFormat, clazz, t, j, cell);
}
}
// 是不是可以放一个钩子,直接让别人创建接口子类,然后调用保存方法。建议不做处理,返回list后使用批量插入,避免重复建立数据库链接
saveDataInterface.save(t);
list.add(t);
}
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
- 下载解析
private static <T> void parseExcelCell(Map<String, String> map, DateFormat dateFormat, Class<T> clazz, T t, int j, Cell cell) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, ParseException {
// 获取属性名称
String fieldName = map.get(j + "");
// 根据属性名获取属性对象
Field field = clazz.getDeclaredField(fieldName);
// 获取属性类型
Class<?> fieldType = field.getType();
// 获取属性对应的set方法名称
String methodName = getFieldSetOrGetMethod(fieldName, "set");
// 获取set方法对象
Method method = clazz.getMethod(methodName, fieldType);
logger.info("获取方法名:" + method.getName());
// 默认时间格式化类型
if(dateFormat == null){
dateFormat = new SimpleDateFormat("yyyy-MM-dd");
}
// 获取单元格属性
CellType cellTypeEnum = cell.getCellTypeEnum();
// 布尔类型
if(CellType.BOOLEAN.toString().equals(cellTypeEnum.toString())){
handleBoolean(t, cell, fieldType, method);
// 数值类型
}else if(CellType.NUMERIC.toString().equals(cellTypeEnum.toString())){
// 时间类型处理
if (HSSFDateUtil.isCellDateFormatted(cell)) {
handleDataNumeric(dateFormat, t, cell, fieldType, method);
// 其他数值类型
}else {
handleOutherNumeric(t, cell, fieldType, method);
}
// 字符串
}else if(CellType.STRING.toString().equals(cellTypeEnum.toString())){
//上传日期是字符串,但是属性是时间类型
if(fieldType == Date.class){
handelDateStr(dateFormat, t, cell, method);
}else {
handleString(t, cell, method);
}
}
}
最后
只是简单封装,定制化的大家需要自己修改。感兴趣的小伙伴可以下载看看
github地址:https://github.com/jiaxuch/poi-excel.git
代码基本实现了功能,不过感觉封装的不太好(比如异常还没处理,日志也没有规范的打印,后期会修正)。如果有更好的封装和想法,也可以留言分享给我,谢谢!