Java无模板导出Excel,Apache-POI插件实现
开发环境
jdk 1.8
maven 3.6
tomcat 8.5
springboot 2.1.4.release
apache-poi 3.6
idea
注意: 我是在现有的基于springboot的分模块项目中集成的文件导出功能,所以就不再从项目构建开始介绍了,如有对springboot项目构建不熟悉,或对构建分模块项目感兴趣的同学,可移步[springboot构建分模块项目...(还没写呢)]
maven依赖
在pom.xml中添加依赖,一下两种方式,根据个人习惯,任选其一;
<!-- 引入方式一 --> <properties> <!-- ↓↓↓↓↓↓ 此处省略项目中用到的基本jar包的版本 ↑↑↑↑↑↑--> <!-- 版本 --> <apache.poi.version>3.6</apache.poi.version> </properties> <dependencies> <!-- ↓↓↓↓↓↓ 此处省略项目中用到的基本jar包 ↑↑↑↑↑↑--> <!-- apache-poi --> <dependency> <groupid>org.apache.poi</groupid> <artifactid>poi</artifactid> <version>${apache.poi.version}</version> </dependency> </dependencies>
<!-- 引入方式二 --> <dependencies> <!-- ↓↓↓↓↓↓ 此处省略项目中用到的基本jar包 ↑↑↑↑↑↑--> <!-- apache-poi --> <dependency> <groupid>org.apache.poi</groupid> <artifactid>poi</artifactid> <version>3.6</version> </dependency> </dependencies>
当pom配置好之后,我们查看右侧maven,检查是否下载成功;
有的同学可能会遇maven中显示apache-poi确实已经被引入了,但是仍然在项目中无法使用。其实此时jar确实已经被下载到本地仓库了,只是没有添加到我们项目中。添加方式参照[解决idea项目启动报错:程序包javax.servlet.http不存在],只是此时的jar来源不再是tomcat目录下,而是我们的本地仓库;
前面都做好的话,就可以编写我们的代码了;
工具类
先说下我设计这个接口之前想到的问题,以及对该接口的应用场景
该工具类设计好之后,可对本项目提供服务,也可包装成接口,对外暴露服务,调用者只需按照我们要求的参数格式给出参数即可
该项目部署之后,将来其他项目需要用到导出功能,只需调用我们的接口即可,
根据以上两点,在工具类的设计上要遵循低耦合的原则,使其高可用、高复用
package com.wayne.utils; import com.alibaba.fastjson.json; import com.google.common.collect.lists; import com.wayne.common.exception.serviceexception; import org.apache.poi.hssf.usermodel.*; import java.io.file; import java.io.fileoutputstream; import java.util.list; import java.util.map; /** * 文件导出工具类 * @author wayne * @date 2019/5/15 */ public class exportutil { /** * 无模块导出excel方法, * 参数data格式: [ * { * "姓名": "张三"; * "年龄": "23"; * "性别": "男" * }, * { * "姓名": "李四"; * "年龄": "24"; * "性别": "男" * } * ] * * @param data 需要导出的数据 * @param filename 导出后保存到本地的文件名 * @return 创建好的excel文件,用于前端下载 * @throws serviceexception 自定义runtimeexception子类异常 */ public static hssfworkbook exportexcel(list<map<string, object>> data, string filename) throws serviceexception { // 从参数data中解析出打印的每列标题,放入title中 list<string> title = lists.newarraylist(); for(map.entry<string, object> entry : data.get(0).entryset()) { title.add(entry.getkey()); } // 新建一个excel文件 hssfworkbook wb = new hssfworkbook(); // excel中的sheet hssfsheet sheet = wb.createsheet(); // sheet中的行,0表示第一行 hssfrow row = sheet.createrow(0); // 设置标题居中 hssfcellstyle cellstyle = wb.createcellstyle(); cellstyle.setalignment(hssfcellstyle.align_center); // sheet中的单元格 hssfcell cell = null; // 给第一行赋值,值为我们从参数中解析出的标题,因此需要我们在传参的时候需要严格按照约定 for(int i = 0; i < title.size(); i++) { cell = row.createcell(i); cell.setcellvalue(title.get(i)); cell.setcellstyle(cellstyle); } // 根据参数内容数量,创建表格行数 for(int i = 0; i < data.size(); i++) { row = sheet.createrow(i + 1); map<string, object> values = data.get(i); // 将参数插入每一个单元格 for(int k = 0; k < title.size(); k++) { object value = values.get(title.get(k)); if(null == value) { value = ""; } string val = json.tojsonstring(value); row.createcell(k).setcellvalue(val); } } // 将文件写到硬盘中,将来根据需求,或写到服务器中,因此在实际开发中,最好将"e:/temp/"配置在.properties配置文件中,仪表项目上线事更换方便 try { fileoutputstream fileoutputstream = new fileoutputstream(new file("e:/temp/" + filename)); wb.write(fileoutputstream); fileoutputstream.flush(); fileoutputstream.close(); } catch (exception e) { throw new serviceexception("文件导出失败"); } return wb; } }
接口层和业务层
接口为传统的接口
在业务层对查询到的data做了处理,已达到符合工具类规范
package com.wayne.impl; import com.alibaba.fastjson.json; import com.wayne.common.dto.responsebean; import com.wayne.common.entity.cmsuser; import com.wayne.mapper.cmsusermapper; import com.wayne.service.exportservice; import com.wayne.utils.exportutil; import org.apache.poi.hssf.usermodel.hssfworkbook; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.service; import java.util.list; import java.util.map; import java.util.uuid; /** * 文件导出service实现类 * @author wayne * @date 2019/5/15 */ @service public class exportserviceimpl implements exportservice { @autowired private cmsusermapper usermapper; /** * 无模板导出excel * @param request 前端传来参数,格式为json字符串的对象,因为在实际开发中可能要用到根据查询参数导出,比如: 导出年龄在18 --- 26之间的、性别为女的用户 * @return */ @override public responsebean exportexcel(string request) { responsebean responsebean; // 将前端参数转换为对象 cmsuser user = json.parseobject(request, cmsuser.class); // 根据条件查询需要导出的数据(后附sql内容) list<map<string, object>> userlist = usermapper.selectbyparams(user); try { // 生成文件名 string filename = uuid.randomuuid() + ".xls"; // 调用工具类生成excel,此时接收到的wb即是完整的excel hssfworkbook wb = exportutil.exportexcel(userlist, filename); //todo 此处可根据实际需要添加是否写到前端,供用户下载 responsebean = responsebean.createinstance(boolean.true, "导出成功", filename); } catch (exception e) { responsebean = responsebean.createinstance(false, 500, e.getmessage()); system.out.println(e.getmessage()); } return responsebean; } }
package com.wayne.web.comtroller; import com.wayne.common.dto.responsebean; import com.wayne.service.exportservice; import org.springframework.beans.factory.annotation.autowired; import org.springframework.web.bind.annotation.postmapping; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.restcontroller; /** * 文件导出接口 * 接口层不做业务处理 * @author wayne * @date 2019/5/15 */ @restcontroller @requestmapping("/export") public class exportcontroller { @autowired private exportservice exportservice; /** * 导出excel * @param request 导出条件 */ @postmapping("/excel") public responsebean exportexcel(string request) { return exportservice.exportexcel(request); } }
其他区相关代码
service
package com.wayne.service; import com.wayne.common.dto.responsebean; /** * @author wayne * @date 2019/5/15 */ public interface exportservice { responsebean exportexcel(string request); }
mapper
mapper没什么好贴出来的吧~~~
mapper.xml
<!-- 此查询方法是从视图中查,目的还是为了降低耦合性,在视图中已经对data做了格式调整 --> <select id="selectbyparams" resulttype="map"> select * from v_user where `用户名` != 'admin' <where> <if test="username != null and username != ''"> and `用户名` = #{username, jdbctype=varchar} </if> <if test="mobile != null and mobile != ''"> and `手机号` = #{mobile,jdbctype=varchar} </if> </where> order by `用户名` </select>
视图
select `cms_user`.`username` as `用户名`, `cms_user`.`realname` as `真实姓名`, `cms_user`.`sex` as `性别`, `cms_user`.`email` as `邮箱`, `cms_user`.`mobile` as `手机号`, `cms_user`.`status` as `状态`, `cms_user`.`create_time` as `创建时间`, `cms_user`.`create_user_id` as `创建者` from `cms_user`
其它
本项目中用到的responsbean为自定义返回到前端的实体类,serviceexception为自定义runtimeexception异常
前端代码就不再展示了,因为此功能在前端只是一个按钮和点击事件
效果展示
因为不确定时间在第几列,所以无法动态将时间列设置为日期格式,需要后续手动处理
预留占位
开发怎能不留扩展字段 (¬_¬)…