文章目录
- 前言📝
- 一、导入依赖
- 二、创建导出工具
- 1.创建模板实体类
- 2.创建自定义注解
- 3.添加动态选择接口
- 4.EasyExcelUtil工具类
- 三、导出、导入Excel接口
- 1.导出接口
- 2.导入接口
- 3.导出结果
- 总结
前言📝
在项目中导入excel时需要通过下拉框选择值传入,所以需要在导出模板的时候,把下拉框数据一起导出到excel中
一、导入依赖
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>${easyexcel.version}</version>
</dependency>
<easyexcel.version>2.2.10</easyexcel.version>
二、创建导出工具
1.创建模板实体类
创建Excel导出模板表头对应的实体类
代码如下(示例):
/*** 表头类** @author wangjian* @date 2024-04-22 10:32*/
@Data
@ColumnWidth(25)
@HeadRowHeight(15)
@ContentRowHeight(15)
public class TravelGroupExportExcel implements Serializable {/*** 游客姓名*/@ExcelProperty(index = 0, value = "*游客姓名")private String userName;/*** 国家地区*/@ExcelProperty(index = 1, value = "*国家地区")@ExcelSelected(sourceClass = CountrySelected.class)private String countryAreaTypeStr;/*** 证件号*/@ExcelProperty(index = 2, value = "*证件号")private String idCard;/*** 联系电话*/@ExcelProperty(index = 3, value = "*联系电话")private String phone;/*** 优待身份*/@ExcelProperty(index = 4, value = "优待身份(选填)")@ExcelSelected(sourceClass = IdentitySelected.class)private String specialCode;/*** 模板版本*/@ExcelProperty(index = 5, value = "模板版本")private String version;
}
2.创建自定义注解
创建自定义注解,标注导出的列为下拉框类型,并为下拉框设置内容
代码如下(示例):
/*** 标注导出的列为下拉框类型,并为下拉框设置内容** @author MaoDeShu* @date 2024-04-22 10:32*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelSelected {/*** 固定下拉内容*/String[] source() default {};/*** 动态下拉内容*/Class<? extends ExcelDynamicSelect>[] sourceClass() default {};/*** 设置下拉框的起始行,默认为第二行*/int firstRow() default 1;/*** 设置下拉框的结束行,默认为最后一行*/int lastRow() default 0x10000;
}
Excel选择解析类
/*** 导入Excel选择解析类** @author MaoDeShu* @date 2024-04-22 10:32*/
@Data
@Slf4j
public class ExcelSelectedResolve {/*** 下拉内容*/private String[] source;/*** 设置下拉框的起始行,默认为第二行*/private int firstRow;/*** 设置下拉框的结束行,默认为最后一行*/private int lastRow;public String[] resolveSelectedSource(ExcelSelected excelSelected) {if (excelSelected == null) {return null;}// 获取固定下拉框的内容String[] source = excelSelected.source();if (source.length > 0) {return source;}// 获取动态下拉框的内容Class<? extends ExcelDynamicSelect>[] classes = excelSelected.sourceClass();if (classes.length > 0) {try {ExcelDynamicSelect excelDynamicSelect = classes[0].newInstance();String[] dynamicSelectSource = excelDynamicSelect.getSource();if (dynamicSelectSource != null && dynamicSelectSource.length > 0) {return dynamicSelectSource;}} catch (InstantiationException | IllegalAccessException e) {log.error("解析动态下拉框数据异常", e);}}return null;}
}
3.添加动态选择接口
创建动态选择接口及实现类,这里可以根据自己的业务逻辑去实现
ExcelDynamicSelect.java
/*** 获取下拉框数据接口** @author MaoDeShu* @date 2024-04-22 10:32*/
public interface ExcelDynamicSelect {/*** 获取动态生成的下拉框可选数据** @return*/String[] getSource();
}
实现类
CountrySelected.java
/*** 获取下拉框数据接口-国家地区** @author MaoDeShu* @date 2024-04-22 10:32*/
@Service
public class CountrySelected implements ExcelDynamicSelect{@Overridepublic String[] getSource() {// 这里根据自己的业务处理,这里示例写死return new ArrayList<String>(){{add("中国");add("中国台湾");add("中国香港");add("中国澳门");}}.toArray(new String[]{});}
}
IdentitySelected .java
/*** 获取下拉框数据接口-身份选择** @author MaoDeShu* @date 2024-04-22 10:32*/
@Service
public class IdentitySelected implements ExcelDynamicSelect{@Overridepublic String[] getSource() {// 这里根据自己的业务处理,这里示例写死return new ArrayList<String>(){{add("单选");add("多选");add("判断");add("问答");}}.toArray(new String[]{});}
}
4.EasyExcelUtil工具类
创建EasyExcelUtil工具类,实现Excel模板导出、导入
/*** excel工具类** @author MaoDeShu* @date 2024-04-22 10:32*/
@Slf4j
public class EasyExcelUtil {/*** 创建即将导出的sheet页(sheet页中含有带下拉框的列)** @param head 导出的表头信息和配置* @param sheetNo sheet索引* @param sheetName sheet名称* @param <T> 泛型* @return sheet页*/public static <T> WriteSheet writeSelectedSheet(Class<T> head, Integer sheetNo, String sheetName) {Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(head);return EasyExcel.writerSheet(sheetNo, sheetName).head(head).registerWriteHandler(new SelectedSheetWriteHandler(selectedMap)).build();}/*** 解析表头类中的下拉注解** @param head 表头类* @param <T> 泛型* @return Map<下拉框列索引, 下拉框内容> map*/private static <T> Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<T> head) {Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();// getDeclaredFields(): 返回全部声明的属性;getFields(): 返回public类型的属性Field[] fields = head.getDeclaredFields();for (int i = 0; i < fields.length; i++) {Field field = fields[i];// 解析注解信息ExcelSelected selected = field.getAnnotation(ExcelSelected.class);ExcelProperty property = field.getAnnotation(ExcelProperty.class);if (selected != null) {ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();String[] source = excelSelectedResolve.resolveSelectedSource(selected);if (source != null && source.length > 0) {excelSelectedResolve.setSource(source);excelSelectedResolve.setFirstRow(selected.firstRow());excelSelectedResolve.setLastRow(selected.lastRow());if (property != null && property.index() >= 0) {selectedMap.put(property.index(), excelSelectedResolve);} else {selectedMap.put(i, excelSelectedResolve);}}}}return selectedMap;}/*** 动态设置注解中的字段值** @param clazz 注解所在的实体类* @param attrName 要修改的注解属性名* @param valueMap 要设置的属性值* @return*/public static Class dynamicReviseAnnotationParam(Class clazz, String attrName, Map<String, String> valueMap) {Field[] declaredFields = clazz.getDeclaredFields();try {if (valueMap != null) {for (Field f : declaredFields) {if (f.isAnnotationPresent(ExcelProperty.class)) {ExcelProperty annotation = f.getAnnotation(ExcelProperty.class);InvocationHandler handler = Proxy.getInvocationHandler(annotation);Field field = handler.getClass().getDeclaredField("memberValues");field.setAccessible(true);// 注解信息Map memberValues = (Map) field.get(handler);String[] arr = (String[]) memberValues.get(attrName);String oldValue = arr[0];String newValue = valueMap.get(oldValue);if (StrUtil.isNotBlank(newValue)) {List newArr = new ArrayList();newArr.add(newValue);memberValues.put(attrName, newArr.toArray(new String[arr.length]));}}}}} catch (NoSuchFieldException | IllegalAccessException e) {log.error("动态添加注解数据失败!");e.printStackTrace();throw new BizException("动态添加注解数据失败!");}return clazz;}/*** 导出带下拉框的模板excel** @param response* @param filename*/public static void exportExcelTemplateBySelected(HttpServletRequest request, HttpServletResponse response, String filename, Class clazz, Map<String, String> replaceMap) {try {if (StrUtil.isBlank(filename)) {filename = "模板下载";}String agent = request.getHeader("USER-AGENT").toLowerCase();if (agent.contains("firefox")) {filename = new String(filename.getBytes(), "ISO8859-1");} else {filename = URLEncoder.encode(filename, "UTF-8");}response.setContentType("application/vnd.ms-excel;charset=UTF-8");response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", filename + ".xlsx"));response.setHeader("Cache-Control", "no-cache");response.setHeader("Pragma", "no-cache");response.setDateHeader("Expires", -1);response.setCharacterEncoding("UTF-8");ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();Class dynamicClazz = dynamicReviseAnnotationParam(clazz, "value", replaceMap);WriteSheet writeSheet = EasyExcelUtil.writeSelectedSheet(dynamicClazz, 0, "sheet");excelWriter.write(new ArrayList<String>(), writeSheet);excelWriter.finish();} catch (Exception e) {e.printStackTrace();log.error("导出模板失败!");throw new BizException("导出模板失败!");}}/*** 模型解析监听器 -- 每解析一行会回调invoke()方法,整个excel解析结束会执行doAfterAllAnalysed()方法** @param <E>*/public static class ModelExcelListener<E> extends AnalysisEventListener<E> {private List<E> dataList = new ArrayList<E>();private Map<Integer, String> dataMap = new HashMap<Integer, String>(16);@Overridepublic void invoke(E object, AnalysisContext context) {dataList.add(object);}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {}@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {log.info("表头数据 excelHead= {}", headMap);dataMap.putAll(headMap);}public List<E> getDataList() {return dataList;}@SuppressWarnings("unused")public void setDataList(List<E> dataList) {this.dataList = dataList;}public Map<Integer, String> getDataMap() {return dataMap;}@SuppressWarnings("unused")public void setDataMap(Map<Integer, String> dataMap) {this.dataMap = dataMap;}}/*** 使用 模型 来读取Excel** @param inputStream Excel的输入流* @param clazz 模型的类* @return 返回 模型 的列表*/public static <E> List<E> readExcelWithModel(InputStream inputStream, Class<?> clazz) {// 解析每行结果在listener中处理ModelExcelListener<E> listener = new ModelExcelListener<>();try {EasyExcel.read(inputStream, clazz, listener).sheet().doRead();} catch (Exception e) {//throw new BusinessException("导入数据有问题,请修改后再上传");throw new BizException("导入数据有问题,请修改后再上传!");}return listener.getDataList();}/*** 使用 模型 来读取Excel** @param inputStream Excel的输入流* @param clazz 模型的类* @return 返回 模型 的列表*/public static <E> ModelExcelListener<E> readListener(InputStream inputStream, Class<?> clazz) {// 解析每行结果在listener中处理ModelExcelListener<E> listener = new ModelExcelListener<>();try {EasyExcel.read(inputStream, clazz, listener).sheet().doRead();} catch (Exception e) {//throw new BusinessException("导入数据有问题,请修改后再上传");throw new BizException("导入数据有问题,请修改后再上传!");}return listener;}}
SelectedSheetWriteHandler.java
/*** 对cell进行下拉框设置操作** @author MaoDeShu* @date 2024-04-22 10:32*/
@AllArgsConstructor
public class SelectedSheetWriteHandler implements SheetWriteHandler {private final Map<Integer, ExcelSelectedResolve> selectedMap;/*** Called before create the sheet*/@Overridepublic void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}/*** Called after the sheet is created*/@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {// 这里可以对cell进行任何操作globalCountryAreaSheet sheet = writeSheetHolder.getSheet();DataValidationHelper helper = sheet.getDataValidationHelper();selectedMap.forEach((k, v) -> {// 设置下拉列表的行: 首行,末行,首列,末列CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), v.getLastRow(), k, k);// 设置下拉列表的值DataValidationConstraint constraint = helper.createExplicitListConstraint(v.getSource());// 设置约束DataValidation validation = helper.createValidation(constraint, rangeList);// 阻止输入非下拉选项的值validation.setErrorStyle(DataValidation.ErrorStyle.STOP);// 处理Excel兼容性问题if (validation instanceof XSSFDataValidation) {validation.setShowErrorBox(true);validation.setSuppressDropDownArrow(true);} else {validation.setSuppressDropDownArrow(false);}validation.createErrorBox("提示", "请输入下拉选项中的内容");sheet.addValidationData(validation);});// 冻结表头sheet.createFreezePane(0,1);}
}
三、导出、导入Excel接口
1.导出接口
@Slf4j
@RestController
@RequestMapping("/api/test")
@RequiredArgsConstructor
public class TestController {@PostMapping("/exportExcel")public void exportExcel(@RequestParam(value = "filename", required = false) @ApiParam(value = "文件名称") String filename,HttpServletRequest request, HttpServletResponse response) {HashMap<String, String> replaceMap = new HashMap<>();replaceMap.put("模板版本", "V1.0");// 这里replaceMap是需要替换表头的值,将"模板版本"替换为"V1.0"EasyExcelUtil.exportExcelTemplateBySelected(request, response, filename, TravelGroupExportExcel.class, replaceMap);}
}
2.导入接口
@Slf4j
@RestController
@RequestMapping("/api/test")
@RequiredArgsConstructor
public class TestController {@PostMapping("/importExcel")public String importExcel(HttpServletRequest request, HttpServletResponse response) {try {MultipartHttpServletRequest multipart = (MultipartHttpServletRequest) request;MultiValueMap<String, MultipartFile> map = multipart.getMultiFileMap();MultipartFile file = null;for (Map.Entry<String, List<MultipartFile>> entry : map.entrySet()) {List<MultipartFile> files = entry.getValue();if (files == null || files.size() == 0) {return "文件为空!";}file = files.get(0);}if (file != null) {EasyExcelUtil.ModelExcelListener listener = EasyExcelUtil.readListener(file.getInputStream(), TravelGroupExcelModel.class);// 获取到excel信息List<TravelGroupExcelModel> excelModelList = listener.getDataList();// 获取excel表头Map<Integer, String> headMap = listener.getDataMap();if (headMap.isEmpty() || headMap.size() != 6) {return "导入模板格式错误,请下载正确的模板修改后重新上传!";}if (excelProperties.getIsCheckVersion()) {String version = headMap.get(headMap.size() - 1);if (!excelProperties.getVersion().equalsIgnoreCase(version)) {return "导入模板版本错误,请下载更新正确的模板后重新上传!";}}if (CollUtil.isEmpty(excelModelList)) {return "获取导入数据为空,请检查模板或数据格式是否正确!";}// 后续处理 excelModelList }} catch (Exception e) {e.printStackTrace();return e.getMessage();}return null;}
}
3.导出结果
总结
以上就是今天要讲的内容,本文仅仅简单介绍了Java使用EasyExcel导出带自定义下拉框数据的Excel模板,侧重于实践教学,希望能给大家一个参考。
⭕关注博主,不迷路 ⭕
创作不易,关注💖、点赞👍、收藏🎉就是对作者最大的鼓励👏,欢迎在下方评论留言🧐