消息模型与生成pdf

标签: 队列  rabbitmq  java  中间件  大数据

今日内容

  • 消息中间件
  • RabbitMQ
  • 五种消息模型
  • SpringAMQP
  • 生产pdf
  • Jaspersoft Studio

第一章 消息中间件(面试)

消息队列中间件是分布式系统中重要的组件,主要解决异步消息,应用解耦,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性架构。

对于消息中间件,常见的角色大致也就有Producer(生产者)、Consumer(消费者)。

1. 应用场景

1.1 异步消息

场景说明:用户注册后,需要发注册邮件和注册短信。

1571300317204

1.2 应用解耦

上面的应用也体现了解耦合

1.3 流量削峰(削峰添谷)

流量削峰也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。

应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。

1571300374683

2. 常见的消息中间件(了解)

常见的消息中间件,有下面这些:

  • ActiveMQ:是Apache出品基于JMS(Java Message Service)规范的一种消息中间件的实现。
  • ZeroMQ:号称”史上最快的消息中间件”,基于C语言开发的。
  • Kafka:Apache下的一个子项目 。特点:可以达到10W/s的吞吐速率;适合处理海量数据。
  • RabbitMQ:是基于AMQP(Advanced Message Queuing Protocol)协议上完成的,基Erlang语言开发的。
  • RocketMQ :阿里巴巴推出。

AMQP和JMS区别: JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨平台跨语言的

第二章 RabbitMQ安装

RabbitMQ是基于AMQP(Advanced Message Queuing Protocol)的一款消息中间件管理系统,官网地址: http://www.rabbitmq.com/

1. 安装和启动

1.1 Erlang

RabbitMQ是基于Erlang语言开发,所以我们需要先安装此环境

官网地址:https://www.erlang.org/

我们的课程中使用的是:22.0版本,资料提供了安装包

注意:安装Erlang语言,不要有中文路径

1.2 RabbitMQ

我们的课程中使用的是:3.7.16版本,资料提供了安装包

注意:安装RabbitMQ,不要有中文路径

安装启动命令 访问路径等

1. 双击rabbitmq-server-3.7.16.exe安装即可(这个目录要记住)

2. 进入到rabbitMq的安装目录中,进入sbin文件夹中,打开dos窗口
2.1 启动
rabbitmqctl start_app
2.2 开启web控制台的访问
rabbitmq-plugins enable rabbitmq_management

3. 浏览器访问管理控制台
http://127.0.0.1:15672/
用户名:guest
密码:guest

* 控制台 15672
* 软件 5672

重置命令

* 重置mq数据 
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app

2. 空间和用户管理

要想使用RabbitMQ,必须创建出空间【类似于数据库服务器中的一个库一样】和用户

2.1 创建空间

alt

2.2 创建用户

alt

2.3 将空间交给用户管理

alt

第三章 五种消息模型(理解)

1. 概述

消息中间件的消息分为两类传输模型:点对点模型发布/订阅模型

alt

RabbitMQ在此基础上进行细化,提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此不予学习。那么也就剩下5种:

  • 1、2(点对点模型)
  • 3、4、5(发布/订阅模型)

alt

2. 演示工程导入

rabbit_mq_demo导入到IDEA中,rabbit_mq_demo是已经编辑好的演示五种消息关系的项目.

3. 消息模型

3.1 Simple消息模型

这是最简单的消息模型,典型的一个生产者对应一个消费者,生产者向队列生成消息,消费者从队列消费消息

alt

ACK确认机制

  • 自动回执:消费者一旦拿到消息,立马发送回执,当业务不是主要的业务,数据不敏感时使用

  • channel.basicConsume(QUEUE_NAME,true, consumer);
    <!--2-->

alt

3.2 Work消息模型

当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。

此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息,注意同一条消息只能被其中一个消费者消费。

alt

3.3 订阅消息模型-Fanout( 广播 )

Fanout类型的交换机与多个队列绑定,当消息发送到此类型的交换机时,所有与之绑定的队列都能收到消息。

alt

3.4 订阅消息模型-Direct

当一个队列与Direct类型的交换机绑定的时候,需要声明出自己能够处理的请求标识(binding key)

当我们程序将消息发送给此类型的交换机时,也需要指定一个标识(routing key),只有routing和binding完全匹配时,绑定的队列才能收到消息。

消息广播给指定用户,通过routing(生产者)和binding(消费者)进行匹配

alt

3.5 订阅消息模型-Topic

Topic类型的交换机与Direct类型的交换机基本功能是一样的,但是它支持使用通配符来书写binding key

常用的通配符有两个:

  • #:匹配一个或多个词

    • 匹配到文件夹下所有
  • *:匹配不多不少恰好1个词

    • 匹配到子文件夹

image-20201023102441660

4. 消息持久化

为了保证在MQ宕机的时候,消息不丢失,对消息、队列、交换机添加持久化的设置

1)生产者开启交换机和消息的持久化

image-20200811152211097

2)消费者开启交换机和队列的持久化

image-20200811152311495

3)删除MQ中现有的交换机和队列, 然后再测试(略)

image-20200811152638867

面试题:如何避免消息丢失?

​ 1)消息的持久化机制,可以防止由于MQ宕机导致的消息丢失。

​ 2)消费者的手动ACK机制,可以防止消费者丢失消息。

1. 点对点
Simple 一个生产者---一个消费者
Work 一个生产者---多个消费者
平均消费
竞争消费
2. 发布订阅
Fanout(广播) 一个生产者---多个消费者
Direct(路由) 一个生产者---多个消费者 ( bindingkey routingkey )
Topic (主题) 一个生产者---多个消费者 ( bindingkey routingkey 支持 routingkey的通配符 * ## )

3. ACk确认机制
自动回执: 消费者拿到消息之后自动回执消息
手动回执: 消费成功之后使用代码手动回执消息

第五章 Spring AMQP(重点)

Spring有很多不同的子项目,其中就有对AMQP的支持,为我们提供了一个模板 bean(AMQPTemplate) 来发送和接收消息,来简化RabbitMQ操作。

官网地址:https://spring.io/projects/spring-amqp

Spring-amqp是对AMQP协议的抽象实现,而spring-rabbit是对协议的具体实现,也是目前的唯一实现。

1. 创建父工程

<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
</dependencies>

<build>
<plugins>
<!-- 设置编译版本为1.8 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>

2. 创建消费者模块

2.1 创建模块

创建rabbit_consumer(消费者)模块

2.2 加入spring的配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">


<!-- 配置连接工厂 -->
<rabbit:connection-factory id="connectionFactory" virtual-host="saas-bk"
host="127.0.0.1" port="5672" username="saas" password="saas"/>
<!-- 定义mq管理 -->
<rabbit:admin connection-factory="connectionFactory"/>

<!-- 声明队列 此队列名称与发送者一致 -->
<rabbit:queue name="spring.test.queue" auto-declare="true" durable="true"/>

<!--绑定队列到交换机-->
<rabbit:topic-exchange id="springTestExchange" name="spring.test.exchange">
<rabbit:bindings>
<rabbit:binding queue="spring.test.queue" pattern="user.#" />
</rabbit:bindings>
</rabbit:topic-exchange>

<!-- 定义消费者监听器 -->
<bean id="testMqListener" class="com.itheima.MqListener"/>

<!-- 定义消费者监听队列 -->
<rabbit:listener-container connection-factory="connectionFactory">
<rabbit:listener ref="testMqListener" queues="spring.test.queue"/>
</rabbit:listener-container>

</beans>

2.3 实现一个监听器负责接收消息

/**
* @description: 定义消费者监听器实现消息监听接口,并重写其中的方法
* @author: mryhl
* @date: Created in 2020/10/23 17:41
* @version: 1.1
*/
public class MqListener implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println("已经监听到生产者发送的信息: " + new String(message.getBody()));
}
}

2.4 启动Spring环境

/**
* @description: 启动spring环境
* @author: mryhl
* @date: Created in 2020/10/23 17:45
* @version: 1.1
*/
public class ConsumerStart {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext act = new ClassPathXmlApplicationContext("applicationContext-mq-consumer.xml");
act.start();
System.in.read();
}
}

3. 创建生产者模块

3.1 创建模块

创建rabbit_producer模块

3.2 加入Spring的配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 配置连接工厂 -->
<rabbit:connection-factory id="connectionFactory" virtual-host="saas-bk"
host="127.0.0.1" port="5672" username="saas" password="saas"/>
<!-- 定义mq管理 -->
<rabbit:admin connection-factory="connectionFactory"/>

<!--定义交换机(topic模式)-->
<rabbit:topic-exchange id="springTestExchange" name="spring.test.exchange"></rabbit:topic-exchange>

<!-- 消息对象json转换类 -->
<bean id="jsonMessageConverter"
class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>

<!-- 定义模版 -->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"
exchange="spring.test.exchange"
message-converter="jsonMessageConverter"/>


</beans>

3.3 加入一个发送消息的类,测试

/**
* @description: 生产者发送消息测试
* @author: mryhl
* @date: Created in 2020/10/23 17:50
* @version: 1.1
*/
public class ProducerTest {
public static void main(String[] args) {
// 启动spring环境
ClassPathXmlApplicationContext act = new ClassPathXmlApplicationContext("applicationContext-mq-producer.xml");
// 获取Template,使用它就可以完成消息发送
AmqpTemplate amqpTemplate = act.getBean(AmqpTemplate.class);
// 发送消息
amqpTemplate.convertAndSend("user.save","Hi,I love you!");
}
}

4. 手动ACK机制(扩展)

Spring整合rabbit之后,默认使用的依旧是自动消息回执,如果需要手动回执,需要处理下面两步骤:

  1. 在消费者的配置文件中配置消息回执为手动回执模式( acknowledge=”manual” )
<!-- 定义消费者监听队列 -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<rabbit:listener ref="testMqListener" queues="spring.test.queue"/>
</rabbit:listener-container>
  1. 当消息正常消费完毕之后,使用代码手动发送回执
public class MqListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
System.out.println("接收到信息了,消息内容是:" + new String(message.getBody()));

channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}

第六章 发送邮件改版(重点)

1. 需求说明

添加用户成功,向MQ中发送一个消息,然后由邮件发送模块监听这个MQ, 拿到消息之后,开始向用户的邮箱中发送一封邮件。

alt

2. 改造生成者工程

2.1 加入Spring的配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">


<!-- 配置连接工厂 :如果使用是linux,ip地址需要改....-->
<rabbit:connection-factory id="connectionFactory" virtual-host="saas-bk"
host="127.0.0.1" port="5672" username="saas" password="saas"/>
<!-- 定义mq管理 -->
<rabbit:admin connection-factory="connectionFactory"/>

<!--定义交换机(topic模式)-->
<rabbit:topic-exchange id="springMailExchange" name="spring.mail.exchange"></rabbit:topic-exchange>

<!-- 消息对象json转换类 将java对象转为》json字符串》二进制数组 -->
<bean id="jsonMessageConverter"
class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>

<!-- 定义模版 -->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"
exchange="spring.mail.exchange"
message-converter="jsonMessageConverter"/>

</beans>

2.2 加入发送消息的代码

/**
* 新增,修改功能
*/
@RequestMapping(value = "/edit", name = "部门新增")
public String edit(User user) {
String oldPwd = user.getPassword();
if (StringUtils.isNotEmpty(oldPwd)){
String pwd = new Md5Hash(oldPwd,user.getEmail(),2).toString();
user.setPassword(pwd);
}

if (StringUtils.isEmpty(user.getId())) {
//1. 设置主键
user.setId(UUID.randomUUID().toString());

//2. 设置企业信息
user.setCompanyId(getCompanyId());
user.setCompanyName(getCompanyName());

userService.save(user);
// 这里发送邮件
String to = user.getEmail();
String title = "saas平台--用户新增成功";
String content = "恭喜您,您的账号已经在saas平台开通成功,请使用当前邮箱作用账号,使用" + oldPwd + "作为密码进行登录";
Map map = new HashMap<>();

map.put("to",to);
map.put("title",title);
map.put("content",content);
//MailUtil.sendMail(to, title, content);
template.convertAndSend("mail.send",map);

} else {
userService.update(user);
}

//重定向到list方法
return "redirect:/system/user/list.do";
}

3. 搭建消费者工程

3.1 创建发送邮件的模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>export_parent</artifactId>
<groupId>com.itheima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>export_mail</artifactId>
<dependencies>
<dependency>
<groupId>com.itheima</groupId>
<artifactId>export_commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

</project>

3.2 加入配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">


<!-- 配置连接工厂 -->
<rabbit:connection-factory id="connectionFactory" virtual-host="saas-bk"
host="127.0.0.1" port="5672" username="saas" password="saas"/>
<!-- 定义mq管理 -->
<rabbit:admin connection-factory="connectionFactory"/>

<!-- 声明队列 此队列名称与发送者一致 -->
<rabbit:queue name="spring.mail.queue" auto-declare="true" durable="true"/>

<!--绑定队列到交换机-->
<rabbit:topic-exchange id="springMailExchange" name="spring.mail.exchange">
<rabbit:bindings>
<rabbit:binding queue="spring.mail.queue" pattern="mail.#" />
</rabbit:bindings>
</rabbit:topic-exchange>

<!-- 定义消费者监听器 -->
<bean id="testMqListener" class="com.itheima.mail.listener.MailListener"/>

<!-- 定义消费者监听队列 -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto">
<rabbit:listener ref="testMqListener" queues="spring.mail.queue"/>
</rabbit:listener-container>

</beans>

3.3 加入监听的类

/**
* @description:
* @author: mryhl
* @date: Created in 2020/10/13 20:47
* @version: 1.1
*/
public class MailStart {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext act =
new ClassPathXmlApplicationContext("classpath*:applicationContext-*.xml");

act.start();

System.in.read();
}
}

3.4 启动Spring环境

/**
* @description:
* @author: mryhl
* @date: Created in 2020/10/13 20:47
* @version: 1.1
*/
public class MailStart {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext act =
new ClassPathXmlApplicationContext("classpath*:applicationContext-*.xml");

act.start();

System.in.read();
}
}

第七章 PDF报表

1. 报表技术概述

在企业级应用开发中,报表下载打印是很重要的一个功能。其实除了Excel报表之外,PDF报表也有广泛的应用场景,例如:货运详情,货运单等。

目前世面上比较流行的制作PDF报表的工具如下:

  • iTextPDF:用于生成PDF文档的一个java类库,使用起来相对繁琐。
  • OpenOffice:是一套跨平台的办公室软件套件,可以灵活的将word或者Excel转化为PDF文档。
  • JasperReport:JasperReport是一个强大、灵活的报表生成工具,能够展示丰富的页面内容并生成PDF。

2. JasperReport介绍

JasperReport 步骤 阶段 生命周期

JasperReport是一个强大、灵活的报表生成工具,能够展示丰富的页面内容,并将之转换成PDF,HTML,或者XML格式。

官网地址:https://community.jaspersoft.com/

使用JasperReport做开发,一共分为三个步骤:

  • 设计:指定PDF页面布局、复杂公式、数据源等,创建出PDF模板(xx.jrxml)
  • 执行:将java查询的数据填充到PDF模板中(xx.jasper + data)
  • 输出:生成PDF报表

image-20201023150441792

第八章 Jaspersoft Studio安装和使用

1. 软件安装

Jaspersoft Studio是一款基于Eclipse的报表设计器,使用该软件可以方便地对报表进行可视化的模板设计。

官网下载地址:https://community.jaspersoft.com/community-download,

资料中已经提供了,可直接使用它进行安装

2. 软件布局介绍

alt

3. 快速入门(重点)

3.1 创建一个新工程

alt

3.2 创建一个新模板

alt

3.3 模板基本布局介绍

alt

3.4 在模板上添加一些元素

alt

3.5 将模板转换为二进制文件

将 .jrxml 文件转换成 .jasper文件

alt

3.6 通过java代码,填充数据,生成pdf

在ExportController添加导出方法

@RequestMapping(value = "/exportPdf1", name = "测试")
public void exportPdf1(String id) throws JRException, IOException {
//1. 获取模板
String realPath = session.getServletContext().getRealPath("/jasper/demo1.jasper");

//2 向模板填充数据
HashMap<String, Object> map = new HashMap<>();
JRDataSource dataSource = new JREmptyDataSource();
JasperPrint jasperPrint = JasperFillManager.fillReport(realPath, map, dataSource);

//3.输出到浏览器
JasperExportManager.exportReportToPdfStream(jasperPrint, response.getOutputStream());
}

4. 中文处理

4.1 修改模板中的文字,为中文楷体

注意: 修改完毕之后要重新编译文件, 然后复制替换java工程中的模板

image-20200811203004897

4.2 在工程中添加中文楷体的库和配置

注意: 修改完之后,重启tomcat

image-20200811203209704

第九章 模板数据填充(对着视频做)

1. Map集合数据填充

1.1 需求说明

要完成如下效果, 注意: 页面上的数据要通过后台动态传递

image-20200709143929324

1.2 制作模板

image-20200811204910541

1.3 填充数据

@RequestMapping(value = "/exportPdf2", name = "测试")
public void exportPdf2(String id) throws JRException, IOException {
//1. 获取模板
String realPath = session.getServletContext().getRealPath("/jasper/demo2.jasper");

//2 向模板填充数据
HashMap<String, Object> map = new HashMap<>();
map.put("username", "安妮");
map.put("age", "18");
map.put("address", "家里蹲");
map.put("company", "腾讯");


JRDataSource dataSource = new JREmptyDataSource();
JasperPrint jasperPrint = JasperFillManager.fillReport(realPath, map, dataSource);

//3.输出到浏览器
JasperExportManager.exportReportToPdfStream(jasperPrint, response.getOutputStream());
}

2. List集合数据填充

2.1 需求说明

要完成如下效果, 注意: 页面上的数据要通过后台动态传递

image-20200709144050594

2.2 制作模板

image-20200811211507727

2.3 填充数据

@RequestMapping(value = "/exportPdf3", name = "测试")
public void exportPdf3(String id) throws JRException, IOException {
//1. 获取模板
String realPath = session.getServletContext().getRealPath("/jasper/demo3.jasper");

//2 向模板填充数据
List<Map<String, Object>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
Map<String, Object> map = new HashMap<>();
map.put("userName", "安妮" + i);
map.put("email", "18" + i);
map.put("deptName", "家里蹲" + i);
map.put("companyName", "腾讯" + i);

list.add(map);
}
JRDataSource dataSource = new JRBeanCollectionDataSource(list);

JasperPrint jasperPrint = JasperFillManager.fillReport(realPath, new HashMap<>(), dataSource);

//3.输出到浏览器
JasperExportManager.exportReportToPdfStream(jasperPrint, response.getOutputStream());
}

第十章 报运单详情PDF生成

1. 思路分析

image-20200813104549638

2. 导入模板(已提供)

image-20200813104602900

3. 数据填充

@RequestMapping(value = "/exportPdf", name = "报运单下载")
public void exportPdf(String id) throws JRException, IOException {
//0. 获取模板
String realPath = session.getServletContext().getRealPath("/jasper/export.jasper");

//1. 获取数据 条件 报运单id 结果 报运单信息和报运单下货物信息
//1-1) 报运单信息
Export export = exportService.findById(id);
Map<String, Object> map = BeanUtil.beanToMap(export);

//1-2) 货物信息
List<ExportProduct> list = exportProductService.findByExportId(id);
JRDataSource dataSource = new JRBeanCollectionDataSource(list);


//2 向模板填充数据
JasperPrint jasperPrint = JasperFillManager.fillReport(realPath, map, dataSource);


//3.输出到浏览器
JasperExportManager.exportReportToPdfStream(jasperPrint, response.getOutputStream());
}

4. pdf下载

@RequestMapping(value = "/exportPdf", name = "报运单下载")
public void exportPdf(String id) throws JRException, IOException {
//0. 获取模板
String realPath = session.getServletContext().getRealPath("/jasper/export.jasper");

//1. 获取数据 条件 报运单id 结果 报运单信息和报运单下货物信息
//1-1) 报运单信息
Export export = exportService.findById(id);
Map<String, Object> map = BeanUtil.beanToMap(export);

//1-2) 货物信息
List<ExportProduct> list = exportProductService.findByExportId(id);
JRDataSource dataSource = new JRBeanCollectionDataSource(list);

//2 向模板填充数据
JasperPrint jasperPrint = JasperFillManager.fillReport(realPath, map, dataSource);


//3.输出到浏览器
//JasperExportManager.exportReportToPdfStream(jasperPrint, response.getOutputStream());

//3. 文件下载
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
JasperExportManager.exportReportToPdfStream(jasperPrint, byteArrayOutputStream);
DownloadUtil.download(byteArrayOutputStream,response,"报运单.pdf");
}
版权声明:本文为weixin_41290516原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41290516/article/details/109595549

智能推荐

循环

与任何程序设计语言一样Java利用条件语句与循环结构确定流程控制,一下总结一下Java中的循环语句: while do while for switch 对于golang来说: switch非常灵活。从第一个expr为true的case开始执行,如果case带有fallthrough,程序会继续执行下一条case,不会再判断下一条case的expr,如果之后的case都有fallthrough,d...

1638 统计只差一个字符的子串数目(动态规划)

1. 问题描述: 给你两个字符串 s 和 t ,请你找出 s 中的非空子串的数目,这些子串满足替换一个不同字符以后,是 t 串的子串。换言之,请你找到 s 和 t 串中恰好只有一个字符不同的子字符串对的数目。比方说, "computer" 和 "computation"...

websocket基本原理

HTTP中一个request只能有一个response。而且这个response也是被动的,不能主动发起 因此过去的服务端推送信息是通过客户端不停的轮询实现的 websocket是双向通信协议,提供了服务端主动推送信息的能力 需要客户端(浏览器)和服务端同时支持 如果经过代理的话,还需要代理支持,否则有些代理在长时间无通信时会自动切断连接 因此WS为了保证连接不被断掉,会发心跳 WebSocket...

mybatis+ehcache二级缓存

导入jar包 mapper.xml文件开启二级缓存 pojo类实现序列化接口 配置ehcache.xml 测试...

python+opencv实现图像拼接

任务 拍摄两张图片去除相同部分,拼接在一起 原图 结果 步骤 读取两张图片 使用sift检测关键点及描述因子 匹配关键点 处理并保存关键点 得到变换矩阵 图像变换并拼接 代码实现 扩展 这里对右边图像进行变换,右边变得模糊,可以修改代码对左边图像变换 这里只有两张图片拼接,可以封装实现多张图片拼接 可以修改代码实现上下图片的拼接...

猜你喜欢

python_sklearn机器学习算法系列之AdaBoost------人脸识别(PCA,决策树)

          注:在读本文之前建议读一下之前的一片文章python_sklearn机器学习算法系列之PCA(主成分分析)------人脸识别(k-NearestNeighbor,KNN)         本文主要目的是通过一个简单的小...

memmove函数与memcpy函数的模拟实现

memmove函数和memcpy函数都是在内存复制任意类型的,但是它俩也有区别。当源区域和目标区域有重复的,memmove函数会复制缓冲区重叠的部分,而memcpy相反,会报出未知错误。 下面给出两个函数的实现 首先,memmove函数。 实现的基本原理如下图。 具体代码如下: memcpy函数的实现很简单,就直接给出源代码了...

SpringFramework核心 - IOC容器的实现 - 总结

1. 概述 把Spring技术内幕第一章和第二章过了一遍,也做了一些笔记, 对IOC容器的实现有了一定皮毛理解,现在跟着源码再过一遍总结一下IOC容器的初始化,Bean的初始化的过程,做一下总结 ① IOC容器和简单工厂模式 在开始之前,先想想我们平时是怎么使用IOC容器为我们管理Bean的,假设我们要把下面的User类交给IOC容器管理 我们不想关心如何创建一个User对象实例的,仅仅在需要他的...

Python和Django的安装

个人博客导航页(点击右侧链接即可打开个人博客):大牛带你入门技术栈  一、下载并安装Python Python 官方下载地址:http://www.python.org/ftp/python/ 我们这里选择的是 Python 2.7.2 。虽然目前最新版是Python 3.2.2, 但是Django目前还不支持 Python 3.2.2。 安装步骤很简单,双击安装包开...