springboot+vue练手级项目,真实的在线博客系统

Posted m0_67403240

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot+vue练手级项目,真实的在线博客系统相关的知识,希望对你有一定的参考价值。

文章目录

spring boot 练手实战项目说明

后端视频链接

前端视频链接

码神之路网站所使用的博客,项目简单,需求明确,容易上手,非常适合做为练手级项目。

最终成品

blog.mszlu.com

项目讲解说明:

  1. 提供前端工程,只需要实现后端接口即可
  2. 项目以单体架构入手,先快速开发,不考虑项目优化,降低开发负担
  3. 开发完成后,开始优化项目,提升编程思维能力
  4. 比如页面静态化,缓存,云存储,日志等
  5. docker部署上线
  6. 云服务器购买,域名购买,域名备案等

项目使用技术 :

springboot + mybatisplus+redis+mysql

代码地址:
个人博客代码地址

基础知识

Spring常用注解,注解 IOC ,AOP,MVC 的理解
mybatisDao层 Mapper层 controller层 service层 model层 entity层 简介
mall商场学习文档
mybatisplus学习文档
mybatisplus配套代码
easycode搭配mybatisplus巨爽
@Autowired 的时候为什么注入接口不是实现类
@Service注解为什么是标注在实现类上的
mapper接口需要加注解吗?通过MapperScan减少注解
@Mapper与@MapperScan注解的作用是什么?

推荐安装插件
配好@Data使用

查看文章代码结构

自动提示编写的代码

快速生成xml文件

区分括号

vscode插件

面试准备

springboot个人博客项目面试准备

1. 工程搭建

前端的工程:

npm install
npm run build
npm run dev

1.1 新建maven工程

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mszlu</groupId>
    <artifactId>blog-parent</artifactId>
    <version>1.0-SNAPSHOT</version>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <!-- 排除 默认使用的logback  -->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- log4j2 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>


        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

删除src文件

1.1.2遇到的bug

maven子工程如何引用父工程

1.2 配置

数据库配置

配置
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
使其打印到控制台
配置
mybatis-plus.global-config.db-config.table-prefix=ms_
代表标识下前缀是ms_,这样使其省略前缀,不用再定义前缀的表名

#server
server.port= 8888
spring.application.name=mszlu_blog
# datasource
spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.table-prefix=ms_




package com.mszlu.blog.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author ljm
 * @Date 2021/10/10 20:11
 * @Version 1.0
 */
//让spring可以扫描到它
@Configuration
//扫包,将此包下的接口生成代理实现类,并且注册到spring容器中
@MapperScan("com.mszlu.blog.dao.mapper")
public class MybatisPlusConfig 

    //分页插件
    //@Configuration可理解为用spring的时候xml里面的<beans>标签。
    // @Bean可理解为用spring的时候xml里面的<bean>标签。
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor()
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    




package com.mszlu.blog.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("/**").allowedOrigins("http://localhost:8080");
    

1.3 启动类

package com.mszlu.blog;

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

@SpringBootApplication
public class BlogApp 

    public static void main(String[] args) 
        SpringApplication.run(BlogApp.class,args);
    

2. 首页-文章列表

2.1 接口说明

接口url:/articles

请求方式:POST

请求参数:

参数名称

参数类型

说明

page

int

当前页数

pageSize

int

每页显示的数量

返回数据:


    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        
            "id": 1,
            "title": "springboot介绍以及入门案例",
            "summary": "通过Spring Boot实现的服务,只需要依靠一个Java类,把它打包成jar,并通过`java -jar`命令就可以运行起来。

这一切相较于传统Spring应用来说,已经变得非常的轻便、简单。",
            "commentCounts": 2,
            "viewCounts": 54,
            "weight": 1,
            "createDate": "2609-06-26 15:58",
            "author": "12",
            "body": null,
            "tags": [
                
                    "id": 5,
                    "avatar": null,
                    "tagName": "444"
                ,
                
                    "id": 7,
                    "avatar": null,
                    "tagName": "22"
                ,
                
                    "id": 8,
                    "avatar": null,
                    "tagName": "11"
                
            ],
            "categorys": null
        ,
        
            "id": 9,
            "title": "Vue.js 是什么",
            "summary": "Vue (读音 /vju?/,类似于 view) 是一套用于构建用户界面的渐进式框架。",
            "commentCounts": 0,
            "viewCounts": 3,
            "weight": 0,
            "createDate": "2609-06-27 11:25",
            "author": "12",
            "body": null,
            "tags": [
                
                    "id": 7,
                    "avatar": null,
                    "tagName": "22"
                
            ],
            "categorys": null
        ,
        
            "id": 10,
            "title": "Element相关",
            "summary": "本节将介绍如何在项目中使用 Element。",
            "commentCounts": 0,
            "viewCounts": 3,
            "weight": 0,
            "createDate": "2609-06-27 11:25",
            "author": "12",
            "body": null,
            "tags": [
                
                    "id": 5,
                    "avatar": null,
                    "tagName": "444"
                ,
                
                    "id": 6,
                    "avatar": null,
                    "tagName": "33"
                ,
                
                    "id": 7,
                    "avatar": null,
                    "tagName": "22"
                ,
                
                    "id": 8,
                    "avatar": null,
                    "tagName": "11"
                
            ],
            "categorys": null
        
    ]

2.2 编码

2.2.0 Spring—基于注解开发

Spring基于注解的开发
每个注解的作用

2.2.1 表结构

文章表

CREATE TABLE `blog`.`ms_article`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `comment_counts` int(0) NULL DEFAULT NULL COMMENT '评论数量',
  `create_date` bigint(0) NULL DEFAULT NULL COMMENT '创建时间',
  `summary` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '简介',
  `title` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题',
  `view_counts` int(0) NULL DEFAULT NULL COMMENT '浏览数量',
  `weight` int(0) NOT NULL COMMENT '是否置顶',
  `author_id` bigint(0) NULL DEFAULT NULL COMMENT '作者id',
  `body_id` bigint(0) NULL DEFAULT NULL COMMENT '内容id',
  `category_id` int(0) NULL DEFAULT NULL COMMENT '类别id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

标签表
id,文章id,标签id,通过文章id可以间接查到标签id

CREATE TABLE `blog`.`ms_tag`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `article_id` bigint(0) NOT NULL,
  `tag_id` bigint(0) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `article_id`(`article_id`) USING BTREE,
  INDEX `tag_id`(`tag_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

用户表

CREATE TABLE `blog`.`ms_sys_user`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `account` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '账号',
  `admin` bit(1) NULL DEFAULT NULL COMMENT '是否管理员',
  `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',
  `create_date` bigint(0) NULL DEFAULT NULL COMMENT '注册时间',
  `deleted` bit(1) NULL DEFAULT NULL COMMENT '是否删除',
  `email` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `last_login` bigint(0) NULL DEFAULT NULL COMMENT '最后登录时间',
  `mobile_phone_number` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号',
  `nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `salt` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加密盐',
  `status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '状态',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

entity层

别名: model层 ,domain层
用途: 实体层,用于存放我们的实体类,与数据库中的属性值基本保持一致,实现set和get的方法。
例子:user表的实体User
文章

package com.mszlu.blog.dao.pojo;

import lombok.Data;

@Data
public class Article 

    public static final int Article_TOP = 1;

    public static final int Article_Common = 0;

    private Long id;

    private String title;

    private String summary;

    private int commentCounts;

    private int viewCounts;

    /**
     * 作者id
     */
    private Long authorId;
    /**
     * 内容id
     */
    private Long bodyId;
    /**
     *类别id
     */
    private Long categoryId;

    /**
     * 置顶
     */
    private int weight = Article_Common;


    /**
     * 创建时间
     */
    private Long createDate;

用户

package com.mszlu.blog.dao.pojo;

import lombok.Data;

@Data
public class SysUser 

    private Long id;

    private String account;

    private Integer admin;

    private String avatar;

    private Long createDate;

    private Integer deleted;

    private String email;

    private Long lastLogin;

    private String mobilePhoneNumber;

    private String nickname;

    private String password;

    private String salt;

    private String status;

标签

package com.mszlu.blog.dao.pojo;

import lombok.Data;

@Data
public class Tag 

    private Long id;

    private String avatar;

    private String tagName;


2.2.2 Controller

controller层。控制器,导入service层,因为service中的方法是我们使用到的,controller通过接收前端传过来的参数进行业务操作,在返回一个指定的路径或者数据表

package com.mszlu.blog.api;

import com.mszlu.blog.dao.pojo.Article;
import com.mszlu.blog.service.ArticleService;
import com.mszlu.blog.vo.Archive;
import com.mszlu.blog.vo.ArticleVo;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.PageParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("articles")
public class ArticleController 

    @Autowired
    private ArticleService articleService;
	//Result是统一结果返回
    @PostMapping
    public Result articles(@RequestBody PageParams pageParams) 
        //ArticleVo 页面接收的数据
        List<ArticleVo> articles = articleService.listArticlesPage(pageParams);

        return Result.success(articles);
    



统一最后的结果

package com.mszlu.blog.vo;

import com.mszlu.blog.dao.pojo.Article;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result 

    private boolean success;

    private Integer code;

    private String msg;

    private Object data;


    public static Result success(Object data) 
        return new Result(true,200,"success",data);
    
    public static Result fail(Integer code, String msg) 
        return new Result(false,code,msg,null);
    

建立与前端交互的Vo文件

package com.mszlu.blog.vo;

import com.mszlu.blog.dao.pojo.ArticleBody;
import com.mszlu.blog.dao.pojo.Category;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.dao.pojo.Tag;
import lombok.Data;

import java.util.List;

@Data
public class ArticleVo 

    private Long id;

    private String title;

    private String summary;

    private int commentCounts;

    private int viewCounts;

    private int weight;
    /**
     * 创建时间
     */
    private String createDate;

    private String author;

    private ArticleBodyVo body;

    private List<TagVo> tags;

    private List<CategoryVo> categorys;


2.2.3 Service

service层主要是写业务逻辑方法,service层经常要调用dao层(也叫mapper层)的方法对数据进行增删改查的操作。*

2.2.3.0 解决mapper爆红

解决mapper爆红

建立service接口

package com.mszlu.blog.service;

import com.mszlu.blog.vo.Archive;
import com.mszlu.blog.vo.ArticleVo;
import com.mszlu.blog.vo.params.PageParams;

import java.util.List;

public interface ArticleService 

    /**
     * 分页查询文章列表
     * @param pageParams
     * @return
     */
    Result listArticle(PageParams pageParams);


建立service接口的实现类

package com.mszlu.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mszlu.blog.dao.ArticleMapper;
import com.mszlu.blog.dao.SysUserMapper;
import com.mszlu.blog.dao.pojo.Article;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.dao.pojo.Tag;
import com.mszlu.blog.service.ArticleService;
import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.service.TagsService;
import com.mszlu.blog.vo.ArticleBodyVo;
import com.mszlu.blog.vo.ArticleVo;
import com.mszlu.blog.vo.TagVo;
import com.mszlu.blog.vo.params.PageParams;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;


@Service
public class ArticleServiceImpl implements ArticleService 

    @Autowired
    private ArticleMapper articleMapper;
    @Autowired
    private TagService tagService;

    @Autowired
    private SysUserService sysUserService;

    @Override
    public Result listArticle(PageParams pageParams) 
        /**
         * 1、分页查询article数据库表
         */
        Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        //是否置顶进行排序,        //时间倒序进行排列相当于order by create_data desc
        queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate);
        Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);
        //分页查询用法 https://blog.csdn.net/weixin_41010294/article/details/105726879
        List<Article> records = articlePage.getRecords();
        // 要返回我们定义的vo数据,就是对应的前端数据,不应该只返回现在的数据需要进一步进行处理
        List<ArticleVo> articleVoList =copyList(records,true,true);
        return Result.success(articleVoList);
    

    private List<ArticleVo> copyList(List<Article> records,boolean isTag,boolean isAuthor) 

        List<ArticleVo> articleVoList = new ArrayList<>();

        for (Article record : records) 
            articleVoList.add(copy(record,isTag,isAuthor));
        
        return articleVoList;

    

    //"eop的作用是对应copyList,集合之间的copy分解成集合元素之间的copy
    private ArticleVo copy(Article article,boolean isTag,boolean isAuthor)
        ArticleVo articleVo = new ArticleVo();
        //BeanUtils.copyProperties用法   https://blog.csdn.net/Mr_linjw/article/details/50236279
        BeanUtils.copyProperties(article, articleVo);
        articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
        //并不是所有的接口都需要标签和作者信息
        if(isTag)
            Long articleId = article.getId();
            articleVo.setTags(tagService.findTagsByArticleId(articleId));
        
        if (isAuthor) 
            //拿到作者id
            Long authorId = article.getAuthorId();

            articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname());
        
        return articleVo;

    

建立用户的service接口

package com.mszlu.blog.service;

import com.mszlu.blog.dao.pojo.SysUser;

public interface UserService 

    SysUser findUserById(Long userId);

建立用户的service接口实现类

package com.mszlu.blog.service.impl;

import com.mszlu.blog.dao.SysUserMapper;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SysUserServiceImpl implements SysUserService 

    @Autowired
    private SysUserMapper sysUserMapper;

    @Override
    public SysUser findUserById(Long id) 
        //根据id查询
        //为防止sysUser为空增加一个判断
        SysUser sysUser = sysUserMapper.selectById(id);
        if (sysUser == null)
            sysUser = new SysUser();
            sysUser.setNickname("码神之路");
        
        return sysUser;
    

2.2.3.1mybatisplus遇到多表查询怎么办

TagMapper的建立中遇到这个问题了,办法是在建立到TagMapper后需要建立xml文件进行读写
xml文件放到resource文件夹下
文件夹名和xml文件名必须和TagMapper.java文件夹保持一致

2.2.3.2创建文件夹时遇到的坑

使用IntelliJ IDEA创建多级文件夹时,文件夹名为com.immer.monitor.persistence 和 com/immer/monitor/persistence 均会显示为如下图所示

但实际结构确实截然不同 com.immer.monitor.persistence 是单个文件夹 而 com/immer/monitor/persistence 是一个文件夹嵌套

会导致资源文件not found 的问题,而且很难排查得到
要不会显示找不到文件夹路径的问题,因为我们要保证mapper.xml要和mapper的文件夹和路径保持一致

如何创建多级文件夹

建立标签的service接口

package com.mszlu.blog.service;

import com.mszlu.blog.dao.pojo.Tag;
import com.mszlu.blog.vo.TagVo;

import java.util.List;

public interface TagsService 


    List<TagVo> findTagsByArticleId(Long id);

建立tag的service接口的实现类

package com.mszlu.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.blog.dao.TagMapper;
import com.mszlu.blog.dao.pojo.Tag;
import com.mszlu.blog.service.TagsService;
import com.mszlu.blog.vo.TagVo;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

@Service
public class TagsServiceImpl implements TagsService 
    @Autowired
    private TagMapper tagMapper;

    /**
     * ms_article_tag是文章和标签的关联表
     * ms_tag为单纯的标签的表
     * @param articleId
     * @return
     */

    @Override
    public List<TagVo> findTagsByArticleId(Long articleId) 
        //mybatisplus无法进行多表查询
        List<Tag> tags = tagMapper.findTagsByArticleId(articleId);
        return copyList(tags);
    

    private List<TagVo> copyList(List<Tag> tagList) 
        List<TagVo> tagVoList = new ArrayList<>();
        for (Tag tag : tagList) 
            tagVoList.add(copy(tag));
        
        return tagVoList;

    

    private TagVo copy(Tag tag) 
        TagVo tagVo = new TagVo();
        BeanUtils.copyProperties(tag, tagVo);
        return tagVo;
    

2.2.4 Dao层

mapper层=dao层,现在用mybatis逆向工程生成的mapper层,其实就是dao层。
dao层对数据库进行数据持久化操作,他的方法语句是直接针对数据库操作的,而service层是针对我们controller,也就是针对我们使用者。service的impl是把mapper和service进行整合的文件。

dao层和service层关系:service层经常要调用dao层的方法对数据进行增删改查的操作,现实开发中,对业务的操作会涉及到数据的操作,而对数据操作常常要用到数据库,所以service层会经常调用dao层的方法。

文章的dao层
由于我们直接继承了mybatisplus的BaseMapper所以基本的增删改查都不用再写了。

package com.mszlu.blog.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.Article;
import com.mszlu.blog.vo.ArticleVo;

import java.util.List;

public interface ArticleMapper extends BaseMapper<Article> 
	

标签的dao层

package com.mszlu.blog.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.Tag;

import java.util.List;

public interface TagMapper extends BaseMapper<Tag> 

    List<Tag> findTagsByArticleId(Long articleId);

作者的dao层

package com.mszlu.blog.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.SysUser;

public interface SysUserMapper extends BaseMapper<SysUser> 

这就是上文提到的mybatisplus遇到多表查询怎么办,我们需要建立自己的xml文件进行联合查询操作

在mapper文件中
利用mapperX插件我们可以创建
方法名为select的命令

    <select id="findTagsByArticleId" resultType="com.mszlu.blog.dao.pojo.Tag"></select>

总的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.mszlu.blog.dao.mapper.TagMapper">

    <sql id="all">
        id,avatar,tag_name as tagName
    </sql>

<!--        List<Tag> findTagsByArticleId(Long articleId);
在这个文件中,id代表方法名,parameterType表示输入变量的类型,resultType表示泛型的类型-->

    <select id="findTagsByArticleId" parameterType="long" resultType="com.mszlu.blog.dao.pojo.Tag">
        select  id,avatar,tag_name as tagName from ms_tag
        where id in (select tag_id from ms_article_tag where article_id=#articleId)
    </select>
</mapper>

2.2.5 测试

3. 首页-最热标签

3.1 接口说明

接口url:/tags/hot

请求方式:GET

请求参数:无

id
标签名称
我们期望点击标签关于文章的所有列表都显示出来

返回数据:


    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        
            "id":1,
            "tagName":"4444"
        
    ]

3.2 编码

3.2.1 Controller

package com.mszlu.blog.api;

import com.mszlu.blog.service.ArticleService;
import com.mszlu.blog.service.TagsService;
import com.mszlu.blog.vo.Archive;
import com.mszlu.blog.vo.ArticleVo;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.TagVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

//@RestController代表我们返回的是json数据,@RequestMapping("tags")表示路径映射
@RestController
@RequestMapping("tags")
public class TagsController 

    @Autowired
    private TagService tagService;

    // 路径 tags/hot
    @GetMapping("hot")
    public Result hot()
        int limit =6;
        return  tagService.hots(limit);
    

vo表示后端与前端交互的数据

package com.mszlu.blog.vo;

import lombok.Data;

@Data
public class TagVo 

    private Long id;

    private String tagName;

3.2.2 Service

建立service接口

package com.mszlu.blog.service;

import com.mszlu.blog.vo.TagVo;

import java.util.List;

public interface TagsService 

    Result hots(int limit);


建立serviceimpl实现类

package com.mszlu.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.blog.dao.TagMapper;
import com.mszlu.blog.dao.pojo.Tag;
import com.mszlu.blog.service.TagsService;
import com.mszlu.blog.vo.TagVo;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

@Service
public class TagsServiceImpl implements TagsService 
    @Autowired
    private TagMapper tagMapper;

     @Override
    public Result hots(int limit) 
        /**
         * 最热标签就是对标签ms_article_tag中的tag_id进行排序数量最多的就是我们的最热标签
         * 1、标签所拥有的文章数量最多就是最热标签
         * 2、查询 根据tag_id分组计数,从大到小排列取前limit个
         */
        List<Long> tagIds = tagMapper.findHotsTagIds(limit);
        //因为id in(1,2,3) 里面不能为空所以我们需要进行判断
        //  CollectionUtils.isEmpty作用 https://blog.csdn.net/qq_42848910/article/details/105717235
        if(CollectionUtils.isEmpty(tagIds))
            return Result.success(Collections.emptyList());
        
        //需求的是tagId 和tagName Tag对象
        //我们的sql语句类似于select * from tag where id in (1,2,3)
        List<Tag> tagList = tagMapper.findTagsByTagIds(tagIds);
        return Result.success(tagList);
    



3.2.3 Dao

TagMapper.java

package com.mszlu.blog.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.Tag;

import java.util.List;

public interface TagMapper extends BaseMapper<Tag> 

    /**
     * 查询最热的标签前n条
     * @param limit
     * @return
     */
    List<Long> findHotsTagIds(int limit);

    List<Tag> findTagsByTagIds(List<Long> tagIds);

TagMapper.xml文件
一定要了解所有表的业务逻辑,知道自己要返回什么值再进行操作
我们通过findHotsTagIds这个方法在ms_article_tag表中找到了tag_id

然后多表查询,tag_id就是ms_tag表中的id我们在findHotsTagIds这个方法中找到了我们想要的前两条id,然后再利用动态mysql这个方法将id,tagName两个选项选择出来。多写多看基本上可以成为一个合格的crud工程师

<?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.mszlu.blog.dao.TagMapper">

<!--    List<Long> findHotsTagIds(int limit);-->
<!--    parameterType="int"是自己加的因为不会自动生成我们输入的标签,#limit为我们自己传的参数-->
<!--  GROUP by 用法  https://www.runoob.com/sql/sql-groupby.html-->
<!--    sql语句的意思是在ms_article_tag表中查找tag_id,根据tag_id将其聚合在一起,再根据count(*)的数量以递减的顺序排序最后限制输出两条数据-->
    <select id="findHotsTagIds" parameterType="int" resultType="java.lang.Long">
        select tag_id from ms_article_tag GROUP BY tag_id ORDER BY count(*) DESC LIMIT #limit
    </select>
<!--    List<Tag> findTagsByTagIds(List<Long> tagIds);因为输入的类型是list所以parameterType的值是list-->
<!--    foreach用法 https://www.cnblogs.com/fnlingnzb-learner/p/10566452.html
            相当于for循环找传进来的一个id集合,每个id通过sql语句找到对应的tag对象-->
    <select id="findTagsByTagIds" parameterType="list" resultType="com.mszlu.blog.dao.pojo.Tag">
        select id,tag_name as tagName from ms_tag
        where  id in
        <foreach collection="tagIds" item="tagId" separator="," open="(" close=")">
            #tagId
        </foreach>
    </select>

</mapper>

3.2.4 测试

最热标签显示出来

4.1. 统一异常处理

不管是controller层还是service,dao层,都有可能报异常,如果是预料中的异常,可以直接捕获处理,如果是意料之外的异常,需要统一进行处理,进行记录,并给用户提示相对比较友好的信息。

package com.mszlu.blog.handler;

import com.mszlu.blog.vo.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

//对加了@controller注解的方法进行拦截处理 Aop的实现
@ControllerAdvice
public class AllExceptionHandler 

    //进行异常处理,处理Exception.class的异常
    @ExceptionHandler(Exception.class)
    @ResponseBody //返回json数据如果不加就返回页面了
    public Result doException(Exception ex) 
        //e.printStackTrace();是打印异常的堆栈信息,指明错误原因,
        // 其实当发生异常时,通常要处理异常,这是编程的好习惯,所以e.printStackTrace()可以方便你调试程序!
        ex.printStackTrace();
        return Result.fail(-999,"系统异常");

    

4.2. 首页-最热文章

在ms_article表中的view_counts表示浏览数量,越多表示越火热

4.2.1 接口说明

接口url:/articles/hot

请求方式:POST

请求参数:

参数名称

参数类型

说明

返回数据:


    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        
            "id": 1,
            "title": "springboot介绍以及入门案例",
        ,
        
            "id": 9,
            "title": "Vue.js 是什么",
        ,
        
            "id": 10,
            "title": "Element相关",
            
        
    ]

4.2.2 Controller

ArticleController.java

    /**
     * 首页最热文章
     * @return
     */
@PostMapping("hot")
    public Result hotArticle()
        int limit = 5;
        return articleService.hotArticle(limit);
    

4.2.3 Service

src/main/java/com/mszlu/blog/service/ArticleService.java

package com.mszlu.blog.service;

import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.PageParams;

/**
 * @Author ljm
 * @Date 2021/10/11 10:30
 * @Version 1.0
 */
public interface ArticleService 

    /**
     * 分页查询文章列表
     * @param pageParams
     * @return
     */
    Result listArticle(PageParams pageParams);

    /**
     * 最热文章
     * @param limit
     * @return
     */
    Result hotArticle(int limit);

src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java

    @Override
    public Result hotArticle(int limit) 
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Article::getViewCounts);
        queryWrapper.select(Article::getId,Article::getTitle);
        //"limit"字待串后要加空格,不要忘记加空格,不然会把数据拼到一起
        queryWrapper.last("limit "+limit);
        //select id,title from article order by view_counts desc limt 5
        List<Article> articles = articleMapper.selectList(queryWrapper);

        //返回vo对象
        return Result.success(copyList(articles,false,false));
    

4.2.4 测试

4.3. 首页-最新文章

和最热文章非常类似,一个是根据浏览量来选择,一个是根据最新创建时间来选择

4.3.1 接口说明

接口url:/articles/new

请求方式:POST

请求参数:

参数名称

参数类型

说明

返回数据:


    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        
            "id": 1,
            "title": "springboot介绍以及入门案例",
        ,
        
            "id": 9,
            "title": "Vue.js 是什么",
        ,
        
            "id": 10,
            "title": "Element相关",
            
        
    ]

4.3.1 Controller

 /**
     * 首页 最新文章
     * @return
     */
    @PostMapping("new")
    public Result newArticles()
        int limit = 5;
        return articleService.newArticles(limit);
    

4.3.2 Service

src/main/java/com/mszlu/blog/service/ArticleService.java

    /**
     * 最新文章
     * @param limit
     * @return
     */
    Result newArticles(int limit);

src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java

@Override
    public Result newArticles(int limit) 
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Article::getCreateDate);
        queryWrapper.select(Article::getId,Article::getTitle);
        queryWrapper.last("limit "+limit);
        //select id,title from article order by create_date desc limit 5
        List<Article> articles = articleMapper.selectList(queryWrapper);

        return Result.success(copyList(articles,false,false));
    

4.4. 首页-文章归档

每一篇文章根据创建时间某年某月发表多少篇文章

4.4.1接口说明

接口url:/articles/listArchives

请求方式:POST

请求参数:

参数名称

参数类型

说明

返回数据:


    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        
            "year": "2021",
            "month": "6",
            "count": 2
        
            
    ]



?        select year(create_date) as year,month(create_date) as month,count(*) as count from ms_article group by year,month

但是
p9 up主给的sql里面create_date 为bigint 13位,直接year()不行,需要先转date型后year()。

select year(FROM_UNIXTIME(create_date/1000)) year,month(FROM_UNIXTIME(create_date/1000)) month, count(*) count from ms_article group by year,month;

这样才能查出来结果

4.4.1 Controller

src/main/java/com/mszlu/blog/controller/ArticleController.java

  /**
     * 首页 文章归档
     * @return
     */
    @PostMapping("listArchives")
    public Result listArchives()
        return articleService.listArchives();
    

下面这个是在src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java中使用的返回值

package com.mszlu.blog.dao.dos;

import lombok.Data;

/**
 * @Author ljm
 * @Date 2021/10/12 17:19
 * @Version 1.0
 * do 对象 数据库 查询出来的对象但是不需要持久化,由于do是关键字所以加了个s成为dos
 */
@Data
public class Archives 

    private Integer year;

    private Integer month;

    private Long count;

4.4.2 Service

src/main/java/com/mszlu/blog/service/ArticleService.java

    /**
     * 文章归档
     * @return
     */
    Result listArchives();

src/main/java/com/mszlu/blog/service/impl/ArticleServiceImpl.java

@Override
public Result listArchives() 
        /*
        文章归档
         */
    List<Archives> archivesList = articleMapper.listArchives();
    return Result.success(archivesList);

4.4.3 Dao

src/main/java/com/mszlu/blog/dao/mapper/ArticleMapper.java

package com.mszlu.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.Article;

import java.util.List;
import java.util.Map;

public interface ArticleMapper extends BaseMapper<Article> 

  List<Archives> listArchives();


src/main/resources/com/mszlu/blog/dao/mapper/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">
<!--创立ArticleMapper.xml文件后再利用mybatisX一键生成select语句-->
<mapper namespace="com.mszlu.blog.dao.mapper.ArticleMapper">


    <select id="listArchives" resultType="com.mszlu.blog.dao.dos.Archives">
        select year(FROM_UNIXTIME(create_date/1000)) as year,month(FROM_UNIXTIME(create_date/1000)) as month, count(*) as count from ms_article
        group by year,month
    </select>

</mapper>

4.4.4 测试

注意:前端工程 需使用当天资料下的app

5.1. 登录

5.1.1 接口说明

接口url:/login

请求方式:POST

请求参数:

参数名称

参数类型

说明

account

string

账号

password

string

密码

返回数据:


    "success": true,
    "code": 200,
    "msg": "success",
    "data": "token"

5.1.2 JWT

登录使用JWT技术。

jwt 可以生成 一个加密的token,做为用户登录的令牌,当用户登录成功之后,发放给客户端。

请求需要登

以上是关于springboot+vue练手级项目,真实的在线博客系统的主要内容,如果未能解决你的问题,请参考以下文章

vue练手小项目--眼镜在线试戴

SpringBoot练手项目总结

手撸一个基于Springboot+Vue的书籍论坛系统,可用于课程设计和毕业设计或者练手

手撸一个基于Springboot+Vue的书籍论坛系统,可用于课程设计和毕业设计或者练手

Spring Boot + Vue 音乐网站开源项目,前后端分离,拿来练手真不错!

手撸一个基于Springboot+Vue的书籍论坛系统,可用于课程设计和毕业设计或者练手