最近由於工作上的需要,需要做一個簡歷產品的下載功能,而下載的形式要去爲PDF,內容要求爲整個簡歷的內容,而且格式上要求和簡歷的格式排版時一致的!前期調研、開發,最後測試上線,差不多花了7天的時間,當然,期間主要完成了主體功能,現在的話,該功能已經相當完善。下面,我主要是總結下我在這個開發的過程中遇到的問題和總結的心得,希望能幫組有這方面需要的人。
原創文章,轉載請註明出處:http://blog.csdn.net/jessonlv
前期調研
前期調研的時候,在網上看了很多關於轉pdf的相關文章和技術框架,詳細的我不想在此一一贅述,總體給我的感覺就是,第一:國外的相關技術框架做的就是好,關於這方面的,基本都是國外的技術,最多也就是國內牛人改改源碼,來適應中文等相關的本土化需要。第二:國內有關生產pdf的需求一般都很簡單,要麼就是簡單文本,複雜的最多也就是相關報表等,基本麼有自己想要實現的那麼複雜的內容、排版。尤其是生成的內容要和也也面上的內容完全一致,樣式排版完全一致!
需求和思路
具體需求就是:
1、產品是一個簡歷產品,簡歷上展示的所有數據都是通過動態獲取的。
2、要求內容一致,並保持樣式排版一致!
首先大家可以看下我們這個產品的下載功能的應用,網址:www.mojianli.com 右上角的下載功能。
思路
大體思路:取出簡歷所有數據-->通過freemarker生成靜態頁面-->將html靜態頁面轉換成PDF
這樣的思路主要是保證pdf的樣式要和頁面樣式一致。
1、首先通過相關功能接口,取出這個簡歷的所有數據。
2、通過freemarker排版輸出的html靜態頁面。靜態頁面的樣式決定了生成的pdf的樣式。
3、讀取html靜態頁面,轉換成pdf。
4、將pdf輸出在瀏覽器,實現下載功能。
開發過程
第一步:取出相關簡歷的所有數據
這個是和項目相關的,就不再此贅述,換成你自己想要生成的pdf內容即可。
第二步:通過freemarker生成靜態頁面。
首先,利用了freemarker框架,對此框架不熟悉的請自行學習,本問的重點是生成pdf,爲了讓大家明白此功能的應用場景,所有寫了很多,但用到的pdf之外的相關技術,請大家自行學習。
freemarker模板代碼:
<#assign freemarkerTool= "com.shengao.mojianli.util.FreemarkerTool"?new()>
<html>
<head>
<title>mojianli</title>
<style>
@font-face {
font-family: 'Microsoft YaHei';
font-family: 'Arial';
}
html, body, p {
margin: 0;
padding: 0;
}
span {
line-height: 1px;
}
body {
font-family: 'Microsoft YaHei';
font-size: 11px;
color: #666666;
}
.wrapper {
width: 900px;
margin: 0 auto;
}
.block {
margin-top: 20px;//+2
}
.align-center {
text-align: center;
margin-left: 252px;
width: 200px;
}
#name {
font-size: 25px;
margin-top: 0px;
color: #333333;
margin-bottom: 0;
}
#phone {
font-size: 12px;
margin-top: 12px;
font-family: "Arial";
}
#email {
font-size: 11px;
margin-top: 4px;
font-family: "Arial";
}
.timestamp {
display: inline-block;
width: 110px;
}
.title {
font-size: 13px;
}
.simple-module {
margin-bottom: 10px;
font-size: 11px;
}
.simple-module-item{
font-size: 11px;
margin-right: 16px;
}
.simple-item {
font-size: 11px;
margin-right: 16px;
}
.item_thing {
font-size: 11px;
margin-right: 6px;
line-height: 18px;
}
.label {
background: #666;
border-radius: 1px;
color: white;
font-size: 7px;
position: relative;
top: -1px;
line-height: 7px;
}
.product {
margin-left: 150px;
margin-bottom: 5px;
margin-top: 4px;
width:750px;
}
.capacity-block {
margin-left: 22px;
}
.things {
padding-left: 12px;
margin-top: 5px;
background: url(img/dot.png) left top no-repeat;
}
.tag {
background: #333333;
border-radius: 1px;
color: white;
font-size: 11px;
padding: 0 1px 1px 1px;
margin-right: 4px;
border-radius: 1px;
}
.enterprise{
margin-bottom: 9px;
}
.enterprise .simple-item {
margin-bottom: 5px;
}
.capacity-group {
margin-top: 5px;
width: 520px;
}
.paragraph {
line-height: 16px;
font-size: 11px;
margin-bottom: 10px;
width: 700px;
}
.h-seperator {
border-color: #666;
margin-top: 7px;
height: 0;
border-top: none;
border-bottom: 1px solid;
margin-bottom: 6px;
}
.company,.department,.title{
color: #333;
}
</style>
</head>
<body>
<#escape x as x!""></#escape>
<div class="wrapper">
<div class="block align-center" id="contact">
<p id="name">${name}</p>
<p id="phone">${phone}</p>
<p id="email">${email}</p>
</div>
<div class="block" id="education">
<span class="title">${exp}</span>
<div class="h-seperator"></div>
<#list education as education>
<p class="simple-module">
<span class="timestamp simple-module-item">${education.start_date} ~ ${education.end_date}</span>
<span class="simple-module-item"><#if education.university??>${education.university}</#if></span>
<span class="simple-module-item"><#if education.colleges??>${education.colleges}</#if> · <#if education.major??>${education.major}</#if></span>
<span class="simple-module-item"><#if education.degree??>${education.degree}</#if></span>
<span class="simple-module-item"><#if education.explain??>${education.explain}</#if></span>
</p>
</#list>
</div>
<!--under-->
<!--割一割-->
<div class="block" id="experience">
<span class="title">${project}</span>
<div class="h-seperator"></div>
<!--項目模板代碼開始-->
<!--項目經歷開始-->
<#list experience as experiences>
<div class="enterprise">
<span class="timestamp simple-item">${experiences.experience.start_date} ~ ${experiences.experience.end_date}</span>
<span class="simple-item company">${experiences.experience.company}</span>
<span class="simple-item department">${experiences.experience.department}</span>
<span class="simple-item title">${experiences.experience.title}</span>
<!--項目名稱開始-->
<#list experiences.projects as projects>
<div class="product">
<span class="simple-item">${projects.project.name}</span>
<span class="simple-item">${projects.project.phase}</span>
<span class="simple-item">${projects.project.core_goal}</span>
<!--標籤、事情開始-->
<#list projects.tags as tags>
<div class="capacity-block">
<div class="capacity-group">
<!--標籤開始-->
<#list tags.tags as tag>
<span class="tag">${tag.base_tag_name}</span>
</#list>
<!--標籤結束-->
<!--事情開始-->
<#list tags.items as item>
<div class="things">
<#list item.labels as label>
<span class="label">${label.base_label_name}</span>
<span class="item_thing">${label.content}</span>
</#list>
</div>
</#list>
<!--事情結束-->
</div>
</div>
</#list>
<!--標籤、事情結束-->
</div>
</#list>
<!--項目名稱結束-->
</div>
</#list>
<!--項目名稱結束-->
<!--項目模板代碼結束-->
</div>
<!--割一割-->
<div class="block" id="honor">
<span class="title">${awards}</span>
<div class="h-seperator"></div>
<#list awardses as awardses>
<p class="simple-module">
<span class="simple-item timestamp">${awardses.start_date} ~ ${awardses.end_date}</span>
<span class="simple-item"><#if awardses.name??>${awardses.name}</#if></span>
<span class="simple-item"><#if awardses.level??>${awardses.level}</#if></span>
<span class="simple-item"><#if awardses.rank??>${awardses.rank}</#if></span>
<span class="simple-item"><#if awardses.number??>${awardses.number}</#if></span>
</p>
</#list>
</div>
<div class="block" id="evaluation">
<span class="title">${evaluate}</span>
<div class="h-seperator"></div>
<#list evaluates as evaluates>
<p class="paragraph">${freemarkerTool(evaluates.content)}</p>
</#list>
</div>
</div>
</body>
</html>
模板相關的數據填充,調用java方法的做法等,網上很多,我也是現學現用的。
利用此模板生成的靜態頁面的樣式,就是你想要的pdf的樣式。
然後是讀取此模板,生成html頁面的代碼:
@RequestMapping(value = "/createPdf.s", method = {RequestMethod.POST,RequestMethod.GET})
public void getAllResumeInfoById(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value="id", required = true) Long id) {
String perName = "";
String positionName = "";
long resumeId = id;
//獲取所有的數據
//個人基本信息
ResumeInfoBean resumeInfo = new ResumeInfoBean();
//教育經歷
List<EducationBean> eduList = new ArrayList<EducationBean>();
//獲獎經歷
List<AwardsBean> awardsList = new ArrayList<AwardsBean>();
//個人評價
List<EvaluateBean> evaList = new ArrayList<EvaluateBean>();
//項目經歷
List<PdfExperience> pdfExperience = new ArrayList<PdfExperience>();
try {
Map<String, Object> map = resumeInfoService.getAllResumeInfoById(id);
resumeInfo = (ResumeInfoBean)map.get("resumeInfo");
eduList = (List<EducationBean>)map.get("education");
awardsList = (List<AwardsBean>)map.get("awards");
evaList = (List<EvaluateBean>)map.get("evaluates");
pdfExperience = (List<PdfExperience>)map.get("experiences");
System.out.println("finish...pdfExperience.size=="+pdfExperience.size());
} catch (Exception e) {
log.warn(e);
JsonUtil.errorToClient(response, 400, e.getMessage());
return;
}
//工程路徑
String path = request.getSession().getServletContext().getRealPath("/");
try {
Configuration cfg = new Configuration();
cfg.setDirectoryForTemplateLoading(new File(getPath(request,response)));
cfg.setObjectWrapper(new DefaultObjectWrapper());
cfg.setDefaultEncoding("UTF-8"); //這個一定要設置,不然在生成的頁面中會亂碼
//設置對象包裝器
cfg.setObjectWrapper(new DefaultObjectWrapper());
//設計異常處理器
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
//準備魔簡歷數據
Map<String, Object> ResumeMap = new HashMap<String, Object>();
//頭部信息bean對象
String name = resumeInfo.getName();
perName = name;
positionName = resumeInfo.getBase_position_name();
String phone = resumeInfo.getMobile();
String email = resumeInfo.getEmail();
ResumeMap.put("name",name);
ResumeMap.put("phone",phone);
ResumeMap.put("email",email);
//定義四個模塊標題
ResumeMap.put("exp","教育經歷");
ResumeMap.put("project","項目經歷");
ResumeMap.put("awards","獲獎經歷");
ResumeMap.put("evaluate","個人評價");
//封裝教育經歷對象數據
ResumeMap.put("education", eduList);
String streducationlist = JsonUtil.list2json(eduList);
//封裝項目經歷數據
ResumeMap.put("experience", pdfExperience);
String strEx= JsonUtil.list2json(pdfExperience);
System.out.print(strEx);
//封裝獲獎經歷數據
ResumeMap.put("awardses",awardsList);
String strawardsList = JsonUtil.list2json(awardsList);
//封裝個人評價數據
ResumeMap.put("evaluates",evaList);
String strevaList = JsonUtil.list2json(evaList);
//獲取指定模板文件
Template template = cfg.getTemplate("mojianli.ftl");
//控制檯打印
template.process(ResumeMap, new PrintWriter(System.out));
//定義輸入文件,默認生成在工程根目錄
String s = getPath(request,response);
path = s+id+"_mojianli.html";
Writer out = new OutputStreamWriter(new FileOutputStream(path),"UTF-8");
//最後開始生成
template.process(ResumeMap, out);
System.out.println("create the html successful!!!"+"path="+request.getSession().getServletContext().getRealPath("/"));
}catch (Exception e){
e.printStackTrace();
jsonObjOutPut.clear();
jsonObjOutPut = JsonUtil.createJsonObject(MSG.STATUS_RESPONSE_FAIL_201, MSG.MSG_RESPONSE_FAIL_201);
stringOutPutData = JsonUtil.object2json(jsonObjOutPut);
JsonUtil.jsonStringToClient(response,stringOutPutData);
}
boolean boo = false;
try {
boo = html2Pdf(request,response,perName,resumeId,positionName);
}catch (Exception e){
e.printStackTrace();
jsonObjOutPut.clear();
jsonObjOutPut = JsonUtil.createJsonObject(MSG.STATUS_RESPONSE_FAIL_201, MSG.MSG_RESPONSE_FAIL_201);
stringOutPutData = JsonUtil.object2json(jsonObjOutPut);
JsonUtil.jsonStringToClient(response,stringOutPutData);
}
}
前半段關於數據的封裝等的代碼可以不管,填上自己的數據就行了。
生成頁面有就是讀取相關頁面,並生成pdf的代碼
//html轉成pdf
private boolean html2Pdf(HttpServletRequest request,HttpServletResponse response,String name,long id,String postionName) throws IOException, DocumentException, ParserConfigurationException {
boolean bl = false;
//工程路徑
/*String separator = File.separator;
String root = request.getSession().getServletContext().getRealPath("");
String path = root+separator+"WEB-INF"+separator+"resources"+name+"_"+id+"_mojianli.html";*/
String path = getPath(request,response)+id+"_mojianli.html";
//獲取已經生成的html頁面的路徑
//String path = "F:\\tomcat_myeclipse\\webapps\\mojianli\\WEB-INF\\resources\\mojianli.html";
//讀取html
FileInputStream fis =new FileInputStream(path);
StringWriter writers = new StringWriter();
InputStreamReader isr = null;
String string = null;
//此處將io流轉換成String
try {
isr = new InputStreamReader(fis,"utf-8");//包裝基礎輸入流且指定編碼方式
//將輸入流寫入輸出流
char[] buffer = new char[2048];
int n = 0;
while (-1 != (n = isr.read(buffer))) {
writers.write(buffer, 0, n);
}
}catch (Exception e){
e.printStackTrace();
} finally {
if (isr != null)
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
jsonObjOutPut.clear();
jsonObjOutPut = JsonUtil.createJsonObject(MSG.STATUS_RESPONSE_FAIL_201, MSG.MSG_RESPONSE_FAIL_201);
stringOutPutData = JsonUtil.object2json(jsonObjOutPut);
JsonUtil.jsonStringToClient(response,stringOutPutData);
}
}
if (writers!=null){
string = writers.toString();
}
System.out.print(string);
//利用renderer來準備數據
ITextRenderer renderer = new ITextRenderer();
ITextFontResolver fontResolver = renderer.getFontResolver();
//設置創建PDF的時候要用的字體,此字體必須要和簡歷模板的字體保持一致!!
fontResolver.addFont(getPath(request, response)+"msyh.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
fontResolver.addFont(getPath(request, response)+"arial.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
//get font family name
BaseFont font = null;
BaseFont font2 = null;
try {
font = BaseFont.createFont(getPath(request, response)+"msyh.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
font2 = BaseFont.createFont(getPath(request, response)+"arial.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
} catch (DocumentException e) {
e.printStackTrace();
jsonObjOutPut.clear();
jsonObjOutPut = JsonUtil.createJsonObject(MSG.STATUS_RESPONSE_FAIL_201, MSG.MSG_RESPONSE_FAIL_201);
stringOutPutData = JsonUtil.object2json(jsonObjOutPut);
JsonUtil.jsonStringToClient(response,stringOutPutData);
}
//fontFamilyName‘s value is the key for font-family
String fontFamilyName = TrueTypeUtil.getFamilyName(font2);
System.out.println("fontFamilyName222="+fontFamilyName);
//設置pdf內容!!
renderer.setDocumentFromString(string);
//設置圖片的絕對路徑
renderer.getSharedContext().setBaseURL("file:"+getPath(request,response)+"\\img");
System.out.println(getPath(request,response)+"img");
renderer.layout();
//create the pdf
//String pdfPath = path+"WEB-INF\\resources\\"+name+"_mojianli.pdf";
String pdfPath = getPath(request, response)+id+"_mojianli.pdf";
FileOutputStream outputStream = new FileOutputStream(pdfPath);//文件輸出根目錄下
renderer.createPDF(outputStream);
//Finishing up
//renderer.finishPDF();
System.out.println("created the pdf !!");
//下載
try{
//downloadPdf(response,request,name,outputStream);
downLoadPdf(request,response,name,id,postionName);
}catch (Exception e ){
e.printStackTrace();
}
bl = true;
return bl;
}
這裏需要注意的兩點是:1、設置中文字體,以及中文字體文件的引用2、引用圖片的問題。 仔細看代碼註釋,上面都有!
生成pdf以後,就是推送到瀏覽器的問題:
public void downLoadPdf(HttpServletRequest request, HttpServletResponse response,String name,long id,String postionName) {
try {
String separator = File.separator;
String root = request.getSession().getServletContext().getRealPath("");
String filePath = root+separator+"WEB-INF"+separator+"resources";
String headerName = new String(name.getBytes("utf-8"),"iso8859_1");//解決下載文件中文標題亂碼問題
String postion = new String(postionName.getBytes("utf-8"),"iso8859_1");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename="+headerName+"-"+postion+".pdf");
OutputStream outputStream = response.getOutputStream();
InputStream inputStream = new FileInputStream(filePath + separator+id+"_mojianli.pdf");
byte[] buffer = new byte[1024];
int i = -1;
while ((i = inputStream.read(buffer)) != -1)
{
outputStream.write(buffer, 0, i);
}
outputStream.flush();
//outputStream.close();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
log.warn(e);
JsonUtil.errorToClient(response, 400, e.getMessage());
return;
}
}
這裏的注意點是:注意下載文件中文標題亂碼問題至此,以上總體的代碼大概是這樣,需要的人,可以多看看,如果有什麼問題,歡迎隨時私信、留言等交流。