java 导入导出excel表格
业务上有需求上传excel表格并读取内容,本文记录一下该方法
表格导入
引入相应的工具包
<dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-annotation</artifactId><version>4.4.0</version></dependency><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-base</artifactId><version>4.4.0</version></dependency><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-web</artifactId><version>4.4.0</version></dependency>
要清楚表格的格式,这样才能建对应的实体类,例如,表格如下:
要明确有3列,每列标题是什么,从第2行开始是正式的数据。
创建对应的实体类
注意使用@Excel注解,name即表格中列的名称,orderNum是列的顺序
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Province {@Excel(name = "id", orderNum = "0")private String id;@Excel(name = "countryCode", orderNum = "1")private String countryCode;@Excel(name = "countryName", orderNum = "2", type = 10)private String countryName;}
controller层写方法接收excel文件并解析
本文举例比较简单,实际项目中可以封装一个解析excel的工具类,controller层收到请求后在service层调用工具类解析,再处理相关逻辑。本文直接在controller层解析了,不写那么麻烦了。
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import com.Vo.Province;
import org.apache.commons.compress.utils.Lists;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;@RestController
public class TestController {@PostMapping("/test/import")public void getList(@RequestPart MultipartFile file) {ImportParams params = new ImportParams();//从第一行开始解析params.setHeadRows(1);List<Province> provinces = Lists.newArrayList();try {//解析后每一行转换成Province对象provinces = ExcelImportUtil.importExcel(file.getInputStream(), Province.class, params);} catch (Exception e) {e.printStackTrace();}System.out.println(provinces);}
}
运行方法进行测试
引入swagger或者使用postman或其他方法调用该接口,传入excel,看是否打印正确。
本文使用postman进行测试,如图:
控制台输出的结果如图,已经正确读到excel的内容。
表格导出
创建对应的实体类
与导入表格类似,创建要导出表格的实体类,注意使用@Excel注解,标明列明和第几列。
比如我想导出一个手机品牌的excel,第一列是序号,第二列是手机品牌名称。
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PhoneVo {@Excel(name = "序号", orderNum = "1")private int code;@Excel(name = "手机品牌名称", orderNum = "1")private String name;
}
controller层写方法导出
本文简写,直接在controller层写逻辑方法了,可按实际情况放到service层写。
比如我用一个枚举列举所有手机品牌,将枚举的内容导出。
@Getter
public enum PhoneEnum {APPLE(0,"苹果"),HUAWEI(1,"华为"),MI(2,"小米"),OPPO(3,"oppo"),SAMSUNG(4,"三星"),;private int code;private String type;PhoneEnum(int code, String type) {this.code = code;this.type = type;}
}
controller层先把枚举内容转换成list,然后调工具类接口生成excel。
@RestController
@RequestMapping("/")
public class TestController {@GetMapping("/test/download")public void getExcel(HttpServletResponse response) throws Exception {// 表格名称String fileName = "手机品牌.xls";// 导出的表格title和sheet页名ExportParams exportParams = new ExportParams("手机品牌", "phone");// 导出的内容的listList<PhoneVo> list = getList();// 传参是要转换的对象类型,和listWorkbook workbook = ExcelExportUtil.exportExcel(exportParams, PhoneVo.class, list);if (workbook != null) {try {response.setCharacterEncoding("UTF-8");response.setHeader("content-Type", "application/vnd.ms-excel");response.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20"));workbook.write(response.getOutputStream());} catch (IOException e) {throw new Exception(e.getMessage());} finally {workbook.close();}}}private List<PhoneVo> getList(){List<PhoneVo> res = Lists.newArrayList();for (PhoneEnum phoneEnum : PhoneEnum.values()) {res.add(PhoneVo.builder().code(phoneEnum.getCode()).name(phoneEnum.getType()).build());}return res;}
}
测试
启动工程,在浏览器中输入url,回车,即可下载excel。
导出结果如图片所示
@Excel注解参数介绍
@Excel 注解源码对于参数的介绍
package cn.afterturn.easypoi.excel.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Excel 导出基本注释* @author JueYue* 2014年6月20日 下午10:25:12*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Excel {/*** 导出时间设置,如果字段是Date类型则不需要设置 数据库如果是string 类型,这个需要设置这个数据库格式*/public String databaseFormat() default "yyyyMMddHHmmss";/*** 导出的时间格式,以这个是否为空来判断是否需要格式化日期*/public String exportFormat() default "";/*** 导入的时间格式,以这个是否为空来判断是否需要格式化日期*/public String importFormat() default "";/*** 时间格式,相当于同时设置了exportFormat 和 importFormat*/public String format() default "";/*** 时间时区*/public String timezone() default "";/*** 数字格式化,参数是Pattern,使用的对象是DecimalFormat*/public String numFormat() default "";/*** 导出时在excel中每个列的高度 单位为字符,一个汉字=2个字符* 优先选择@ExportParams中的 height*/@Deprecatedpublic double height() default 10;/*** 导出类型 1 从file读取 2 是从数据库中读取 默认是文件 同样导入也是一样的*/public int imageType() default 1;/*** 文字后缀,如% 90 变成90%*/public String suffix() default "";/*** 是否换行 即支持\n*/public boolean isWrap() default true;/*** 合并单元格依赖关系,比如第二列合并是基于第一列 则{1}就可以了*/public int[] mergeRely() default {};/*** 纵向合并内容相同的单元格*/public boolean mergeVertical() default false;/*** 导出时,对应数据库的字段 主要是用户区分每个字段, 不能有annocation重名的 导出时的列名* 导出排序跟定义了annotation的字段的顺序有关 可以使用a_id,b_id来确实是否使用*/public String name();/*** 导出时,表头双行显示,聚合,排序以最小的值参与总体排序再内部排序* 导出排序跟定义了annotation的字段的顺序有关 可以使用a_id,b_id来确实是否使用* 优先弱与 @ExcelEntity 的name和show属性*/public String groupName() default "";/*** 是否需要纵向合并单元格(用于含有list中,单个的单元格,合并list创建的多个row)*/public boolean needMerge() default false;/*** 展示到第几个可以使用a_id,b_id来确定不同排序*/public String orderNum() default "0";/*** 值得替换 导出是{a_id,b_id} 导入反过来,所以只用写一个*/public String[] replace() default {};/*** 字典名称*/public String dict() default "";/*** 下拉列表,仅支持replace和dict,dict优先* @return*/public boolean addressList() default false;/*** 导入路径,如果是图片可以填写,默认是upload/className/ IconEntity这个类对应的就是upload/Icon/**/public String savePath() default "/excel/upload/img";/*** 导出类型 1 是文本 2 是图片,3 是函数,10 是数字, 11 特殊符号 默认是文本*/public int type() default 1;/*** 导出时在excel中每个列的宽 单位为字符,一个汉字=2个字符 如 以列名列内容中较合适的长度 例如姓名列6 【姓名一般三个字】* 性别列4【男女占1,但是列标题两个汉字】 限制1-255*/public double width() default 10;/*** 是否自动统计数据,如果是统计,true的话在最后追加一行统计,把所有数据都和* 这个处理会吞没异常,请注意这一点* @return*/public boolean isStatistics() default false;/*** 这个是不是超链接,如果是需要实现接口返回对象* @return*/public boolean isHyperlink() default false;/*** 导入时会校验这个字段,看看这个字段是不是导入的Excel中有,如果没有说明是错误的Excel* 本意是想用true的,想想还是false比较好* 可以使用a_id,b_id来确实是否使用* @return*/public String isImportField() default "false";/*** 固定的某一列,解决不好解析的问题* @return*/public int fixedIndex() default -1;/*** 是否需要隐藏该列* @return*/public boolean isColumnHidden() default false;/*** 枚举导出使用的字段* @return*/public String enumExportField() default "";/*** 枚举导入使用的函数* @return*/public String enumImportMethod() default "";/*** 数据脱敏规则* 规则1: 采用保留头和尾的方式,中间数据加星号* 如: 身份证 6_4 则保留 370101********1234* 手机号 3_4 则保留 131****1234* 规则2: 采用确定隐藏字段的进行隐藏,优先保留头* 如: 姓名 1,3 表示最大隐藏3位,最小一位* 李 --> ** 李三 --> 李** 张全蛋 --> 张*蛋* 李张全蛋 --> 李**蛋* 尼古拉斯.李张全蛋 -> 尼古拉***张全蛋* 规则3: 特殊符号后保留* 如: 邮箱 1~@ 表示只保留第一位和@之后的字段* afterturn@wupaas.com -> a********@wupaas.com* 复杂版本请使用接口* {@link cn.afterturn.easypoi.handler.inter.IExcelDataHandler}*/public String desensitizationRule() default "";
}
测试几个常用的参数
修改导出excel例子中的PhoneVo,用来测试suffix,format,replace参数
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class PhoneVo {@Excel(name = "序号", orderNum = "1")private int code;// suffix:后面加上“手机” replace:replace = {"展示数据_替换数据"}@Excel(name = "手机品牌名称", orderNum = "1",suffix = "手机", replace = {"OPPO_oppo"})private String name;// format指定时间转换的格式@Excel(name = "时间", orderNum = "2",format = "yyyy-MM-dd-HH:mm:ss")private Timestamp time;
}
controller层方法修改如下
private List<PhoneVo> getList() {List<PhoneVo> res = Lists.newArrayList();for (PhoneEnum phoneEnum : PhoneEnum.values()) {res.add(PhoneVo.builder().code(phoneEnum.getCode()).name(phoneEnum.getType()).time(new Timestamp(System.currentTimeMillis())).build());}return res;}
其余不动,在浏览器中输入url,回车,查看下载的excel。