全站静态化
在大型网站中,比如主流电商商品页,访问者看到的页面基本上是静态页面。为什么都要把页面静态化呢?其实把页面静态化,好处有很多。例如:访问速度快,更有利于搜索引擎收录等。
目前主流的静态化主要有两种:
- 1、纯静态方式:将动态页面抓取并保存为静态页面,页面实际存在于服务器的硬盘中。
- 2、伪静态方式:通过WEB服务器的 URL Rewrite 把外部请求的静态地址转化为实际的动态页面地址,页面本质上是动态页面,静态页面是不存在的。
这两种方法都达到了实现URL静态化的效果,但是也各有各自的特点。
优缺点对比
对比项 | 动态 | 伪静态 | 纯静态 |
---|
网站打开速度 | 中等 | 中等 | 快(无DB查询、CPU计算消耗) |
搜索引擎抓取和索引 | 不利 | 有利 | 有利(速度快,权重高) |
安全性 | 低 | 低 | 高(不适用程序、DB,攻击目标少) |
稳定性 | 低 | 低 | 高(不受程序、DB故障影响) |
硬盘容量 | 低 | 低 | 高(静态化网页文件需要存储在硬盘中) |
服务器压力 | 高 | 高 | 低(无DB查询、CPU计算消耗) |
实时性 | 高 | 高 | 低(数据变更后,需要手动触发静态化;静态化难度随网站复杂度提升而提升) |
Freemarker实现Html全站静态化
1、引入maven依赖
复制
| <dependency> |
| <groupId>org.freemarker</groupId> |
| <artifactId>freemarker</artifactId> |
| <version>{最新稳定版}</version> |
| </dependency> |
复制
2、freemarker工具类
- 1、配置FreeMarkerConfigurer(推荐托管在Spring容器,和复用SpringMVC同一套配置)
| <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> |
| <property name="freemarkerSettings"> |
| <bean class="org.springframework.beans.factory.config.PropertiesFactoryBean"> |
| <property name="location" value="classpath:freemarker.properties" /> |
| </bean> |
| </property> |
| <property name="templateLoaderPath" value="/WEB-INF/template/" /> |
| <property name="freemarkerVariables"> |
| <bean |
| class="org.springframework.beans.factory.config.PropertiesFactoryBean"> |
| <property name="location" value="classpath:freemarker.variables.properties" /> |
| </bean> |
| </property> |
| </bean> |
复制
- 2、引入HtmlTemplateUtil.java文件
| 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; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public class HtmlTemplateUtil { |
| |
| private static FreeMarkerConfigurer freemarkerConfig; |
| |
| public static FreeMarkerConfigurer loadConfig(){ |
| if (freemarkerConfig == null) { |
| freemarkerConfig = (FreeMarkerConfigurer) SpringContentAwareUtil.getBeanByName("freemarkerConfig"); |
| } |
| return freemarkerConfig; |
| } |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
| |
| |
| |
| |
| |
| |
| |
| public static String generateString(Map<?, ?> content, String templateName) { |
| String htmlText = ""; |
| try { |
| |
| Template tpl = loadConfig().getConfiguration().getTemplate(templateName); |
| htmlText = FreeMarkerTemplateUtils.processTemplateIntoString(tpl, content); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| return htmlText; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static void generateFile(Map<?, ?> content, String templatePathName, String filePathName) { |
| Writer out = null; |
| try { |
| |
| File htmlFile = new File(WebPathUtil.webPath() + filePathName); |
| if (!htmlFile.getParentFile().exists()) { |
| htmlFile.getParentFile().mkdirs(); |
| } |
| |
| 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); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| } |
复制
要点:
1、“freemarkerConfig”中的配置“templateLoaderPath”,为include新ftl文件时的根目录“/”位置(省去了繁琐的相对路径配置,相当方便);
2、“freemarkerConfig”必须放在spring中,springMVC和静态化UTIL公用;因为web.xml加载顺序为:web.xml加载顺序,listener(spring) -> filter -> servlet(springMVC);因此,如果放在springMVC中,Service中注入不了该config,静态化UTIL注入时还未初始化bean会报错;
3、freemarker静态化-单页面
复制
| public static void main(String[] args) { |
| String temp = generateString(null, "hello.ftl"); |
| System.out.println(temp); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
复制
4、freemarker静态化-分页
- 1、分页代码:HtmlGenerateServiceImpl.java
| |
| List<WallInfo> wallInfoList = wallInfoDao.getPageList(0, 10000); |
| generateHtmlPagination(wallInfoList, null, 20, "net/wall/index.ftl", "wall/", "index"); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private void generateHtmlPagination(List<?> allList, Map<String, Object> paramMap,int pagesize, String templatePathName , String filePath , String index){ |
| int allCount = allList!=null?allList.size():0; |
| |
| Map<String, Object> params = new HashMap<String, Object>(); |
| 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> |
| <div class="well text-justify">${item.content}</div> |
| </#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 > |
| <ul class="pagination"> |
| |
| <#if pageNum-1 gte 1><li><a href="<@htmlPagingName pageNum=pageNum-1 html_base_url=html_base_url index=index />" >«</a></li> |
| <#else><li class="disabled"><a>«</a></li></#if> |
| |
| <#if pageNum-1 gte 5> |
| <li><a href="<@htmlPagingName pageNum=1 html_base_url=html_base_url index=index />" >1</a></li> |
| <li><a href="<@htmlPagingName pageNum=2 html_base_url=html_base_url index=index />" >2</a></li> |
| <li><a>...</a></li> |
| <li><a href="<@htmlPagingName pageNum=pageNum-2 html_base_url=html_base_url index=index />" >${pageNum-2}</a></li> |
| <li><a href="<@htmlPagingName pageNum=pageNum-1 html_base_url=html_base_url index=index />" >${pageNum-1}</a></li> |
| <#elseif 1 lte (pageNum-1) > |
| <#list 1..(pageNum-1) as item> |
| <li><a href="<@htmlPagingName pageNum=item html_base_url=html_base_url index=index />" >${item}</a></li> |
| </#list> |
| </#if> |
| |
| <li class="active" ><a href="<@htmlPagingName pageNum=pageNum html_base_url=html_base_url index=index />" >${pageNum}</a></li> |
| |
| <#if pageNumAll-pageNum gte 5> |
| <li><a href="<@htmlPagingName pageNum=pageNum+1 html_base_url=html_base_url index=index />" >${pageNum+1}</a></li> |
| <li><a href="<@htmlPagingName pageNum=pageNum+2 html_base_url=html_base_url index=index />" >${pageNum+2}</a></li> |
| <li><a>...</a></li> |
| <li><a href="<@htmlPagingName pageNum=pageNumAll-1 html_base_url=html_base_url index=index />" >${pageNumAll-1}</a></li> |
| <li><a href="<@htmlPagingName pageNum=pageNumAll html_base_url=html_base_url index=index />" >${pageNumAll}</a></li> |
| <#elseif (pageNum+1) lte pageNumAll > |
| <#list (pageNum+1)..pageNumAll as item> |
| <li><a href="<@htmlPagingName pageNum=item html_base_url=html_base_url index=index />" >${item}</a></li> |
| </#list> |
| </#if> |
| |
| <#if pageNum+1 lte pageNumAll><li><a href="<@htmlPagingName pageNum=pageNum+1 html_base_url=html_base_url index=index />" >»</a></li> |
| <#else><li class="disabled"><a>»</a></li></#if> |
| </ul> |
| </#macro> |
复制
补充:freemarker动态分页
| package com.xxl.core.model.vo; |
| |
| import java.io.Serializable; |
| import java.util.List; |
| |
| @SuppressWarnings("serial") |
| public class Pager<T> implements Serializable{ |
| |
| private int page; |
| private int pagesize; |
| @SuppressWarnings("unused") |
| private int offset; |
| private List<T> data; |
| private int total; |
| @SuppressWarnings("unused") |
| private int totalPage; |
| |
| 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<T> getData() { |
| return data; |
| } |
| public void setData(List<T> 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; |
| } |
| |
| } |
复制
- 2、后端分页代码
| |
| @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<OrderInfo> pager = new Pager<OrderInfo>(page, pagesize); |
| orderService.selectPage(pager, identity); |
| |
| model.addAttribute("pager", pager); |
| return "net/order/orderList"; |
| } |
复制
| |
| @Override |
| public void selectPage(Pager<OrderInfo> pager, LoginIdentity identity) { |
| List<OrderInfo> 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); |
| } |
复制
- 3、前端模板
复制
| |
| <#if pager?exists && pager.data?exists > |
| <#list pager.data as item> |
| <tr> |
| <td>${item.orderId}</td> |
| <td>${item.userId}</td> |
| <td>${item.prodId}</td> |
| <td>${item.orderTime?string('yyyy-MM-dd')}</td> |
| </tr> |
| </#list> |
| </#if> |
| |
| <@netCommon.pager pager=pager baseUrl=base_url+'order/orderList.do' /> |
复制
- 4、分页标签,公共模板:net.common.ftl
| <#macro pager pager baseUrl> |
| |
| <#if pager.page gt 1><a href="${baseUrl}?page=${pager.page - 1}" >上页</a> |
| <#else>上页</#if> |
| |
| |
| <#if pager.page-1 gte 5> |
| <a href="${baseUrl}?page=1" >1</a> |
| <a href="${baseUrl}?page=2" >2</a> |
| <a>...</a> |
| <a href="${baseUrl}?page=${pager.page-2}" >${pager.page-2}</a> |
| <a href="${baseUrl}?page=${pager.page-1}" >${pager.page-1}</a> |
| <#elseif 1 lte (pager.page-1) > |
| <#list 1..(pager.page-1) as item> |
| <a href="${baseUrl}?page=${item}" >${item}</a> |
| </#list> |
| </#if> |
| |
| |
| ${pager.page} |
| |
| |
| <#if pager.totalPage-pager.page gte 5> |
| <a href="${baseUrl}?page=${pager.page}" >${pager.page+1}</a> |
| <a href="${baseUrl}?page=${pager.page}" >${pager.page+2}</a> |
| <a>...</a> |
| <a href="${baseUrl}?page=${pager.page}" >${pager.page-1}</a> |
| <a href="${baseUrl}?page=${pager.page}" >${pager.page}</a> |
| <#elseif (pager.page+1) lte pager.totalPage > |
| <#list (pager.page+1)..pager.totalPage as item> |
| <a href="${baseUrl}?page=${pager.page}" >${item}</a> |
| </#list> |
| </#if> |
| |
| |
| <#if pager.page lt pager.totalPage><a href="${baseUrl}?page=${pager.page+1}" >下页</a> |
| <#else>下页</#if> |
| </#macro> |
复制