• 博客项目(前台功能实现)


    博客项目(前台接口实现)

    文章目录

    1.前置知识

    1.1Controller

    • 在controller层需要调用到service层的代码,做相关业务的处理。

    • 需要统一返回相同的响应格式,不管在前端还是后端,我们在项目中需要统一返回响应的格式。

    • 我们可以定义一个统一返回响应格式的类,这个类前后台都可以用得到,我们定义在公共模块
      在这里插入图片描述

    1.1.1ResponseResult类

    统一响应类,响应给前端。

    package com.jierlung.domain;
    
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.jierlung.enums.AppHttpCodeEnum;
    
    
    import java.io.Serializable;
    
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class ResponseResult<T> implements Serializable {
        private Integer code;
        private String msg;
        private T data;
    
        public ResponseResult() {
            this.code = AppHttpCodeEnum.SUCCESS.getCode();
            this.msg = AppHttpCodeEnum.SUCCESS.getMsg();
        }
    
        public ResponseResult(Integer code, T data) {
            this.code = code;
            this.data = data;
        }
    
        public ResponseResult(Integer code, String msg, T data) {
            this.code = code;
            this.msg = msg;
            this.data = data;
        }
    
        public ResponseResult(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public static ResponseResult errorResult(int code, String msg) {
            ResponseResult result = new ResponseResult();
            return result.error(code, msg);
        }
        public static ResponseResult okResult() {
            ResponseResult result = new ResponseResult();
            return result;
        }
        public static ResponseResult okResult(int code, String msg) {
            ResponseResult result = new ResponseResult();
            return result.ok(code, null, msg);
        }
    
        public static ResponseResult okResult(Object data) {
            ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMsg());
            if(data!=null) {
                result.setData(data);
            }
            return result;
        }
    
        public static ResponseResult errorResult(AppHttpCodeEnum enums){
            return setAppHttpCodeEnum(enums,enums.getMsg());
        }
    
        public static ResponseResult errorResult(AppHttpCodeEnum enums, String msg){
            return setAppHttpCodeEnum(enums,msg);
        }
    
        public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
            return okResult(enums.getCode(),enums.getMsg());
        }
    
        private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String msg){
            return okResult(enums.getCode(),msg);
        }
    
        public ResponseResult<?> error(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
            return this;
        }
    
        public ResponseResult<?> ok(Integer code, T data) {
            this.code = code;
            this.data = data;
            return this;
        }
    
        public ResponseResult<?> ok(Integer code, T data, String msg) {
            this.code = code;
            this.data = data;
            this.msg = msg;
            return this;
        }
    
        public ResponseResult<?> ok(T data) {
            this.data = data;
            return this;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    1.1.2该类的方法

    在这里插入图片描述

    1.2Service

    我们在Service接口中去继承IService方法

    在这里插入图片描述

    1.3ServiceImpl

    在ServiceImpl写具体的业务逻辑。
    在这里插入图片描述

    1.4Mapper

    我们在Mapper接口中去继承Mybatis-plus的BaseMapper的里面的方法。
    在这里插入图片描述

    1.5Vo的理解

    java开发规范手册解释

    在这里插入图片描述

    其实Vo就是返回给前端展示的数据,比如说我现在表里面查询出来10个字段,而需要前端展示的字段只有5个,难道全部返回给前端吗,显然不合理。

    我们需要定义一个Vo对应表的实体类,类中就是展示给前端的字段数据。

    例如:

    实体类Article(文章表)

    package com.jierlung.domain.entity;
    
    import java.util.Date;
    import java.io.Serializable;
    
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.experimental.Accessors;
    
    /**
     * 文章表(Article)表实体类
     *
     * @author makejava
     * @since 2022-10-10 15:27:26
     */
    @SuppressWarnings("serial")
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @TableName("sg_article")
    @Accessors(chain = true)
    public class Article {
        @TableId
        private Long id;
        //标题
        private String title;
    
        @TableField(exist = false)
        //分类的标题
        private String categoryName;
    
        //文章内容
        private String content;
        //文章摘要
        private String summary;
        //所属分类id
        private Long categoryId;
        //缩略图
        private String thumbnail;
        //是否置顶(0否,1是)
        private String isTop;
        //状态(0已发布,1草稿)
        private String status;
        //访问量
        private Long viewCount;
        //是否允许评论 1是,0否
        private String isComment;
        
        private Long createBy;
        
        private Date createTime;
        
        private Long updateBy;
        
        private Date updateTime;
        //删除标志(0代表未删除,1代表已删除)
        private Integer delFlag;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    而根据展示的内容我们可以去掉不必要的字段创建对应的Vo:articleListVo

    package com.jierlung.domain.vo;
    
    import com.baomidou.mybatisplus.annotation.TableId;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.Date;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class articleListVo {
    
        private Long id;
        //标题
        private String title;
        //文章摘要
        private String summary;
        //所属分类id
        private String categoryName;
        //缩略图
        private String thumbnail;
    
        //访问量
        private Long viewCount;
    
        private Date createTime;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    然后可以通过Bean拷贝的形式对Vo进行赋值,比如我查询出来一个List集合的article,然后通过拷贝的形式,去给Vo赋值,然后将Vo对象返回给前端。

    后面的接口实现中,不在重复对Vo的编写,读者可以根据返还给前端的数据展示相应的字段,编写Vo

    1.6可能会用到的相关插件

    在这里插入图片描述

    可以根据对应表自动生成对应的实体类和mappe、Service、ServiceImpl、Controller。

    1.7设置字面量

    可以设置一个类,表示字面量,比如说文章的状态是0,查询的条件如果直接写0,不太方便理解,写字面量的形式更加方便。

    在这里插入图片描述

    1.8后端接口测试工具

    ApiPost7或者postman都可以,操作过程差不多。

    例如:查询热门文章列表

    在这里插入图片描述

    在这里插入图片描述

    2.热门文章接口分析

    2.1热门文章接口位置

    热门文章接口肯定是前台展示的数据,应该在blog的前台模块中

    在这里插入图片描述

    2.2接口的路径

    前端代码的接口路径:

    在这里插入图片描述

    后端:

    @RestController
    @RequestMapping("/article")
    public class ArticleController {
        @Autowired
        ArticleService articleService;
    
        @GetMapping("/articleList")
        public ResponseResult articleList(Integer pageNum,Integer pageSize,Long categoryId){
            return articleService.articleList(pageNum,pageSize,categoryId);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.3前端展示效果

    在这里插入图片描述

    2.4接口响应的格式

    可以看到需要返回一个code和msg,和一个data

    在这里插入图片描述

    2.5需求以及分析数据表

    需求:

    1. 查询的必须是正式发布的文章
    2. 查询前十条数据
    3. 根据访问量从大到小排序
    4. 展示文章的标题和浏览量
    5. 不能把已删除的查询出来

    文章表

    在这里插入图片描述

    分析:

    1. 可以根据文章表的字段,查询文章的状态,0表示已发布
    2. 查询前十条数据,需要查询到的文章分页展示,前10条
    3. 展示文章的标题和浏览量,可以将实体类封装对应的Vo返回给前端
    4. 删除标志,这里的删除是逻辑删除(也就是说删除了,其实底层没有删除,查询的时候看逻辑删除字段的状态查询数据)

    2.6代码实现

     public ResponseResult hotArticleList() {
            //查询热门文章,封装成ResponseResult返回
            LambdaQueryWrapper<Article>  queryWrapper=new LambdaQueryWrapper<Article>();
            //必须是正式文章
            queryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);
            //按照浏览量进行排序
            queryWrapper.orderByDesc(Article::getViewCount);
    
            //最多只查询10条
            Page<Article> page = new Page<>(1, 10);
            page(page, queryWrapper);
            List<Article> articles = page.getRecords();
    
            //bean拷贝
            ArrayList<HotArticleVo> articleVos = new ArrayList<>();
            for (Article article:articles){
                HotArticleVo vo = new HotArticleVo();
                BeanUtils.copyProperties(article,vo);
                articleVos.add(vo);
            }
            return ResponseResult.okResult(articleVos);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    步骤解析:

    第一步:构建article的查询对象,封装一系列的查询条件,例如:降序,必须是正式文章…

    第二步:构建分页查询对象,只展示前十条数据

    第三步:得到查询后并且经过条件过滤后的的分页对象,将其转化为list集合

    第四步:封装vo(因为返回给前端的数据没有那么多,其他的数据没必要返回)

    因为:

    在这里插入图片描述

    在这里插入图片描述

    前端只需要这些数据,我们可以定义vo的方式去对应返回给前端的字段,定义一定要从本来实体类出发,简而言之就是去掉不需要的字段。

    第五步:通过bean拷贝的方式去从article实体类去拷贝到articleVo实体类上,最后面统一封装返回

    第六步测试:
    在这里插入图片描述

    总结一下:首先看对应的涉及到的表,例如这是对应这article表,看需要什么字段,你就给我返回什么字段,在编写相应的逻辑就可以了。

    3.文章分类接口分析

    3.1文章接口位置

    文章分类的接口肯定是前台展示的数据,应该在blog的前台模块中

    3.2接口路径

    前端返回的接口路径

    在这里插入图片描述

    后端:

    @RestController
    @RequestMapping("/category")
    public class CategoryController {
    
        @Autowired
        private CategoryService categoryService;
    
        @GetMapping("/getCategoryList")
        public ResponseResult getCategoryList(){
            return categoryService.getCategoryList();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.3文章接口展示效果

    在这里插入图片描述

    3.4接口响应的格式

    在这里插入图片描述

    3.5需求以及分析数据表

    需求:

    1. 必须是正式的文章的分类
    2. 必须是正常状态的文章

    数据表:

    在这里插入图片描述

    分析:

    1. 可以看到前端需要返回分类名和对应的分类id
    2. 我们可以先查询文章表,然后得到对应的分类id的集合,然后去查询分类表(为什么需要这样子做,而不是直接查询目录表,因为考虑到一个问题就是如果文章的状态是不对的,不正常的状态。所以应该先拿到正常的全部文章的id集合,然后在从目录表中查询相应的数据封装返回)

    3.5代码实现

    CategoryServiceImpl

    @Service
    public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
        @Autowired
        private ArticleService articleService;
    
        @Override
        public ResponseResult getCategoryList() {
            //查询文章表,状态为已发布
            LambdaQueryWrapper<Article> articleWrapper = new LambdaQueryWrapper<Article>();
            articleWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);
            List<Article> articleList = articleService.list(articleWrapper);
    
            //获取文章分类的id,并且去重
            Set<Long> categoryIds = articleList.stream()
                    .map(article -> article.getCategoryId())
                    .collect(Collectors.toSet());
    
            //查询分列表
            List<Category> categories = listByIds(categoryIds);
            categories = categories.stream()
                    .filter(category -> SystemConstants.STATUS_NORMAL.equals(category.getStatus()))
                    .collect(Collectors.toList());
    
            //封装Vo
            List<CategoryVo> categoryVoList = BeanCopyUtils.copyBeanList(categories, CategoryVo.class);
    
            return ResponseResult.okResult(categoryVoList);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    步骤分析:

    第一步:我拿到正常的article集合,然后通过stream流的形式,我得到一个文章的id的list

    在这里插入图片描述

    第二步:调用mybatis-plus的自带的listByIds方法得到category的集合,通过stream流的方式去过滤一些条件,最后面封装vo,拷贝到vo返回即可。

    第三步:测试

    在这里插入图片描述

    3.6自定义Bean拷贝

    也就是说方法中可以进行单个对象和集合对象的拷贝

    public class BeanCopyUtils {
    
        private BeanCopyUtils() {
        }
    
        public static <V> V copyBean(Object source,Class<V> clazz) {
            //创建目标对象
            V result = null;
            try {
                result = clazz.newInstance();
                //实现属性copy
                BeanUtils.copyProperties(source, result);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //返回结果
            return result;
        }
        public static <O,V> List<V> copyBeanList(List<O> list,Class<V> clazz){
            return list.stream()
                    .map(o -> copyBean(o, clazz))
                    .collect(Collectors.toList());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4.文章列表接口分析

    4.1文章列表接口位置

    文章列表接口肯定是前台展示的数据,应该在blog的前台模块中。
    在这里插入图片描述

    参数解释:

    1. 因为文章可能有很多需要分页
    2. 因为分类这也需要调用这个列表展示,所以需要分类id,如果不传那就是首页,如果传了那就是点击了分类。

    4.2文章列表展示效果

    在这里插入图片描述

    4.3接口相应的格式

    在这里插入图片描述

    4.4需求以及分析数据表

    分析:在这我需要查询到article的信息,而json数据里面有categoryName,我需要从category表中去查到名字去赋值到article表中,而关联信息就是传入的categoryId。

    也就是说:我查到的文章列表的数据,我通过比较如果我的categoryId的aritlce表和category表,如果相同的话就是一篇文章,我就在article实体类赋值我的categoryName

    首先分析:

    在这里插入图片描述

    在article实体类上添加字段:

    在这里插入图片描述

    4.5代码实现

     public ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId) {
    
           //查询条件
            LambdaQueryWrapper<Article> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            // 如果有categoryId 就要 查询时和传入的相同
            lambdaQueryWrapper.eq(Objects.nonNull(categoryId)&&categoryId>0, Article::getCategoryId,categoryId);
           //状态是正式发布的
            lambdaQueryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL);
           //对isTop进行排序
            lambdaQueryWrapper.orderByDesc(Article::getIsTop);
            //分页查询
            Page<Article> page = new Page<>(pageNum, pageSize);
            page(page, lambdaQueryWrapper);
    
            //查询categoryName方式一
           /* List
    articles = page.getRecords(); for (Article article : articles) { Category category = categoryService.getById(article.getCategoryId()); article.setCategoryName(category.getName()); }*/ //查询categoryName方式二 List<Article> articles = page.getRecords(); articles = articles.stream() .map(article -> article.setCategoryName(categoryService.getById(article.getCategoryId()).getName())) .collect(Collectors.toList()); //封装查询结果 List<Article> articleListVos = BeanCopyUtils.copyBeanList(page.getRecords(), Article.class); PageVo pageVo = new PageVo(articleListVos,page.getTotal()); return ResponseResult.okResult(pageVo); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    5.文章详情接口分析

    5.1文章详情接口位置

    点击阅读全文即可跳转到文章详情界面
    在这里插入图片描述

    在这里插入图片描述

    参数说明:

    需要文章的id

    5.2接口相应的需求以及分析

    在这里插入图片描述

    5.3代码实现

     public ResponseResult getArticleDetail(Long id) {
            //根据id查询文章
            Article article = getById(id);
    
            //转换成VO
            ArticleDetailVo articleDetailVo = BeanCopyUtils.copyBean(article, ArticleDetailVo.class);
    
            //根据分类id去查询分类名
            Long categoryId = articleDetailVo.getCategoryId();
    
            Category category = categoryService.getById(categoryId);
    
            if(category != null){
                articleDetailVo.setCategoryName(category.getName());
            }
    
            //封装相应返回
            return ResponseResult.okResult(articleDetailVo);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    步骤分析:

    第一步:首先根据文章id得到相应的文章

    第二步:转化为vo对象,根据分类的id得到category

    第三步:判断如果不为空,设置vo的categoryName,封装返回

    5.4添加FastJson配置类

    引入依赖,创建配置类,统一处理日期。

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
          // 设置允许跨域的路径
            registry.addMapping("/**")
                    // 设置允许跨域请求的域名
                    .allowedOriginPatterns("*")
                    // 是否允许cookie
                    .allowCredentials(true)
                    // 设置允许的请求方式
                    .allowedMethods("GET", "POST", "DELETE", "PUT")
                    // 设置允许的header属性
                    .allowedHeaders("*")
                    // 跨域允许时间
                    .maxAge(3600);
        }
    
        @Bean//使用@Bean注入fastJsonHttpMessageConvert
        public HttpMessageConverter fastJsonHttpMessageConverters() {
            //1.需要定义一个Convert转换消息的对象
            FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
            FastJsonConfig fastJsonConfig = new FastJsonConfig();
            fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
            fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
    
            SerializeConfig.globalInstance.put(Long.class, ToStringSerializer.instance);
    
            fastJsonConfig.setSerializeConfig(SerializeConfig.globalInstance);
            fastConverter.setFastJsonConfig(fastJsonConfig);
            HttpMessageConverter<?> converter = fastConverter;
            return converter;
        }
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            converters.add(fastJsonHttpMessageConverters());
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    6.友链查询

    6.1友链查询接口位置

    在这里插入图片描述

    在这里插入图片描述

    6.2接口相应的需求以及分析

    对应的数据表

    在这里插入图片描述

    返回的数据格式

    在这里插入图片描述在这里插入图片描述

    6.3代码实现

    @Service("linkService")
    public class LinkServiceImpl extends ServiceImpl<LinkMapper, Link> implements LinkService {
        @Override
        public ResponseResult getAllLink() {
            //查询所有审核通过的
            LambdaQueryWrapper<Link> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Link::getStatus, SystemConstants.LINK_STATUS_NORMAL);
            List<Link> links = list(queryWrapper);
            //转换成vo
            List<LinkVo> linkVos = BeanCopyUtils.copyBeanList(links, LinkVo.class);
            //封装返回
            return ResponseResult.okResult(linkVos);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    步骤分析:

    第一步:因为查询的字段刚好数据库表相对应,构建查询对象。

    第二步:封装Vo返回即可。

    7.登录/退出登录功能接口分析

    7.1登录/退出登录接口位置

    在这里插入图片描述

    7.2接口相应的需求以及分析

    对应的数据表
    在这里插入图片描述

    返回的数据格式

    {
    	"code": 200,
    	"data": {
    		"token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxZjhmYWVjM2U3YzI0YTE4OGUyMjQyMzNjMDNlMmQyZiIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY2ODQ2ODQwOSwiZXhwIjoxNjY4NDcyMDA5fQ.gbmWqXLKv0LoRI5yv5qEy8jQwdyay1tDLzQbmvAU1Mk",
    		"userInfoVo": {
    			"avatar": "https://11111",
    			"email": "22222@qq.com",
    			"id": "1",
    			"nickName": "sg888",
    			"sex": "1"
    		}
    	},
    	"msg": "操作成功"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    在这里插入图片描述

    7.3代码实现

    UserDetailServiceImpl

    @Service
    public class UserDetailServiceImpl implements UserDetailsService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //根据用户名查询用户信息
            LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(User::getUserName, username);
            User user = userMapper.selectOne(lambdaQueryWrapper);
            if(Objects.isNull(user)){
              throw new RuntimeException("用户不存在!");
            }
    
            //返回用户信息
            //TODO 查询权限信息封装
    
            return new LoginUser(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    自定义登录的实现类,去实现UserDetailsService的接口里面的loadUserByUsername方法,再次登录的时候,Spring Security会自动调用覆写的方法,方法里面自定义从数据库查询用户的相关信息,返回一个UserDetails的对象。

    LoginUser

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class LoginUser implements UserDetails {
        private User user;
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;
        }
    
        @Override
        public String getPassword() {
            return user.getPassword();
        }
    
        @Override
        public String getUsername() {
            return user.getUserName();
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在这里插入图片描述

    SecurityConfig

    我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。

    我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码校验。

    我们可以定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    BlogLoginServiceImpl

    package com.jierlung.service.Impl;
    
    import com.jierlung.domain.ResponseResult;
    import com.jierlung.domain.entity.LoginUser;
    import com.jierlung.domain.entity.User;
    import com.jierlung.domain.vo.BlogUserLoginVo;
    import com.jierlung.domain.vo.UserInfoVo;
    import com.jierlung.service.BlogLoginService;
    import com.jierlung.utils.BeanCopyUtils;
    import com.jierlung.utils.JwtUtil;
    import com.jierlung.utils.RedisCache;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Service;
    
    
    import java.util.Objects;
    
    @Service
    public class BlogLoginServiceImpl implements BlogLoginService {
        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        private RedisCache redisCache;
    
        @Override
        public ResponseResult login(User user) {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            if (Objects.isNull(authenticate)) {
                throw new RuntimeException("用户名或密码错误");
            }
            //获取用户id,生成token
            LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
            String userId = loginUser.getUser().getId().toString();
            String jwt = JwtUtil.createJWT(userId);
            //把用户信息存入redis当中
            redisCache.setCacheObject("bloglogin:" + userId, loginUser);
    
            //Bean拷贝
            UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
    
            BlogUserLoginVo blogUserLoginVo = new BlogUserLoginVo(jwt, userInfoVo);
            return ResponseResult.okResult(blogUserLoginVo);
        }
    
        @Override
        public ResponseResult logout() {
            //获取token 解析获取userid
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
            //获取userid
            Long userId = loginUser.getUser().getId();
            //删除redis中的用户信息
            redisCache.deleteObject("bloglogin:"+userId);
            return ResponseResult.okResult();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    JwtAuthenticationTokenFilter

    package com.jierlung.filter;
    
    import com.alibaba.fastjson.JSON;
    import com.jierlung.domain.ResponseResult;
    import com.jierlung.domain.entity.LoginUser;
    import com.jierlung.enums.AppHttpCodeEnum;
    
    import com.jierlung.utils.JwtUtil;
    import com.jierlung.utils.RedisCache;
    import com.jierlung.utils.WebUtils;
    import io.jsonwebtoken.Claims;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Objects;
    
    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
        @Autowired
        private RedisCache redisCache;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, IOException {
            //获取token
            String token = request.getHeader("token");
            if (!StringUtils.hasText(token)) {
                //放行
                filterChain.doFilter(request, response);
                return;
            }
            //解析获取userid
            Claims claims=null;
            try {
                claims = JwtUtil.parseJWT(token);
    
            } catch (Exception e) {
                ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
                WebUtils.renderString(response, JSON.toJSONString(result));
                return;
            }
            String userId = claims.getSubject();
            LoginUser loginUser = redisCache.getCacheObject("bloglogin:" + userId);
    
            //存入securityContextHolder
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            //放行
            filterChain.doFilter(request, response);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    流程:首先会进入过滤器,会检查是否携带了token,如果携带就解析放行,如果没有token就会去到自定义实现类去查数据库,生成token返回,数据存入到redis中。

    在这里插入图片描述

    7.4登录/退出登录总结

    登录认证需要有spring security的知识,如果还没学到,请去学习相关内容,而具体流程可以看我这篇文章:Spring Security安全认证流程

    8.评论列表

    8.1评论列表接口的位置

    在这里插入图片描述

    8.2接口相应的需求以及分析

    在这里插入图片描述

    在这里插入图片描述

    相关数据表
    在这里插入图片描述

    8.3代码实现

    @Service("CommentService")
    public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
    
        @Autowired
        private UserService userService;
    
        @Override
        public ResponseResult commentList(String commentType, Long articleId, Integer pageNum, Integer pageSize) {
            //查询所有文章列表
    
            //查询条件
            LambdaQueryWrapper<Comment> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            //对文章id进行判断
            lambdaQueryWrapper.eq(SystemConstants.ARTICLE_COMMENT.equals(commentType),Comment::getArticleId, articleId);
    
            //根评论的id为-1
            lambdaQueryWrapper.eq(Comment::getRootId, -1);
    
            //评论类型
            lambdaQueryWrapper.eq(Comment::getType, commentType);
    
            //分页查询
            Page<Comment> page = new Page<>(pageNum, pageSize);
            page(page, lambdaQueryWrapper);
    
            List<CommentVo> commentVoList = toCommentVoList(page.getRecords());
    
            //查询所有根评论对应的子评论
            for (CommentVo commentVo : commentVoList) {
                //查询对应的子评论
                List<CommentVo> children=getChildren(commentVo.getId());
                commentVo.setChilden(children);
            }
    
            return ResponseResult.okResult(new PageVo(commentVoList, page.getTotal()));
        }
    
        @Override
        public ResponseResult addComment(Comment comment) {
           if(!StringUtils.hasText(comment.getContent())){
               throw  new SystemException(AppHttpCodeEnum.CONTENT_NOT_NULL);
           }
    
            save(comment);
    
            return ResponseResult.okResult();
        }
    
        private List<CommentVo> getChildren(Long id) {
            LambdaQueryWrapper<Comment> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(Comment::getRootId, id);
            lambdaQueryWrapper.orderByAsc(Comment::getCreateTime);
            List<Comment> comments = list(lambdaQueryWrapper);
    
            List<CommentVo> commentVos = toCommentVoList(comments);
            return commentVos;
    
    
        }
    
        private List<CommentVo> toCommentVoList(List<Comment> list) {
            List<CommentVo> commentVos = BeanCopyUtils.copyBeanList(list, CommentVo.class);
            //遍历commentVo
            for (CommentVo commentVo : commentVos) {
                //查询getCreateBy,对应的用户
                String nickName = userService.getById(commentVo.getCreateBy()).getNickName();
                commentVo.setUsername(nickName);
    
                //通过对toCommentUserId查询用户的昵称并赋值
                //如果toCommentUserId不为-1才进行查询
    
                if(commentVo.getToCommentUserId()!=-1){
                    String toCommentUserName = userService.getById(commentVo.getToCommentUserId()).getNickName();
                    commentVo.setToCommentUserName(toCommentUserName);
                }
            }
            return commentVos;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    关键步骤解析:

    第一步:首先我拿到所以评论的集合(暂时不考虑子评论情况),注意判断条件中,友链中也有评论,所以需要去判断类型。

    第二步:拿到分页的评论的对象,转化为list集合,通过Bean拷贝的方式,将其转化为Vo对象,注意这里有三个字段是Vo中没有的,MP在赋值这里为空,我们需要手动去加上这三个字段,分别是

    在这里插入图片描述

    username字段和toCommentUserName字段我们可以在Bean拷贝时,手动赋值,因为username我们可以根据查出来的评论的createby查到该用户,在调用该用户的方法去为评论的Vo赋值。

    toCommentUserName字段我们可以通过toCommentUserId不等于-1,就证明不是根评论,我们就可以查到评论该评论的评论者的用户(子评论人)然后在为Vo赋值。

    第三步:我已经拿到完整的评论Vo对象,我该查询子评论,遍历评论Vo对象,编写方法,如果子评论的rootId一样的话,收集到该集合,在重复步骤二。

    第四步:封装返回对象。

    9.友链评论列表

    方法和上面一模一样,在这复用之前的方法,在方法里面加上评论的类型。
    在这里插入图片描述

    10.个人信息查询

    10.1个人信息查询接口位置

    在这里插入图片描述

    在这里插入图片描述

    10.2接口相应的需求以及分析

    在这里插入图片描述

    相关数据表

    在这里插入图片描述

    10.3代码实现

     @Override
        public ResponseResult userInfo() {
            //获取用户当前的id
            Long userId = SecurityUtils.getUserId();
    
            //根据用户id查询用用户信息
            User user = getById(userId);
    
            //封装拷贝
            UserInfoVo vo = BeanCopyUtils.copyBean(user, UserInfoVo.class);
            return ResponseResult.okResult(vo);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    步骤解析:

    因为登录了,就产生了token,直接拿到token,去查询用户信息即可,最后面封装返回。

    11.头像上传

    在这使用七牛云来保存图片
    在这里插入图片描述

    也就说,上传到七牛云,前端通过读取千牛云的图片,不通过Web服务器来存储。

    11.1头像上传接口位置

    在这里插入图片描述

    11.2接口相应的需求以及分析

    响应数据

    在这里插入图片描述

    11.3代码实现

    配置在yml文件中配置oss

    在这里插入图片描述

    @ConfigurationProperties(prefix = "oss")
    
    public class UploadServiceImpl implements UploadService {
    
        @Override
        public ResponseResult uploadImg(MultipartFile img) {
    
    
            String originalFilename = img.getOriginalFilename();
            //判断类型
            if(!originalFilename.endsWith(".png")){
                throw new SystemException(AppHttpCodeEnum.FILE_TYPE_ERROR);
            }
    
            //如果判断通过上传文件到oss
            String filePath = PathUtils.generateFilePath(originalFilename);
            String url = uploadOss(img,filePath);
    
            return ResponseResult.okResult(url);
        }
    
        private String accessKey;
        private String secretKey;
        private String bucket;
        private String  uploadOss(MultipartFile imgFile, String filePath){
            //构造一个带指定 Region 对象的配置类
            Configuration cfg = new Configuration(Region.autoRegion());
            //...其他参数参考类注释
    
            UploadManager uploadManager = new UploadManager(cfg);
            //...生成上传凭证,然后准备上传
    //        String accessKey = "your access key";
    //        String secretKey = "your secret key";
    //        String bucket = "sg-blog";
    
            //默认不指定key的情况下,以文件内容的hash值作为文件名
            String key = filePath;
    
            try {
    
                InputStream inputStream = imgFile.getInputStream();
                Auth auth = Auth.create(accessKey, secretKey);
                String upToken = auth.uploadToken(bucket);
    
                try {
                    Response response = uploadManager.put(inputStream,key,upToken,null, null);
                    //解析上传成功的结果
                    DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
                    System.out.println(putRet.key);
                    System.out.println(putRet.hash);
                    return "http://rl4fyr0mn.hn-bkt.clouddn.com/"+key;
                } catch (QiniuException ex) {
                    Response r = ex.response;
                    System.err.println(r.toString());
                    try {
                        System.err.println(r.bodyString());
                    } catch (QiniuException ex2) {
                        //ignore
                    }
                }
            } catch (Exception ex) {
                //ignore
            }
            return "www";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    12.更新用户

    12.1更新用户接口位置

    在这里插入图片描述

    12.2代码实现

    @Override
    public ResponseResult updateUserInfo(User user) {
        updateById(user);
        return ResponseResult.okResult();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    13.注册用户

    13.1注册用户的接口位置

    13.2接口相应的需求以及分析

    在这里插入图片描述

    在这里插入图片描述

    13.3代码实现

    @Override
        public ResponseResult register(User user) {
            //对数据进行非空判断
            if(!StringUtils.hasText(user.getUserName())){
                throw new SystemException(AppHttpCodeEnum.USERNAME_NOT_NULL);
            }
            if(!StringUtils.hasText(user.getPassword())){
                throw new SystemException(AppHttpCodeEnum.PASSWORD_NOT_NULL);
            }
            if(!StringUtils.hasText(user.getEmail())){
                throw new SystemException(AppHttpCodeEnum.EMAIL_NOT_NULL);
            }
            if(!StringUtils.hasText(user.getNickName())){
                throw new SystemException(AppHttpCodeEnum.NICKNAME_NOT_NULL);
            }
            //对数据进行是否存在的判断
            if(userNameExist(user.getUserName())){
                throw new SystemException(AppHttpCodeEnum.USERNAME_EXIST);
            }
            if(nickNameExist(user.getNickName())){
                throw new SystemException(AppHttpCodeEnum.NICKNAME_EXIST);
            }
            //...
            //对密码进行加密
            String encodePassword = passwordEncoder.encode(user.getPassword());
            user.setPassword(encodePassword);
            //存入数据库
            save(user);
            return ResponseResult.okResult();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    14.AOP实现日志记录代码

    14.0复习知识

    相关注解
    @Component 将当前类注入到Spring容器内

    @Aspect :表明是一个切面类

    @Before :前置通知,在方法执行之前执行

    @After :后置通知,在方法执行之后执行

    @AfterRuturning :返回通知,在方法返回结果之后执行

    @AfterThrowing :异常通知,在方法抛出异常之后执行

    @Around :环绕通知,围绕着方法执行

    @Pointcut :切入点,PointCut(切入点)表达式有很多种,其中execution用于使用切面的连接点。

    上面所提到的五种通知方法中,除了环绕通知外,其他的四个通知注解中,加或者不加参数JoinPoint都可以,如果有用到JoinPoint的地方就加,用不到就可以不加。

    JoinPoint:里包含了类名、被切面的方法名,参数等属性。

    环绕通知:参数必须为ProceedingJoinPoint,pjp.proceed相应于执行被切面的方法。

    返回通知:可以加returning = “XXX”,XXX即为被切入方法的返回值,本例中是controller类中方法的返回值。

    异常通知:可以加throwing = “XXX”,供读取异常信息。

    返回通知和异常通知只会执行一个,如果产生异常,返回通知就不执行,后置通知一定会执行

    环绕通知一般单独使用,环绕通知可以替代上面的四种通知,后面单独介绍。

    相关概念
    Joinpoint(连接点):所谓连接点是指那些被拦截到的点,在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点,通俗的说就是被增强类中的所有方法

    PointCut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义,通俗的说就是被增强类中的被增强的方法,因为被增强类中并不是所有的方法都被代理了

    Advice(通知/增强):所谓通知是指拦截到 Joinpoint (被增强的方法)之后所要做的事情就是通知,通俗的说就是对被增强的方法进行增强的代码

    通知的类型:前置通知,返回通知,异常通知,后置通知,环绕通知

    前置通知:在被代理方法执行之前执行

    返回通知:在被代理方法执行之后执行

    异常通知:在被代理方法执行出错的时候执行

    后置通知:无论怎样都会执行

    注意:返回通知和异常通知只能有一个会被执行,因为发生异常执行异常通知,然后就不会继续向下执行,自然后置通知也就不会被执行,反之亦然。

    Aspect(切面):是切入点和通知(引介)的结合,通俗的说就是建立切入点和通知方法在创建时的对应关系

    14.1打印日志需求

    需要通过日志的接口信息,便于后期的调试排查,并可能有很多接口都需要进行日志的记录。

    在这里插入图片描述

    14.2步骤分析

    第一步:编写一个自定义注解

    在这里插入图片描述

    第二步:在目标的Controller类方法中添加一个方法

    在这里插入图片描述

    第三步:定义切面类
    在这里插入图片描述

    14.3代码实现

    SystemLog

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface SystemLog {
        String businessName();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    LogAspect

    @Component
    @Aspect
    @Slf4j
    public class LogAspect {
    
        @Pointcut("@annotation(com.sangeng.annotation.SystemLog)")
        public void pt(){
    
        }
    
        @Around("pt()")
        public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
    
            Object ret;
            try {
                handleBefore(joinPoint);
                ret = joinPoint.proceed();
                handleAfter(ret);
            } finally {
                // 结束后换行
                log.info("=======End=======" + System.lineSeparator());
            }
    
            return ret;
        }
    
        private void handleAfter(Object ret) {
            // 打印出参
            log.info("Response       : {}", JSON.toJSONString(ret));
        }
    
        private void handleBefore(ProceedingJoinPoint joinPoint) {
    
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = requestAttributes.getRequest();
            //获取被增强方法上的注解对象
            SystemLog systemLog = getSystemLog(joinPoint);
            log.info("=======Start=======");
            // 打印请求 URL
            log.info("URL            : {}",request.getRequestURL());
            // 打印描述信息
            log.info("BusinessName   : {}",systemLog.businessName());
            // 打印 Http method
            log.info("HTTP Method    : {}",request.getMethod());
            // 打印调用 controller 的全路径以及执行方法
            log.info("Class Method   : {}.{}",joinPoint.getSignature().getDeclaringTypeName(),((MethodSignature) joinPoint.getSignature()).getName());
            // 打印请求的 IP
            log.info("IP             : {}",request.getRemoteHost());
            // 打印请求入参
            log.info("Request Args   : {}", JSON.toJSONString(joinPoint.getArgs()) );
        }
    
        private SystemLog getSystemLog(ProceedingJoinPoint joinPoint) {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            SystemLog systemLog = methodSignature.getMethod().getAnnotation(SystemLog.class);
            return systemLog;
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    15.更新博客浏览量

    15.1需求分析

    在这里插入图片描述

    15.2代码实现

    首先在应用启动时,把mysql的博客浏览量存储到redis中

    在应用启动时,需要去继承CommandLineRunner,启动时执行的代码

    @Component
    public class ViewCountRunner implements CommandLineRunner {
    
        @Resource
        private ArticleMapper articleMapper;
    
        @Autowired
        private RedisCache redisCache;
    
        @Override
        public void run(String... args) throws Exception {
            //查询博客信息  id  viewCount
            List<Article> articles = articleMapper.selectList(null);
            Map<String, Integer> viewCountMap = articles.stream()
                    .collect(Collectors.toMap(article -> article.getId().toString(), article -> {
                        return article.getViewCount().intValue();//
                    }));
            //存储到redis中
            redisCache.setCacheMap("article:viewCount",viewCountMap);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    定义接口,点击浏览文章时,该文章的浏览器+1

    在这里插入图片描述

    在这里插入图片描述

    添加定时任务,给定的时间,同步数据到mysql中去,使得数据保持一致

    关于crone表达式,可以自行去网上查找相关内容学习

    在这里插入图片描述

    @Component
    public class UpdateViewCountJob {
    
        @Autowired
        private RedisCache redisCache;
    
        @Autowired
        private ArticleService articleService;
    
        @Scheduled(cron = "0/55 * * * * ?")
        public void updateViewCount(){
            //获取redis中的浏览量
            Map<String, Integer> viewCountMap = redisCache.getCacheMap("article:viewCount");
    
            List<Article> articles = viewCountMap.entrySet()
                    .stream()
                    .map(entry -> new Article(Long.valueOf(entry.getKey()), entry.getValue().longValue()))
                    .collect(Collectors.toList());
            //更新到数据库中
            articleService.updateBatchById(articles);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    修改查询文章的代码,冲redis中去查询数据,获得实时的数据。

     @Override
        public ResponseResult getArticleDetail(Long id) {
            //根据id查询文章
            Article article = getById(id);
            //从redis中获取viewCount
            Integer viewCount = redisCache.getCacheMapValue("article:viewCount", id.toString());
            article.setViewCount(viewCount.longValue());
            //转换成VO
            ArticleDetailVo articleDetailVo = BeanCopyUtils.copyBean(article, ArticleDetailVo.class);
            //根据分类id查询分类名
            Long categoryId = articleDetailVo.getCategoryId();
            Category category = categoryService.getById(categoryId);
            if(category!=null){
                articleDetailVo.setCategoryName(category.getName());
            }
            //封装响应返回
            return ResponseResult.okResult(articleDetailVo);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    16.总结

    前台部分到此结束,学会自行去分析,根据接口去写代码,一步步去分析业务逻辑。

  • 相关阅读:
    OPENAPI3.0 与 SpringBoot 开发实战: 新型高效开发模式,实现代码与API分离,高效开发,开发必看!!!
    springboot:整合其它项目
    Lombok安装及Lombok使用
    物理机--VMWare安装、配置虚拟化centos7系统流程
    Perl 语言入门学习
    武装你的WEBAPI-OData聚合查询
    CSS基础
    【Docker】Docker安装与入门
    vue的生命周期
    Python Opencv实践 - 人脸识别CascadeClassifier
  • 原文地址:https://blog.csdn.net/qq_52166656/article/details/128049169