KekeBlog项目实战(更新中)

一、前言

1. 项目简介

本项目是前后端分离项目,而我们所做的只有完整的后端开发工作,前端已经写好,故不做任何开发,仅开发后端。项目包含完整的后端中前台和后台的代码编写

前端项目下载链接:

https://pan.baidu.com/s/1TdFs4TqxlHh4DXyLwYuejQ 
提取码:mfkw

2. 技术栈

前端:Vue + ElementUI 

后端:SpringBoot + SpringSecurity + Maven + MybatisPlus + Mysql + Redis + EasyExcel +        Swagger2 + Echarts  

3. 运行环境

Intellj IDEA 2022.1.4

jdk 1.8.0_381

Apache Maven 3.8.6

mysql 8.0.29

二、项目搭建

1. 思考

由于项目后端分为前台和后台两个部分,且都有用到相同的实体等等,所以考虑到代码的复用性,我们把后端的前台和后台管理的两个模块的相同代码抽取出来放在一个公共模块里。即两个子模块,一个父模块的多模块项目

两个子模块分别是:博客前台模块keke-blog,博客后台模块keke-admin

公共模块:keke-framework

2. 工程创建

在F盘中创建一个目录BlogProject然后在该目录下创建模块

2.1 创建父模块(父)

KekeBlog项目实战(更新中)_第1张图片

将默认的项目结构修改如下

KekeBlog项目实战(更新中)_第2张图片

我们把这个工程,作为我们的父级工程,所以src目录就不再需要了,仅需要pom.xml配置文件即可。故把src目录删除,在pom.xml中添加至以下内容(注意要删除上面的properties标签)



    4.0.0

    com.keke
    KekeBlog
    1.0-SNAPSHOT

    
    
        UTF-8
        1.8
    

    
    
        
            
            
                org.springframework.boot
                spring-boot-dependencies
                2.5.0
                pom
                import
            

            
            
                com.alibaba
                fastjson
                1.2.33
            

            
            
                io.jsonwebtoken
                jjwt
                0.9.0
            

            
            
                com.baomidou
                mybatis-plus-boot-starter
                3.4.3
            

            
            
                com.aliyun.oss
                aliyun-sdk-oss
                3.10.2
            


            
                com.alibaba
                easyexcel
                3.0.5
            


            
                io.springfox
                springfox-swagger2
                2.9.2
            
            
                io.springfox
                springfox-swagger-ui
                2.9.2
            
        
    

    
        
            
                
                org.apache.maven.plugins
                maven-compiler-plugin
                3.1
                
                
                    ${java.version}
                    ${java.version}
                    ${project.build.sourceEncoding}
                
            
        
    


2.2 创建公共模块(子)

KekeBlog项目实战(更新中)_第3张图片

创建公共模块keke-framework,该模块实现依赖统一添加

KekeBlog项目实战(更新中)_第4张图片

公共模块的pom.xml文件配置如下,注意不要指定版本,因为其继承了父模块,版本已锁定



    
        KekeBlog
        com.keke
        1.0-SNAPSHOT
    
    4.0.0

    keke-framework

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.projectlombok
            lombok
            true
        
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
        

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
        
            com.alibaba
            fastjson
        
        
        
            io.jsonwebtoken
            jjwt
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
        
        
        
            mysql
            mysql-connector-java
        

        
        
            com.aliyun.oss
            aliyun-sdk-oss
        

        
        
            org.springframework.boot
            spring-boot-starter-aop
        


        
            com.alibaba
            easyexcel
        
        
            io.springfox
            springfox-swagger2
        

        
            io.springfox
            springfox-swagger-ui
        
    

2.3 创建博客后台模块(子)

KekeBlog项目实战(更新中)_第5张图片

后台模块依赖于公共模块,所以我们修改pom.xml文件至如下



    
        KekeBlog
        com.keke
        1.0-SNAPSHOT
    
    4.0.0

    keke-admin

    
    
        
            com.keke
            keke-framework
            1.0-SNAPSHOT
        
    

可以看到,依赖都加载进入该子模块

KekeBlog项目实战(更新中)_第6张图片

2.4 创建博客前台模块(子)

KekeBlog项目实战(更新中)_第7张图片

操作同博客后台模块一样,在pom.xml文件中写入



    
        KekeBlog
        com.keke
        1.0-SNAPSHOT
    
    4.0.0

    keke-blog
    
    
        
            com.keke
            keke-framework
            1.0-SNAPSHOT
        
    

2.5 Maven聚合工程总结

以上操作概括为一个项目为父模块,在父模块中锁定依赖的版本。父模块又有三个子模块,分别为公共模块,博客后台模块和博客前台模块。公共模块中添加项目所需要的模块,博客后台模块和博客前台模块去依赖公共模块,从而达到依赖的继承

可以看到,父模块中多了三个子module,并且KekeBlog为root(根),这样的Maven聚合工程,使得在Maven生命周期中,只需要操作父模块,其余所有模块就都会一起进行操作,省去了逐个模块操作的复杂工作

KekeBlog项目实战(更新中)_第8张图片

三、准备工作

1. SQL导入

数据库sql文件不需要自己去写,解压sql.zip文件后有11个sql脚本,全部导入数据库即可

链接:https://pan.baidu.com/s/1DQCGN4wISSDlOkqnVWYwxA 
提取码:mfkw

2. 前台yml配置

server:
  port: 7777

spring:
  # 数据库连接信息
  datasource:
    url: jdbc:mysql://localhost:3306/keke_blog?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password:
    driver-class-name: com.mysql.cj.jdbc.Driver

  servlet:
    # 文件上传
    multipart:
      # 单个上传文件的最大允许大小
      max-file-size: 20MB
      # HTTP请求中包含的所有文件的总大小的最大允许值
      max-request-size: 20MB

mybatis-plus:
  configuration:
    # 日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 逻辑删除的字段
      logic-delete-field: delFlag
      # 代表已删除的值
      logic-delete-value: 1
      # 代表未删除的值
      logic-not-delete-value: 0
      # 主键自增策略,以mysql数据库为准
      id-type: auto

3. EasyCode插件

3.1 插件安装

搜索EasyCode,安装这个插件

KekeBlog项目实战(更新中)_第9张图片

3.2 插件使用

第一步连接数据库

KekeBlog项目实战(更新中)_第10张图片

KekeBlog项目实战(更新中)_第11张图片

第二步右键某个表,选中easycode,generate code

KekeBlog项目实战(更新中)_第12张图片

KekeBlog项目实战(更新中)_第13张图片

我选择在keke-framework模块下创建实体类,点击确定,查看生成的实体类如下

KekeBlog项目实战(更新中)_第14张图片

3.3 自定义模板

但它生成的实体类通常跟我们预期的不太符合,比如get/set方法我们期望用lombok去代替,那么我们应该去配置一下(图片上的是我配置好的)

可以在idea的settings里面修改

KekeBlog项目实战(更新中)_第15张图片

把entity.java.vm修改为如下,点击应用即可

##导入宏定义
$!{define.vm}

##保存文件(宏定义)
#save("/entity", ".java")

##包路径(宏定义)
#setPackageSuffix("entity")

##自动导入包(全局变量)
$!{autoImport.vm}
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

##表注释(宏定义)
#tableComment("表实体类")
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class $!{tableInfo.name} {
#foreach($column in $tableInfo.fullColumn)
    #if(${column.comment})//${column.comment}#end

    private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
#end

}

把dao.java.vm修改为如下,点击应用即可

##导入宏定义
$!{define.vm}

##设置表后缀(宏定义)
#setTableSuffix("Dao")

##保存文件(宏定义)
#save("/dao", "Dao.java")

##包路径(宏定义)
#setPackageSuffix("dao")

import com.baomidou.mybatisplus.core.mapper.BaseMapper;


##表注释(宏定义)
#tableComment("表数据库访问层")
public interface $!{tableName} extends BaseMapper<$!tableInfo.name> {

}

KekeBlog项目实战(更新中)_第16张图片

把service.java.vm修改为如下,点击应用即可

##导入宏定义
$!{define.vm}

##设置表后缀(宏定义)
#setTableSuffix("Service")

##保存文件(宏定义)
#save("/service", "Service.java")

##包路径(宏定义)
#setPackageSuffix("service")

import com.baomidou.mybatisplus.extension.service.IService;


##表注释(宏定义)
#tableComment("表服务接口")
public interface $!{tableName} extends IService<$!tableInfo.name> {

}

KekeBlog项目实战(更新中)_第17张图片

把serviceimpl.java.vm修改为如下,点击应用即可

##导入宏定义
$!{define.vm}

##设置表后缀(宏定义)
#setTableSuffix("ServiceImpl")

##保存文件(宏定义)
#save("/service/impl", "ServiceImpl.java")

##包路径(宏定义)
#setPackageSuffix("service.impl")

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

##表注释(宏定义)
#tableComment("表服务实现类")
@Service("$!tool.firstLowerCase($tableInfo.name)Service")
public class $!{tableName} extends ServiceImpl<$!{tableInfo.name}Mapper, $!{tableInfo.name}> implements $!{tableInfo.name}Service {

}

KekeBlog项目实战(更新中)_第18张图片

4. 代码(可用生成器)

第一步: 在keke-framework工程的src/main/java目录新建com.keke.domain.entity.Article类,写入如下

package com.keke.domain.entity;

import java.util.Date;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 文章表(Article)表实体类
 *
 * @author makejava
 * @since 2023-10-10 09:48:35
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Article {
    
    private Long id;
    //标题
    private String title;
    //文章内容
    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;

}

第二步: 在keke-framework工程的src/main/java目录新建com.keke.mapper.ArticleMapper接口,写入如下

package com.keke.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.keke.domain.entity.entity.Article;


/**
 * 文章表(Article)表数据库访问层
 *
 * @author makejava
 * @since 2023-10-10 09:59:36
 */
public interface ArticleMapper extends BaseMapper
{ }

第三步: 在keke-framework工程的src/main/java目录新建com.huanf.service.ArticleService接口,写入如下

package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.entity.entity.Article;


/**
 * 文章表(Article)表服务接口
 *
 * @author makejava
 * @since 2023-10-10 09:59:37
 */
public interface ArticleService extends IService
{ }

第四步: 在keke-framework工程的src/main/java目录新建com.keke.service.impl.ArticleServiceImpl类,写入如下

package com.keke.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.entity.entity.Article;
import com.keke.mapper.ArticleMapper;
import com.keke.service.ArticleService;
import org.springframework.stereotype.Service;

/**
 * 文章表(Article)表服务实现类
 *
 * @author makejava
 * @since 2023-10-10 09:59:39
 */
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl implements ArticleService {

}

我们做完上面的一系列代码操作后,包结构应如下

KekeBlog项目实战(更新中)_第19张图片

5. 博客前台测试

第一步: 在keke-blog工程的src/main/java目录新建com.keke.KeBlogApplication类,作为启动类,写入如下

package com.keke;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class KeBlogApplication {
     public static void main(String[] args) {
          SpringApplication.run(KeBlogApplication.class,args);
     }
}

第二步: 在keke-blog工程的resources目录新建File,文件名为application.yml文件,写入如下

server:
  port: 7777
spring:
  # 数据库连接信息
  datasource:
    url: jdbc:mysql://localhost:3306/keke_blog?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password:
    driver-class-name: com.mysql.cj.jdbc.Driver

    servlet:
      # 文件上传
      multipart:
        # 单个上传文件的最大允许大小
        max-file-size: 20MB
        # HTTP请求中包含的所有文件的总大小的最大允许值
        max-request-size: 20MB

  mybatis-plus:
    configuration:
        # mp日志
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
        db-config:
          # 逻辑删除的字段
          logic-delete-field: delFlag
          # 代表已删除的值
          logic-delete-value: 1
          # 代表未删除的值
          logic-not-delete-value: 0
          # 主键自增策略,以mysql数据库为准,而不是mp默认的雪花算法
          id-type: auto

第三步: 在keke-blog工程的src/main/java目录新建com.keke.controller.ArticleController类,写入如下

package com.keke.controller;

import com.keke.domain.entity.entity.Article;
import com.keke.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/article")
public class ArticleController {

     @Autowired
     private ArticleService articleService;

     @GetMapping("/list")
     public List
test(){ return articleService.list(); } }

第四步: 在keke-blog工程的KeBlogApplication引导类,修改为如下,主要就是添加了一个Mapper包扫描。不然我们用不了(注入不了)公共模块的类(bean)

package com.keke;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.keke.mapper")
public class KeBlogApplication {
     public static void main(String[] args) {
          SpringApplication.run(KeBlogApplication.class,args);
     }
}

第五步,由于之前keke-blog还是依赖keke-framework公共类的老版本jar包,所以我们需要需要在maven中选择父工程重新install一下

KekeBlog项目实战(更新中)_第20张图片

第六步我们启动后访问还是报错,查看错误提示发现是实体类和数据库表没有映射到

KekeBlog项目实战(更新中)_第21张图片

果断在实体类添加@TableName注解,代码如下

package com.keke.domain.entity.entity;

import java.util.Date;
import java.io.Serializable;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 文章表(Article)表实体类
 *
 * @author makejava
 * @since 2023-10-10 10:06:59
 */
@SuppressWarnings("serial")
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("ke_article")  //添加表名注解
public class Article {
    
    private Long id;
    //标题
    private String title;
    //文章内容
    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;

}

启动keke-blog工程,访问list接口成功

KekeBlog项目实战(更新中)_第22张图片

四、博客前台模块-热门文章列表

1. 文章表字段分析

KekeBlog项目实战(更新中)_第23张图片

2. 需求分析

需要查询浏览量最高的前10篇文章的信息。要求展示文章标题和浏览量。把能让用户自己点击跳转到具体的文章详情进行浏览

注意:不能把草稿展示出来,不能把删除了的文章查询出来。要按照浏览量进行降序排序

3. 统一响应格式

第一步: 在keke-framework公共模块的src/main/java目录新建com.keke.enums.AppHttpCodeEnum类,写入如下,作用是封装code和msg

package com.keke.enums;


public enum AppHttpCodeEnum {
    // 成功
    SUCCESS(200,"操作成功"),
    // 登录
    NEED_LOGIN(401,"需要登录后操作"),
    NO_OPERATOR_AUTH(403,"无权限操作"),
    SYSTEM_ERROR(500,"出现错误"),
    USERNAME_EXIST(501,"用户名已存在"),
    PHONENUMBER_EXIST(502,"手机号已存在"), EMAIL_EXIST(503, "邮箱已存在"),
    REQUIRE_USERNAME(504, "必需填写用户名"),
    LOGIN_ERROR(505,"用户名或密码错误");
    int code;
    String msg;

    AppHttpCodeEnum(int code, String errorMessage){
        this.code = code;
        this.msg = errorMessage;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

第二步: 在keke-framework公共模块的domain目录新建ResponseResult类,写入如下,作为统一响应格式的类

package com.keke.domain;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.keke.enums.AppHttpCodeEnum;
import java.io.Serializable;


//统一响应格式。实体类,或者这个类严格来说叫响应体
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult 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);
    }
    //空参构造方法,这里会创建一个对象,msg和code都默认是成功的值,适用处理一些响应简单的接口
    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;
    }

}

4. 代码实现

第一步: 把keke-framework公共模块的ArticleService修改为如下,定义了hotArticleList方法

package com.keke.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.entity.Article;


/**
 * 文章表(Article)表服务接口
 *
 * @author makejava
 * @since 2023-10-10 09:59:37
 */
public interface ArticleService extends IService
{ ResponseResult hotArticleList(); }

第二步: 把keke-framework公共模块的ArticleServiceImpl修改为如下,实现了hotArticleList方法

package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.entity.Article;
import com.keke.mapper.ArticleMapper;
import com.keke.service.ArticleService;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 文章表(Article)表服务实现类
 *
 * @author makejava
 * @since 2023-10-10 09:59:39
 */
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl implements ArticleService {

     @Override
     public ResponseResult hotArticleList() {
          //查询热门文章 封装成ResponseResult返回
          LambdaQueryWrapper
lambdaQueryWrapper = new LambdaQueryWrapper<>(); //必须是正式文章 lambdaQueryWrapper.eq(Article::getStatus,0); //按照浏览量进行排序 lambdaQueryWrapper.orderByDesc(Article::getViewCount); //最多查询10条,设置mp分页对象的参数分别为1和10 Page
page = new Page<>(1,10); //将page对象和lambdaQueryWrapper查询条件封装成page page(page,lambdaQueryWrapper); //page.getRecords()获取到所有符合条件的数据(也就是文章) List
articles = page.getRecords(); //返回ResponseResult对象 return ResponseResult.okResult(articles); } }

第三步: 把keke-blog工程的ArticleController类,修改为如下,增加了文章列表的统一响应格式的代码

package com.keke.controller;

import com.keke.domain.ResponseResult;
import com.keke.domain.entity.entity.Article;
import com.keke.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/article")
public class ArticleController {

     @Autowired
     private ArticleService articleService;

     //测试
     @GetMapping("/list")
     public List
test(){ return articleService.list(); } //热门文章 @GetMapping("/hotArticleList") public ResponseResult hotArticleList(){ ResponseResult result = articleService.hotArticleList(); return result; } }

第四步用postman发送请求,可以看到后端返回的响应数据,在控制台也打印出了mp的sql日志

KekeBlog项目实战(更新中)_第24张图片

KekeBlog项目实战(更新中)_第25张图片

5. 前端工程启动

第一步,解压前端工程,创建web目录结构如下,解压至如下目录

KekeBlog项目实战(更新中)_第26张图片

第二步: 运行前端项目,请确保电脑有安装node.js,然后以管理员身份打开命令行窗口,输入如下

KekeBlog项目实战(更新中)_第27张图片

cd f:
cd /BlogProject/web/keke-blog-vue
npm install 
npm run dev

执行完上述操作后就会出现如下

KekeBlog项目实战(更新中)_第28张图片

6. 前后端联调

打开前端页面http://localhost:8080后,发现并没有显示出来热门文章列表,F12打开控制台发现是CORS报错,原因是后端没有解决跨域问题,没有放行一些请求

KekeBlog项目实战(更新中)_第29张图片

7. 后端解决跨域

在keke-framework工程的src/main/java目录新建com.keke.config.WebConfig类,写入如下,然后重新运行keke-blog工程的启动类

package com.keke.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@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);
    }

}

再次启动工程,访问前端页面,就可以看到热门文章列表了

KekeBlog项目实战(更新中)_第30张图片

8. 响应格式优化

我们在查询热门文章列表时,后端返回给前端的数据中,可以看到是返回了所有字段的数据

KekeBlog项目实战(更新中)_第31张图片

但通常前端是不需要所有字段的数据的,仅仅需要表中某几个字段的数据即可,并且在实际项目中, 有些敏感字段是不能返回给前端使得前台用户看到,为了满足这一需求,我们通常把响应给前端的数据(前端需要的数据所封装而成的对象)称为VO(是Value Object的缩写,表示值对象

下面来创建热门文章VO,并把完善ServiceImpl代码

8.1 创建VO 

在keke-frameword工程的src/main/java目录新建com.keke.vo.HotArticleVO类,写入如下

package com.keke.domain.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class HotArticleVo {
     //id
     private Long id;
     //标题
     private String title;
     //访问量
     private Long viewCount;
}

8.2 BeanCopy工具类

这个工具类主要是实现不同Bean之间的相互转换,把一个bean对象中的字段拷贝到另一个bean对象的字段

它在spring项目中常用来封装VO对象向前端传递数据。

两对象中对应字段名和类型应完全相同,否则无法拷贝

在keke-framework工程的src/main/java目录新建com.keke.utils.BeanCopyUtils类,写入如下

package com.keke.utils;

import org.springframework.beans.BeanUtils;

import java.util.List;
import java.util.stream.Collectors;


//这个类用到很多泛型知识,可以对应去补一下
public class BeanCopyUtils {

    //私有的空参构造方法
    private BeanCopyUtils() {
    }

    //1.单个实体类的拷贝(暂时还用不上)。第一个参数是要拷贝的对象,第二个参数是类的字节码对象
    public static  V copyBean(Object source,Class clazz) {
        //创建目标对象
        V result = null;
        try {
            result = clazz.newInstance();
            //实现属性拷贝。也就是把source的属性拷贝到这个目标对象。BeanUtils是spring提供的工具类
            BeanUtils.copyProperties(source, result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //返回结果
        return result;
    }


    //2.集合的拷贝(在ArticleServiceImpl类里面会使用到)。第一个参数是要拷贝的集合,第二个参数是类的字节码对象
    public static  List copyBeanList(List list,Class clazz){
        //不使用for循环,使用stream流进行转换
        return list.stream()
                .map(o -> copyBean(o, clazz))
                //把结果转换成集合
                .collect(Collectors.toList());
    }
}

8.3 字面量处理

实际项目中都不允许直接在代码中使用字面值(代码中的固定值)。都需要定义成常量来使用。这种方式有利于提高代码的可维护性。字面值如下图

KekeBlog项目实战(更新中)_第32张图片

在keke-framework工程的src/main/java目录新建com.keke.constants.SystemConstants类,写入如下,作用是定义常量

package com.keke.constants;


//字面值(代码中的固定值)处理,把字面值都在这里定义成常量
public class SystemConstants {

    /**
     *  文章是草稿
     */
    public static final int ARTICLE_STATUS_DRAFT = 1;
    
    /**
     *  文章是正常分布状态
     */
    public static final int ARTICLE_STATUS_NORMAL = 0;
    
    /**
     * 文章列表当前查询页数
     */
    public static final int ARTICLE_STATUS_CURRENT = 1;

    /**
     * 文章列表每页显示的数据条数
     */
    public static final int ARTICLE_STATUS_SIZE = 10;
    
}

8.3 修改impl类

把keke-framework工程的ArticleServiceImpl类修改为如下,调用BeanCopyUtils类的copyBeanList方法,将Article和HotArticlVo之间进行转换

package com.keke.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.keke.constants.SystemConstants;
import com.keke.domain.ResponseResult;
import com.keke.domain.entity.entity.Article;
import com.keke.domain.vo.HotArticleVo;
import com.keke.mapper.ArticleMapper;
import com.keke.service.ArticleService;
import com.keke.utils.BeanCopyUtils;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 文章表(Article)表服务实现类
 *
 * @author makejava
 * @since 2023-10-10 09:59:39
 */
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl implements ArticleService {

     @Override
     public ResponseResult hotArticleList() {
          //查询热门文章 封装成ResponseResult返回
          LambdaQueryWrapper
lambdaQueryWrapper = new LambdaQueryWrapper<>(); //必须是正式文章 lambdaQueryWrapper.eq(Article::getStatus, SystemConstants.ARTICLE_STATUS_NORMAL); //按照浏览量进行排序 lambdaQueryWrapper.orderByDesc(Article::getViewCount); //最多查询10条,设置mp分页对象的参数分别为1和10 Page
page = new Page<>(SystemConstants.ARTICLE_STATUS_CURRENT,SystemConstants.ARTICLE_STATUS_SIZE); //将page对象和lambdaQueryWrapper查询条件封装成page page(page,lambdaQueryWrapper); //page.getRecords()获取到所有符合条件的数据(也就是文章) List
articles = page.getRecords(); //BeanCopy List hotArticleVos = BeanCopyUtils.copyBeanList(articles, HotArticleVo.class); //返回ResponseResult对象 return ResponseResult.okResult(hotArticleVos); } }

 

你可能感兴趣的:(KekeBlog,java,spring,boot,intellij-idea,spring,maven)