java实际项目反射、自定义注解的运用实现itext生成PDF的详细应用教程

开篇引语

小伙伴在学习java是否有这样的困混不知道反射是干嘛的不知道注解有什么用。导致很多人看 java基础的时候迷迷糊糊,那是你还没有在实际项目中遇到,不知道该如何使用它们。接下来我会为你们详细讲解实际项目中是如何运用反射和自定义注解的,不管你现在对反射和注解了解多少,这篇博客站在实际应用角度从业务介绍到代码实现每一步都给出了详细贴图和代码竭力为大家讲解,无论你是否感兴趣你都应该先点赞收藏起来方便以后用到了过来查看。

一、业务场景描述

将14天的气象预报数据以PDF的形式展示,现在已有14天预报的数据,如下。

 要将上面的14条数据放进下面的这个模板中,最终以PDF的形式生成。效果图如下

 二、使用工具准备好模板,最后保存生成PDF

这里需要使用软件工具Adobe Acrobat DC,需要工具的小伙伴可以评论区留言发送:资料。

具体如何使用工具和生成PDF可以看:java使用itex生成PDF-CSDN博客

  

三、代码实现

 实体类展示,这里建议可以大概看一下,先不用看为什么这样设计实体类,以及自定义注解的定义,后面代码具体地方引用了下面的实体类再来看就会清晰很多。

package com.hw.ioz.web.dto;import com.zye.ioz.common.annotations.ReportFiled;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;/*** @author:Ttc* @date 2024/1/22  14:36* @description: 导出大屏预报数据DTO*/
@Data
public class ForecastDataDTO {/*** 日期*/@ApiModelProperty(value = "日期", notes = "")@ReportFiled(value = "date")private String formattedDate;/*** 天气*/@ApiModelProperty(value = "天气", notes = "")@ReportFiled(value = "weather")private String weaDay;/*** 大屏显示风力等级*/@ApiModelProperty(value = "大屏显示风力等级", notes = "")@ReportFiled(value = "wind")private String largeScreenWind;/*** 最高温度*/@ApiModelProperty(value = "最高温度", notes = "")@ReportFiled(value = "tem_max")private String tem1;/*** 最低温度*/@ApiModelProperty(value = "最低温度", notes = "")@ReportFiled(value = "tem_min")private String tem2;
}
package com.hw.ioz.web.dto;import lombok.AllArgsConstructor;
import lombok.Data;/*** @author:Ttc* @date 2024/1/22  15:40*/
@Data
@AllArgsConstructor
public class PdfFiledDto {/*** 文本域内容*/private String val;/*** 字体大小*/private float fontsize;/*** 自定义字体*/private String fontPath;
}
package com.hw.ioz.web.dto;import com.zye.ioz.common.annotations.ReportFiled;
import com.zye.ioz.common.annotations.ReportList;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.util.List;import static com.zye.ioz.common.constants.PdfFontConstants.FONT_MS_YH;/*** @author:Ttc* @date 2024/1/18  17:51* @description: 大屏天气14天预报导出pdf日期显示dto*/@Data
public class ReportDateDTO {/*** 年*/@ApiModelProperty(value = "年", notes = "")@ReportFiled(value = "year", description = "date", font = FONT_MS_YH, fontsize = 13.5f)private String year;/*** 月*/@ApiModelProperty(value = "月", notes = "")@ReportFiled(value = "month", description = "date", font = FONT_MS_YH, fontsize = 13.5f)private String month;/*** 日*/@ApiModelProperty(value = "日", notes = "")@ReportFiled(value = "day", description = "date", font = FONT_MS_YH, fontsize = 13.5f)private String day;@ReportList("report_")private List<ForecastDataDTO> forecastDataDTOList;
}
package com.zye.ioz.common.annotations;import java.lang.annotation.*;/*** @author:Ttc* @date 2024/1/23  14:48* @description: pdf*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ReportList {/*** 报表集合名称* 作为其中元素的前缀* @return*/String value() default "";
}
package com.zye.ioz.common.annotations;import java.lang.annotation.*;/*** @author:Ttc* @date 2024/1/18  17:54* @description: 报告字段*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ReportFiled {String value() default "";float fontsize() default 12.0f;/*** 字体路径* 如果为空,使用默认字体* @return*/String font() default "";String prefix() default "";String description() default "";
}

下面controller层代码调用的方法中传递的参数templatePath为pdf模板的存放路径地址
我是从yml文件中读取的,部分yml展示:

template:weather: D:\lightning2022\templatefont: D:\lightning2022\font\
@Api(tags = "PDF下载")
@RestController
@RequestMapping("/api/wea")
@Slf4j
public class BnWeaInterfaceHistoryController {@Value("${template.weather}")
private String templatePath;@GetMapping("/pdfExport")@ApiOperation(value = "大屏14天预报数据PDF下载")public void parkingPdfExport(HttpServletResponse response) {bnWeaInterfaceHistoryService.exportReport(response,templatePath);}
}

下面这段代码是将处理完成后的地址返回,拿到地址以后,使用字符输入流读取刚刚返回的对应PDF地址的文件,然后再用字节输出流/二进制输出流返回给前端进行下载处理。到此下载功能完成!后面是核心方法实现的详细介绍。

@Service
@Slf4j
public class BnWeaInterfaceHistoryServiceImpl extends ServiceImpl<BnWeaInterfaceHistoryMapper, BnWeaInterfaceHistory> implements BnWeaInterfaceHistoryService {@Value("${template.font}")String fontPath;@Overridepublic void exportReport(HttpServletResponse response, String templatePath) {List<ForecastDataDTO> list = bnWeaInterfaceHistoryMapper.exportLargeScreenForecastData("舟山");LocalDateTime now = LocalDateTime.now();Integer year = now.getYear();Integer month = now.getMonth().getValue();Integer day = now.getDayOfMonth();ReportDateDTO reportDateDTO = new ReportDateDTO();reportDateDTO.setDay(day.toString());reportDateDTO.setMonth(month.toString());reportDateDTO.setYear(year.toString());reportDateDTO.setForecastDataDTOList(list);String tmpPath = generateByTemplate(templatePath, reportDateDTO);if (org.springframework.util.StringUtils.isEmpty(tmpPath)) {log.error("找不到PDF路径!");return;}String path = tmpPath;File file = new File(path);log.info("PDF文件:{}", path);response.reset();response.setContentType("application/octet-stream");response.setCharacterEncoding("utf-8");response.setContentLength((int) file.length());response.setHeader("Content-Disposition", "attachment;filename=" + "weather"); // 导出名称try {BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));byte[] buff = new byte[1024];OutputStream os = response.getOutputStream(); // 输出流int i = 0;while ((i = bis.read(buff)) != -1) { // 读取文件os.write(buff, 0, i);os.flush();}log.info("发送完成");} catch (IOException e) {log.error("导出失败", e);}}
}

这里主要完成制作好的PDF模板中文本域所需要的值,这里的值还要经过一系列处理收集到map集合中。而这段主要是在收集完map集合的数据以后,再循环取出map集合中的值,将value中存放的PdfFiledDto对象取出来,把该对象中存放的文本域的值,大小,字体设置到PDF模板中。所有数据设置完成以后方法返回生成的PDF文件的保存地址,供下载使用。

/*** 由模板生成PDF,返回模板文件路径** @return*/private String generateByTemplate(String templatePath, ReportDateDTO reportDateDTO) {String tmpName = "ZSWeather14Days.pdf";String tmpPath = templatePath + "/" + tmpName;String newTmpFileName = PdfUtils.getNewFileName(tmpName);SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");String todayStr = df.format(new Date());// 生成的新文件路径(方法返回的模板路径)String pdfPath = templatePath + "/" + todayStr + "/" + newTmpFileName;String filePath = templatePath + "/" + todayStr;File path = new File(filePath);if (!path.exists()) {path.mkdirs();}log.info("pdf文件路径:{}", filePath);try {//设置中文字体BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);PdfReader reader = new PdfReader(tmpPath);// 读取pdf模板ByteArrayOutputStream bos = new ByteArrayOutputStream();     //获取字节数组输出流PdfStamper stamper = new PdfStamper(reader, bos);  //操作pdf文件AcroFields form = stamper.getAcroFields(); //获取pdf模板中的属性form.addSubstitutionFont(bf);      //为属性设置字体Map<String, PdfFiledDto> data = getParamMap(reportDateDTO, null, 0);for (Map.Entry<String, PdfFiledDto> entry : data.entrySet()) {if (entry.getValue().getFontsize() > 0) {form.setFieldProperty(entry.getKey(), "textsize", entry.getValue().getFontsize(), null);}BaseFont baseFont;if (!org.springframework.util.StringUtils.isEmpty(entry.getValue().getFontPath())) {baseFont = BaseFont.createFont(fontPath + entry.getValue().getFontPath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);} else {baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);}form.setFieldProperty(entry.getKey(), "textfont", baseFont, null);
//                    form.setFieldProperty(entry.getKey(), "textfont", BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED), null);form.setField(entry.getKey(), entry.getValue().getVal());}stamper.setFormFlattening(true);// 如果为false,生成的PDF文件可以编辑,如果为true,生成的PDF文件不可以编辑stamper.close();reader.close();//生成pdf路径FileOutputStream fos = new FileOutputStream(pdfPath);fos.write(bos.toByteArray());fos.flush();if (fos != null) {fos.close();}if (bos != null) {bos.close();}log.info("pdf模板生成了");} catch (Exception e) {e.printStackTrace();}return pdfPath;}

 这里将查询的数据进行反射处理,经过反射得到数据对象的字段后,再获取每个字段上的自定义注解。

public Map<String, PdfFiledDto> getParamMap(Object param, String prefix, int index) {Map<String, PdfFiledDto> map = new HashMap<>(16);if (param == null) {return map;}Field[] fields = param.getClass().getDeclaredFields();for (Field field : fields) {Object formVal = ReflectUtil.getFieldValue(param, field.getName());ReportFiled reportFiled = field.getAnnotation(ReportFiled.class);if (reportFiled != null) {fillParamMapByReportFiled(map, formVal, reportFiled, prefix, index);}ReportList reportList = field.getAnnotation(ReportList.class);if (reportList != null) {String formKeyPrefix = reportList.value();if (formVal instanceof List) {List<Object> formObjList = (List<Object>) formVal;for (int i = 0; i < formObjList.size(); i++) {Object subObj = formObjList.get(i);Map<String, PdfFiledDto> subMap = getParamMap(subObj, formKeyPrefix, i + 1);map.putAll(subMap);}}}}return map;}private static void fillParamMapByReportFiled(Map<String, PdfFiledDto> map,Object formVal, ReportFiled reportFiled, String prefix, int index) {String formKey = reportFiled.value();if (StrUtil.isNotBlank(prefix)) {// 获取实际keyformKey = handleFormKey(prefix, index, formKey);}if (formVal == null) {map.put(formKey, getPdfFiledDto("", -1.0f));return;}fillParamMapByObject(map, reportFiled, formKey, formVal, index);}

两个自定义注解:

package com.zye.ioz.common.annotations;import java.lang.annotation.*;/*** @author:Ttc* @date 2024/1/18  17:54* @description: 报告字段*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ReportFiled {String value() default "";float fontsize() default 12.0f;/*** 字体路径* 如果为空,使用默认字体* @return*/String font() default "";String prefix() default "";String description() default "";
}

package com.zye.ioz.common.annotations;import java.lang.annotation.*;/*** @author:Ttc* @date 2024/1/23  14:48* @description: pdf*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ReportList {/*** 报表集合名称* 作为其中元素的前缀* @return*/String value() default "";
}

 具体的实现方法代码如下,这里的代码完成了PDF中需要数据的查询,以及数据设置到对应的map集合中使得,map集合数据的key对应PDF模板设置的文本域的名称,value则是我们提前查好的数据的值。 

/*** 解析注解中的内容,构造 PdfFiledDto* 并添加进map** @param map* @param reportFiled* @param formKey* @param item*/private static void fillParamMapByObject(Map<String, PdfFiledDto> map, ReportFiled reportFiled,String formKey, Object item, int index) {
//        formKey = formKey + StrPool.UNDERLINE + index;if (item == null || StrUtil.isEmpty(item.toString())) {// 没有数据时,显示 "/"map.put(formKey, getPdfFiledDto("/", reportFiled.fontsize()));} else if ("date".equals(reportFiled.description())) {map.put(formKey, getPdfFiledDto(reportFiled.prefix() + item, reportFiled.fontsize(), reportFiled.font()));} else {map.put(formKey, getPdfFiledDto(reportFiled.prefix() + item, reportFiled.fontsize(), reportFiled.font()));}}private static String handleFormKey(String prefix, int index, String formKey) {prefix = prefix.endsWith(StrPool.UNDERLINE) ? prefix : prefix + StrPool.UNDERLINE;return StrUtil.isBlank(formKey) ?prefix + index: prefix + formKey + StrPool.UNDERLINE + index;}private static PdfFiledDto getPdfFiledDto(String val, float fontsize) {return new PdfFiledDto(val, fontsize, null);}private static PdfFiledDto getPdfFiledDto(String val, float fontsize, String fontPath) {return new PdfFiledDto(val, fontsize, fontPath);}

 来看看运行后的下载结果吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/501374.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Camunda实现bpmn 2.0各种类型的任务

基于Camunda实现bpmn中各种类型任务 ​ Camunda Modeler -为流程设置器&#xff08;建模工具&#xff09;&#xff0c;用来构建我们的流程模型。Camunda Modeler流程绘图工具&#xff0c;支持三种协议类型流程文件分别为&#xff1a;BPMN、DMN、Form。 ​ Camunda Modeler下载…

怕没经验?请看这根伦敦银实操指南

伦敦银是带有高杠杆、可以双向操作的投资品种&#xff0c;这就决定了其买卖过程涉及一定的技术&#xff0c;投资者参与其中需要具备有一定的交易技巧和经验。新手投资者如果缺少经验&#xff0c;在入市前没有信心&#xff0c;可以先看看以下的操作指南。 伦敦银投资者入市前应该…

持安科技亮相张江高科895创业营,总评分第三名荣获「最具创新性企业」!

近日&#xff0c;张江高科895创业营&#xff08;第十三季&#xff09;信息安全专场Demo day&结营仪式在上海集成电路设计产业园圆满落幕。本季创业营通过多种渠道在海内外甄选优秀创业项目&#xff0c;一共择优录取了29家入营&#xff0c;最终甄选出9家代表参加Demo day路演…

基于java Springboot实现课程评分系统设计和实现

基于java Springboot实现课程评分系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取源…

crc16计算

crc16计算&#xff0c;以生成式G(x)x16x15x21,为例 1、函数如下&#xff1a; //crc&#xff1a;G(x) x16x15x21 #define POLY 0x8005 //对应的生成式的多项式&#xff0c;可以查&#xff08;在在线计算crc工具下查&#xff09; unsigned short crc16_2(unsigned char *da…

《Spring Security 简易速速上手小册》第4章 授权与角色管理(2024 最新版)

文章目录 4.1 理解授权4.1.1 基础知识详解授权的核心授权策略方法级安全动态权限检查 4.1.2 主要案例&#xff1a;基于角色的页面访问控制案例 Demo 4.1.3 拓展案例 1&#xff1a;自定义投票策略案例 Demo测试自定义投票策略 4.1.4 拓展案例 2&#xff1a;使用方法级安全进行细…

代码随想录算法训练营第四十七天|198. 打家劫舍、213. 打家劫舍 II、337. 打家劫舍 III。

198. 打家劫舍 题目链接&#xff1a;打家劫舍 题目描述&#xff1a; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷…

光耦合器在电路板上的作用

在不断创新的电子世界中&#xff0c;一个关键组件在确保电子设备无缝运行方面默默地发挥着至关重要的作用&#xff1a;光耦合器。光耦合器经常被普通消费者忽视&#xff0c;它是电路板上的无名英雄&#xff0c;在维护电子系统的完整性和安全性方面发挥着关键作用。 什么是光耦合…

【JS】大文件上传(切片上传)

大文件上传&#xff08;切片上传&#xff09; 切片上传文件切片示例 切片上传 切片上传是指将一个大文件切割为若干个小文件&#xff0c;分为多个请求依次上传&#xff0c;后台再将文件碎片拼接为一个完整的文件&#xff0c;即使某个碎片上传失败&#xff0c;也不会影响其它文…

Springboot项目集成短信验证码(超简单)

操作流程 注册验证码平台创建验证码模版开始集成&#xff08;无需引入第三方库&#xff09; 注册并登陆中昱维信验证码平台 获取AppID和AppKey。 创建验证码模版 创建验证码模版&#xff0c;获取验证码模版id 开始集成 创建controller import org.springframework.web.bi…

mysql学习笔记4——表操作

表的创建 表的删除 在表中插入数据 插入多行数据只插入特定列数据 对插入数据限制约束条件 常用约束条件有&#xff1a; 非空约束&#xff08;not null&#xff09;&#xff1a;约束的字段不能为null 唯一约束&#xff08;unique&#xff09;&#xff1a;约束的字段不能重复…

本地maven库缓存导入私库

为了加速编译代码&#xff0c;想将本地maven缓存导入内网私库使用。 脚本网上搜的 #!/bin/bash # copy and run this script to the root of the repository directory containing files # this script attempts to exclude uploading itself explicitly so the script name …