關于Java生成HTML,可參考我的這篇文章: FreeMarker之根據模型生成HTML代碼
當然了,該篇文章也會給你很多啟發,比如,根據html生成html,大家不要小看這個,著名的WordPress部落格文章,本質上就是這個機制,每發表一篇文章相當于新生成的一個HTML,内容不一樣,樣式基本是一緻的。
下面進入正題:
一、導入Maven依賴
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.foresee</groupId>
<artifactId>mypdf</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.4.2</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>core-renderer</artifactId>
<version>R8</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>net.sf.barcode4j</groupId>
<artifactId>barcode4j-light</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
</project>
二、編寫Java代碼
package com.beck.util;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.krysalis.barcode4j.HumanReadablePlacement;
import org.krysalis.barcode4j.impl.code39.Code39Bean;
import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
import org.krysalis.barcode4j.tools.UnitConv;
import sun.misc.BASE64Encoder;
public class BarcodeUtil {
public static final String IMG_TYPE_PNG="image/png";
public static final String IMG_TYPE_GIF="image/gif";
public static final String IMG_TYPE_JPEG="image/jpeg";
public static String generateToBase64(String msg,String imgType) throws IOException {
ByteArrayOutputStream ous = new ByteArrayOutputStream();
generateToStream(msg,imgType, ous);
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(ous.toByteArray());
}
public static File generateToFile(String msg,String imgType, File file)
throws IOException {
FileOutputStream out = new FileOutputStream(file);
try {
generateToStream(msg,imgType, out);
} finally {
if (out != null) {
out.close();
}
}
return file;
}
public static byte[] generateToByte(String msg,String imgType) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
generateToStream(msg,imgType, out);
return out.toByteArray();
} finally {
if (out != null) {
out.close();
}
}
}
public static void generateToStream(String msg, String imgType,OutputStream out)
throws IOException {
if (msg == null || out == null) {
return;
}
Code39Bean bean = new Code39Bean();
// 精細度
int dpi = 150;
// module寬度
double moduleWidth = UnitConv.in2mm(1.0f / dpi);
bean.setModuleWidth(moduleWidth);
// 不顯示文字
bean.setMsgPosition(HumanReadablePlacement.HRP_NONE);
bean.setHeight(5);
bean.setWideFactor(3);
bean.doQuietZone(false);
// 輸出到流
BitmapCanvasProvider canvas = new BitmapCanvasProvider(out, imgType,
dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0);
// 生成條形碼
bean.generateBarcode(canvas, msg);
// 結束繪制
canvas.finish();
}
}
package com.beck.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import freemarker.template.Configuration;
import freemarker.template.Template;
public class FreemarkerTemplate {
private final Configuration configuration = new Configuration(
Configuration.VERSION_2_3_23);
private String charset;
public FreemarkerTemplate(String charset) {
this.charset = charset;
configuration.setEncoding(Locale.CHINA, charset);
configuration.setClassicCompatible(true);//處理空值為空字元串
}
public void setTemplateClassPath(Class resourceLoaderClass,
String basePackagePath) {
configuration.setClassForTemplateLoading(resourceLoaderClass,
basePackagePath);
}
public void setTemplateDirectoryPath(String templatePath)
throws IOException {
configuration.setDirectoryForTemplateLoading(new File(templatePath));
}
public void processToStream(String templateFileName,
Map<String, Object> dataMap, Writer writer) throws Throwable {
Template template = configuration.getTemplate(templateFileName);
template.process(dataMap, writer);
}
public void processToFile(String templateFileName,
Map<String, Object> dataMap, File outFile) throws Throwable {
Writer writer = new OutputStreamWriter(new FileOutputStream(outFile),
charset);
try {
processToStream(templateFileName, dataMap, writer);
} catch (Throwable e) {
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
}
}
}
public String processToString(String templateFileName,
Map<String, Object> dataMap) throws Throwable {
Writer writer = new StringWriter(2048);
try {
processToStream(templateFileName, dataMap, writer);
return writer.toString();
} finally {
if (writer != null) {
writer.close();
}
}
}
}
package com.beck.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import com.itextpdf.text.pdf.BaseFont;
public class PDFUtils {
private static String fontBasePath=PDFUtils.class.getResource("/").getPath()+"font/";
public static void htmlFileToPDFStream(File htmlFile, OutputStream output,
File imageBaseURL) throws Throwable {
if (htmlFile == null) {
throw new RuntimeException("htmlFile is null");
}
if (output == null) {
throw new RuntimeException("output is null");
}
// 生成ITextRenderer執行個體
ITextRenderer renderer = new ITextRenderer();
// 關聯html頁面
renderer.setDocument(htmlFile.toURI().toURL().toString());
// 設定擷取圖檔的基本路徑
renderer.getSharedContext().setBaseURL(
imageBaseURL.toURI().toURL().toString());
// 設定字型路徑,必須和html設定一緻
ITextFontResolver fontResolver = renderer.getFontResolver();
fontResolver.addFont(fontBasePath+"msyh.ttc", BaseFont.IDENTITY_H,
BaseFont.NOT_EMBEDDED);
fontResolver.addFont(fontBasePath+"msyhl.ttc", BaseFont.IDENTITY_H,
BaseFont.NOT_EMBEDDED);
fontResolver.addFont(fontBasePath+"msyhbd.ttc", BaseFont.IDENTITY_H,
BaseFont.NOT_EMBEDDED);
renderer.layout();
renderer.createPDF(output);
}
public static void htmlFileToPDFFile(File htmlFile, File pdfFile,
File imageBaseURL) throws Throwable {
OutputStream output = new FileOutputStream(pdfFile);
try {
htmlFileToPDFStream(htmlFile, output, imageBaseURL);
} finally {
if (output != null) {
output.close();
}
}
}
}
package com.beck.util;
public class Receipt {
private String receiptType;
private double amount;
public String getReceiptType() {
return receiptType;
}
public void setReceiptType(String receiptType) {
this.receiptType = receiptType;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
}
三、在src/main/resources建立HTML頁面和相關的檔案夾
建立font、images、templates檔案夾
檔案夾如圖所示:

html代碼:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>報帳單</title>
<style type="text/css" rel="stylesheet">
* {
margin: 0;
padding: 0;
}
body {
font-family: "Microsoft YaHei UI";
font-size: 12px;
max-width: 555px;
margin: 0 auto;
padding: 20px;
}
h1 {
text-align: center;
font-size: 18px;
color: #000;
font-weight: bold;
}
.comptitle {
height: 66px;
}
.comptitle h2 {
float: left;
height: 66px;
line-height: 66px;
font-size: 14px;
color: #000;
font-weight: bold;
}
.barcode {
width: 162px;
float: right;
padding: 10px 0;
text-align: center;
}
.barcode img {
width: 162px;
height: 30px;
display: table-cell;
vertical-align: middle;
margin-bottom: 5px;
}
.barcode h3 {
font-size: 12px;
color: #000;
}
.stafftbl, .detailstbl, .approverrd {
border-collapse: collapse;
border: 1px solid #f3f3f3;
}
.stafftbl th, .stafftbl td, .detailstbl th, .detailstbl td, .approverrd th, .approverrd td {
height: 20px;
line-height: 20px;
border: 1px solid #f3f3f3;
}
.stafftbl th {
font-weight: normal;
background: #f8f9fd;
text-align: right;
color: #777;
padding-right: 12px;
}
.stafftbl td {
padding-left: 10px;
}
.wrap p {
color: #000;
margin: 12px 0 10px;
}
.detailstbl th, .approverrd th {
font-weight: normal;
color: #777;
background: #f8f9fd;
border: none;
}
.detailstbl td, .approverrd td {
text-align: center;
}
.sum {
background: #f8f9fd;
color: #777;
}
.detailstbl td.sumdata {
text-align: right;
padding-right: 20px;
font-weight: bold;
}
</style>
</head>
<body>
<div class="wrap">
<h1>費用報帳單</h1>
<div class="comptitle">
<h2>*****有限公司</h2>
<div class="barcode"><img src="${barcodeImg}" alt=""></img>
<h3>${barcode}</h3>
</div>
</div>
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="stafftbl">
<tbody>
<tr>
<th width="18%">報帳人</th>
<td width="32%">${userName}</td>
<th width="18%">填寫日期</th>
<td>${submitDate}</td>
</tr>
<tr>
<th>費用所屬部門</th>
<td colspan="3">${deptName}</td>
</tr>
<tr>
<th>所屬項目</th>
<td colspan="3">營運平台2.0</td>
</tr>
<tr>
<th>報帳事由</th>
<td colspan="3">${reimReason}</td>
</tr>
</tbody>
</table>
<p>費用明細</p>
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="detailstbl">
<tbody>
<tr>
<th width="8%">序号</th>
<th>發票類型</th>
<th width="10%">發票張數</th>
<th width="12%">發票金額</th>
<th width="12%">報帳金額</th>
<th>專票稅額</th>
<th width="28%">用途</th>
</tr>
<#list receiptList as item>
<tr>
<td >${list_index+1}</td>
<td>${item.receiptType}</td>
<td>1</td>
<td>¥${item.amount}</td>
<td>¥${item.amount}</td>
<td> </td>
<td> </td>
</tr>
</#list>
<tr>
<td colspan="2" class="sum">總報帳金額(小寫)</td>
<td colspan="5" class="sumdata" >¥259.46</td>
</tr>
<tr>
<td colspan="2" class="sum">總報帳金額(大寫)</td>
<td colspan="5" class="sumdata" >貳佰伍拾玖元肆角陸分</td>
</tr>
</tbody>
</table>
<p>審批記錄</p>
<table width="100%" border="0" cellspacing="0" cellpadding="0" class="approverrd">
<tbody>
<tr>
<th width="8%">序号</th>
<th>節點名稱</th>
<th>電子簽名</th>
<th>簽名日期</th>
<th width="8%">操作</th>
<th width="25%">備注</th>
</tr>
<tr>
<td>1</td>
<td>送出</td>
<td>張三/平台支撐部</td>
<td>2018-09-18 16:18:41</td>
<td>送出</td>
<td> </td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
圖檔要求必須符合png、jpg等格式,隻要是這些格式的都行。
四、編寫測試類
package com.beck.util;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class Test {
public static void main(String[] args) throws Throwable {
String barcode = "12345678910";
String uuid=UUID.randomUUID().toString();
String htmlBasePath="E://Demo//workspace//mypdf//src//main//resources//template";//html的根目錄
String tempathBasePath=Test.class.getResource("/").getPath()+"template/";//模闆路徑
String pdfBasePath="E://Demo//workspace//mypdf//src//main//resources//template";//生成的pdf目錄
String barCodePath="E://Demo//workspace//mypdf//src//main//resources//images//"+uuid+".png";
String htmlPath=htmlBasePath+uuid+".html";
String pdfPath=pdfBasePath+uuid+".pdf";
//生成條形碼
File barCodeFile=new File(barCodePath);
BarcodeUtil.generateToFile(barcode,BarcodeUtil.IMG_TYPE_PNG,barCodeFile);
System.out.println("生成條形碼完成!");
//生成html
FreemarkerTemplate tp=new FreemarkerTemplate("UTF-8");
tp.setTemplateDirectoryPath(tempathBasePath);
//封裝資料 start ,必須是Map
Map<String, Object> dataMap = new HashMap<String, Object>();
dataMap.put("barcode",barcode);
dataMap.put("barcodeImg",barCodePath);
dataMap.put("userName", "遊總");
dataMap.put("submitDate", "2018-09-18");
dataMap.put("deptName", "研發平台");
//費用明細
List<Receipt> receiptList=new ArrayList<Receipt>();//Receipt 必須是public類型的類
Receipt r1=new Receipt();
r1.setReceiptType("普通發票");
r1.setAmount(100);
Receipt r2=new Receipt();
r2.setReceiptType("普通發票");
r2.setAmount(100);
receiptList.add(r1);
receiptList.add(r2);
dataMap.put("receiptList", receiptList);
//封裝資料 end
File htmlFile=new File(htmlPath);
tp.processToFile("expense.html", dataMap,htmlFile);
System.out.println("生成html完成!");
//生成pdf
PDFUtils.htmlFileToPDFFile(htmlFile, new File(pdfPath), new File(htmlBasePath));
System.out.println("生成pdf完成!");
}
}
運作結果如圖所示(控制台列印這些就表示沒有問題,如果控制台報錯,常見錯誤就是檔案找不到異常,改下路徑就可以了,一般情況是沒有其他錯誤):
pdf顯示如圖:
html顯示如圖:
條形碼顯示如圖:
源碼位址:https://github.com/youcong1996/study_simple_demo.git
如果實在上述代碼,不能運作,大家可以将源碼移植過來,運作效果是沒有問題的。