Freemarker静态化模板语法用法参考:https://editor.csdn.net/md/?articleId=102540054
——————————————————————————
静态页面的生成步骤图解:
1.获取,页面数据模型的数据
1.1:先定义获取,数据模型的接口:
//静态化页面相关所需要的信息
@Api(value="cms配置管理接口",description = "cms配置管理接口,提供数据模型的管理、查询接口")
public interface CmsConfigControllerApi {
@ApiOperation("根据id查询CMS配置信息")
public CmsConfig getmodel(String id);
}
1.2:定义访问Cms_Config数据库的dao接口:
//Cms页面配置信息的dao
public interface CmsConfigRepository extends MongoRepository<CmsConfig,String> {
}
1.3:在PageService中定义Cms_ConfigService实现根据id查询CmsConfig信息
//###单查Cms_config数据库的数据模型的方法
public CmsConfig getConfigById(String id){
Optional<CmsConfig> optional = cmsConfigRepository.findById(id);
if (optional.isPresent()){
return optional.get();
}
return null;
}
1.4:定义取数据模型的controller
//取cms模型数据的接口实现,接口在api的config中
@RestController
//这个远程调用的数据模型的方法,是配置好服务的的nginx.exe调用
@RequestMapping("/cms/config")
public class CmsConfigController {
@Autowired
private PageService pageService;
//取得cms配置页面信息
@GetMapping("/getmodel/{id}")
public CmsConfig getmodel(@PathVariable String id){
return pageService.getConfigById(id);
}
}
1.5:再配置类中添加OkHttp3Client,无头浏览器请求插件,方便远程调用数据模型
//往IOC容易中注入,用OkHttp3Client请求插件,用于请求页面模型数据
@Bean
public RestTemplate restTemplate(){
return new RestTemplate(new OkHttp3ClientHttpRequestFactory()) ;
}
2.获取,GridFS介绍,模板文件,转换为String格式
2.1:GridFS简介:
GridFS是MongoDB提供的用于持久化存储文件的模块,CMS使用MongoDB存储数据,使用GridFS可以快速集成
开发。
它的工作原理是:
在GridFS存储文件是将文件分块存储,文件会按照256KB的大小分割成多个块进行存储,GridFS使用两个集合
(collection)存储文件,一个集合是chunks, 用于存储文件的二进制数据;一个集合是files,用于存储文件的元数
据信息(文件名称、块大小、上传时间等信息)。
从GridFS中读取文件要对文件的各各块进行组装、合并。
官网地址:https://docs.mongodb.com/manual/core/gridfs/
存文件的示例代码:
//理解为上传器组件
@Autowired
GridFsTemplate gridFsTemplate;
@Test
public void cun(){
//要存文件的绝对路径,
File file = new File("D:\\Code\\xcEdu\\xcEduService01\\xc-service-manage-cms\\src\\test\\java\\com\\xuecheng\\manage_cms\\dao\\index_banner.ftl");
try {
//把绝对路径,塞进输入流
FileInputStream inputStream = new FileInputStream(file);
//通过store方法,把绝对路径中的,具体文件,存进GridFS库中,并且返回一个存入文件的id
ObjectId objectId = gridFsTemplate.store(inputStream, "index_banner.ftl");
System.err.println(objectId);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
存储原理说明:
文件存储成功得到一个文件id
此文件id是fs.files集合中的主键。
可以通过文件id查询fs.chunks表中的记录,得到文件的内容。
取文件的示例代码:
1)在config包中定义Mongodb的配置类,如下:
需要先在配置文件中,配置数据库路径:
GridFSBucket用于打开下载流对象
freemarker:
cache: false #关闭模板缓存,方便测试
settings:
template_update_delay: 0
//mongo数据库的配置类
@Configuration
public class MongoConfig {
//取配置文件中的mongodb数据库路径和相应参数
@Value("${spring.data.mongodb.database}")
String db;
//再相应的数据库中创建一个GridFS下载器
@Bean
public GridFSBucket getGridFSBucket(MongoClient mongoClient){
//通过请求路径指向一个数据库
MongoDatabase database = mongoClient.getDatabase(db);
//再指向的数据库中,创建可操作的grid文件管理器的数据库对象
GridFSBucket bucket = GridFSBuckets.create(database);
return bucket;
}
}
示例代码:
// 获取页面的模板信息的方法
private String getTemplateByPageId(String pageId) {
//根据页面id,取页面对象
CmsPage cmsPage = this.getById(pageId);
if (cmsPage == null) {
//为空,抛异常,页面不存在
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
//从页面信息中拿模板的id
String templateId = cmsPage.getTemplateId();
if (StringUtils.isEmpty(templateId)){
//如果模板id属性为空,就抛异常,页面模板为空
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
}
//通过模板信息管理类,查询模板信息
Optional<CmsTemplate> optional = cmsTemplateRepository.findById(templateId);
if (optional.isPresent()){
//如果有数据,就把模板数据取出来
CmsTemplate cmsTemplate = optional.get();
//再取模板文件的id
String templateFileId = cmsTemplate.getTemplateFileId();
//根据模板文件的id,查询gridfs文件管理器里面的GridFSFile文件对象
//通过,模板id,去fs.files中的”——id“的列中查找,ftl文件名对应fs.chunks集合表中的流数据
GridFSFile gridFSFile = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(templateFileId)));
//通过配置类中,配置好的下载器。去下载这个GridFSFile文件对象
// 解释:开启一个下载流,通过grid文件id,去下载
GridFSDownloadStream gridFSDownloadStream =
gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
//创建GridFsResource对象,获取流,
// 参数1:文件管理器的路径,参数2:配置好的下载流
GridFsResource gridFsResource =
new GridFsResource(gridFSFile,gridFSDownloadStream);
从流中取数据,把流文件用utf-8 的字节解析为字符串
try {
String content = IOUtils.toString(gridFsResource.getInputStream(), "utf-8");
//返回字符串页面
return content;
} catch (IOException e) {
e.printStackTrace();
}
} //取模型页面完成
//如果查询到的模板信息为空,就不去取模板
return null;
}
3、删除文件,了解:项目中没有使用
@Test
public void testDelFile() throws IOException {
//根据文件id删除fs.files和fs.chunks中的记录
gridFsTemplate.delete(Query.query(Criteria.where("_id").is("5b32480ed3a022164c4d2f92")));
}
3.组装静态化页面:有了模型数据+ftl的String页面,就开始组装,生成页面:
3.1最终组装页面的全部代码:
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile;
import com.xuecheng.framework.domain.cms.CmsConfig;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.framework.domain.cms.CmsTemplate;
import com.xuecheng.framework.domain.cms.request.QueryPageRequest;
import com.xuecheng.framework.domain.cms.response.CmsCode;
import com.xuecheng.framework.domain.cms.response.CmsPageResult;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.QueryResponseResult;
import com.xuecheng.framework.model.response.QueryResult;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.manage_cms.dao.CmsConfigRepository;
import com.xuecheng.manage_cms.dao.CmsPageRepository;
import com.xuecheng.manage_cms.dao.CmsTemplateRepository;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
@Service
public class PageService {
@Autowired
private CmsPageRepository cmsPageRepository;
//获取模型数据的,对象。
@Autowired
private RestTemplate restTemplate;
//cms_Template数据库的dao
@Autowired
private CmsTemplateRepository cmsTemplateRepository;
//cms配置页面的模型数据查询dao
@Autowired
private CmsConfigRepository cmsConfigRepository;
//再jar包中有,存ftl文件的上传器
@Autowired
GridFsTemplate gridFsTemplate;
//再mongoConfig,类中配置的
@Autowired
private GridFSBucket gridFSBucket;
/**
* 页面列表分页查询
* @param page 当前页码
* @param size 页面显示个数
* @param queryPageRequest 查询条件
* @return 页面列表
*/
public QueryResponseResult findList(int page, int size, QueryPageRequest queryPageRequest) {
//判断传进来的页面,小于第一页,就默认查询第一页
if (page <= 0){
page = 1;
}
//如果传进来的不是小于0,证明页面正确,就-1,因为数据库是从0开始,
page = page - 1;
//判断传进来的显示数量,小于10,就默认查询10条
if (size <= 0){
size = 10;
}
//组装查询所需要的页码和数量,的pageable对象
Pageable pageable = PageRequest.of(page,size);
//__________分页信息组装好后,开始组装条件查询_____________
//**开始**提前准备一个空的条件查询对象
Example<CmsPage> example = null;
//****先判断,条件查询的对象是否为空,不为空,才开始组装查询条件****
if (queryPageRequest!=null){
//先准备一个cms分页对象。
CmsPage cmsPage = new CmsPage();
//如果站点id,不为空,就组装cms对象
String siteId = queryPageRequest.getSiteId();
if (siteId!=null && !siteId.equals("")){
cmsPage.setSiteId(siteId);
}
//如果站点模板id,不为空,就组装cms对象
String TemplateId = queryPageRequest.getTemplateId();
if (TemplateId!=null && !TemplateId.equals("")){
cmsPage.setTemplateId(TemplateId);
}
//如果别名,不为空,就组装cms对象
String PageAliase = queryPageRequest.getPageAliase();
if (PageAliase!=null && !PageAliase.equals("")){
cmsPage.setPageAliase(PageAliase);
}
//别名属于模糊查询,所以需要,设置模糊条件查询匹配器
//ExampleMatcher.GenericPropertyMatchers.contains() 包含关键字
//ExampleMatcher.GenericPropertyMatchers.startsWith() 前缀匹配
ExampleMatcher exampleMatcher = ExampleMatcher.matching()
//链式点出,通过关键字匹配,数据库中的PageAliase列
.withMatcher("pageAliase",ExampleMatcher.GenericPropertyMatchers.contains());
//把组装好的cms条件查询对象和模糊条件匹配器,
// 添加进条件查询的静态方法中。得到一个条件查询对象
example = Example.of(cmsPage,exampleMatcher);
}
//********分页信息组装好后,开始组装条件查询结束********
//执行查询,参数1,条件查询的对象,参数2,分页参数值对象
Page<CmsPage> cmsPagePageList = cmsPageRepository.findAll(example,pageable);
//封装分页数据集合和总记录数的对象
QueryResult queryResult = new QueryResult();
//得到分页数据集合
queryResult.setList(cmsPagePageList.getContent());
// 得到总记录数
queryResult.setTotal(cmsPagePageList.getTotalElements());
//封装返回到页面的固定格式对象
QueryResponseResult queryResponseResult =
new QueryResponseResult(CommonCode.SUCCESS,queryResult);
return queryResponseResult;
}
//_________新增页面方法,
public CmsPageResult add(CmsPage cmsPage){
if (cmsPage == null) {
//证明进来的参数没有,也就是参数异常
ExceptionCast.cast(CommonCode.INVALID_PARAM);
}
//进来的对象,不为空,就执行校验
CmsPage page = cmsPageRepository.findByPageNameAndPageWebPathAndSiteId
(cmsPage.getPageName(), cmsPage.getPageWebPath(), cmsPage.getSiteId());
if (page == null) {
//如果校验没有这个页面,就执行添加页面的操作
//添加页面之前,先把页面id,清除,让MongoDB数据库自己生成主键
cmsPage.setPageId(null);
//执行添加页面的操作
CmsPage cmsPage1 = cmsPageRepository.save(cmsPage);
//返回resultful规范数据
return new CmsPageResult(CommonCode.SUCCESS,cmsPage1);
}
//如果校验到了,就直接不让新增
ExceptionCast.cast(CmsCode.CMS_ADDPAGE_EXISTSNAME);
//返回一个null,防止语法报错,前面的异常会拦截,下面是不可达代码
return null;
}
//修改操作,包括,数据单查回显,数据修改2个方法
//单查回显
public CmsPage getById(String id){
//单查取得optional对象
Optional<CmsPage> byId = cmsPageRepository.findById(id);
//判断optional对象不为空就取cms页面,进行返回
if (byId.isPresent()) {
CmsPage cmsPage = byId.get();
return cmsPage;
}
return null;
}
//数据修改
public CmsPageResult edit(String id,CmsPage cmsPage){
//先查询通过id要修改的对象
CmsPage one = getById(id);
//手动设置其中的属性,原因在于,有些列不允许修改
if(one!=null){
//准备更新数据
//设置要修改的数据
//更新模板id
one.setTemplateId(cmsPage.getTemplateId());
//更新所属站点
one.setSiteId(cmsPage.getSiteId());
//更新页面别名
one.setPageAliase(cmsPage.getPageAliase());
//更新页面名称
one.setPageName(cmsPage.getPageName());
//更新访问路径
one.setPageWebPath(cmsPage.getPageWebPath());
//更新物理路径
one.setPagePhysicalPath(cmsPage.getPagePhysicalPath());
//更新静态模板数据的url
one.setDataUrl(cmsPage.getDataUrl());
//提交修改
cmsPageRepository.save(one);
return new CmsPageResult(CommonCode.SUCCESS,one);
}
//如果没有找到cms对象,就返回失败
return new CmsPageResult(CommonCode.FAIL,null);
}
//删除cms页面
public ResponseResult delete(String id){
//先查询有没有这个对象
Optional<CmsPage> optional = cmsPageRepository.findById(id);
if (optional.isPresent()){//不是空的就返回true,执行删除页面
cmsPageRepository.deleteById(id);
return new ResponseResult(CommonCode.SUCCESS);
}
//返回删除失败
return new ResponseResult(CommonCode.FAIL);
}
//###单查Cms_config数据库的数据模型的方法
public CmsConfig getConfigById(String id){
Optional<CmsConfig> optional = cmsConfigRepository.findById(id);
if (optional.isPresent()){
return optional.get();
}
return null;
}
//页面静态化方法
/**
* 静态化程序获取页面的DataUrl
*
* 静态化程序远程请求DataUrl获取数据模型。
*
* 静态化程序获取页面的模板信息
*
* 执行页面静态化
*/
public String getPageHtml(String pageId){
//取数据模型对象
Map model= this.getModelByPageId(pageId);
if (model==null){
//数据模型获取不到
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_DATAISNULL);
}
//获取页面的模板信息
String template = getTemplateByPageId(pageId);
//有这个模板,就执行静态化模板,生成新的Html,
// 参数1,数据模型对象,参数2,静态模板
String html = this.generateHtml(template, model);
//返回组装好的静态页面
return html;
}
//执行静态化,通过页面模板和数据模型,生成
private String generateHtml(String templateContent,Map model){
//创建ramework配置对象
Configuration configuration = new Configuration(Configuration.getVersion());
//创建ramework模板加载器
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
//往模板加载器中,加入名为:template ,的html模板字符串
stringTemplateLoader.putTemplate("template",templateContent);
//向configuration中,配置模板加载器
configuration.setTemplateLoader(stringTemplateLoader);
//获取字符串静态模板,调用api,和数据模型进行组装得到静态化页面
try {
Template template = configuration.getTemplate("template");
//组装
String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
return content;
} catch (Exception e) {//异常改为Exception
e.printStackTrace();
}
//如果组装失败,返回null
return null;
}
// 获取页面的模板信息的方法
private String getTemplateByPageId(String pageId) {
//根据页面id,取页面对象
CmsPage cmsPage = this.getById(pageId);
if (cmsPage == null) {
//为空,抛异常,页面不存在
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
//从页面信息中拿模板的id
String templateId = cmsPage.getTemplateId();
if (StringUtils.isEmpty(templateId)){
//如果模板id属性为空,就抛异常,页面模板为空
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
}
//通过模板信息管理类,查询模板信息
Optional<CmsTemplate> optional = cmsTemplateRepository.findById(templateId);
if (optional.isPresent()){
//如果有数据,就把模板数据取出来
CmsTemplate cmsTemplate = optional.get();
//再取模板文件的id
String templateFileId = cmsTemplate.getTemplateFileId();
//根据模板文件的id,查询gridfs文件管理器里面的GridFSFile文件对象
//通过,模板id,去fs.files中的”——id“的列中查找,ftl文件名对应fs.chunks集合表中的流数据
GridFSFile gridFSFile = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(templateFileId)));
//通过配置类中,配置好的下载器。去下载这个GridFSFile文件对象
// 解释:开启一个下载流,通过grid文件id,去下载
GridFSDownloadStream gridFSDownloadStream =
gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
//创建GridFsResource对象,获取流,
// 参数1:文件管理器的路径,参数2:配置好的下载流
GridFsResource gridFsResource =
new GridFsResource(gridFSFile,gridFSDownloadStream);
从流中取数据,把流文件用utf-8 的字节解析为字符串
try {
String content = IOUtils.toString(gridFsResource.getInputStream(), "utf-8");
//返回字符串页面
return content;
} catch (IOException e) {
e.printStackTrace();
}
} //取模型页面完成
//如果查询到的模板信息为空,就不去取模板
return null;
}
//通过cms页面id,得到页面数据模型,封装为map
private Map getModelByPageId(String pageId){
// 得到页面,判断是否有,没有就抛异常
CmsPage cmsPage = this.getById(pageId);
if (cmsPage == null) {
//抛出没有这个页面数据的异常
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
//有这个页面,就取Dataurl
String dataUrl = cmsPage.getDataUrl();
//判断地址为空,抛异常,获取不到url的异常
if (StringUtils.isEmpty(dataUrl)){
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_DATAISNULL);
}
//通过restTemplate请求dataUrl获取数据
ResponseEntity<Map> forEntity = restTemplate.getForEntity(dataUrl, Map.class);
// 得到模型数据后,转换为json格式封装到另一个map中。
Map body = forEntity.getBody();
//返回json格式的模板信息Map
return body;
}
}
3.2:页面预览访问的Controller,包含所有预览流程
/**
* @author Administrator
* @version 1.0
* @create 2018-09-15 16:20
**/
//由于要向浏览器输出,html静态页面,是字符串,而不是json数据格式,所以用@controller
@Controller
public class CmsPagePreviewController extends BaseController {
@Autowired
PageService pageService;
//页面预览
@RequestMapping(value="/cms/preview/{pageId}",method = RequestMethod.GET)
public void preview(@PathVariable("pageId") String pageId) throws IOException {
//执行静态化
String pageHtml = pageService.getPageHtml(pageId);
//通过response对象,得到输出流
ServletOutputStream outputStream = response.getOutputStream();
//输出流,把页面用utf-8字符,输出到浏览器
outputStream.write(pageHtml.getBytes("utf-8"));
}
}
4.2 页面预览测试
配置Nginx代理,的config文件,配置服务
为了通过nginx请求静态资源(css、图片等),通过nginx代理进行页面预览。
#cms页面预览,的cms服务节点配置
upstream cms_server_pool{
server 127.0.0.1:31001 weight=10;
#并发量多,后面就可以添加类似的节点,达到负载均衡,server 127.0.0.1:31001 weight=10;
}
#页面预览服务
location /cms/preview/ {
proxy_pass http://cms_server_pool/cms/preview/;
}