# learn-eblog **Repository Path**: MXjz/learn-eblog ## Basic Information - **Project Name**: learn-eblog - **Description**: No description available - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-08-16 - **Last Updated**: 2021-09-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # learn-eblog ## 预备知识 ### `ApplicationRunner`的使用和理解 SpringBoot项目启动的时候,有时候需要在启动之后执行某一段代码,这个时候就用到了ApplicationRunner这个接口,里面定义一个run(ApplicationArguments args)方法,我们需要自己写一个类去实现这个这接口,并实现接口里面的run(ApplicationArguments args)方法。 如果有多个代码段需要执行,可以使用@Order来设置执行的顺序 ### `@ControllerAdvice`的使用 可以实现三个方面的功能: 1. 全局异常绑定 2. 全局数据绑定 3. 全局数据预处理 #### 全局异常处理 ```java @ControllerAdvice public class MyGlobalExceptionHandler { @ExceptionHandler(Exception.class) public ModelAndView customException(Exception e) { ModelAndView mv = new ModelAndView(); mv.addObject("message", e.getMessage()); mv.setViewName("myerror"); return mv; } } ``` 可在该类中定义多个方法,不同的方法处理不同的异常,例如专门处理空指针的方法,专门处理数组越界的方法等。 #### 全局数据绑定 可以用来做一些初始化的数据操作,我们可以将一些公共的数据定义在添加了@ControllerAdvice注解的类中,这样,在每个Controller的接口中,都能访问到这些数据 ```java @ControllerAdvice public class MyGlobalExceptionHandler { // 定义全局数据 @ModelAttribute(name = "md") public Map mydata() { HashMap map = new HashMap<>(); map.put("age", 99); map.put("gender", "男"); return map; } } ``` 使用@ModelAttribute注解标记该方法的返回数据是一个**全局数据**,默认情况下,这个全局数据的key就是返回的变量名,value是方法返回值,定义完成后,在任何一个Controller接口中,都可以获取到这里定义的数据: ```java @RestController public class HelloController { @GetMapping("/hello") public String hello(Model model) { Map map = model.asMap(); System.out.println(map); int i = 1 / 0; return "hello controller advice"; } } ``` #### 全局数据预处理 考虑我有两个实体类,Book 和 Author,分别定义如下: ```java public class Book { private String name; private Long price; //getter/setter } public class Author { private String name; private Integer age; //getter/setter } ``` 这时定义一个数据添加接口 ```java @PostMapping("/book") public void addBook(Book book, Author author) { System.out.println(book); } ``` 添加操作有问题,因为两个实体类都有一个name属性,从前端传递时,无法区分,此时通过@ControllerAdvice的全局数据预处理可以解决这个问题 解决步骤如下: 1. 给接口中的变量取别名 ```java @PostMapping("/book") public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) { System.out.println(book); System.out.println(author); } ``` 2. 进行请求数据预处理 在 @ControllerAdvice 标记的类中添加如下代码: ```java @InitBinder("b") public void b(WebDataBinder binder) { binder.setFieldDefaultPrefix("b."); } @InitBinder("a") public void a(WebDataBinder binder) { binder.setFieldDefaultPrefix("a."); } ``` @InitBinder("b") 注解表示该方法用来处理和Book和相关的参数,在方法中,给参数添加一个 b 前缀,即请求参数要有b前缀. ### Freemarker相关知识 #### TemplateDirectiveModel 自定义标签详解 java中freemarker通过实现TemplateDirectiveModel接口,用户可以自定义标签(指令)进行任意操作,任意文本写入模板的操作 ```java public void execute(Environment env, Map parameters, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException { // ... } ``` - env:系统环境变量,通常用它来输出相关内容,如Writer out = env.getOut(); - params:自定义标签传过来的对象,其key=自定义标签的参数名,value=TemplateModel类型,TemplateModel是一个接口类型,通常我们都使用TemplateScalarModel接口来代替它获取一个String值,如`TemplateScalarModel.getAsString()`,当然还有其他常用的接口,如`TemplateNumberModel获取number,TemplateHashModel`等 - loopsVars:循环替代变量 - TemplateDirectiveBody:用于处理自定义标签中的内容,如<@myDirective>将要被处理的内容\ #### 创建自定义标签 1. 实现TemplateDirectiveModel这个接口中的execute方法 通过body.render()向模板传递自己想要传的数据 2. 将实现TemplateDirectiveModel接口的类注入到ioc容器中 3. 将bean设置到freemarker config的全局变量中 4. ftl中引用自定义标签 【注】Freemarker自定义标签中,如果标签内什么也没有,开始标签和结束标签绝对不能再同一行,不然会报错。 ## 本周热议功能 本周发表并且评论最多的文章排行,如果直接查询数据库的话很快就可以实现,只需要限定文章创建时间,然后按照评论数量倒叙取前几篇即可。但是这里我们可以用redis来实现,我们可以使用redis的有序集合zset来实现排行榜功能。 几个命令: 1. `zrange key start stop [WITHSCORES]` withscores代表是否显示顺序号,start和stop代表所在的位置的索引。可以理解为:将集合元素依照顺序值升序排序再输出,start和stop限制遍历的范围 2. `zincrby key increment member` 为有序集合key的成员member的score值加上增量increment 3. `ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight[weight ...]] [AGGREGATE SUM|MIN|MAX]` 计算给定的一个或多个有序集的并集,其中给定key的数量必须以numkeys参数指定,并将该并集(结果集)储存到destination - 使用 *WEIGHTS* 选项时,可以为各个有序集合输入指定一个乘法系数(Multiplication factor )。这意味着在将每个有序集合输入中的每个元素的分值传递给聚合函数(Aggregation function)之前,会将该分值乘以对应的系数。当未给定 *WEIGHTS* 选项时,乘法系数默认为 1。 - 使用 *AGGREGATE* 选项时,可以指定并集运算结果的聚合方式。该选项默认值为 SUM,即将输入中所有存在该元素的集合中对应的分值全部加一起。当选项被设置为 MIN 或 MAX 任意值时,结果集合将保存输入中所有存在该元素的集合中对应的分值的最小或最大值。 ![](./imgs/ZUNIONSTORE指令示例.png) **实现逻辑:** 1. 查询mysql获取近七天的所有评论数大于0的文章 2. 把文章的评论数量作为有序集合的score,文章id作为id存储到zset中 3. 本周热议上有标题和评论数量,因此,我们还需要把文章的基本信息存储到redis中,这样得到文章的id之后,我们再从缓存中得到标题等信息,这里我们可以使用hash的结构来存储文章的信息 4. 另外,因为是本周热议,如果文章发表超过七天之后就没用了,所以我们可以给文章的有序集合一个有效时间,超过7天之后自动删除缓存