1、Excel导入
总结一下目标,就是要将excel中的数据行、逐一提取,最后得到一个list,这个list的每个元素就是excel的每个数据行的实例,之后的操作就是常规的java逻辑处理了。
可以把这个过程分为三步:
1)接收数据
后台使用 MultipartFile 接收文件数据(这里复习一下,springmvc接收参数的几种方式)。
前端如果是前后端分离项目我们不用管,
如果是jsp项目可以用文件标签传参。
2)提取数据
提取数据的底层是用IO流实现的,我们这里使用封装好的工具类。excel工具类有很多、很多,我一般是使用适配性最好的,不然一会儿springboot项目、一会儿spring项目等等,还得换不同的工具类;效率什么的不是首要考虑项。
使用工具类后,我们就得到了一个List<List>
:Excel的每个数据行组成一个List
,多个数据行就组成了List<List>
这里有一个小坑,说明一下;有时候你导入的数据,如果是数字比如32位的卡号等等,用工具类提取出来成了科学计数法、或者后面加了小数点;这就说明你用的这个工具类没有将数字类型数据进行处理,你需要在工具类中找到数字类型,添加toText()
方法。
当然你也可以使用我后面提供的工具类,这个问题已经作了处理。
3)将list的元素处理成实例对象,方便后续处理
在作转换的时候,还可以加一些校验、限制,比如限制excel导入总行数不得超过多少、限制某列参数不能重复等等。
下面提供一下我用的excel导入工具类,springboot或者spring项目都可以用
public class ImportExeclUtil { private static int totalRows = 0;// 总行数 private static int totalCells = 0;// 总列数 private static String errorInfo;// 错误信息 /** 无参构造方法 */ public ImportExeclUtil() { } public static int getTotalRows() { return totalRows; } public static int getTotalCells() { return totalCells; } public static String getErrorInfo() { return errorInfo; } /** * * 根据流读取Excel文件 * * * @param inputStream * @param isExcel2003 * @return * @see [类、类#方法、类#成员] */ public List<List<String>> read(InputStream inputStream, boolean isExcel2003) throws IOException { List<List<String>> dataLst = null; /** 根据版本选择创建Workbook的方式 */ Workbook wb = null; if (isExcel2003) { wb = new HSSFWorkbook(inputStream); } else { wb = new XSSFWorkbook(inputStream); } dataLst = readDate(wb); return dataLst; } /** * * 读取数据 * * @param wb * @return * @see [类、类#方法、类#成员] */ private List<List<String>> readDate(Workbook wb) { List<List<String>> dataLst = new ArrayList<List<String>>(); /** 得到第一个shell */ Sheet sheet = wb.getSheetAt(0); /** 得到Excel的行数 */ totalRows = sheet.getPhysicalNumberOfRows(); /** 得到Excel的列数 */ if (totalRows >= 1 && sheet.getRow(0) != null) { totalCells = sheet.getRow(0).getPhysicalNumberOfCells(); } /** 循环Excel的行 */ for (int r = 1; r < totalRows; r++) { Row row = sheet.getRow(r); if (row == null) { continue; } List<String> rowLst = new ArrayList<String>(); /** 循环Excel的列 */ for (int c = 0; c < getTotalCells(); c++) { Cell cell = row.getCell(c); String cellValue = ""; if (null != cell) { // 以下是判断数据的类型 switch (cell.getCellTypeEnum()) { case NUMERIC: // 数字 //如果是日期的话 if(cell != null && HSSFDateUtil.isCellDateFormatted(cell)){ Date d = cell.getDateCellValue(); DateFormat formater = new SimpleDateFormat("yyyy/MM/dd"); String da = formater.format(d); cellValue = da; break; } cellValue = NumberToTextConverter.toText(cell.getNumericCellValue()); break; case STRING: // 字符串 cellValue = cell.getStringCellValue(); break; case BOOLEAN: // Boolean cellValue = cell.getBooleanCellValue() + ""; break; case FORMULA: // 公式 cellValue = cell.getCellFormula() + ""; break; case BLANK: // 空值 cellValue = ""; break; case ERROR: // 故障 cellValue = "非法字符"; break; default: cellValue = "未知类型"; break; } } rowLst.add(cellValue); } /** 保存第r行的第c列 */ dataLst.add(rowLst); } return dataLst; } /** * * 根据Excel表格中的数据判断类型得到值 * * @param cell * @return * @see [类、类#方法、类#成员] */ /*private static String getCellValue(Cell cell) { String cellValue = ""; if (null != cell) { // 以下是判断数据的类型 switch (cell.getCellType()) { case HSSFCell.CELL_TYPE_NUMERIC: // 数字 ;: // 数字 if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) { Date theDate = cell.getDateCellValue(); SimpleDateFormat dff = new SimpleDateFormat("yyyy-MM-dd"); cellValue = dff.format(theDate); } else { DecimalFormat df = new DecimalFormat("0"); cellValue = df.format(cell.getNumericCellValue()); } break; case HSSFCell.CELL_TYPE_STRING: // 字符串 cellValue = cell.getStringCellValue(); break; case HSSFCell.CELL_TYPE_BOOLEAN: // Boolean cellValue = cell.getBooleanCellValue() + ""; break; case HSSFCell.CELL_TYPE_FORMULA: // 公式 cellValue = cell.getCellFormula() + ""; break; case HSSFCell.CELL_TYPE_BLANK: // 空值 cellValue = ""; break; case HSSFCell.CELL_TYPE_ERROR: // 故障 cellValue = "非法字符"; break; default: cellValue = "未知类型"; break; } } return cellValue; }*/ /** * * 根据实体成员变量的类型得到成员变量的值 * * @param realValue * @param fields * @param f * @param cellValue * @return * @see [类、类#方法、类#成员] */ private static Object getEntityMemberValue(Object realValue, Field[] fields, int f, String cellValue) { String type = fields[f].getType().getName(); switch (type) { case "char": case "java.lang.Character": case "java.lang.String": realValue = cellValue; break; case "java.util.Date": realValue = StringUtils.isBlank(cellValue) ? null : DateUtil.strToDate(cellValue, DateUtil.YYYY_MM_DD); break; case "java.lang.Integer": realValue = StringUtils.isBlank(cellValue) ? null : Integer.valueOf(cellValue); break; case "int": case "float": case "double": case "java.lang.Double": case "java.lang.Float": case "java.lang.Long": case "java.lang.Short": case "java.math.BigDecimal": realValue = StringUtils.isBlank(cellValue) ? null : new BigDecimal(cellValue); break; default: break; } return realValue; } /** * * 根据路径或文件名选择Excel版本 * * * @param filePathOrName * @param in * @return * @throws IOException * @see [类、类#方法、类#成员] */ public static Workbook chooseWorkbook(String filePathOrName, InputStream in) throws IOException { /** 根据版本选择创建Workbook的方式 */ Workbook wb = null; boolean isExcel2003 = ExcelVersionUtil.isExcel2003(filePathOrName); if (isExcel2003) { wb = new HSSFWorkbook(in); } else { wb = new XSSFWorkbook(in); } return wb; } static class ExcelVersionUtil { /** * * 是否是2003的excel,返回true是2003 * * * @param filePath * @return * @see [类、类#方法、类#成员] */ public static boolean isExcel2003(String filePath) { return filePath.matches("^.+\\.(?i)(xls)$"); } /** * * 是否是2007的excel,返回true是2007 * * * @param filePath * @return * @see [类、类#方法、类#成员] */ public static boolean isExcel2007(String filePath) { return filePath.matches("^.+\\.(?i)(xlsx)$"); } } public static class DateUtil { // ======================日期格式化常量=====================// public static final String YYYY_MM_DDHHMMSS = "yyyy-MM-dd HH:mm:ss"; public static final String YYYY_MM_DD = "yyyy-MM-dd"; public static final String YYYY_MM = "yyyy-MM"; public static final String YYYY = "yyyy"; public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; public static final String YYYYMMDD = "yyyyMMdd"; public static final String YYYYMM = "yyyyMM"; public static final String YYYYMMDDHHMMSS_1 = "yyyy/MM/dd HH:mm:ss"; public static final String YYYY_MM_DD_1 = "yyyy/MM/dd"; public static final String YYYY_MM_1 = "yyyy/MM"; /** * * 自定义取值,Date类型转为String类型 * * @param date 日期 * @param pattern 格式化常量 * @return * @see [类、类#方法、类#成员] */ public static String dateToStr(Date date, String pattern) { SimpleDateFormat format = null; if (null == date) return null; format = new SimpleDateFormat(pattern, Locale.getDefault()); return format.format(date); } /** * 将字符串转换成Date类型的时间 *
* * @param s 日期类型的字符串
* datePattern :YYYY_MM_DD
* @return java.util.Date */ public static Date strToDate(String s, String pattern) { if (s == null) { return null; } Date date = null; SimpleDateFormat sdf = new SimpleDateFormat(pattern); try { date = sdf.parse(s); } catch (ParseException e) { e.printStackTrace(); } return date; } }}
再提供一个应用实例:
@ApiOperation(value = "以导入excel方式,上传要申请学分的用户") @GetMapping(value = "/uuApplyUserInfo") public AjaxResult uuApplyUserInfo(@RequestParam(value = "files",required = false) MultipartFile files) { try { //工具类 ImportExeclUtil readExcelUtil = new ImportExeclUtil(); List<List<String>> read = readExcelUtil.read(files.getInputStream(), true); if (CollectionUtils.isNotEmpty(read)){ List<ApplyCreditUserDto> importList = read.stream().map(e -> { ApplyCreditUserDto importDto = new ApplyCreditUserDto(); importDto.setUserName(e.get(0)); importDto.setCreditCardNo(e.get(1)); importDto.setCreditCardPwd(e.get(2)); return importDto; }).collect(Collectors.toList()); if (CollectionUtils.isEmpty(importList)){ return AjaxResult.error("不能导入空文件"); } //最多导入1W条 final int maxInt = 10000; if (importList.size() > maxInt){ return AjaxResult.error("导入最多修改1W条"); } List<String> orderIds = importList.stream() .map(ApplyCreditUserDto::getUserName) .distinct() .collect(Collectors.toList()); if (!Objects.equals(orderIds.size(),importList.size())){ return AjaxResult.error("导入信息中,有用户"); } //调用业务层 return applyCreditLogService.uuApplyUserInfo(importList); }else{ return AjaxResult.error("不能导入空文件"); } } catch (Exception e) { e.printStackTrace(); return AjaxResult.error("导入失败,更新数据库时报错!报错信息:" + e.toString()); } }
2、将数据导出为Excel
这里先说明一下这里要做的是把从数据库查到的数据行,导出到excel;和下载一个静态文件(比如下载Excel静态文件模板),是不一样的需求。
1)引入依赖
这里用的是poi导出excel,版本如下
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.1</version> </dependency>
2)自定义注解
为了方便,提高复用效率,这里自定义2个注解;
第一个:@EnableExcel,用来开启Excel表格的导出,用在装导出数据的实体类上;
/** * 标记类是否开启Excel * @Author: Sunlong * @date: 2020/5/10 20:29 */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface EnableExcel {}
第二个:@ExcelRow,用在装导出数据的实体类的属性上,用来映射字段与excel的对应关系;
/** * excel 表格 列名注解 * * @author sunlong */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface ExcelRow { /** * Excel 对应列名 * @return */ String name(); /** * excel 列名备注 * @return */ String note() default "";}
3)代码逻辑—提取导出工具类
a、通过反射获取自定义注解EnableExcel 判断是否开启Excel导出
b、通过反射获取自定义注解ExcelRow 获取列对应的属性
c、把属性对应的列下标取出来,属性名做为key,下标做为value放到map中
d、遍历要导出的数据集合,通过属性描述器PropertyDescriptor获取对应属性下标及属性值并设置到cell单元格中
还是为了方便,已经提高复用效率,我们将上述代码提取成一个工具类,如下:
public class ExportExcelUtils { /** * workbook * @param titleList * @return */ public static HSSFWorkbook getWorkBook(List<String> titleList){ //第一步,创建一个workbook,对应一个Excel文件 HSSFWorkbook wb = new HSSFWorkbook(); // 一个sheet HSSFSheet sheet = wb.createSheet("sheet1"); HSSFRow rowTitle = sheet.createRow(0); // 第一行 标题 // 设置标题 for (int i = 0; i < titleList.size(); i++) { HSSFCell cell = rowTitle.createCell(i); cell.setCellValue(titleList.get(i)); } //合并单元格CellRangeAddress构造参数依次表示起始行,截至行,起始列, 截至列 /*sheet.addMergedRegion(new CellRangeAddress(0,0,0,4)); sheet.addMergedRegion(new CellRangeAddress(titleList.size()-1,titleList.size()-1,titleList.size()-1,titleList.size()+1));*/ return wb; } public static <T> HSSFWorkbook getWorkBook(List<String> titleList , List<T> dataList) throws IntrospectionException, InvocationTargetException, IllegalAccessException { if (CollectionUtils.isNotEmpty(dataList)) { T t1 = dataList.get(0); Class<?> t1Class = t1.getClass(); EnableExcel enableExcel = t1Class.getAnnotation(EnableExcel.class); if (enableExcel == null) { throw new IllegalArgumentException("EnableExcel 注解没有在实体类启用"); } Field[] fields = t1Class.getDeclaredFields(); if (fields != null && fields.length > 0) { Map<String , Integer> titleMap = new HashMap<>(titleList.size()); // 存放属性名称对应的下标 int fieldExcelSize = 0; // 类中ExcelRow 注解的数量 for (Field field : fields) { field.setAccessible(true); String fieldName = field.getName(); ExcelRow excelRow = field.getAnnotation(ExcelRow.class); if (excelRow != null) { String name = excelRow.name(); if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("ExcelRow 注解name属性不能为空"); } int index = titleList.indexOf(name.trim()); if (index != -1) { fieldExcelSize++; titleMap.put(fieldName , index); } } } if (!(titleList.size() == fieldExcelSize)) { throw new IllegalArgumentException("ExcelRow 注解name属性对应的列数不对"); } HSSFWorkbook workBook = getWorkBook(titleList); HSSFSheet sheet = workBook.getSheetAt(0); for (T t : dataList) { int lastRowNum = sheet.getLastRowNum(); HSSFRow row = sheet.createRow(lastRowNum + 1); BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass()); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { String fieldName = propertyDescriptor.getName(); if (titleMap.containsKey(fieldName)) { Method readMethod = propertyDescriptor.getReadMethod(); if (readMethod != null) { Class<?> returnType = readMethod.getReturnType(); String simpleName = returnType.getSimpleName(); Object invoke = readMethod.invoke(t); String value = ""; // 可以根据不同的类型返回不同的数据 if ("date".equalsIgnoreCase(simpleName)) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); if (invoke != null) { value = simpleDateFormat.format(invoke); } } if (invoke != null && "".equals(value)) { value = invoke.toString(); } row.createCell(titleMap.get(fieldName)).setCellValue(value); } } } } return workBook; } } return null; }}
4)应用实例
创建一个用来装导出数据的类(为了不与项目其他功能冲突,我一般都是新建一个专门做导出的实体类)
@EnableExcel@Datapublic class UserEntity { @ExcelRow(name = "name") private String username; @ExcelRow(name = "pass") private String password; @ExcelRow(name = "date") private Date createDate;}
模拟导出功能:
public class Test { public static void main(String[] args) throws IllegalAccessException, IntrospectionException, InvocationTargetException, IOException { List<String> titleList = new ArrayList<>(); titleList.add("name"); titleList.add("pass"); titleList.add("date"); List<UserEntity> userEntities = new ArrayList<>(); for (int i = 0; i < 10; i++) { UserEntity userEntity1 = new UserEntity(); userEntity1.setUsername("username"+i); userEntity1.setPassword("password"+i); userEntity1.setCreateDate(new Date()); userEntities.add(userEntity1); } HSSFWorkbook workBook = ExportExcelUtils.getWorkBook(titleList, userEntities); if (workBook != null) { File file = new File("D:\\test_export.xlsx"); workBook.write(file); } }}