栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【Spring Cloud】新闻头条微服务项目:FreeMarker模板引擎实现文章静态页面生成

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap

【Spring Cloud】新闻头条微服务项目:FreeMarker模板引擎实现文章静态页面生成

 

个人简介: 

> 个人主页:赵四司机
> 学习方向:JAVA后端开发 
> 种一棵树最好的时间是十年前,其次是现在!
> ⏰往期文章:SpringBoot项目整合微信支付
> 李喜欢的话麻烦点点关注喔,你们的支持是我的最大动力。

前言:

最近在做一个基于SpringCloud+Springboot+Docker的新闻头条微服务项目,所用教程为黑马的教程,现在项目开发进入了尾声,我打算通过写文章的形式进行梳理一遍,并且会将梳理过程中发现的Bug进行修复,有需要改进的地方我也会继续做出改进。这一系列的文章我将会放入微服务项目专栏中,这个项目适合刚接触微服务的人作为练手项目,假如你对这个项目感兴趣你可以订阅我的专栏进行查看,需要资料可以私信我,当然要是能给我点个小小的关注就更好了,你们的支持是我最大的动力。

目录

一:文章列表加载

1.需求分析

2.表结构分析

3.功能实现

(1)项目搭建

(2)Nacos配置

(3)代码实现

二:FreeMarker引入

1.概述

 2.需求分析

3.代码实现

(1)模板信息

 (2)业务层实现


一:文章列表加载

1.需求分析

        用户成功登录之后,首先会加载的是推荐频道的文章,文章按封面又分为四种类型,分别为无图文章、单图文章、双图文章、三图文章。当然用户还可以选择其他频道进行浏览,并且可以点击文章查看详情。除此之外,用户下拉操作还会进行文章列表刷新,上拉操作会执行加载更多动作。

 

2.表结构分析

        数据库表的设计分成了三张表,分别为文章信息表,文章配置表,文章内容表。至于为什么要拆分成三张表而不是用一张表来存储,这是因为文章的内容信息一般会比较长,这时候放在一张表那么无论什么时候都会去数据库中获取所有数据进行返回,但是有时候用户只是在首页进行访问,并没有点击文章进去查看,这时候假如是一张表结构的话用户再首页不断滑动也到数据库中获取文章的全部内容,这样显然是很费IO的,特别是获取文章内容这种数据量较大的数据。那么这时候设计成三张表就很合理了,只要用户不点击文章我就不需要去数据库中加载文章详细信息,我只需要加载简单的标题、封面、作者等信息即可,减少了数据库的压力。三张表结构如下:

①ap_article文章信息表:

②ap_article_config:文章配置表:

③ap_article_content文章内容表:

3.功能实现

(1)项目搭建

在tbug-headlines-service下创建tbug-headlines-article工程作为子工程,项目结构如下:

(2)Nacos配置

在Nacos添加如下配置

 配置文件:

spring:
  redis:
    host: 49.234.52.192
    password: 440983
    port: 6379
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/headlines_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
    username: root
    password: 440983
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
  mapper-locations: classpath*:mapper
    ResponseResult load(Short loadtype, ArticleHomeDto dto);

}

实现类:

package com.my.article.service.serviceImpl;


import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.my.article.mapper.ApArticleMapper;
import com.my.article.service.ApArticleService;
import com.my.common.constans.ArticleConstas;
import com.my.model.article.dtos.ArticleHomeDto;
import com.my.model.article.pojos.ApArticle;
import com.my.model.common.dtos.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;

@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl implements ApArticleService {
    @Autowired
    private ApArticleMapper apArticleMapper;

    //单页最大加载文章数
    private final static short MAX_ARTICLE_SIZE = 50;

    
    @Override
    public ResponseResult load(Short loadtype, ArticleHomeDto dto) {
        //1.参数校验
        //1.1校验单页加载文章数
        Integer size = dto.getSize();
        if(size == null || size == 0) {
            log.warn("未设置单页加载文章数");
            size = 10;
        }
        size = Math.min(size, MAX_ARTICLE_SIZE);
        dto.setSize(size);

        //1.2类型参数校验
        if(!loadtype.equals(ArticleConstas.LOADTYPE_LOAD_MORE) && !loadtype.equals(ArticleConstas.LOADTYPE_LOAD_NEW)) {
            loadtype = ArticleConstas.LOADTYPE_LOAD_MORE;
        }
        //1.3文章频道校验
        if(dto.getTag().isEmpty()) {
            dto.setTag(ArticleConstas.DEFAULT_TAG);
        }
        //前端Bug校正(tag=0时候表示“其他”频道,id为7)
        if(dto.getTag().equals("0")) {
            dto.setTag("7");
        }

        //1.4时间校验
        if(dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
        if(dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());

        //2.查询文章
        List articleList = apArticleMapper.loadArticleList(dto, loadtype);
        //3.返回结果
        return ResponseResult.okResult(articleList);
    }
}

controller层:

package com.my.article.controller.v1;

import com.my.article.service.ApArticleService;
import com.my.common.constans.ArticleConstas;
import com.my.model.article.dtos.ArticleHomeDto;
import com.my.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {
    @Autowired
    private ApArticleService apArticleService;

    
    @PostMapping("/load")
    public ResponseResult load(@RequestBody ArticleHomeDto dto){
        // return apArticleService.load(ArticleConstas.LOADTYPE_LOAD_MORE,dto);
        return apArticleService.load2(dto,ArticleConstas.LOADTYPE_LOAD_MORE,true);
    }

    
    @PostMapping("/loadmore")
    public ResponseResult loadmore(@RequestBody ArticleHomeDto dto){
        return apArticleService.load(ArticleConstas.LOADTYPE_LOAD_MORE,dto);
    }

    
    @PostMapping("/loadnew")
    public ResponseResult loadnew(@RequestBody ArticleHomeDto dto){
        return apArticleService.load(ArticleConstas.LOADTYPE_LOAD_NEW,dto);
    }
}

二:FreeMarker引入

1.概述

        FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

        模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。关于FreeMarker的基础使用我这里就不做介绍了,你可以查看我这篇文章:freemarker模板引擎的常用命令介绍

 2.需求分析

        当自媒体创作者在创作端创作好文章并审核通过后通过Kafka将文章信息发送给article微服务进行保存,这时候article微服务就需要利用FreeMarker模板引擎将文章对象转换成html静态页面并上传至MinIO,至于什么是MinIO我将在下一篇文章介绍。这样用户点击文章查看详情时候就能直接从MinIO中获取静态页面进行展示。

3.代码实现

(1)模板信息

在tbug-headlines-article微服务的resource包下创建templates包并添加下面文件





    
    
    
    土豆头条
    
    
    
    




    
        
            
        

        
            
                
            
            
                
                {{ publishTime | timestampToDateTime }}
            
            
                
                
            
        

        
            <#if content??>
                <#list content as item>
                    <#if item.type='text'>
                        ${item.value}
                    <#else>
                        
                            
                        
                    
                
            
        

        
            
                
                不喜欢
            
        

        
        
            
                
                    
                
                
                    
                        
                        
                            {{ item.likes || '' }}
                            
                        
                    

                    
                        
                    
                    
                        
                            {{ item.createdTime | timestampToDateTime }}
                        
                        
                            回复 {{
                                item.reply || '' }}
                            
                        
                    
                
            
        
    
    
    
        
            
                
            
        
        
            
        
        
            
        
        
            
        
    

    
    
        
        
            
                
                    
                
                
                    
                        
                        
                            {{ item.likes || '' }}
                            
                        
                    

                    
                        
                    
                    
                        
                        
                            {{ item.createdTime | timestampToDateTime }}
                        
                    
                
            
        
        
        
            
                
                    
                
            
            
                
            
            
                
            
            
                
            
        
    







<#---->




 (2)业务层实现
package com.my.article.service;


import com.my.model.article.pojos.ApArticle;

public interface ArticleFreemarkerService {

    
    public void buildArticleToMinIO(ApArticle apArticle, String content);
}
package com.my.article.service.serviceImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.my.article.mapper.ApArticleContentMapper;
import com.my.article.mapper.ApArticleMapper;
import com.my.article.service.ArticleFreemarkerService;
import com.my.common.constans.ArticleConstas;
import com.my.file.service.FileStorageService;
import com.my.model.article.pojos.ApArticle;
import com.my.model.article.pojos.ApArticleContent;
import com.my.model.search.vos.SearchArticleVo;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

@Slf4j
@Service
@Transactional
public class ArticleFreemarkerServiceImpl implements ArticleFreemarkerService {
    @Autowired
    private Configuration configuration;

    @Autowired
    private FileStorageService fileStorageService;

    @Autowired
    private ApArticleMapper apArticleMapper;

    
    @Override
    @Async
    public void buildArticleToMinIO(ApArticle apArticle, String apArticleContent) {
        StringWriter out = new StringWriter();
        if(StringUtils.isNotBlank(apArticleContent)){
            try {
                if(apArticle.getStaticUrl() != null) {
                    //删除原来Url
                    fileStorageService.delete(apArticle.getStaticUrl());
                    log.info("成功删除原来的html静态文件");
                }
                log.info("开始生成新的静态html文件...");
                //1.文章内容通过freemarker生成html文件
                Template template = configuration.getTemplate("article.ftl");

                Map params = new HashMap<>();
                params.put("content", JSONArray.parseArray(apArticleContent));

                template.process(params, out);
            } catch (Exception e) {
                e.printStackTrace();
            }

            //2.把html文件上传到minio中
            InputStream is = new ByteArrayInputStream(out.toString().getBytes());
            String path = fileStorageService.uploadHtmlFile("", apArticle.getId() + ".html", is);
            log.info("将html文件上传到minio中:{}",path);

            //3.修改ap_article表,保存static_url字段
            ApArticle article = new ApArticle();
            article.setId(apArticle.getId());
            article.setStaticUrl(path);
            apArticleMapper.updateById(article);

            //4.创建索引,发送消息
            createArticleESIndex(article,apArticleContent,path);
        }
    }

    @Autowired
    private KafkaTemplate kafkaTemplate;
    
    private void createArticleESIndex(ApArticle article, String apArticleContent, String path) {
        SearchArticleVo searchArticleVo = new SearchArticleVo();
        BeanUtils.copyProperties(apArticleContent,searchArticleVo);
        searchArticleVo.setContent(apArticleContent);
        searchArticleVo.setStaticUrl(path);

        //发送消息
        kafkaTemplate.send(ArticleConstas.ARTICLE_ES_SYNC_TOPIC, JSON.toJSONString(searchArticleVo));
    }
}

下篇预告:MinIO实现文章静态页面存取

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1041296.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号