这段时间,在做项目时,设计到需要带参数的批量生成报告。尝试了很多方法,包括grafana等BI工具。虽然grafana这种BI工具可以在线查看,但是无法导出。甚至当想把报告整合邮件发送时,grafana就显得无能为力了。于是在搜索了各个工具后发现了 ireporter 。不得不说,现在这个时代,我们遇到的无法解决研发场景,肯定有人也遇到了,并且已经有了很好的解决方案。
ireporter支持实现编辑好报告模板,然后通过sql查询和参数控制,实现报告的在线生成。并提供导出pdf和html的格式,支持报告生成后直接访问和下载。或作为邮件的附件,直接发送,解决了我的场景。
废话不多说,直接上代码。
在整合之前,你需要提前准备好 ireporter 的模板文件。ireporter的下载安装,网上有很多教程,此处就不再赘述,可自行搜索相关内容。
模板准备步骤:
<dependency>
<groupId>net.sf.jasperreportsgroupId>
<artifactId>jasperreportsartifactId>
<version>6.17.0version>
dependency>
<dependency>
<groupId>net.sf.jasperreportsgroupId>
<artifactId>jasperreports-fontsartifactId>
<version>6.17.0version>
dependency>
<dependency>
<groupId>com.lowagiegroupId>
<artifactId>iTextAsianartifactId>
<version>5.2.0.2version>
<scope>systemscope>
<systemPath>${project.basedir}/lib/iTextAsian_5.2.0.2.jarsystemPath>
dependency>
<dependency>
<groupId>net.sf.barcode4jgroupId>
<artifactId>barcode4jartifactId>
<version>2.1version>
dependency>
<dependency>
<groupId>net.sourceforge.barbecuegroupId>
<artifactId>barbecueartifactId>
<version>1.5-beta1version>
dependency>
<dependency>
<groupId>org.apache.xmlgraphicsgroupId>
<artifactId>batik-bridgeartifactId>
<version>1.14version>
dependency>
ireporter在生成报表时,需要读取数据库数据,因此需要一个数据库连接。此处为了说明,简单创建了一个数据库连接。实际项目中,需要对该工具类做适当的封装。
public class ConnectionProvider {
// 数据库连接信息,这里由于我的模板使用的是运营中心数据库,这里默认用研发云查询库数据库作为数据源
// 实际项目中,需要对此部分配置信息进行封装
private static String driverClassName ="com.mysql.jdbc.Driver";
private static String username="username";
private static String password="password";
private static String url="jdbc:mysql://ip:port/databaseName?useSSL=false";
static{
try {
Class.forName(driverClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection(){
try {
return DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
编写工具类,调用ireporter报告模板,生成相应的报告。
@Component
public class JasperReporterHandle {
private static final Logger LOGGER = LoggerFactory.getLogger(JasperReporterHandle.class);
/**
* 导出报表为 pdf
* @param templateName ireporter工具生成的模板名称,使用全路径
* @param params 报告模板中定义的参数列表
* @param filePath 导出的pdf文件名称,使用全路径。实际开发中,此参数可省略,直接返回文件流,共页面直接下载。
*/
public void processReporter2PDF(String templateName, Map<String, Object> params, String filePath) {
//1.读取模板文件
Resource resource = new ClassPathResource(templateName);
FileInputStream fis = null;
FileOutputStream fileOutputStream = null;
// 2. 获取数据库连接
Connection connection = ConnectionProvider.getConnection();
try {
fis = new FileInputStream(resource.getFile());
//2.模板和数据整合
JasperPrint jasperPrint = JasperFillManager
.fillReport(fis, params, connection);
//3.导出PDF
File outFile = new File(filePath);
// 文件不存在,且创建失败,则直接返回
if (!outFile.exists() && !outFile.createNewFile()) {
LOGGER.error("file create failed. ");
return; }
fileOutputStream = new FileOutputStream(outFile);
JasperExportManager.exportReportToPdfStream(jasperPrint, fileOutputStream);
} catch (JRException | IOException e) {
e.printStackTrace();
} finally {
// 关闭文件流
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 导出报表为 pdf
* @param templateName ireporter工具生成的模板名称,使用全路径
* @param params 报告模板中定义的参数列表
* @param fileName 导出的html文件名称,使用全路径。
*/
public void processReporter2Html(String templateName, Map<String, Object> params, String fileName) {
// 1. 获取模板
Resource resource = new ClassPathResource(templateName);
FileInputStream fis = null;
// 2. 获取数据库连接
Connection connection = ConnectionProvider.getConnection();
try {
fis = new FileInputStream(resource.getFile());
// 3. 获取报告对象
JasperPrint jasperPrint = JasperFillManager.fillReport(fis, params, connection);
// 4. 导出至html文件
JasperExportManager.exportReportToHtmlFile(jasperPrint, fileName);
} catch (IOException | JRException e) {
e.printStackTrace();
}
finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Service层
@Service
public class JasperReporterServiceImpl implements JasperReporterService {
@Autowired
private JasperReporterHandle jasperReporterHandle;
// todo 换成自己的路径
private static final String FILE_PATH = "C:/temp/jsreporter/";
public void processReport2PDF(Map<String, Object> param, String fileName) {
// 模板名称
String templateName = "static/data_center_billing_mail.jasper";
String fileFullPath = FILE_PATH + fileName;
jasperReporterHandle.processReporter2PDF(templateName, param, fileFullPath);
}
@Override
public void processReport2Html(Map<String, Object> param, String fileName) {
// 模板名称
String templateName = "static/data_center_billing_mail.jasper";
String outFileName = FILE_PATH + fileName;
jasperReporterHandle.processReporter2Html(templateName, param, outFileName);
}
}
controller 层
@Controller
@RequestMapping(value = "/test")
public class TestController {
@Autowired
private JasperReporterService jasperReporterService;
@GetMapping("processReport2PDF/{projectId}")
@ResponseBody
public String processReport2PDF(@PathVariable("projectId") Integer projectId){
Map<String, Object> param = new HashMap<>();
param.put("projectId", projectId);
jasperReporterService.processReport2PDF(param, "resource-reporter-by-project.pdf");
return "OK";
}
@GetMapping("/processReport2Html/{projectId}")
@ResponseBody
public String processReport2Html(@PathVariable("projectId") Integer projectId) {
Map<String, Object> param = new HashMap<>();
param.put("projectId", projectId);
jasperReporterService.processReport2Html(param, "resource-reporter-by-project.html");
return "OK";
}
}
resource-reporter-by-project.html_files中存放的是表格对应的图片。
使用ireporter的一个缺点是,模板文件需要提前准备,虽然可以通过提供上传页面来支持,但是导致的结果是需要在两个页面之间切换。如果只是少数几个人使用,可以保持这种模式没问题。如果使用的人数较多,可以考虑对ireporter进行二次开发,提供在线模式,并将生成的模板放到云服务器上。这样其他应用可以直接读取云服务上的模板文件,而免去在应用之间的跳转。