springboot + Velocity引擎,生成PDF文件
Java 生成PDF模版很火的一个方案就是先生成html,再转化成PDF文件
优点:有现成的成熟框架,只需要专注html模版生成自己想要的格式
缺点:如果很多场景下需要生成PDF文件,那就要定义很多个html模版,不好维护,不好扩展
但是,,,,,小子有一个不成熟的方案,请各位大佬指点
思路:
就像生成一个excel模版一样,我们只需要一行一列的填充数据,根本不需要关心模版,或者说根本就没有模版!
那其实PDF文件,不也是一行一列的数据吗,我们只需要用一套html模版,把所有数据都动态渲染不就可以了吗?
代码思路:
1、定义一个html模版
2、再定义一个和html融合的实体类(标注着数据的位置、格式等);
3、融合的实体类赋值
4、最终html文件就由这两者来生成
5、html转化为PDF 完结
代码
所支持的PDF内容有 (普通文本,图片,表格)先贴一下最终目标图片

html 模版
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> </head> <body> <div> #foreach($item in $pdfTemplateDO.properties) #if($!item.valueType == 2) <div width="100%" style="display: flex;flex-direction: column;align-items: center;"> <img src=$!{item.value} alt="图片地址错误" width="$!{item.imgWidth}px" height="$!{item.imgHeight}px"> </div> #else <div class="textSet"> #if($!item.displayName != 'null') $!{item.displayName}:$!{item.displayNameBlank} #else <p><p/> #end #if($!item.value != 'null') $!{item.value} #else <p><p/> #end </div> #end #end <br/> <br/> #if($!pdfTemplateDO.tableProperties.size()>0) <table border="1" cellSpacing="0" width="100%"> #foreach($tableProperty in $pdfTemplateDO.tableProperties) <tr> #foreach($item in $tableProperty) <td> #if($!item=='null') <p></p> #else <p> $!{item}</p> </td> #end #end </tr> #end </table> #end </div> </body> <style> .textSet{ width: auto; height: auto; font-size:20px; font-family:Times,serif; margin-bottom:10px; } table { width:100%; border-collapse:collapse; text-align: center; } </style> </html>
Java 代码
/**
上述融合类 **/ @Data @AllArgsConstructor @NoArgsConstructor @Builder public class PdfTemplateDTO { /** * 单属性 */ private List<SingleProperty> properties; /** * 列属性(表头以及每一行数据) */ private List<ColumnProperty> lineProperties; /** * 列属性转化格式(用于表格展示) */ private List<List<String>> tableProperties = new ArrayList<>(); public void linePropertiesConvertTable(){ if (CollectionUtils.isEmpty(this.lineProperties)){ this.tableProperties = new ArrayList<>(); return; } List<String> collectHeadName = this.lineProperties.stream().map(v -> v.getTableHeadName()).collect(Collectors.toList()); //表头 tableProperties.add(collectHeadName); //表体(行列转化) for (int i = 0; i < this.lineProperties.get(0).getTableValue().size(); i++) { List<String> row = new ArrayList<>(); for (int j = 0; j<this.lineProperties.size(); j++){ //获取第j行数据的第i个数据 String s = lineProperties.get(j).getTableValue().get(i); row.add(s); } tableProperties.add(row); } } }
/**
自定义注解
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface AssignGeneratePDF {
/**
* 模版中展示的名称
* @return
*/
String displayName() default "";
/**
* 模版中的顺序(可拓展xy支持一行展示多列)
* @return
*/
int sort() default 9999;
/**
* 属性类型
* 1、单属性
* 2、表格属性
*/
int propertyType() default 1;
/**
* 值类型
* 1、文本
* 2、图片
*/
int valueType() default 1;
int imgWidth() default 200;
int imgHeight() default 120;
}
/**
核心实现代码
**/
public class GeneratePDFUtil {
static String pdfTemplateDO = "pdfTemplateDO";
static String templateName = "PDFTemplate.html";
private static final String wms_basic_data = "wms_basic_data";
private static final String defaultFileName = System.currentTimeMillis()+".pdf";
private static final String MODEL = "model";
private static final String DATA = "data";
private static String space = " ";
private static Integer thirteen = 12;
static {
// Velocity初始化
Velocity.setProperty(RuntimeConstants.OUTPUT_ENCODING, StandardCharsets.UTF_8);
Velocity.setProperty(RuntimeConstants.INPUT_ENCODING, StandardCharsets.UTF_8);
Velocity.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
Velocity.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
Velocity.init();
}
public static void getPDFFileVO(Object dataSource, String fileName){
FileVO fileVO = new FileVO();
if (null == dataSource){
return fileVO;
}
Loggers.BIZ.warn(GeneratePDFUtil.class.getName(),"getPDFFileVO","获取PDF文件","数据源信息",JSONUtil.toJsonStr(dataSource),null);
ByteArrayOutputStream byteArrayOutputStream = generatePDF(dataSource);
}
public static ByteArrayOutputStream generatePDF(Object dataSource){
try {
PdfTemplateDTO pdfTemplateDTO = getFullTemplateDate(dataSource);
//融合数据到html的容器
VelocityContext context = new VelocityContext();
context.put(pdfTemplateDO, pdfTemplateDTO);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
pdfFile(context, templateName, outputStream);
return outputStream;
} catch (IllegalAccessException e) {
throw new ApplicationException("pdf 文件字节流生成失败"+e.getMessage());
}
}
/**
* 根据数据源生成模版
* 注意属性类型建议设置成String
* @param dataSource
* @return
* @throws IllegalAccessException
*/
public static PdfTemplateDTO getFullTemplateDate(Object dataSource) throws IllegalAccessException {
PdfTemplateDTO pdfTemplateDO = new PdfTemplateDTO();
if (dataSource == null){
return pdfTemplateDO;
}
//单属性初始化
ArrayList<SingleProperty> propertyDOS = new ArrayList<>();
//按照表头对应此列的格式先收集数据(为列排序)
HashMap<String, ColumnProperty> mapCollect = new HashMap<>();
Class<?> aClass = dataSource.getClass();
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (!field.isAnnotationPresent(AssignGeneratePDF.class)){
continue;
}
AssignGeneratePDF annotation = field.getAnnotation(AssignGeneratePDF.class);
//单属性
if (annotation.propertyType() == 1){
SingleProperty singleProperty = new SingleProperty();
singleProperty.setDisplayName(annotation.displayName());
singleProperty.setSort(annotation.sort());
String v = toJSONString(field.get(dataSource));
singleProperty.setValue(v);
singleProperty.setValueType(annotation.valueType());
singleProperty.setImgWidth(annotation.imgWidth());
singleProperty.setImgHeight(annotation.imgHeight());
propertyDOS.add(singleProperty);
}else if (annotation.propertyType() == 2){
//列属性
Object o = field.get(dataSource);
if(CollectionUtils.isEmpty((List)o)){
continue;
}
if (!(o instanceof List)){
throw new RuntimeException("AssignGeneratePDF 自定义注解使用失败,type=2不可修饰非List属性");
}
List data = (List)o;
if (CollectionUtils.isEmpty(data)){
continue;
}
//循环表格数据
for (Object datum : data) {
Class<?> aClassSub = datum.getClass();
Field[] fieldListSub = aClassSub.getDeclaredFields();
for (Field fieldSub : fieldListSub){
fieldSub.setAccessible(true);
if (!fieldSub.isAnnotationPresent(AssignGeneratePDF.class)){
continue;
}
AssignGeneratePDF annotationSub = fieldSub.getAnnotation(AssignGeneratePDF.class);
String displayName = annotationSub.displayName();
if (fieldSub.get(datum) instanceof List || annotationSub.propertyType() != 1) {
Loggers.BIZ.warn(GeneratePDFUtil.class.getName(),"getFullTemplateDate",String.format("数据源:%s", JSONUtil.toJsonStr(dataSource)),
"AssignGeneratePDF 表格属性内字段propertyType只能为1,不可为集合",null);
continue;
}
String v = toJSONString(fieldSub.get(datum));
ColumnProperty columnPropertyDO = mapCollect.get(displayName);
if (columnPropertyDO == null){
columnPropertyDO = new ColumnProperty();
columnPropertyDO.setSort(annotationSub.sort());
columnPropertyDO.setTableHeadName(displayName);
List<String> tableValue = new ArrayList<>();
columnPropertyDO.setTableValue(tableValue);
tableValue.add(v);
mapCollect.put(displayName,columnPropertyDO);
}else {
columnPropertyDO.getTableValue().add(v);
}
}
}
}
}
//空格适配
spaceAaptation(propertyDOS);
//排序
List<SingleProperty> finalProperties = propertyDOS.stream().sorted(Comparator.comparing(SingleProperty::getSort)).collect(Collectors.toList());
List<ColumnProperty> finalLineValues = mapCollect.values().stream().sorted(Comparator.comparing(ColumnProperty::getSort)).collect(Collectors.toList());
pdfTemplateDO.setProperties(finalProperties);
pdfTemplateDO.setLineProperties(finalLineValues);
pdfTemplateDO.linePropertiesConvertTable();
return pdfTemplateDO;
}
/**
* displayName和值之间的距离
* @param properties
*/
private static void spaceAaptation(List<SingleProperty> properties){
double max = properties.stream()
.filter(v -> v.getDisplayName() != null)
.map(v -> getStringLength(v.getDisplayName()))
.max(Comparator.comparing(v -> v)).orElse(1d);
//最大个数上+1个空格 作为标长
for (SingleProperty property : properties) {
if(property.getDisplayName() == null){
continue;
}
double spaceNumber = max - getStringLength(property.getDisplayName());
StringBuilder stringBuilder = new StringBuilder();
for (Integer i = 0; i < spaceNumber*2; i++) {
stringBuilder.append(space);
}
property.setDisplayNameBlank(stringBuilder.toString());
}
}
/**
* 英文算一半
* @return
*/
private static double getStringLength(String str){
if (str == null){
return 0L;
}
double valueLength = 0;
String chinese = "[\u4e00-\u9fa5]";
for (int i = 0; i <str.length(); i++) {
// 获取一个字符
String temp = str.substring(i, i + 1);
// 判断是否为中文字符
if (temp.matches(chinese)) {
// 中文字符长度为1
valueLength += 1;
} else {
// 其他字符长度为0.5
valueLength += 0.5;
}
}
return valueLength;
}
public static String toJSONString(Object o) {
String result = null;
if (o == null) {
return result;
} else if (o instanceof String) {
result = (String)o;
} else if (o instanceof Number) {
result = o.toString();
} else if (o instanceof Boolean) {
result = o.toString();
} else if (o instanceof Date) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
result = dateFormat.format(o);
} else {
throw new IllegalArgumentException("pdf生成 not support data type : " + o.getClass());
}
return result;
}
/**
* 据模板生成pfd格式文件
*
* @param context 上下文对象
* @param template pdf模板
* @param outputStream 生成ofd文件输出流
*/
public static void pdfFile(Context context, String template, OutputStream outputStream) {
try (PdfWriter pdfWriter = new PdfWriter(outputStream)) {
PdfDocument pdfDocument = new PdfDocument(pdfWriter);
pdfDocument.setDefaultPageSize(PageSize.A4);
ConverterProperties properties = new ConverterProperties();
FontProvider fontProvider = new FontProvider();
// 字体设置,解决中文不显示问题
PdfFont sysFont = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H");
fontProvider.addFont(sysFont.getFontProgram(), "UniGB-UCS2-H");
properties.setFontProvider(fontProvider);
Template pfdTemplate = Velocity.getTemplate(template, "UTF-8");
StringWriter writer = new StringWriter();
//模版 模版数据填充
pfdTemplate.merge(context, writer);
HtmlConverter.convertToPdf(writer.toString(), pdfDocument, properties);
pdfDocument.close();
} catch (Exception e) {
throw new RuntimeException("PFD文件生成失败", e);
}
}
}
测试java 代码
/**测试实体类
**/
@Data
public class ShangJiaDan{
@AssignGeneratePDF(displayName = "ASN单号",sort = 1)
private String asnNo;
@AssignGeneratePDF(displayName = "操作人姓名",sort = 2)
private String operationUserName;
@AssignGeneratePDF(displayName = "姓名",sort = 3)
private String userName;
@AssignGeneratePDF(displayName = "日期",sort = 4)
private Date date;
@AssignGeneratePDF(propertyType = 2)
private List<ShangJiaDan> tableDate;
@AssignGeneratePDF(valueType = 2,sort = 5)
private String imageUrl;
}
@SpringBootTest @RunWith(SpringRunner.class) public class ExportService { @Test public void testPdf(){ ShangJiaDan shangJiaDan = new ShangJiaDan(); shangJiaDan.setUserName("陈那位i吃为我"); shangJiaDan.setAsnNo(" nvousi2382"); shangJiaDan.setOperationUserName("小冰i好"); shangJiaDan.setDate(new Date()); shangJiaDan.setImageUrl("https://x0.ifengimg.com/res/2021/5EAA10AC691D371504428B1E8C9CA0B5A632A344_size1548_w1150_h638.png"); ShangJiaDan shangJiaDan1 = new ShangJiaDan(); shangJiaDan1.setUserName("第二位"); shangJiaDan1.setAsnNo(" 02222"); shangJiaDan1.setDate(new Date()); ShangJiaDan shangJiaDan2 = new ShangJiaDan(); shangJiaDan2.setUserName("第是你位"); shangJiaDan2.setOperationUserName("奥力给"); shangJiaDan2.setAsnNo(" 02222"); shangJiaDan2.setDate(new Date()); shangJiaDan1.setImageUrl("3232.roepe.png"); shangJiaDan.setTableDate(Arrays.asList(shangJiaDan1,shangJiaDan2,shangJiaDan1)); FileVO pdfFileVO = GeneratePDFUtil.getPDFFileVO(shangJiaDan, "1.pdf"); String fileUrl = pdfFileVO.getFileUrl(); System.out.println(fileUrl); } }
测试结果

浙公网安备 33010602011771号