背景
这段时间,在做项目时,设计到需要带参数的批量生成报告。尝试了很多方法,包括grafana等BI工具。虽然grafana这种BI工具可以在线查看,但是无法导出。甚至当想把报告整合邮件发送时,grafana就显得无能为力了。于是在搜索了各个工具后发现了 ireporter 。不得不说,现在这个时代,我们遇到的无法解决研发场景,肯定有人也遇到了,并且已经有了很好的解决方案。
ireporter支持实现编辑好报告模板,然后通过sql查询和参数控制,实现报告的在线生成。并提供导出pdf和html的格式,支持报告生成后直接访问和下载。或作为邮件的附件,直接发送,解决了我的场景。
废话不多说,直接上代码。
事前准备
在整合之前,你需要提前准备好 ireporter 的模板文件。ireporter的下载安装,网上有很多教程,此处就不再赘述,可自行搜索相关内容。
模板准备步骤:
-
安装好软件之后,打开软件,配置一个简单的报告模板,并保存。 -
右键项目名称,选择【compile report】编译报告。 -
将【*.jasper】模板文件放到项目工程下
整合 spring boot 工程
maven引用
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.17.0</version>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-fonts</artifactId>
<version>6.17.0</version>
</dependency>
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>iTextAsian</artifactId>
<version>5.2.0.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/iTextAsian_5.2.0.2.jar</systemPath>
</dependency>
<dependency>
<groupId>net.sf.barcode4j</groupId>
<artifactId>barcode4j</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>net.sourceforge.barbecue</groupId>
<artifactId>barbecue</artifactId>
<version>1.5-beta1</version>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-bridge</artifactId>
<version>1.14</version>
</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);
public void processReporter2PDF(String templateName, Map<String, Object> params, String filePath) {
Resource resource = new ClassPathResource(templateName);
FileInputStream fis = null;
FileOutputStream fileOutputStream = null;
Connection connection = ConnectionProvider.getConnection();
try {
fis = new FileInputStream(resource.getFile());
JasperPrint jasperPrint = JasperFillManager
.fillReport(fis, params, connection);
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();
}
}
}
}
public void processReporter2Html(String templateName, Map<String, Object> params, String fileName) {
Resource resource = new ClassPathResource(templateName);
FileInputStream fis = null;
Connection connection = ConnectionProvider.getConnection();
try {
fis = new FileInputStream(resource.getFile());
JasperPrint jasperPrint = JasperFillManager.fillReport(fis, params, connection);
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;
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";
}
}
测试
- 启动服务,浏览器访问地址。
2. 查看生成的文件
resource-reporter-by-project.html_files中存放的是表格对应的图片。
访问生成的html文件如下图。
结语
使用ireporter的一个缺点是,模板文件需要提前准备,虽然可以通过提供上传页面来支持,但是导致的结果是需要在两个页面之间切换。如果只是少数几个人使用,可以保持这种模式没问题。如果使用的人数较多,可以考虑对ireporter进行二次开发,提供在线模式,并将生成的模板放到云服务器上。这样其他应用可以直接读取云服务上的模板文件,而免去在应用之间的跳转。
|