静态化页面解决方案

目录

页面静态化的简单流程

静态化页面例子(轮播图)

把模版和获取的数据整合

静态化页面的标准流程

预览页面案例(需要先看上面的内容)

获取到页面需要的数据

获取到模版

生成静态化页面

controller层

配置Nginx

页面预览前台

静态化结合rabbitMQ发布页面

页面发布流程


前言

前面会用轮播图做例子,整篇内容需要实现模板的管理

涉及到的技术

数据库使用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;
    }
发布了143 篇原创文章 · 获赞 36 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/yzj17025693/article/details/90733679