在大型网站中,比如主流电商商品页,访问者看到的页面基本上是静态页面。为什么都要把页面静态化呢?其实把页面静态化,好处有很多。例如:访问速度快,更有利于搜索引擎收录等。
目前主流的静态化主要有两种:
这两种方法都达到了实现URL静态化的效果,但是也各有各自的特点。
对比项 | 动态 | 伪静态 | 纯静态 |
---|---|---|---|
网站打开速度 | 中等 | 中等 | 快(无DB查询、CPU计算消耗) |
搜索引擎抓取和索引 | 不利 | 有利 | 有利(速度快,权重高) |
安全性 | 低 | 低 | 高(不适用程序、DB,攻击目标少) |
稳定性 | 低 | 低 | 高(不受程序、DB故障影响) |
硬盘容量 | 低 | 低 | 高(静态化网页文件需要存储在硬盘中) |
服务器压力 | 高 | 高 | 低(无DB查询、CPU计算消耗) |
实时性 | 高 | 高 | 低(数据变更后,需要手动触发静态化;静态化难度随网站复杂度提升而提升) |
org.freemarker
freemarker
{最新稳定版}
package com.xxl.util.core.util;
import freemarker.ext.beans.BeansWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import java.io.*;
import java.util.Map;
/**
* HTML模板.Util
*
* 功能简介:
* 1、根据Ftl生成Html字符串;
* 2、根据Ftl生成Html文件(网站静态化);
* 3、Ftl支持使用静态类方法;
*
* @author xuxueli
*/
public class HtmlTemplateUtil {
private static FreeMarkerConfigurer freemarkerConfig;
public static FreeMarkerConfigurer loadConfig(){
if (freemarkerConfig == null) {
freemarkerConfig = (FreeMarkerConfigurer) SpringContentAwareUtil.getBeanByName("freemarkerConfig");
}
return freemarkerConfig;
}
/**
* generate static model
* @param packageName
* @return
*/
public static TemplateHashModel generateStaticModel(String packageName) {
try {
BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
TemplateHashModel staticModels = wrapper.getStaticModels();
TemplateHashModel fileStatics = (TemplateHashModel) staticModels.get(packageName);
return fileStatics;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 生成HTML字符串
*
* @param content : 页面传参
* @param templateName : 模板名称路径,相对于模板目录
*/
public static String generateString(Map, ?> content, String templateName) {
String htmlText = "";
try {
// 通过指定模板名获取FreeMarker模板实例
Template tpl = loadConfig().getConfiguration().getTemplate(templateName);
htmlText = FreeMarkerTemplateUtils.processTemplateIntoString(tpl, content);
} catch (Exception e) {
e.printStackTrace();
}
return htmlText;
}
/**
* 生成HTML文件
*
* @param content : 页面传参
* @param templatePathName : 模板名称路径,相对于模板目录
* @param filePathName : 文件名称路径,相对于项目跟目录
* DEMO:HtmlTemplateUtil.generate(freemarkerConfig, new HashMap(), "net/index.ftl", "/index.html");
*/
public static void generateFile(Map, ?> content, String templatePathName, String filePathName) {
Writer out = null;
try {
// mkdirs
File htmlFile = new File(WebPathUtil.webPath() + filePathName);
if (!htmlFile.getParentFile().exists()) {
htmlFile.getParentFile().mkdirs();
}
// process
Template template = loadConfig().getConfiguration().getTemplate(templatePathName);
out = new OutputStreamWriter(new FileOutputStream(WebPathUtil.webPath() + filePathName), "UTF-8");
template.process(content, out);
out.flush();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (TemplateException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
out = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
String temp = generateString(null, "hello.ftl");
System.out.println(temp);
/*// generate String
Map params= new HashMap();
params.put("WebPathUtil", HtmlTemplateUtil.generateStaticModel(WebPathUtil.class.getName()));
String result = HtmlTemplateUtil.generateString(params, "freemarker-test.ftl");
// generate Html File
HtmlTemplateUtil.generateFile(params, "freemarker-test.ftl", "freemarker-test.html");*/
}
}
要点:
1、“freemarkerConfig”中的配置“templateLoaderPath”,为include新ftl文件时的根目录“/”位置(省去了繁琐的相对路径配置,相当方便);
2、“freemarkerConfig”必须放在spring中,springMVC和静态化UTIL公用;因为web.xml加载顺序为:web.xml加载顺序,listener(spring) -> filter -> servlet(springMVC);因此,如果放在springMVC中,Service中注入不了该config,静态化UTIL注入时还未初始化bean会报错;
public static void main(String[] args) {
String temp = generateString(null, "hello.ftl");
System.out.println(temp);
/*// generate String
Map params= new HashMap();
params.put("WebPathUtil", HtmlTemplateUtil.generateStaticModel(WebPathUtil.class.getName())); // 可以在Ftl中使用静态工具类 "${WebPathUtil.webPath()}"
String result = HtmlTemplateUtil.generateString(params, "freemarker-test.ftl");
// generate Html File
HtmlTemplateUtil.generateFile(params, "freemarker-test.ftl", "freemarker-test.html");*/
}
// 调用处:一面墙,html分页
List wallInfoList = wallInfoDao.getPageList(0, 10000);
generateHtmlPagination(wallInfoList, null, 20, "net/wall/index.ftl", "wall/", "index");
/**
* html分页工具
* @param allList : html分页列表
* @param pagesize : 每页显示记录数量
* @param templatePathName : 模板页面,完整地址 (相相对于freeamrker配置根目录)
* @param filePath : html文件,路径目录 --/-/
* @param index : html文件,默认前缀 index
*/
private void generateHtmlPagination(List> allList, Map paramMap,int pagesize, String templatePathName , String filePath , String index){
int allCount = allList!=null?allList.size():0;
Map params = new HashMap();
params.put("pageNumAll", 1);
params.put("pageNum", 1);
params.put("filePath", filePath);
params.put("index", index);
if (MapUtils.isNotEmpty(paramMap)) {
params.putAll(paramMap);
}
if (allCount > 0) {
int pageNumAll = allCount%pagesize==0 ? allCount/pagesize : allCount/pagesize + 1;
for (int pageNum = 1; pageNum <= pageNumAll; pageNum++) {
params.put("pageNumAll", pageNumAll);
params.put("pageNum", pageNum);
int from = (pageNum-1)*pagesize;
int to = (from + pagesize) <= allCount ? (from + pagesize) : allCount;
params.put("pageList", allList.subList(from, to));
String fileName = (pageNum==1) ? index : (index + "_" + pageNum);
HtmlTemplateUtil.generate(params, templatePathName, filePath + fileName + ".html");
}
} else {
HtmlTemplateUtil.generate(params, templatePathName, filePath + index + ".html");
}
}
2、分页模板:index.ftl
<#list pageList as item>
${item.content}
#list>
<#import "/net/common/common.html.pagination.ftl" as pagination>
<@pagination.htmlPaging pageNumAll=pageNumAll pageNum=pageNum html_base_url=base_url+filePath index=index />
3、分页模板,公共分页标签:common.html.pagination.ftl
<#--
html分页模板,文件名称
------------------
pageNum : 当前页数、(1-max)
html_base_url : html文件路径、
-->
<#macro htmlPagingName pageNum html_base_url index >
<#if pageNum == 1 >${html_base_url}${index}.html
<#else>${html_base_url}${index}_${pageNum}.html#if>
#macro>
<#--
html分页模板
------------------
pageNumAll : 总页数、
pageNum : 当前页数、
html_base_url : html文件路径、
-->
<#macro htmlPaging pageNumAll pageNum html_base_url index >
<#if pageNum-1 gte 1>- «
<#else>- «
#if>
<#if pageNum-1 gte 5>
- 1
- 2
- ...
- ${pageNum-2}
- ${pageNum-1}
<#elseif 1 lte (pageNum-1) >
<#list 1..(pageNum-1) as item>
- ${item}
#list>
#if>
- ${pageNum}
<#if pageNumAll-pageNum gte 5>
- ${pageNum+1}
- ${pageNum+2}
- ...
- ${pageNumAll-1}
- ${pageNumAll}
<#elseif (pageNum+1) lte pageNumAll >
<#list (pageNum+1)..pageNumAll as item>
- ${item}
#list>
#if>
<#if pageNum+1 lte pageNumAll>- »
<#else>- »
#if>
#macro>
package com.xxl.core.model.vo;
import java.io.Serializable;
import java.util.List;
@SuppressWarnings("serial")
public class Pager implements Serializable{
private int page; // 入参-第N页
private int pagesize; // 入参-每页长度
@SuppressWarnings("unused")
private int offset; // 起始行号【return this.page-1<0 ? 0 : (this.page-1)*this.rows;】
private List data; // 查询-分页数据
private int total; // 查询-总记录数
@SuppressWarnings("unused")
private int totalPage; // 查询-总页数【return (total + pagesize - 1)/pagesize;】
public Pager(int page, int pagesize){
this.page = page;
this.pagesize = pagesize;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public int getPagesize() {
return pagesize;
}
public void setPagesize(int pagesize) {
this.pagesize = pagesize;
}
public int getOffset() {
return this.page-1<0 ? 0 : (this.page-1)*this.pagesize;
}
public void setOffset(int offset) {
this.offset = offset;
}
public List getData() {
return data;
}
public void setData(List data) {
this.data = data;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public int getTotalPage() {
return (total + pagesize - 1)/pagesize;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
}
// Controller层代码
@RequestMapping("/orderList")
@PermessionType
public String orderList(HttpServletRequest request, Model model,
@RequestParam(required=false, defaultValue="1")int page,
@RequestParam(required=false, defaultValue="20")int pagesize){
Pager pager = new Pager(page, pagesize);
orderService.selectPage(pager, identity);
model.addAttribute("pager", pager);
return "net/order/orderList";
}
// Service层代码
@Override
public void selectPage(Pager pager, LoginIdentity identity) {
List data = orderInfoDao.selectPage(pager.getOffset(), pager.getPagesize(), identity.getUserId());
int total = orderInfoDao.selectPageCount(pager.getOffset(), pager.getPagesize(), identity.getUserId());
pager.setData(data);
pager.setTotal(total);
}
<#if pager?exists && pager.data?exists >
<#list pager.data as item>
${item.orderId}
${item.userId}
${item.prodId}
${item.orderTime?string('yyyy-MM-dd')}
#list>
#if>
<@netCommon.pager pager=pager baseUrl=base_url+'order/orderList.do' />
<#macro pager pager baseUrl>
<#if pager.page gt 1>上页
<#else>上页#if>
<#if pager.page-1 gte 5>
1
2
...
${pager.page-2}
${pager.page-1}
<#elseif 1 lte (pager.page-1) >
<#list 1..(pager.page-1) as item>
${item}
#list>
#if>
${pager.page}
<#if pager.totalPage-pager.page gte 5>
${pager.page+1}
${pager.page+2}
...
${pager.page-1}
${pager.page}
<#elseif (pager.page+1) lte pager.totalPage >
<#list (pager.page+1)..pager.totalPage as item>
${item}
#list>
#if>
<#if pager.page lt pager.totalPage>下页
<#else>下页#if>
#macro>