归档文章列表
1.1 接口说明
接口url:/articles
请求方式:POST
请求参数:
参数名称 | 参数类型 | 说明 |
---|---|---|
year | string | 年 |
month | string | 月 |
返回数据:
{
"success": true,
"code": 200,
"msg": "success",
"data": [文章列表,数据同之前的文章列表接口]
}
由于需要统计年月份,mybatisPlus的queryWrapper不好统计,只能用sql语句
所以文章详情接口需要全部重新用xml的方式实现
前端参数也需要多加两个成员,year和month
package com.example.blog.vo.params;
import lombok.Data;
@Data
public class PageParams
{
//当前的页数
private int page = 1;
//每页查询的数量
private int pageSize = 10;
private Long categoryId;
private Long tagId;
private String year;
private String month;
public String getMonth()
{
if(this.month != null && this.month.length() ==1)
{
return "0"+this.month;
}
return this.month;
}
}
ArticleController:
/**
* 首页 文章列表
* @param pageParams
* @return
*/
@PostMapping
public Result listArticle(@RequestBody PageParams pageParams)
{
return articleService.listArticle(pageParams);
}
ArticleServiceImpl:
/**
* 分页查询 article数据库表
* @param pageParams
* @return
*/
//加上此注解 代表要对此接口记录日志
@LogAnnotation(module="文章",operator="获取文章列表")
@Override
public Result listArticle(PageParams pageParams)
{
Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());
IPage<Article> articleIPage = articleMapper.listArticle(
page,
pageParams.getCategoryId(),
pageParams.getTagId(),
pageParams.getYear(),
pageParams.getMonth());
List<Article> records = articleIPage.getRecords();
return Result.success(copyList(records,true,true));
}
ArticleMapper:
package com.example.blog.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.blog.dos.Archives;
import com.example.blog.entity.Article;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ArticleMapper extends BaseMapper<Article>
{
public List<Archives> listArchives();
IPage<Article> listArticle(Page<Article> page,
Long categoryId,
Long tagId,
String year,
String month);
}
ArticleMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis配置文件-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.blog.dao.mapper.ArticleMapper">
<resultMap id="articleMap" type="com.example.blog.entity.Article">
<id column="id" property="id"></id>
<result column="comment_counts" property="commentCounts"></result>
<result column="create_date" property="createDate"></result>
<result column="summary" property="summary"></result>
<result column="title" property="title"></result>
<result column="view_counts" property="viewCounts"></result>
<result column="weight" property="weight"></result>
<result column="author_id" property="authorId"></result>
<result column="body_id" property="bodyId"></result>
<result column="category_id" property="categoryId"></result>
</resultMap>
<select id="listArchives" resultType="com.example.blog.dos.Archives">
select FROM_UNIXTIME(create_date/1000,'%Y') as year, FROM_UNIXTIME(create_date/1000,'%m') as month,count(*) as count from ms_article group by year,month
</select>
<!--参数:
page,
pageParams.getCategoryId(),
pageParams.getTagId(),
pageParams.getYear(),
pageParams.getMonth()-->
<!--这里FROM_UNIXTIME(create_date/1000,'%Y')查出来是0X月份,所以month参数那里需要加 public String getMonth()-->
<select id="listArticle" resultMap="articleMap">
select * from ms_article
<where>
<if test="categoryId != null">
and category_id=#{category_id}
</if>
<if test="tagId != null">
and id in (select article_id from ms_article_tag where tag_id =#{tagId})
</if>
<if test="year != null and year.length>0 and month!=null and month.length>0">
and (FROM_UNIXTIME(create_date/1000,'%Y')=#{year} and FROM_UNIXTIME(create_date/1000,'%m')=#{month})
</if>
</where>
order by weight,create_date DESC
</select>
</mapper>
统一缓存处理(优化)
1.首先写一个缓存注解,这个注解加在哪个地方,哪里就进行一个缓存。切点为添加了该注解的方法。
package com.example.blog.common.aop;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache
{
//设置过期时间
long expire() default 1 * 60 * 1000;
//缓存表示 key
String name() default "";
}
2.再设置环绕通知,看redis有没有存入该返回值
有的话直接返回,就不用调用切点方法了,没有的话再调用切点方法,将返回值存入redis中
CacheAspect:
package com.example.blog.common.aop;
import com.alibaba.fastjson.JSON;
import com.example.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.Duration;
@Aspect
@Component
@Slf4j
//缓存的切面(切点为添加了该注解的方法)
public class CacheAspect
{
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Pointcut("@annotation(com.example.blog.common.aop.Cache)")
public void pt(){}
@Around("pt()")//环绕通知
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
Signature signature = joinPoint.getSignature();
//获取切点的类名
String className = joinPoint.getTarget().getClass().getSimpleName();
//获取切点的方法名
String methodName = signature.getName();
//获取切点的参数
Class[] parameterTypes = new Class[joinPoint.getArgs().length];//存储参数类型的数组
Object[] args = joinPoint.getArgs();//存储参数的数组
String params = "";
for(int i=0;i<args.length;i++)
{
if(args[i] != null)
{
params += JSON.toJSONString(args[i]);
parameterTypes[i] = args[i].getClass();
}else
{
parameterTypes[i] = null;
}
}
if(StringUtils.isNotEmpty(params))
{
//MD5加密 以防出现key过长以及字符串转义获取不到的情况
params = DigestUtils.md5Hex(params);
}
//通过parameterTypes获得切点
Method method = joinPoint.getSignature().getDeclaringType().getMethod(methodName,parameterTypes);
//获得cache注解
Cache annotation = method.getAnnotation(Cache.class);
//拿到切点设置的缓存过期时间
long expire = annotation.expire();
//拿到切点设置的缓存名称
String name = annotation.name();
//先从redis获得,看是否该缓存存入了redis
String redisKey = name + "::" + className + "::"+ methodName + "::" +params;
String redisValue = redisTemplate.opsForValue().get(redisKey);
if(StringUtils.isNotEmpty(redisValue))
{
log.info("走了缓存~~~,{},{}",className,methodName);
return JSON.parseObject(redisValue, Result.class);
}
//如果为空,代表需要调用切点方法,再把返回值存入redis里面
Object proceed = joinPoint.proceed();
redisTemplate.opsForValue().set(redisKey,JSON.toJSONString(proceed), Duration.ofMillis(expire));
log.info("存入缓存~~~ {},{}",className,methodName);
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return Result.fail(-999,"系统错误");
}
}
3.使用该注解
package com.example.blog.controller;
import com.example.blog.common.aop.Cache;
import com.example.blog.service.ArticleService;
import com.example.blog.vo.Result;
import com.example.blog.vo.params.ArticleParam;
import com.example.blog.vo.params.PageParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RequestMapping("/articles")
@RestController
public class ArticleController
{
@Autowired
private ArticleService articleService;
/*如果参数时放在请求体中,application/json传入后台的话,那么后台要用@RequestBody才能接收到;
如果不是放在请求体中的话,那么后台接收前台传过来的参数时,要用@RequestParam来接收。
或者形参前 什么也不写也能接收。*/
@PostMapping("/publish")
public Result publish(@RequestBody ArticleParam articleParam)
{
return articleService.publish(articleParam);
}
/**
* 首页 文章列表
* @param pageParams
* @return
*/
@PostMapping
@Cache(expire = 5 *60 * 1000,name = "list_article")
public Result listArticle(@RequestBody PageParams pageParams)
{
return articleService.listArticle(pageParams);
}
/**
* 首页最热文章
* @return
*/
@Cache(expire = 5 *60 * 1000,name = "hot_article")
@PostMapping("hot")
public Result hotArticles()
{
int limit = 6;
return articleService.hotArticle(limit);
}
/**
* 首页最新文章
*/
@PostMapping("new")
@Cache(expire = 5 *60 * 1000,name = "new_article")
public Result newArticles()
{
int limit = 5;
return articleService.newArticle(limit);
}
/**
* 文章归档
*/
@PostMapping("listArchives")
@Cache(expire = 5 *60 * 1000,name = "list_archives")
public Result listArchives()
{
return articleService.listArchives();
}
/**
* 文章详情
*/
@PostMapping("/view/{id}")
public Result findArticleById(@PathVariable("id") Long articleId)
{
return articleService.findArticleById(articleId);
}
}
不过这里有一个bug,加了缓存之后,点击文章详情出现文章加载失败
原因:
if(StringUtils.isNotEmpty(redisValue))
{
log.info("走了缓存~~~,{},{}",className,methodName);
return JSON.parseObject(redisValue, Result.class);
}
如果该切点方法以及把返回值存入缓存,那么JSON在解析类型为Long的id时,又会出现精度不够的情况
解决:把以下的id都改为String类型
ArticleServiceImpl:
copy方法,加入
articleVo.setId(String.valueOf(article.getId()));
对应impl的所有copy方法都需要修改
前端也有一个bug,当评论成功之后,后端返回给前端的数据应该是null,所以前端that.comments.unshift(data.data)是把Null加入进去,应该是直接重新获取评论列表
this.getCommentsByArticle()