需求
BIRT 一般是可以通过单独部署一个Webviewer的工程来处理客户的report查看请求.但是有些需求可能是根据业务规则定制查询条件生成report,然后定期生成report放在数据库里,供客户查询或是report通过邮件发给客户。
目标
- 生成的report为PDF,存在硬盘上
- report可以接收参数
- report调用Javacode来获取数据集
Sample - car report
开发一个report,内容显示车辆的年份,型号,厂商。数据如下
static List<Car> cars;
static{
Car car1 = new Car();
car1.setYear("2000");
car1.setMake("Chevrolet");
car1.setModel("Corvette");
Car car2 = new Car();
car2.setYear("2005");
car2.setMake("Dodge");
car2.setModel("Viper");
Car car3 = new Car();
car3.setYear("2002");
car3.setMake("Ford");
car3.setModel("Mustang GT");
cars = Arrays.asList( car1, car2, car3 ) ;
}
步骤
-
下载download birt开发工具 http://download.eclipse.org/birt/downloads 选All in one,安装后打开BIRT.exe
-
File->new->New Report , 新建立一个report,命名为Car.rptdesign
-
在Outline里找到Data set,新建DataSet,在OutPut columns填入
- Year type选Interger
- model type选String
- make type选String
-
在Outline里找到Data source,新键DataSource,在下拉框里选
Scripted Data Source
,Scripted Data Source
是指通过script来获取到数据源。 -
在Outline里找到刚刚建立Data set,双击找到DataSource,关联上刚刚建立的DataSource。Data set 是指从DataSource来组装出需要的数据集合。
例如:
DataSource 返回
userId | firstname | lastname |
---|---|---|
1 | zhang | san |
2 | Li | si |
Data set 则组装成
userId | name |
---|---|
1 | zhang san |
2 | Li si |
-
在Outline里找到Report Parameters,新建立一个参数为year,Data type为String,Display type为Text Box
-
在Outline里找到Body,在Outline里找到Rport Items里,拖动一个Table到右侧的layout里,会自动弹出对话框,DataSet选刚才的建立的DataSet,选中binding columns
预览report,可以在birt里选window->preference->web browser,勾上use external web browser ,在下面的选项框里选你的浏览器。然后在工具栏里选择播放标志的下拉选
view report as html
或其它的选项
到这里一个简单的Report就建立好了
- 代码在github上,可以下载下来对照理解java 工程如下图
有必要的代码说明如下:
- pom.xml
注意
a) org.eclipse.birt.runtime:org.eclipse.orbit.mongodb排除掉,不然会启动报错
b) 加入org.hectorclient:hector-core .在Birt中创建Datasource的时候,下拉框里要选Scripted Data Source
,不要默认选第一个,第一个是Cassandra Scrpted Data Source
.就需要加入这个包。
<dependency>
<groupId>org.eclipse.birt.runtime</groupId>
<artifactId>org.eclipse.birt.runtime</artifactId>
<version>4.4.2</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.birt.runtime</groupId>
<artifactId>org.eclipse.orbit.mongodb</artifactId>
</exclusion>
</exclusions>
</dependency>
- BirtEngineFactory
public class BirtEngineFactory implements FactoryBean<IReportEngine>, ApplicationContextAware, DisposableBean { public IReportEngine getObject(){
EngineConfig config = new EngineConfig();
//config.getAppContext()是个map
//将ApplicationContext放到map里,key为pring
config.getAppContext().put("spring", this.context );
config.setLogConfig( null != this._resolvedDirectory ? this._resolvedDirectory.getAbsolutePath() : null , this.logLevel);
try {
Platform.startup( config );
}
catch ( BirtException e ) {
throw new RuntimeException ( "Could not start the Birt engine!", e) ;
}
IReportEngineFactory factory = (IReportEngineFactory) Platform.createFactoryObject( IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY );
IReportEngine be = factory.createReportEngine( config );
this.birtEngine = be ;
return be ;
}
}
- BirtConfiguration
public class BirtConfiguration {
@Value("${birt.log.location}")
String logLocation;
//产生FactoryBean<IReportEngine>
//在使用的依赖IReportEngine注入时候会调用到
@Bean
protected BirtEngineFactory engine(){
BirtEngineFactory factory = new BirtEngineFactory() ;
//Enable BIRT Engine Logging
factory.setLogLevel( Level.INFO);
factory.setLogDirectory( new FileSystemResource(logLocation));
return factory ;
}
}
- BirtController
通过http//127.0.0.1/report/car/{searchBy}/{seachValue} 来生成car report,url中的占位符代表查询的条件和值
@RestController
public class BirtController {
@Autowired
BirtReportGenerator birtReportGenerator;
Logger logger = LoggerFactory.getLogger(BirtController.class);
@PostMapping("/report/car/{searchBy}/{seachValue}")
public void test(@PathVariable("searchBy") String searchBy,@PathVariable("seachValue") String searchValue){
ReportParameter rm=new ReportParameter("car","PDF");
rm.setParameter(searchBy, searchValue);
try {
ByteArrayOutputStream baos=birtReportGenerator.generate(rm);
FileOutputStream fops = new FileOutputStream("c:/test/carreport_"+System.currentTimeMillis()+".pdf");
fops.write(baos.toByteArray());
fops.close();
baos.close();
} catch (Exception e) {
logger.error("Error: " + e.getMessage());
}
}
}
- BirtReportGenerator
public class BirtReportGenerator {
@Autowired
private IReportEngine birtEngine ;
public ByteArrayOutputStream generate(ReportParameter rptParam) throws Exception{
//ByteArrayOutputStream 底层维护了一个byte[],可以自动扩容
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IReportRunnable runnable = null;
ClassPathResource cpr=new ClassPathResource("report/car.rptdesign");
runnable = birtEngine
.openReportDesign(cpr.getInputStream());
IRunAndRenderTask runAndRenderTask = birtEngine.createRunAndRenderTask(runnable);
runAndRenderTask.setParameterValues(setParameters(runnable, rptParam.getParameter()));
IRenderOption options =new RenderOption();
if (rptParam.getFormat().equalsIgnoreCase("pdf")) {
PDFRenderOption pdfOptions = new PDFRenderOption(options);
pdfOptions.setOutputFormat("pdf");
pdfOptions.setOption(IPDFRenderOption.PAGE_OVERFLOW, IPDFRenderOption.FIT_TO_PAGE_SIZE);
pdfOptions.setOutputStream(baos);
runAndRenderTask.setRenderOption(pdfOptions);
}
runAndRenderTask.getAppContext().put(EngineConstants.APPCONTEXT_CLASSLOADER_KEY,
this.getClass().getClassLoader());
runAndRenderTask.run();
runAndRenderTask.close();
return baos;
}
protected HashMap<String, Object> setParameters(IReportRunnable report, Map<String,Object> m) throws Exception {
HashMap<String, Object> parms = new HashMap<String, Object>();
IGetParameterDefinitionTask task = birtEngine.createGetParameterDefinitionTask(report);
//拿到birt里所有的parameter定义
Collection<IParameterDefnBase> params = task.getParameterDefns(true);
Iterator<IParameterDefnBase> iter = params.iterator();
while (iter.hasNext()) {
IParameterDefnBase param = (IParameterDefnBase) iter.next();
Object val=m.get(param.getName());
//如果拿到birt的parameter有定义
if (val!=null) {
parms.put(param.getName(),val);
}
}
task.close();
return parms;
}
-
Birt里建立的Car.rptdesign这个文件放到java工程的resouces/report里
-
整体流程
http//127.0.0.1/report/car/year/2000->
BirtController->BirtReportGenerator->IReportEngine
IReportEngine
在factorybean里的getObject()里就将spring的applicationContext对象放到了IReportEngine
的config里,代码
EngineConfig config = new EngineConfig();
config.getAppContext().put("spring", this.context );
而这个context是可以在Car.rptdesign的script里使用的
以下介绍Birt的script调用java工程里的代码
- 在Birt里打开Car.rptdesign,在Outline里找到Scripts,点Car.rptdesign,然后在右边的窗口里切换到script,在左上放的script下来里找达到initialize,然后输入以下代码
spring=reportContext.getAppContext().get("spring");
var carService=spring.getBean("carService");
如图
- 在Birt里打开Car.rptdesign,在Outline里找到dataset,然后在右边的窗口里切换到script,在左上的script下来里找到open,然后输入以下代码
var carService=spring.getBean("carService");
listdata=carService.getCarsByYear(params["year"]);
count=0;
如图
在左上的script下来里找到fetch,然后输入以下代码
count=0;
if (listdata.size()>count) {
car=listdata.get(count);
row.year=car.getYear();
row.mode=car.getMode();
row.make=car.getMake();
count++;
return true;
}
return false;
如图
运行
通过postman来测试
- 启动Bootstrap工程
- 代开postman,输入http://127.0.0.1:8080/report/car/year/2000
- 查看c:/test/ 下会生产carreport_xxxxx.pdf
通过springtest测试
- 在src/main/test里增加一个测试类
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class DemoApplicationTests {
@Autowired
private MockMvc mvc;
@Test
public void exampleTest() throws Exception {
this.mvc.perform(post("/report/car/year/2000")).andExpect(status().isOk())
.andExpect(content().string("Hello World"));
}
}
发生异常排除
查看log,log 配置在application.properties里的birt.log.location=c:/logs/
参考文档
- https://spring.io/blog/2012/01/30/spring-framework-birt
- https://www.eclipse.org/birt/documentation/integrating/reapi.php
- https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-with-mock-environment
最后再放上github代码