目录
前言
前面会用轮播图做例子,整篇内容需要实现模板的管理
涉及到的技术
数据库使用MongoDB,静态化模版使用freemark
spring boot+spring mvc
Nginx
把模版文件存入MongoDB gridFS技术
请求远程接口技术 OKHttpClient
MongoDB基础 https://blog.csdn.net/yzj17025693/article/details/89888812
freemark基础 https://blog.csdn.net/yzj17025693/article/details/90479000
rabbitMQ基础 https://blog.csdn.net/yzj17025693/article/details/90739420
页面静态化的简单流程
静态化页面例子(轮播图)
dataUrl是MongoDB里 "cms_page" 文档(表) 的一个字段
存的是一个链接,需要从这个链接读取对应的数据填充到页面里
链接的后面的一串数字,那是id,通过这个id取数据
而这个id是一个叫 "cms_config" 文档(表)的主键
从这里面取出数据图片的地址,因为我们现在要做的页面是轮播图,所以是通过id图片的数据
现在需要静态化轮播图,也就是说,通过freemark的模版+上面的取得的数据 创建一个html页面
需要注意的是就是这个localhost在我的电脑上是Nginx的默认地址,图片是放在Nginx里了
代码实现
创建获取数据的接口
获取数据的controller层
获取数据的service层,只是根据id从cms_config里取出数据而已
获取到了数据之后,就应该创建一个模版index_banner.ftl
只是简简单单的展示一个轮播图的模版,在resources/templates文件夹下,spring boot会自动加载
<!DOCTYPE html>
<html lang=
"en">
<head>
<meta charset=
"UTF-8">
<title>Title</title>
<link rel=
"stylesheet" href=
"http://localhost/plugins/normalize-css/normalize.css" />
<link rel=
"stylesheet"
href=
"http://localhost/plugins/bootstrap/dist/css/bootstrap.css" />
<link rel=
"stylesheet" href=
"http://localhost/css/page-learing-index.css" />
<link rel=
"stylesheet" href=
"http://localhost/css/page-header.css" />
</head>
<body>
<div class=
"banner-roll">
<div class=
"banner-item">
<#if model??>
<#-- value就是图片的地址,访问这个地址,需要把前台页面部署到Nginx里先-->
<#list model as item>
<div class=
"item" style=
"background-image: url(${item.value});"></div>
</#list>
</#if>
<#--上面的循环已经代替了下面-->
<#--
<div class=
"item" style=
"background-image: url(../img/widget-bannerA.jpg);"></div>
<div class=
"item" style=
"background-image: url(../img/widget-banner3.png);"></div>
<div class=
"item" style=
"background-image: url(http://localhost/img/widget-bannerB.jpg);"></div>
<div class=
"item" style=
"background-image: url(../img/widget-bannerA.jpg);"></div>
<div class=
"item" style=
"background-image: url(../img/widget-banner3.png);"></div>
-->
</div>
<div class=
"indicators"></div>
</div>
<script type=
"text/javascript" src=
"http://localhost/plugins/jquery/dist/jquery.js">
</script>
<script type=
"text/javascript"
src=
"http://localhost/plugins/bootstrap/dist/js/bootstrap.js"></script>
<script type="text/javascript">
var tg = $('.banner-item .item');
var num = 0;
for (i = 0; i < tg.length; i++) {
$('.indicators').append('<span></span>');
$('.indicators').find('span').eq(num).addClass('active');
}
function roll() {
tg.eq(num).animate({
'opacity': '1',
'z-index': num
}, 1000).siblings().animate({
'opacity': '0',
'z-index': 0
}, 1000);
$('.indicators').find('span').eq(num).addClass('active').siblings().removeClass('active');
if (num >= tg.length - 1) {
num = 0;
} else {
num++;
}
}
$('.indicators').find('span').click(function() {
num = $(this).index();
roll();
});
var timer = setInterval(roll, 3000);
$('.banner-item').mouseover(function() {
clearInterval(timer)
});
$('.banner-item').mouseout(function() {
timer = setInterval(roll, 3000)
});
</script>
</body>
</html>
把模版和获取的数据整合
上面我们写了获取数据的接口,现在需要在另一个接口里调用另一个接口,可以使用远程调用
当然也可以直接获取数据的service,传入id即可,但是这样会加大耦合性
直接putAll(body),而页面也能获取到数据,是因为spring mvc会把方法参数也一起打包返回给前台
远程调用使用的是OKhttp,而spring mvc提供了RestTemplate接口,所以我们只需要注册对应的类然后返回即可使用
需要引入OKhttp的依赖
静态化页面的标准流程
1 从数据库中取出模版,而不是向上面一样直接把模版放到项目里
2 通过模版的id取模版的模版的数据,数据里存放了模版文件的id
3 通过模版的文件id找到对应的文件,从MongoDB里读取出来
4 再从数据库里取出页面的数据(比如上面的轮播图)
5 把页面数据和模版整合后,通过命令创建一个新的html
取出模版分析
有一个叫 "cms_template" 的文档(表)
通过这个模版的_id 获取到这条数据里的templateField
而模版的_id的获取方式,在测试的时候只能从数据库里复制,预览页面案例里会讲到如何
通过 templateField 可以从这2个文档(表)中获取到对应的数据
这2个表是gridFS(MongoDB的一个工具)生成的,fs.files用于存储文件的元信息,比如文件名,文件创建时间
而fs.chunks是存储文件的二进制数据,使用gridFS存的话,会自动给文件拆分成每个256KB大小的块
gridFS的知识在spring data MongoDB里面有,最上面贴了链接
fs.chunks的表结构
fs.files的表结构,他们是一对多的逻辑关系(非物理关系,因为MongoDB没有外键)
fs.files是一的一方, 因为一个文件可能会被拆分成多个256K的fs.chunks
而fs.chunks的files_id字段就对应着fs.files主键
预览页面案例(需要先看上面的内容)
正式的项目里,比如说cms管理页面的项目,需要预览页面,这时候会有一个表格框
里面显示了很多页面可以管理,有预览页面,编辑页面,删除页面,
那么分页查询这些页面数据到表格框的时候
就会把对应模版的id查出来,这时候再点击预览的时候,就会把模版的id给传输过去
上面的模版id的问题就解决了
假设现在在MongoDB里就有一个文档(表)叫cms_page
里面有模版的id,以及请求数据的url
获取到页面需要的数据
//获取页面的数据
public Map getModelByPageId(String pageId)
{
//通过页面的id,查询页面的数据
CmsPage cmsPage = this.getById(pageId);
//取出请求数据的dataUrl
String dataUrl = cmsPage.getDataUrl();
//如果url找不到,则抛出异常
if(StringUtils.isEmpty(dataUrl)){
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_DATAURLISNULL);
}
//远程获取到数据体
ResponseEntity<Map> forEntity = restTemplate.getForEntity(dataUrl, Map.class);
Map body = forEntity.getBody();
return body;
}
获取到模版
//获取到模版
public 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)){
//页面模板为空异常
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
}
//通过页面模版的id取得模版的信息
Optional<CmsTemplate> optional = cmsTemplateRepository.findById(templateId);
if(optional.isPresent()){
CmsTemplate cmsTemplate = optional.get();
//取得模板文件id
String templateFileId = cmsTemplate.getTemplateFileId();
//通过模版文件id,取出模板文件内容
GridFSFile gridFSFile =
gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(templateFileId)));
//打开下载流对象
GridFSDownloadStream gridFSDownloadStream =
gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
//创建GridFsResource
GridFsResource gridFsResource = new GridFsResource(gridFSFile,gridFSDownloadStream);
try {
String content = IOUtils.toString(gridFsResource.getInputStream(), "utf‐8");
return content;
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
生成静态化页面
//生成静态化页面
public String generateHtml(String template,Map model){
try {
//生成配置类
Configuration configuration = new Configuration(Configuration.getVersion());
//在模板加载器里放入模版
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
stringTemplateLoader.putTemplate("template",template);
//把模板加载器设置到configuration里
configuration.setTemplateLoader(stringTemplateLoader);
//再通过configuration获取到模版,此时我们从数据库里查询出的String类型的模版
//已经变成了ftl格式的模版
Template template1 = configuration.getTemplate("template");
//利用spring提供给FreeMarker的工具类,把数据填充进模版,然后 返回静态页面
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template1, model);
return html;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
整合方法方便controller层调用
//静态化页面
public String getPageHtml(String pageId){
//通过页面id获取页面模型数据
Map model = this.getModelByPageId(pageId);
if(model == null){
//获取页面模型数据为空异常
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_DATAISNULL);
}
//获取页面模板
String templateContent = getTemplateByPageId(pageId);
if(StringUtils.isEmpty(templateContent))
{
//页面模板为空异常
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_TEMPLATEISNULL);
}
//执行静态化
String html = generateHtml(templateContent, model);
if(StringUtils.isEmpty(html)){
//静态化异常
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_HTMLISNULL);
}
return html;
}
controller层
测试的时候直接把页面内容输出即可
记住,此时的数据库里应该是没有数据的,可以把之前测试的ftl上传到数据库里,然后修改一下对应的id
配置Nginx
通过nginx代理进行页面预览,因为前台资源是部署在Nginx里的,这样可以避免权限问题
在conf文件里配置,weight是权重,因为后面还会配置很多的服务器
页面预览前台
前台是部署在独立的一个地方
添加一个页面预览的标签,这里使用的vue.js
此时打开前台的cms管理,点击预览,就会访问Nginx,然后访问到对应的网站静态资源
静态化结合rabbitMQ发布页面
rabbitMQ基础 https://blog.csdn.net/yzj17025693/article/details/90739420
还需要知道rabbitMQ和spring boot的整合
每个服务器监听MQ,并且设置routing key,这个routing key为站点的id
所以我们采用rabbitMQ的路由模式
页面发布流程
点击页面发布之后,取出模版+数据组合成html,再把html存到数据库,html是很多的,而模版是固定的
当打开页面的时候页面的时候,向MQ发送消息,然后从GridFS下载html问题发布到Nginx目录中即可直接打开首页访问
新建client工程(消费方)
spring boot配置文件如下
假如有10台电脑,都是做门户网站的,这个routing key是一样的,但是queue是不一样的
把rabbitConfig类写好来,用于注册交换机,路由,队列,从配置文件读取routing key
@Configuration
public class RabbitmqConfig
{
//队列bean的名称
public static final String QUEUE_CMS_POSTPAGE = "queue_cms_postpage";
//交换机的名称
public static final String EX_ROUTING_CMS_POSTPAGE="ex_routing_cms_postpage";
//队列的名称
@Value("${xuecheng.mq.queue}")
public String queue_cms_postpage_name;
//routingKey 即站点Id
@Value("${xuecheng.mq.routingKey}")
public String routingKey;
/**
* 交换机配置使用direct类型
* @return the exchange
*/
@Bean(EX_ROUTING_CMS_POSTPAGE)
public Exchange EXCHANGE_TOPICS_INFORM()
{
//传入交换机的名称
return ExchangeBuilder.directExchange(EX_ROUTING_CMS_POSTPAGE).durable(true).build();
}
//声明队列
@Bean(QUEUE_CMS_POSTPAGE)
public Queue QUEUE_CMS_POSTPAGE()
{
Queue queue = new Queue(queue_cms_postpage_name);
return queue;
}
/**
* 绑定队列到交换机
*
* @param queue the queue
* @param exchange the exchange
* @return the binding
*/
@Bean
public Binding BINDING_QUEUE_INFORM_SMS(@Qualifier(QUEUE_CMS_POSTPAGE) Queue queue,
@Qualifier(EX_ROUTING_CMS_POSTPAGE) Exchange exchange)
{
//routingKey是站点的id
return BindingBuilder.bind(queue).to(exchange).with(routingKey).noargs();
}
}
把html文件从数据库查询保存到项目的物理路径,也就是Nginx配置的目录下,这样能通过Nginx直接访问
//将页面html保存到页面物理路径
public void savePageToServerPath(String pageId)
{
//通过页面id获取页面的内容
Optional<CmsPage> optional = cmsPageRepository.findById(pageId);
if(!optional.isPresent()){
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
//根据站点的id,查询到站点
CmsPage cmsPage = optional.get();
CmsSite cmsSite = this.getCmsSiteById(cmsPage.getSiteId());
//获取到站点后,取到站点的物理地址+页面的物理地址+页面的名称
//站点的物理地址一般是ip
//页面的物理地址,也就是页面存放的地址
String sitePhysicalPath=cmsSite.getSitePhysicalPath();
if(sitePhysicalPath==null){sitePhysicalPath="";}
String pagePath =sitePhysicalPath+ cmsPage.getPagePhysicalPath() +
cmsPage.getPageName();
//通过Page获取到html的id,这个page只是一个很小的页面
//比如轮播图页面,只有轮播图
String htmlFileId = cmsPage.getHtmlFileId();
//获取到html文件
InputStream inputStream = this.getFileById(htmlFileId);
if(inputStream == null){
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_HTMLISNULL);
}
FileOutputStream fileOutputStream = null;
try {
//把html文件保存到物理路径
fileOutputStream = new FileOutputStream(new File(pagePath));
//将文件内容保存到服务物理路径
IOUtils.copy(inputStream,fileOutputStream);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//根据文件id获取文件内容的流
public InputStream getFileById(String fileId)
{
try {
GridFSFile gridFSFile =
gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
GridFSDownloadStream gridFSDownloadStream =
gridFSBucket.openDownloadStream(gridFSFile.getObjectId());
GridFsResource gridFsResource = new GridFsResource(gridFSFile,gridFSDownloadStream);
return gridFsResource.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Autowired
CmsSiteRepository cmsSiteRepository;
//根据站点id得到站点
public CmsSite getCmsSiteById(String siteId){
Optional<CmsSite> optional = cmsSiteRepository.findById(siteId);
if(optional.isPresent()){
CmsSite cmsSite = optional.get();
return cmsSite;
}
return null;
}
上面GridFSBucket是一个我们自己定义的下载流
@Configuration
public class MongoConfig
{
@Value("${spring.data.mongodb.database}")
String db;
@Bean
public GridFSBucket getGridFSBucket(MongoClient mongoClient){
MongoDatabase database = mongoClient.getDatabase(db);
GridFSBucket bucket = GridFSBuckets.create(database);
return bucket;
}
}
发布者方
点击发布之后,会把ftl文件和数据文件结合成html存放在数据库
这里需要用到之前写的getPageHtml,用于把ftl文件和数据文件结合
//页面发布
public ResponseResult postPage(String pageId)
{
//获取到静态化页面
String pageHtml = this.getPageHtml(pageId);
if(StringUtils.isEmpty(pageHtml)){
ExceptionCast.cast(CmsCode.CMS_GENERATEHTML_HTMLISNULL);
}
//保存静态化文件到MongoDB
CmsPage cmsPage = saveHtml(pageId, pageHtml);
//发送消息
sendPostPage(pageId);
return new ResponseResult(CommonCode.SUCCESS);
}
@Autowired
RabbitTemplate rabbitTemplate;
//发送页面发布消息
private void sendPostPage(String pageId){
CmsPage cmsPage = this.getById(pageId);
if(cmsPage == null){
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
Map<String,String> msgMap = new HashMap<>();
msgMap.put("pageId",pageId);
//消息内容
String msg = JSON.toJSONString(msgMap);
//获取站点id作为routingKey
String siteId = cmsPage.getSiteId();
//发布消息
this.rabbitTemplate.convertAndSend(config.RabbitmqConfig.EX_ROUTING_CMS_POSTPAGE,siteId, msg);
}
//保存静态页面内容
private CmsPage saveHtml(String pageId,String content){
//查询页面
Optional<CmsPage> optional = cmsPageRepository.findById(pageId);
if(!optional.isPresent()){
ExceptionCast.cast(CmsCode.CMS_PAGE_NOTEXISTS);
}
CmsPage cmsPage = optional.get();
//存储之前先删除
String htmlFileId = cmsPage.getHtmlFileId();
if(StringUtils.isNotEmpty(htmlFileId)){
gridFsTemplate.delete(Query.query(Criteria.where("_id").is(htmlFileId)));
}
//把html内容转换到流
//保存html文件到GridFS
InputStream inputStream = IOUtils.toInputStream(content);
ObjectId objectId = gridFsTemplate.store(inputStream, cmsPage.getPageName());
//存储之后获取到文件html文件id,然后把文件id放到page里
String fileId = objectId.toString();
//将文件id存储到cmspage中
cmsPage.setHtmlFileId(fileId);
cmsPageRepository.save(cmsPage);
return cmsPage;
}