需求
做excel导出----接口统一,写一个接口即可,避免过多冗余代码,和后期维护工作。
前言
最近项目好多excel导出的需求,真的太多了,所以一个一个写,就算是cv也太浪费了。然后就想了一下,是不是可以做一个统一导出的逻辑。然后用了半天的时间写了一个。因为有些知识忘记了,又复习了一下。
这里用到了 数据库和反射和easyPoi。当然,该方案是需要返回数据格式统一,这里使用Map格式。
那咱们废话不多说,开始!!!
开始实现
步骤一:数据库设计
首先我们既然要统一接口,那肯定是需要一些配置,因为每个导出用到的格式虽然一样,但是数据是不一样的,需要调用不同的service获取数据(我这里直接调用的Controller方法,因为根据我们这个项目逻辑,所以这么写的,Controller处理完成数据后,前端可以使用,然后导出接口也可以使用,两全其美),所以,所以,我们需要将配置找个地方存起来,可以选择配置文件,然后读取,可以使用数据库存储。我使用的是第二种方式,数据库。
因为有一些导出比较复杂,不一定是就导出一个表,可能是两个表,这里我的想法就是多sheet导出。
然后下面是我设计的数据库
-- 主表 一个模板对应多个sheet
CREATE TABLE `excel_configure` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT ‘导出的时候,只需要根据该id查询模板即可‘,
`service_name` varchar(255) DEFAULT NULL COMMENT ‘Service名称,Service的路径,反射使用(我这里使用的是Controller)‘,
`service_method_name` varchar(100) DEFAULT NULL COMMENT ‘Service方法名称,反射使用(Controller方法名)‘,
`template_url` varchar(255) DEFAULT NULL COMMENT ‘模板地址‘,
`file_name` varchar(100) DEFAULT NULL COMMENT ‘导出的文件名称‘,
`save_path` varchar(255) DEFAULT NULL COMMENT ‘导出的路径‘,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC
-- 从表
CREATE TABLE `excel_configure_type` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type_name` varchar(100) DEFAULT NULL COMMENT ‘返回的某个数据名称(相当于key)‘,
`sheet_name` varchar(100) DEFAULT NULL COMMENT ‘该数据的sheet名称‘,
`p_id` int(11) DEFAULT NULL COMMENT ‘模板id‘,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC
步骤二:接口编写
还是那句话,大家可以根据自己项目的业务逻辑来进行更改。这个逻辑只符合本项目的数据(如果你也是map数据,那是没有问题的。只需要吧接口什么的更改一下,缕清逻辑即可。不懂可以问,毕竟我已经把坑踩过了。)
Controller
@Resource
private ExcelConfigureService ExcelConfigureService;
/**
* 统一导出接口---多sheet
* 单个sheet也是可以用的,但是必须符合下面的逻辑,返回数据必须是page列表或者是比例列表。
* @param response 响应
* @param params 前端传入的参数
*/
@RequestMapping("/exportAllSheet")
public void exportAllSheet(HttpServletResponse response, @RequestParam Map<String,String> params){
//1.获取该模板数据-->根据传入参数获取数据库中模板配置-->后面根据模板配置通过反射获得对应的类和方法,进行获取导出数据
ExcelConfigureEntity excelConfigureEntity = ExcelConfigureService.queryObject(Integer.parseInt(params.get("export")));
//2.获取当前上下文环境,spring容器
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
//2.1 加载class类
Class<?> clazz = null;
try {
clazz = Class.forName(excelConfigureEntity.getServiceName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//2.2 获得方法
Method m = null;
try {
m = clazz.getMethod(excelConfigureEntity.getServiceMethodName(), Map.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
//2.3 执行方法,获得数据
R r = null;
try {
//2.3.1 切割被调用的路径,拿到最后一个类名,
String s = excelConfigureEntity.getServiceName().split("\\.")[excelConfigureEntity.getServiceName().split("\\.").length - 1];
//2.3.2 类名首字母转小写 -->首字母大写在ioc容器中找不到该类
s = (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
//2.3.3 执行
r = (R) m.invoke(wac.getBean(s), params);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//3. 填充数据
Map<String, Object> map = new HashMap<String, Object>();
//下面的参数根据自己的业务逻辑来,不需要直接删掉即可
//3.1 开始时间-->根据前端条件获取-->固定参数
String start = params.get("dateScopeStart");
//3.2 结束时间-->根据前端条件获取-->固定参数
String end = params.get("dateScopeEnd");
//3.3 获取年-->根据前端条件获取-->固定参数
String year = params.get("year");
//3.4 获取月-->根据前端条件获取-->固定参数
String month = params.get("month");
//3.5 标题名称-->根据数据库配置读取
map.put("title", excelConfigureEntity.getFileName()+(start!=null&&end!=null?start+"~"+end:"")+(year!=null?year+"年-"+(month!=null&&!month.equals("")?month+"月":"全年度"):""));
//4.遍历查询每个sheet数据
//4.1 定义遍历索引
int i = 0;
//4.2 定义需要填充的sheet名称数组-->长度是数据库读取到的集合长度
String [] sheetArr = new String[excelConfigureEntity.getExcelConfigureTypeEntityList().size()];
//4.3 遍历集合
for (ExcelConfigureTypeEntity entity : excelConfigureEntity.getExcelConfigureTypeEntityList()) {
//5.填充每个sheet列表数据
//5.1 sheet名称
sheetArr[i++] = entity.getSheetName();
//5.2 判断是否为page,为page需要转换为PageUtils工具
if ("page".equals(entity.getTypeName())) {
//5.3 是page
//5.3.1 根据类型获取到通过反射拿到的Map数据
PageUtils pageUtils = (PageUtils) r.get(entity.getTypeName());
//5.3.2 获取到数据列表
List<Map<String,Object>> listMap = (List<Map<String, Object>>) pageUtils.getList();
//5.3.3 将数据添加到需要导出的Map中--->就是excel导出数据--->名字是maplist+该类型名称(数据库中配置的)
map.put("maplist"+entity.getTypeName(), listMap);
}else {
//5.4 不是page,说明需要计算比例数据
//5.4.1 获取比例数据-->根据类型获取到通过反射拿到的Map数据
Map<String,Object> projectChartData = (Map<String, Object>) r.get(entity.getTypeName());
//5.4.2 将比例数据做处理,拿到处理好的数据Map
Map<String, List<String>> proportion = new ProportionUtils().proportion(projectChartData);
//5.4.3 定义临时list<Map>,用于接收遍历出的每一条比例
List<Map<String,Object>> listMap = new ArrayList<>();
//5.4.4 遍历比例数据,将比例数据处理成需要的格式
for (Map.Entry<String, List<String>> entry : proportion.entrySet()) {
//5.4.4.1 定义Map,用于接收每次遍历出来的数据,放到临时List<Map>中--->放在循环外会导致每次数据都会更新成最后一次的数据,因为地址值原因
Map<String, Object> tempMap = new HashMap<>();
tempMap.put("name",entry.getKey());//比例名称
tempMap.put("count",entry.getValue().get(0));//比例数量
tempMap.put("proportion",entry.getValue().get(1));//比例%
//5.4.4.2 添加到临时Map中
listMap.add(tempMap);
}
//5.4.5 比例数据处理完毕,将数据添加到导出的Map中--->就是excel导出数据--->名字是maplist+该类型名称(数据库中配置的)
map.put("maplist"+entity.getTypeName(),listMap);
}
}
//6. 根据模板导出-->模板路径根据数据库配置读取, true表示多sheet
TemplateExportParams tempParams = new TemplateExportParams(excelConfigureEntity.getTemplateUrl(),true);
//6.1 设置每个sheet名称-->数组格式-->在上面4.2已定义, 5.1处理数据时已经处理完毕,可直接使用
tempParams.setSheetName(sheetArr);
//6.2 调用工具easyPoi,进行导出 -->传入模板数据&需要导出的Map数据
Workbook workbook = ExcelExportUtil.exportExcel(tempParams, map);
/*6.3 保存地址-->暂时使用默认下载地址-->如需要指定下载地址,在数据库中配置后打开本段代码即可
File savefile = new File(excelConfigureEntity.getSavePath());
if (!savefile.exists()) {
savefile.mkdirs();
}
*/
//7. 保存
OutputStream output = null;
try {
//7.1 通过响应获得输出流
output = response.getOutputStream();
//7.2 输出文件名称-->通过时间转换对象,将时间转换成指定格式
String fileName = excelConfigureEntity.getFileName()+"-"+new SimpleDateFormat("yyyy-MM-dd").format(new Date());
//7.3 添加响应头
response.setHeader("Content-disposition", "attachment;filename="+new String(fileName.getBytes(), "ISO8859-1")+".xlsx");
//7.4 响应格式
response.setContentType("application/msexcel;charset=UTF-8");
//7.5 输出
workbook.write(output);
} catch (IOException e) {
e.printStackTrace();
}finally {
//7.6 执行完毕,关闭流,避免消耗内存
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后给大家分享一下测试接口-单sheet的
excel模板
测试结果
Java代码
虽然是测试,但是也是读取的真实数据
/**
* 测试接口---ok
* @param response
* @param params
* @throws Exception
*/
@RequestMapping("/testExport1")
public void testExport(HttpServletResponse response, @RequestParam Map<String,String> params) throws Exception {
//获取当前上下文环境,spring容器
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
//加载class类
Class<?> clazz = Class.forName("com.lcworld.service.SysUserService");
//执行方法--读数据库
Method m = clazz.getMethod("queryList", Map.class);
//执行方法,获得数据
Object invoke = m.invoke(wac.getBean("sysUserService"), params);
List<Map<String, Object>> listMap = (List<Map<String, Object>>) invoke;
//加载模板
TemplateExportParams tempParams = new TemplateExportParams("E:\\test.xlsx");
//填充数据
Map<String, Object> map = new HashMap<String, Object>();
map.put("test", "2014-12-25");
map.put("test1", 2000000.00);
map.put("maplist", listMap);
Workbook workbook = ExcelExportUtil.exportExcel(tempParams, map);
//保存地址
File savefile = new File("E:\\");
if (!savefile.exists()) {
savefile.mkdirs();
}
//保存
OutputStream output = response.getOutputStream();
Date now = new Date();
//时间格式化
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
//输出文件名称
String fileName = "检测项目数量-"+format.format(now);
//添加响应头
response.setHeader("Content-disposition", "attachment;filename="+new String(fileName.getBytes(), "ISO8859-1")+".xlsx");
//响应格式
response.setContentType("application/msexcel;charset=UTF-8");
workbook.write(output);
//关闭流
output.close();
}
单sheet接口导出
前提还是每个需要导出的接口(这里是调用的service), 返回数据必须是统一的,统一map
数据库
-- 单sheet导出表
CREATE TABLE excel_configure(
id INT PRIMARY KEY AUTO_INCREMENT,
service_name VARCHAR(255) COMMENT ‘Service名称(类的路径)‘,
service_method_name VARCHAR(20) COMMENT ‘Service方法名称‘,
template_url VARCHAR(255) COMMENT ‘模板地址‘,
file_name VARCHAR(20) COMMENT ‘导出文件名称‘,
save_path VARCHAR(255) COMMENT ‘导出路径‘
)ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
Controller
/**
* 统一导出入口---单个sheet
* @param response
* @param params
*/
@RequestMapping("/exportAll")
public void exportAll(HttpServletResponse response, @RequestParam Map<String,String> params){
//获取该模板数据
ExcelConfigureEntity excelConfigureEntity = ExcelConfigureService.queryObject(Integer.parseInt(params.get("export")));
//获取当前上下文环境,spring容器
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
//加载class类
Class<?> clazz = null;
try {
clazz = Class.forName(excelConfigureEntity.getServiceName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//获得方法
Method m = null;
try {
m = clazz.getMethod(excelConfigureEntity.getServiceMethodName(), Map.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
//执行方法,获得数据
String[] arr = excelConfigureEntity.getServiceName().split("\\.");
List<Map<String, Object>> listMap = null;
try {
//首字母转小写
String s = arr[arr.length - 1];
s = (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
listMap = (List<Map<String, Object>>) m.invoke(wac.getBean(s), params);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//填充数据
Map<String, Object> map = new HashMap<String, Object>();
String start = params.get("dateScopeStart");//开始时间
String end = params.get("dateScopeEnd");//结束时间
map.put("title", excelConfigureEntity.getFileName()+(start!=null&&end!=null?start+"~"+end:""));
map.put("maplist", listMap);
//根据模板导出
Workbook workbook = ExcelExportUtil.exportExcel(new TemplateExportParams(excelConfigureEntity.getTemplateUrl()), map);
//保存地址--暂时使用默认地址
/*
File savefile = new File(excelConfigureEntity.getSavePath());
if (!savefile.exists()) {
savefile.mkdirs();
}
*/
//保存
OutputStream output = null;
try {
output = response.getOutputStream();
Date now = new Date();
//时间格式化
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
//输出文件名称
String fileName = excelConfigureEntity.getFileName()+"-"+format.format(now);
//添加响应头
response.setHeader("Content-disposition", "attachment;filename="+new String(fileName.getBytes(), "ISO8859-1")+".xlsx");
//响应格式
response.setContentType("application/msexcel;charset=UTF-8");
//输出
workbook.write(output);
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭流
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
OK,这篇就这样
写的有点笼统,因为项目接口数据是不一样的,所以,没有办法直接让你使用,可以试着理解一下,遇到问题问就可以。