(eblog)4、自定义 Freemaker 标签实现博客首页数据填充

3,356 阅读5分钟

公众号:MarkerHub(关注获取更多项目资源)

eblog 代码仓库:github.com/markerhub/e…

eblog 项目视频: www.bilibili.com/video/BV1ri…


开发文档目录:

(eblog)1、项目架构搭建、首页页面初始化

(eblog)2、整合Redis,以及项目优雅的异常处理与返回结果封装

(eblog)3、用Redis的zset有序集合实现一个本周热议功能

(eblog)4、自定义 Freemaker 标签实现博客首页数据填充

(eblog)5、博客分类填充、登录注册逻辑

(eblog)6、博客发布收藏、用户中心的设置

(eblog)7、消息异步通知、细节调整

(eblog)8、博客搜索引擎开发、后台精选

(eblog)9、即时群聊开发,聊天记录等


前后端分离项目vueblog请点击这里:超详细!4小时开发一个SpringBoot+vue前后端分离博客项目!!


自定义 freemarker 标签

首先要来弄的是置顶的数据。为了让大家能对 freemarker 有更多的了解,我们定义一个 freemarker 标签,然后使用标签去展示我们的数据。

想要自定义 freemarker 标签需要实现这个接口并且重写 excute 方法:

public interface TemplateDirectiveModel extends TemplateModel {
  public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException;
}

上面的参数如下:

  • env:系统环境变量,通常用它来输出相关内容,如 Writer out = env.getOut()。

  • params:自定义标签传过来的对象,其 key = 自定义标签的参数名,value 值是 TemplateModel 类型,而 TemplateModel 是一个接口类型,通常我们都使用 TemplateScalarModel 接口来替代它获取一个 String 值,如 TemplateScalarModel.getAsString(); 当然还有其它常用的替代接口,如 TemplateNumberModel 获取 number,TemplateHashModel 等。

  • loopVars  循环替代变量。

  • body 用于处理自定义标签中的内容,如 <@myDirective> 将要被处理的内容;当标签是<@myDirective /> 格式时,body=null。

但是直接这些参数来处理我们业务其实还有点麻烦,为了更好使用这个接口,我们需要再封装多一层,这里我引用了 mblog 项目的对这个接口的封装。

com.example.common.template

  • DirectiveHandler

  • TemplateDirective

  • TemplateModelUtils

上面的 3 个类我都是在 mblog 项目中复制过来的,就改了一下包路径

mblog 原路径:

gitee.com/mtons/mblog…

有了上面的封装之后,我们要定义个标签模板只需要继承 TemplateDirective,重写 getName()和 excute(DirectiveHandler handler);可以看到 excute 方法中之后 handler 了,这个 handler 我们使用起来就很简便了。

下面我们来定义一个博客列表的标签 com.example.templates.PostsTemplate

@Component
public class PostsTemplate extends TemplateDirective {
    @Autowired
    PostService postService;
    @Override
    public String getName() {
        return "posts";
    }
    @Override
    public void execute(DirectiveHandler handler) throws Exception {
        Long categoryId = handler.getLong("categoryId", 0);
        int pn = handler.getInteger("pn", 1);
        int size = handler.getInteger("size", 10);
        String order = handler.getString("order", "created");
        Page page = new Page(pn, size);
        IPage results = postService.paging(page, categoryId, order);
        handler.put(RESULTS, results).render();
    }
}

上面的代码可以知道,我们标签有几个参数:categoryId、pn、size、order。然后封装成了两个类:Page、QueryWrapper,都是 mybatis-plus 里面的封装类,用于分页和参数查询。

为了可以做缓存,这里我们没有直接传 QueryWrapper 过去,而是直接传参数过去,这样我们可以用参数作为缓存的 key。

下面我们去具体看看 postService.paging(Page page, Long categoryId, String order); 方法。

@Override
@Cacheable(cacheNames = "cache_post", key = "'page_' + #page.current + '_' + #page.size " +
        "+ '_query_' +#categoryId + '_' + #order")
public IPage paging(Page page, Long categoryId, String order) {
    QueryWrapper wrapper = new QueryWrapper<Post>()
            .eq(categoryId != 0, "category_id", categoryId)
            .orderByDesc(order);
    IPage<PostVo> pageData = postMapper.selectPosts(page, wrapper);
    return pageData;
}

上面接口又调用了 mapper 的接口,并且使用了缓存注解 @Cacheable 表示缓存结果,key 是 page 的 pn、size、还有 categoryId、order 的参数。好了,上面我定义了一个模板标签,需要在页面中使用还需要把这个标签配置到 freemarker 中,因此我们定义一个 FreemarkerConfig。

  • com.example.config.FreemarkerConfig
@Configuration
public class FreemarkerConfig {
    @Autowired
    private freemarker.template.Configuration configuration;
    @Autowired
    private ApplicationContext applicationContext;
    @PostConstruct
    public void setUp() {
        configuration.setSharedVariable("posts", applicationContext.getBean(PostsTemplate.class));
    }
}

页面使用标签

好了,freemarker 标签就是这样生成了,下面我们在页面中使用。我们定义的标签名称是 posts,结果是 results。所以置顶模块,我们这样写:

  • templates/index.ftl

得到的结果如下:

首页侧边栏 - 本周热议(续)

在上一次作业中,我们完成了本周热议的数据接口:

但是还没在页面中展示我们的数据,一般按照逻辑,我们应该页面中写个 ajax,然后调用这接口展示数据,现在我们学会了自定义 freemarker,我觉得定义一个标签更方便点,因此,这里我们修改一下接口,把接口改成模板标签。

ok,我们安装刚才的流程,梳理一下:

  • 首先定义一个模板 HotsTemplate,继承 TemplateDirective

  • 重写 getName 和 execute 方法

  • 然后在 FreemarkerConfig 中配置注入标签

  • 页面中使用标签

/**
 * 本周热议
 */
@Component
public class HotsTemplate extends TemplateDirective {
    @Autowired
    RedisUtil redisUtil;
    @Override
    public String getName() {
        return "hots";
    }
    @Override
    public void execute(DirectiveHandler handler) throws Exception {
        Set<ZSetOperations.TypedTuple> lastWeekRank = redisUtil.getZSetRank("last_week_rank", 0, 6);
        List<Map<String, Object>> hotPosts = new ArrayList<>();
        for (ZSetOperations.TypedTuple typedTuple : lastWeekRank) {
            Map<String, Object> map = new HashMap<>();
            map.put("comment_count", typedTuple.getScore());
            map.put("id", redisUtil.hget("rank_post_" + typedTuple.getValue(), "post:id"));
            map.put("title", redisUtil.hget("rank_post_" + typedTuple.getValue(), "post:title"));
            hotPosts.add(map);
        }
        handler.put(RESULTS, hotPosts).render();
    }
}

上面 execute 的内容是把之前的本周热议的接口搬过来的。然后是 freemarkerConfig.setUp

configuration
.
setSharedVariable
(
"hots"
,
 applicationContext
.
getBean
(
HotsTemplate
.
class
));

有了这行代码,页面中就可以使用了。

  • templates/inc/right.ftl
<dl class="fly-panel fly-list-one">
    <dt class="fly-panel-title">本周热议</dt>
    <@hots>
        <#list results as post>
            <dd>
                <a href="jie/detail.html">${post.title}</a>
                <span><i class="iconfont icon-pinglun1"></i> ${post.comment_count}</span>
            </dd>
        </#list>
    </@hots>
</dl>

好了,这一次的作业的内容了。我们要学会快速自定义 freemarker 标签。freemarker 模板引擎还是比较主流的页面引擎。

(完)

MarkerHub 文章索引:

github.com/MarkerHub/J…