栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

文章目录
  • spring boot 练手实战项目说明
  • 基础知识
  • 面试准备
  • 1. 工程搭建
    • 1.1 新建maven工程
    • 1.1.2遇到的bug
    • 1.2 配置
    • 1.3 启动类
  • 2. 首页-文章列表
    • 2.1 接口说明
    • 2.2 编码
    • 2.2.0 Spring---基于注解开发
      • 2.2.1 表结构
      • entity层
      • 2.2.2 Controller
      • 2.2.3 Service
    • 2.2.3.0 解决mapper爆红
    • 2.2.3.1mybatisplus遇到多表查询怎么办
    • 2.2.3.2创建文件夹时遇到的坑
      • 2.2.4 Dao层
      • 2.2.5 测试
  • 3. 首页-最热标签
    • 3.1 接口说明
    • 3.2 编码
      • 3.2.1 Controller
      • 3.2.2 Service
      • 3.2.3 Dao
      • 3.2.4 测试
  • 4.1. 统一异常处理
  • 4.2. 首页-最热文章
    • 4.2.1 接口说明
    • 4.2.2 Controller
    • 4.2.3 Service
    • 4.2.4 测试
  • 4.3. 首页-最新文章
    • 4.3.1 接口说明
    • 4.3.1 Controller
    • 4.3.2 Service
  • 4.4. 首页-文章归档
    • 4.4.1接口说明
    • 4.4.1 Controller
    • 4.4.2 Service
    • 4.4.3 Dao
    • 4.4.4 测试
  • 5.1. 登录
    • 5.1.1 接口说明
    • 5.1.2 JWT
    • 5.1.3 Controller
    • 5.1.4 Service
    • 5.1.5 登录参数,redis配置,统一错误码
    • 5.1.6 测试
  • 5.2. 获取用户信息
    • 5.2.1 接口说明
    • 5.2.2 Controller
    • 5.2.3 Service
    • 5.2.4 LoginUserVo
    • 5.2.5 测试
  • 5.3. 退出登录
    • 5.3.1 接口说明
    • 5.3.2 Controller
    • 5.3.3 Service
    • 5.3.4 测试
  • 6.1. 注册
    • 6.1.1 接口说明
    • 6.1.2 Controller
    • 6.1.3 Service
    • 6.1.4 加事务
    • 6.1.5 测试
  • 6.2. 登录拦截器
    • 6.2.1 拦截器实现
    • 6.2.2 使拦截器生效
    • 6.2.3 测试
  • 6.3. ThreadLocal保存用户信息
  • 7.1. ThreadLocal内存泄漏
  • 7.2. 文章详情
    • 7.2.1 接口说明
    • 7.2.2 涉及到的表
    • 7.2.3 Controller
    • 7.2.4 Service
    • 7.2.5 测试
  • 7.3. 使用线程池 更新阅读次数
    • 7.3.1 线程池配置
    • 7.3.1 使用
    • 7.3.3 测试
  • Bug修正
  • 8.1. 评论列表
    • 8.1.1 接口说明
    • 8.1.2 Controller
    • 8.1.3 Service
  • 8.2. 评论
    • 8.2.1 接口说明
    • 8.2.2 加入到登录拦截器中
    • 8.2.3 Controller
    • 8.2.4 Service
  • 写文章
  • 9.1. 所有文章分类
    • 9.1.1 接口说明
    • 9.1.2 Controller
    • 9.1.3 Service
  • 9.2. 所有文章标签
    • 9.2.1 接口说明
    • 9.2.2 Controller
    • 9.2.3 Service
  • 9.3. 发布文章
    • 9.3.1 接口说明
    • 9.3.2 Controller
    • 9.3.3 Service
    • 9.3.4 测试
  • 9.4. AOP日志
  • bug修正
  • 10.1. 文章图片上传
    • 10.1.1 接口说明
    • 10.1.2 Controller
    • 10.1.3 使用七牛云
    • 10.1.4 测试
  • 10.2. 导航-文章分类
    • 10.2.1 查询所有的文章分类
      • 10.2.1.1 接口说明
      • 10.2.1.2 Controller
      • 10.2.1.3 Service
    • 10.2.2 查询所有的标签
      • 10.2.2.1 接口说明
      • 10.2.2.3 Controller
      • 10.2.2.4 Service
  • 10.3. 分类文章列表
    • 10.3.1 接口说明
    • 10.3.2 Controller
    • 10.3.3 Service
  • 10.4. 标签文章列表
    • 10.4.1 接口说明
    • 10.4.2 Controller
    • 10.4.3 Service
    • 10.4.4 修改原有的查询文章接口
    • 10.4.5 测试
  • 11.1. 归档文章列表
    • 11.1.1 接口说明
    • 11.1.2 文章列表参数
    • 11.1.3 使用自定义sql 实现文章列表
    • 11.1.4 测试
  • 11.2. 统一缓存处理(优化)
  • 11.3. 思考别的优化
  • 管理后台
  • 12.1. 搭建项目
    • 12.1.1 新建maven工程 blog-admin
    • 12.1.2 配置
    • 12.1.3 启动类
    • 12.1.4 导入前端工程
    • 12.1.5 新建表
  • 12.2. 权限管理
    • 12.2.1 Controller
    • 12.2.2 Service
    • 12.2.3 测试
  • 12.3. Security集成
    • 12.3.1 添加依赖
    • 12.3.2 配置
    • 12.3.3 登录认证
    • 12.3.4 权限认证
  • 12.4. 作业
  • 13.总结技术亮点
  • 14.前端
  • 15、缓存一致性问题

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巨爽

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

查看文章代码结构

自动提示编写的代码

快速生成xml文件

区分括号

vscode插件

面试准备

springboot个人博客项目面试准备

1. 工程搭建

前端的工程:

npm install
npm run build
npm run dev
1.1 新建maven工程


    4.0.0

    com.mszlu
    blog-parent
    1.0-SNAPSHOT


    
        org.springframework.boot
        spring-boot-starter-parent
        2.5.0
        
    

    
        UTF-8
        UTF-8
        1.8
    

    

        
            org.springframework.boot
            spring-boot-starter
            
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
            
        

        
        
            org.springframework.boot
            spring-boot-starter-log4j2
        

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

        
            org.springframework.boot
            spring-boot-starter-mail
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-starter-data-redis
        


        
            com.alibaba
            fastjson
            1.2.76
        

        
            mysql
            mysql-connector-java
        

        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        

        
            org.apache.commons
            commons-lang3
        

        
            commons-collections
            commons-collections
            3.2.2
        

        
            com.baomidou
            mybatis-plus-boot-starter
            3.4.3
        
        
            org.projectlombok
            lombok
        
        
        
            joda-time
            joda-time
            2.10.10
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


删除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;


//让spring可以扫描到它
@Configuration
//扫包,将此包下的接口生成代理实现类,并且注册到spring容器中
@MapperScan("com.mszlu.blog.dao.mapper")
public class MybatisPlusConfig {

    //分页插件
    //@Configuration可理解为用spring的时候xml里面的标签。
    // @Bean可理解为用spring的时候xml里面的标签。
    @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("
    private Long authorId;
    
    private Long bodyId;
    
    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 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 tags;

    private List 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 {

    

    List listArticlesPage(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) {
        
        Page
page = new Page<>(pageParams.getPage(), pageParams.getPageSize()); LambdaQueryWrapper
queryWrapper = new LambdaQueryWrapper<>(); //是否置顶进行排序, //时间倒序进行排列相当于order by create_data desc queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate); Page
articlePage = articleMapper.selectPage(page, queryWrapper); //分页查询用法 https://blog.csdn.net/weixin_41010294/article/details/105726879 List
records = articlePage.getRecords(); // 要返回我们定义的vo数据,就是对应的前端数据,不应该只返回现在的数据需要进一步进行处理 List articleVoList =copyList(records,true,true); return Result.success(articleVoList); } private List copyList(List
records,boolean isTag,boolean isAuthor) { List 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 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;

    

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

    private List copyList(List tagList) {
        List 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
{ }

标签的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 {

    List 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 {
}

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

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

    

总的xml文件如下所示







    
        id,avatar,tag_name as tagName
    



    
        select id,tag_name as tagName from ms_tag
        where  id in
        
            #{tagId}
        
    


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

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


public interface ArticleService {

    
    Result listArticle(PageParams pageParams);

    
    Result hotArticle(int limit);
}

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

    @Override
    public Result hotArticle(int limit) {
        LambdaQueryWrapper
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
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
 
    @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

    
    Result newArticles(int limit);

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

@Override
    public Result newArticles(int limit) {
        LambdaQueryWrapper
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
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

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


@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

    
    Result listArchives();

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

@Override
public Result listArchives() {
        
    List 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
{ List listArchives(); }

src/main/resources/com/mszlu/blog/dao/mapper/ArticleMapper.xml








    
        select * from ms_permission where id in (select permission_id from ms_admin_permission where admin_id=#{adminId})
    

12.4. 作业

添加角色,用户拥有多个角色,一个角色拥有多个权限

13.总结技术亮点

1、jwt + redis

token令牌的登录方式,访问认证速度快,session共享,安全性

redis做了令牌和用户信息的对应管理,
1,进一步增加了安全性
2. 登录用户做了缓存
3.灵活控制用户的过期(续期,踢掉线等)

2、threadLocal使用了保存用户信息,请求的线程之内,可以随时获取登录的用户,做了线程隔离

3、在使用完ThreadLocal之后,做了value的删除,防止了内存泄漏(这面试说强引用。弱引用。不是明摆着让面试官间JVM嘛)

4·、线程安全-update table set value = newValue where id=1 and value=oldValue

5、线程池应用非常广,面试7个核心参数(对当前的主业务流程无影响的操作,放入线程池执行)

1.登录,记录日志

6·权限系统重点内容

7·统一日志记录,统一缓存处理

14.前端

先找到Home.vue,一般这里放主页
views文件夹一般存放页面
components文件夹一般存放vue自定义的组件
一般views用到各个组件

router文件夹存放路由,通过不同的路径跳转到不同的页面
store一般做存储用的
utils文件夹一般是工具类
request一般是请求
api就是跟后端的一些接口的定义
dist文件夹打包之后产生的静态页面

首先看
config目录中的dev.env.js配置后端访问路径
部署生产环境

再看static目录,category是图片路径

在数据库中这样配置

再看src目录

api表示后端接口访问的定义,囊括了所有后端的访问接口

以api文件夹下的article.js为例子

import request from '@/request'


export function getArticles(query, page) {
  return request({
    url: '/articles',//访问路径
    method: 'post',//访问方式post
    //传递参数
    data: {
      page: page.pageNumber,
      pageSize: page.pageSize,
      name: page.name,
      sort: page.sort,
      year: query.year,
      month: query.month,
      tagId: query.tagId,
      categoryId: query.categoryId
    }
  })
}

export function getHotArtices() {
  return request({
    url: '/articles/hot',//接口路径的名称也可以随意更改
    method: 'post'//访问方式,想改成get直接修改即可
  })
}

export function getNewArtices() {
  return request({
    url: '/articles/new',
    method: 'post'
  })
}

export function viewArticle(id) {
  return request({
    url: `/articles/view/${id}`,
    method: 'post'
  })
}

export function getArticlesByCategory(id) {
  return request({
    url: `/articles/category/${id}`,
    method: 'post'
  })
}

export function getArticlesByTag(id) {
  return request({
    url: `/articles/tag/${id}`,
    method: 'post'
  })
}


export function publishArticle(article,token) {
  return request({
    headers: {'Authorization': token},
    url: '/articles/publish',
    method: 'post',
    data: article
  })
}

export function listArchives() {
  return request({
    url: '/articles/listArchives',
    method: 'post'
  })
}

export function getArticleById(id) {
  return request({
    url: `/articles/${id}`,
    method: 'post'
  })
}

在login.js文件中

import request from '@/request'

export function login(account, password) {
  const data = {
    account,
    password
  }
  return request({
    url: '/login',
    method: 'post',
    data
  })
}

export function logout(token) {
  return request({
    headers: {'Authorization': token},//在后端通过headers获取token
    url: '/logout',
    method: 'get'
  })
}

export function getUserInfo(token) {
  return request({
    headers: {'Authorization': token},
    url: '/users/currentUser',
    method: 'get'
  })
}

export function register(account, nickname, password) {
  const data = {
    account,
    nickname,
    password
  }
  return request({
    url: '/register',
    method: 'post',
    data
  })
}

在home.vue文件夹中







components文件夹下的srccomponentsBaseFooter.vue文件夹







对应图片最下方
srcviewsBaseHeader.vue文件头







对应图片最上方

BaseHeader.vue中的logout本质上调用store文件夹下的index.js文件

15、缓存一致性问题

之前在文章列表读取,最新文章等接口的时候我们加了缓存,但是加了缓存会有一些问题,当我们修改或者用户浏览了文章,那么最新的修改和文章的浏览数量无法及时的更新,那么应该怎么做呢?

这里我们采用RocketMQ来解决这个问题。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/852409.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号