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

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

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

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

文章目录
  • spring boot 练手实战项目说明
  • 基础知识
  • 1. 工程搭建
    • 1.1 新建maven工程
    • 1.2 配置
    • 1.3 启动类
  • 2. 首页-文章列表
    • 2.1 接口说明
    • 2.2 编码
      • 2.2.1 表结构
      • 2.2.2 Controller
      • 2.2.3 Service
      • 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 测试
  • 1. 统一异常处理
  • 2. 首页-最热文章
    • 2.1 接口说明
    • 2.2 Controller
    • 2.3 Service
    • 2.4 测试
  • 3. 首页-最新文章
    • 3.1 接口说明
    • 3.1 Controller
    • 3.2 Service
  • 4. 首页-文章归档
    • 4.1接口说明
    • 4.1 Controller
    • 4.2 Service
    • 4.3 Dao
    • 4.4 测试
  • 1. 登录
    • 1.1 接口说明
    • 1.2 JWT
    • 1.3 Controller
    • 1.4 Service
    • 1.5 登录参数,redis配置,统一错误码
    • 1.6 测试
  • 2. 获取用户信息
    • 2.1 接口说明
    • 2.2 Controller
    • 2.3 Service
    • 2.4 LoginUserVo
    • 2.5 测试
  • 3. 退出登录
    • 3.1 接口说明
    • 3.2 Controller
    • 3.3 Service
    • 3.4 测试
  • 1. 注册
    • 1.1 接口说明
    • 1.2 Controller
    • 1.3 Service
    • 1.4 加事务
    • 1.5 测试
  • 2. 登录拦截器
    • 2.1 拦截器实现
    • 2.2 使拦截器生效
    • 2.3 测试
  • 3. ThreadLocal保存用户信息
  • 1. ThreadLocal内存泄漏
  • 2. 文章详情
    • 2.1 接口说明
    • 2.2 涉及到的表
    • 2.3 Controller
    • 2.4 Service
    • 2.5 测试
  • 3. 使用线程池 更新阅读次数
    • 3.1 线程池配置
    • 3.1 使用
    • 3.3 测试
  • Bug修正
  • 1. 评论列表
    • 1.1 接口说明
    • 1.2 Controller
    • 1.3 Service
  • 2. 评论
    • 2.1 接口说明
    • 2.2 加入到登录拦截器中
    • 2.3 Controller
    • 2.4 Service

spring boot 练手实战项目说明

视频链接

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

最终成品

blog.mszlu.com

项目讲解说明:

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

项目使用技术 :

springboot + mybatisplus+redis+mysql

基础知识

mybatisDao层 Mapper层 controller层 service层 model层 entity层 简介
mall商场学习文档
mybatisplus学习文档
mybatisplus配套代码
easycode搭配mybatisplus巨爽

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
            
        
    


1.2 配置
#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;

@Configuration
//扫包,将此包下的接口生成代理实现类,并且注册到spring容器中
@MapperScan("com.mszlu.blog.dao")
public class MybatisPlusConfig {
	//分页插件
    @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
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);
    }
}

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

}

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 SysUserService sysUserService;
    @Autowired
    private TagsService tagsService;

    public ArticleVo copy(Article article,boolean isAuthor,boolean isBody,boolean isTags){
        ArticleVo articleVo = new ArticleVo();
        BeanUtils.copyProperties(article, articleVo);
        if (isAuthor) {
            SysUser sysUser = sysUserService.findSysUserById(article.getAuthorId());
            articleVo.setAuthor(sysUser.getNickname());
        }
        articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
        if (isTags){
           List tags = tagsService.findTagsByArticleId(article.getId());
           articleVo.setTags(tags);
        }
        return articleVo;
    }

    private List copyList(List
records,boolean isAuthor,boolean isBody,boolean isTags) { List articleVoList = new ArrayList<>(); for (Article article : records) { ArticleVo articleVo = copy(article,isAuthor,isBody,isTags); articleVoList.add(articleVo); } return articleVoList; } @Override public List listArticlesPage(PageParams pageParams) { QueryWrapper
queryWrapper = new QueryWrapper<>(); Page
page = new Page<>(pageParams.getPage(),pageParams.getPageSize()); Page
articlePage = articleMapper.selectPage(page, queryWrapper); List articleVoList = copyList(articlePage.getRecords(),true,false,true); return articleVoList; } }
package com.mszlu.blog.service;

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

public interface UserService {

    SysUser findUserById(Long userId);
}

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 UserServiceImpl implements UserService {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Override
    public SysUser findUserById(Long userId) {
        SysUser sysUser = sysUserMapper.selectById(userId);
        if (sysUser == null) {
            sysUser = new SysUser();
            sysUser.setNickname("码神之路");
        }
        return sysUser;
    }
}

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

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;

    public TagVo copy(Tag tag){
        TagVo tagVo = new TagVo();
        BeanUtils.copyProperties(tag,tagVo);
        return tagVo;
    }
    public List copyList(List tagList){
        List tagVoList = new ArrayList<>();
        for (Tag tag : tagList) {
            tagVoList.add(copy(tag));
        }
        return tagVoList;
    }

    @Override
    public List findTagsByArticleId(Long id) {
        List tags = tagMapper.findTagsByArticleId(id);
        return copyList(tags);
    }
}

2.2.4 Dao
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
{ }
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);
}

package com.mszlu.blog.dao;

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

public interface SysUserMapper extends baseMapper {
}







    
        id,avatar,tag_name as tagName
    

    
        select   from ms_tag where id in
        
            #{tagId}
        
    
    

4.4 测试

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

1. 登录 1.1 接口说明

接口url:/login

请求方式:POST

请求参数:

参数名称参数类型说明
accountstring账号
passwordstring密码

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": "token"
}
1.2 JWT

登录使用JWT技术。

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

请求需要登录的资源或者接口的时候,将token携带,后端验证token是否合法。

jwt 有三部分组成:A.B.C

A:Header,{“type”:“JWT”,“alg”:“HS256”} 固定

B:playload,存放信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息

C: 签证,A和B加上秘钥 加密而成,只要秘钥不丢失,可以认为是安全的。

jwt 验证,主要就是验证C部分 是否合法。

依赖包:

  
        io.jsonwebtoken
        jjwt
        0.9.1
    

工具类:

package com.mszlu.blog.utils;

import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JWTUtils {

    private static final String jwtToken = "123456Mszlu!@#$$";

    public static String createToken(Long userId){
        Map claims = new HashMap<>();
        claims.put("userId",userId);
        JwtBuilder jwtBuilder = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken
                .setClaims(claims) // body数据,要唯一,自行设置
                .setIssuedAt(new Date()) // 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000));// 一天的有效时间
        String token = jwtBuilder.compact();
        return token;
    }

    public static Map checkToken(String token){
        try {
            Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
            return (Map) parse.getBody();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;

    }

}

1.3 Controller
package com.mszlu.blog.controller;

import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("login")
public class LoginController {

    @Autowired
    private LoginService loginService;

    @PostMapping
    public Result login(@RequestBody LoginParam loginParam){
		//登录 验证用户  访问用户表
        return loginService.login(loginParam);
    }
}

1.4 Service
package com.mszlu.blog.service;

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

public interface LoginService {
    
    Result login(LoginParam loginParam);
}

md5加密的依赖包:

  
        commons-codec
        commons-codec
    
package com.mszlu.blog.service.impl;

import com.alibaba.fastjson.JSON;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.utils.JWTUtils;
import com.mszlu.blog.vo.ErrorCode;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements LoginService {

	//加密盐用于加密
    private static final String slat = "mszlu!@#";
    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Result login(LoginParam loginParam) {
            
        String account = loginParam.getAccount();
        String password = loginParam.getPassword();
        if (StringUtils.isBlank(account) || StringUtils.isBlank(password)){
            return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
        }
        String pwd = DigestUtils.md5Hex(password + slat);
        SysUser sysUser = sysUserService.findUser(account,pwd);
        if (sysUser == null){
            return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(),ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
        }
        //登录成功,使用JWT生成token,返回token和redis中
        String token = JWTUtils.createToken(sysUser.getId());
        redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
        return Result.success(token);
    }

//生成我们想要的密码,放于数据库用于登陆
    public static void main(String[] args) {
        System.out.println(DigestUtils.md5Hex("admin"+slat));
    }
}

SysUserServiceImpl.java

   @Override
    public SysUser findUser(String account, String pwd) {
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysUser::getAccount,account);
        queryWrapper.eq(SysUser::getPassword,pwd);
        queryWrapper.select(SysUser::getId,SysUser::getAccount,SysUser::getAvatar,SysUser::getNickname);
        queryWrapper.last("limit 1");
        SysUser sysUser = sysUserMapper.selectOne(queryWrapper);
        return sysUser;
    }

SysUserService.java

    SysUser findUser(String account, String pwd);
1.5 登录参数,redis配置,统一错误码
package com.mszlu.blog.vo.params;

import lombok.Data;

@Data
public class LoginParam {

    private String account;

    private String password;
}

spring.redis.host=localhost
spring.redis.port=6379
package com.mszlu.blog.vo;

public enum  ErrorCode {

    PARAMS_ERROR(10001,"参数有误"),
    ACCOUNT_PWD_NOT_EXIST(10002,"用户名或密码不存在"),
    NO_PERMISSION(70001,"无访问权限"),
    SESSION_TIME_OUT(90001,"会话超时"),
    NO_LOGIN(90002,"未登录"),;

    private int code;
    private String msg;

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

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

1.6 测试

使用postman测试,因为登录后,需要跳转页面,进行token认证,有接口未写,前端会出现问题。

token前端获取到之后,会存储在 storage中 h5 ,本地存储

2. 获取用户信息 2.1 接口说明

接口url:/users/currentUser

请求方式:GET

请求参数:

参数名称参数类型说明
Authorizationstring头部信息(TOKEN)

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": {
        "id":1,
        "account":"1",
        "nickaname":"1",
        "avatar":"ss"
    }
}
2.2 Controller
package com.mszlu.blog.controller;

import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("users")
public class UserController {

    @Autowired
    private SysUserService sysUserService;

    @GetMapping("currentUser")
    public Result currentUser(@RequestHeader("Authorization") String token){

        return sysUserService.findUserByToken(token);
    }
}

2.3 Service
    
 Result findUserByToken(String token);
 	
@Override
    public Result findUserByToken(String token) {
            
        Map map = JWTUtils.checkToken(token);
        if (map == null){
            return Result.fail(ErrorCode.NO_LOGIN.getCode(),ErrorCode.NO_LOGIN.getMsg());
        }
        String userJson = redisTemplate.opsForValue().get("TOKEN_" + token);
        if (StringUtils.isBlank(userJson)){
            return Result.fail(ErrorCode.NO_LOGIN.getCode(),ErrorCode.NO_LOGIN.getMsg());
        }
        SysUser sysUser = JSON.parseObject(userJson, SysUser.class);
        LoginUserVo loginUserVo = new LoginUserVo();
        loginUserVo.setAccount(sysUser.getAccount());
        loginUserVo.setAvatar(sysUser.getAvatar());
        loginUserVo.setId(sysUser.getId());
        loginUserVo.setNickname(sysUser.getNickname());
        return Result.success(loginUserVo);
    }
2.4 LoginUserVo
package com.mszlu.blog.vo;

import lombok.Data;

@Data
public class LoginUserVo {
	//与页面交互

    private Long id;

    private String account;

    private String nickname;

    private String avatar;
}

2.5 测试 3. 退出登录 3.1 接口说明

接口url:/logout

请求方式:GET

请求参数:

参数名称参数类型说明
Authorizationstring头部信息(TOKEN)

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": null
}
3.2 Controller
package com.mszlu.blog.controller;

import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("logout")
public class LogoutController {

    @Autowired
    private LoginService loginService;

    @GetMapping
    public Result logout(@RequestHeader("Authorization") String token){
        return loginService.logout(token);
    }
}

3.3 Service
  @Override
    public Result logout(String token) {
    //后端直接删除redis中的token
        redisTemplate.delete("TOKEN_"+token);
        return Result.success(null);
    }
3.4 测试 1. 注册 1.1 接口说明

接口url:/register

请求方式:POST

请求参数:

参数名称参数类型说明
accountstring账号
passwordstring密码
nicknamestring昵称

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": "token"
}
1.2 Controller
package com.mszlu.blog.controller;

import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.params.LoginParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("register")
public class RegisterController {

    @Autowired
    private LoginService loginService;

    @PostMapping
    public Result register(@RequestBody LoginParam loginParam){
    //sso 单点登录,后期如果把登录注册功能 提出去(单独的服务,可以独立提供接口服务)
        return loginService.register(loginParam);
    }
}

参数LoginParam中 添加新的参数nickname。

package com.mszlu.blog.vo.params;

import lombok.Data;

@Data
public class LoginParam {

    private String account;

    private String password;

    private String nickname;
}

1.3 Service
 @Override
    public Result register(LoginParam loginParam) {
            
         String account = loginParam.getAccount();
        String password = loginParam.getPassword();
        String nickname = loginParam.getNickname();
        if (StringUtils.isBlank(account)
                || StringUtils.isBlank(password)
                || StringUtils.isBlank(nickname)
        ){
            return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
        }
        SysUser sysUser = this.sysUserService.findUserByAccount(account);
        if (sysUser != null){
            return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(),ErrorCode.ACCOUNT_EXIST.getMsg());
        }
        sysUser = new SysUser();
        sysUser.setNickname(nickname);
        sysUser.setAccount(account);
        sysUser.setPassword(DigestUtils.md5Hex(password+slat));
        sysUser.setCreateDate(System.currentTimeMillis());
        sysUser.setLastLogin(System.currentTimeMillis());
        sysUser.setAvatar("/static/img/logo.b3a48c0.png");
        sysUser.setAdmin(1); //1 为true
        sysUser.setDeleted(0); // 0 为false
        sysUser.setSalt("");
        sysUser.setStatus("");
        sysUser.setEmail("");
        this.sysUserService.save(sysUser);

        //token
        String token = JWTUtils.createToken(sysUser.getId());

        redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
        return Result.success(token);
    }

在ErrorCode.java中添加一条

ACCOUNT_EXIST(10004,"账号已存在"),

SysUserService:

    
 SysUser findUserByAccount(String account);
    
    void save(SysUser sysUser);

SysUserServiceImpl :

  	@Override
    public SysUser findUserByAccount(String account) {
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysUser::getAccount,account);
        queryWrapper.last("limit 1");
        return sysUserMapper.selectOne(queryWrapper);
    }

    @Override
    public void save(SysUser sysUser) {
         //保存用户这 id会自动生成
        //这个地方 默认生成的id是 分布式id 雪花算法
        //mybatis-plus
        this.sysUserMapper.insert(sysUser);
    }
1.4 加事务
@Service
@Transactional
public class LoginServiceImpl implements LoginService {}

当然 一般建议加在 接口上,通用一些。

测试的时候 可以将redis 停掉,那么redis连接异常后,新添加的用户 应该执行回滚操作。

1.5 测试 2. 登录拦截器

每次访问需要登录的资源的时候,都需要在代码中进行判断,一旦登录的逻辑有所改变,代码都得进行变动,非常不合适。

那么可不可以统一进行登录判断呢?

可以,使用拦截器,进行登录拦截,如果遇到需要登录才能访问的接口,如果未登录,拦截器直接返回,并跳转登录页面。

2.1 拦截器实现
package com.mszlu.blog.handler;

import com.alibaba.fastjson.JSON;
import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.service.LoginService;
import com.mszlu.blog.vo.ErrorCode;
import com.mszlu.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private LoginService loginService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //在执行controller方法(Handler)之前进行执行
        

        if (!(handler instanceof HandlerMethod)){
        //handler 可能是 RequestResourceHandler springboot 程序 访问静态资源 默认去classpath下的static目录去查询
            return true;
        }
        String token = request.getHeader("Authorization");
        log.info("=================request start===========================");
        String requestURI = request.getRequestURI();
        log.info("request uri:{}",requestURI);
        log.info("request method:{}",request.getMethod());
        log.info("token:{}", token);
        log.info("=================request end===========================");

        if (token == null){
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }
        SysUser sysUser = loginService.checkToken(token);
        if (sysUser == null){
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }
        //是登录状态,放行
                //登录验证成功,放行
        //我希望在controller中 直接获取用户的信息 怎么获取?

        return true;
    }
}

2.2 使拦截器生效
package com.mszlu.blog.config;

import com.mszlu.blog.handler.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //跨域配置
        registry.addMapping("
        if (!(handler instanceof HandlerMethod)){
            //handler 可能是 RequestResourceHandler springboot 程序 访问静态资源 默认去classpath下的static目录去查询
            return true;
        }
        String token = request.getHeader("Authorization");

        log.info("=================request start===========================");
        String requestURI = request.getRequestURI();
        log.info("request uri:{}",requestURI);
        log.info("request method:{}",request.getMethod());
        log.info("token:{}", token);
        log.info("=================request end===========================");


        if (StringUtils.isBlank(token)){
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }
        SysUser sysUser = loginService.checkToken(token);
        if (sysUser == null){
            Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(JSON.toJSONString(result));
            return false;
        }
        //登录验证成功,放行
        //我希望在controller中 直接获取用户的信息 怎么获取?
        UserThreadLocal.put(sysUser);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
       //如果不删除 ThreadLocal中用完的信息 会有内存泄漏的风险
        UserThreadLocal.remove();
    }
}

package com.mszlu.blog.controller;

import com.mszlu.blog.dao.pojo.SysUser;
import com.mszlu.blog.utils.UserThreadLocal;
import com.mszlu.blog.vo.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("test")
public class TestController {

    @RequestMapping
    public Result test(){
//        SysUser
        SysUser sysUser = UserThreadLocal.get();
        System.out.println(sysUser);
        return Result.success(null);
    }
}

1. ThreadLocal内存泄漏

实线代表强引用,虚线代表弱引用

每一个Thread维护一个ThreadLocalMap, key为使用弱引用的ThreadLocal实例,value为线程变量的副本。

强引用,使用最普遍的引用,一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。

如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。

弱引用,JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。

2. 文章详情 2.1 接口说明

接口url:/articles/view/{id}

请求方式:POST

请求参数:

参数名称参数类型说明
idlong文章id(路径参数)

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": "token"
}
2.2 涉及到的表

content存放makedown格式的信息
content_html存放html格式的信息

CREATE TABLE `blog`.`ms_article_body`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
  `content_html` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
  `article_id` bigint(0) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `article_id`(`article_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 38 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
package com.mszlu.blog.dao.pojo;

import lombok.Data;

@Data
public class ArticleBody {

    private Long id;
    private String content;
    private String contentHtml;
    private Long articleId;
}

avata分类图标路径
category_name图标分类的名称
description分类的描述

CREATE TABLE `blog`.`ms_category`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  `category_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
package com.mszlu.blog.dao.pojo;

import lombok.Data;

@Data
public class Category {

    private Long id;

    private String avatar;

    private String categoryName;

    private String description;
}

2.3 Controller
    @PostMapping("view/{id}")
    public Result findArticleById(@PathVariable("id") Long articleId){
        return articleService.findArticleById(articleId);
    }
2.4 Service

ArticleService.java

    
    Result findArticleById(Long articleId);

body_id对应第二张表ms_article_body上的id

ArticleServiceImpl.java

    @Override
    public Result findArticleById(Long articleId) {
        
        Article article = this.articleMapper.selectById(articleId);
        ArticleVo articleVo = copy(article, true, true,true,true);
        //查看完文章了,新增阅读数,有没有问题呢?
        //查看完文章之后,本应该直接返回数据了,这时候做了一个更新操作,更新时加写锁,阻塞其他的读操作,性能就会比较低
        // 更新 增加了此次接口的 耗时 如果一旦更新出问题,不能影响 查看文章的操作
        //线程池  可以把更新操作 扔到线程池中去执行,和主线程就不相关了
        threadService.updateArticleViewCount(articleMapper,article);
        return Result.success(articleVo);
    }
package com.mszlu.blog.vo;

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 CategoryVo category;

}

ArticleVo中的属性填充:

    private List copyList(List
records, boolean isTag, boolean isAuthor) { List articleVoList = new ArrayList<>(); for (Article record : records) { articleVoList.add(copy(record,isTag,isAuthor,false,false)); } return articleVoList; } private List copyList(List
records, boolean isTag, boolean isAuthor,boolean isBody) { List articleVoList = new ArrayList<>(); for (Article record : records) { articleVoList.add(copy(record,isTag,isAuthor,isBody,false)); } return articleVoList; } private List copyList(List
records, boolean isTag, boolean isAuthor,boolean isBody,boolean isCategory) { List articleVoList = new ArrayList<>(); for (Article record : records) { articleVoList.add(copy(record,isTag,isAuthor,isBody,isCategory)); } return articleVoList; } private ArticleVo copy(Article article, boolean isTag, boolean isAuthor, boolean isBody, boolean isCategory){ ArticleVo articleVo = new ArticleVo(); 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){ Long authorId = article.getAuthorId(); articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname()); } if (isBody){ ArticleBodyVo articleBody = findArticleBody(article.getId()); articleVo.setBody(articleBody); } if (isCategory){ CategoryVo categoryVo = findCategory(article.getCategoryId()); articleVo.setCategory(categoryVo); } return articleVo; } @Autowired private CategoryService categoryService; private CategoryVo findCategory(Long categoryId) { return categoryService.findCategoryById(categoryId); } @Autowired private ArticleBodyMapper articleBodyMapper; private ArticleBodyVo findArticleBody(Long articleId) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ArticleBody::getArticleId,articleId); ArticleBody articleBody = articleBodyMapper.selectOne(queryWrapper); ArticleBodyVo articleBodyVo = new ArticleBodyVo(); articleBodyVo.setContent(articleBody.getContent()); return articleBodyVo; }
package com.mszlu.blog.vo;

import lombok.Data;

@Data
public class CategoryVo {

    private Long id;

    private String avatar;

    private String categoryName;
}

package com.mszlu.blog.vo;

import lombok.Data;

@Data
public class ArticleBodyVo {

    private String content;
}

package com.mszlu.blog.service;

import com.mszlu.blog.vo.CategoryVo;


public interface CategoryService {

    CategoryVo findCategoryById(Long id);
}

package com.mszlu.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.blog.dao.mapper.CategoryMapper;
import com.mszlu.blog.dao.pojo.Category;
import com.mszlu.blog.service.CategoryService;
import com.mszlu.blog.vo.CategoryVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class CategoryServiceImpl implements CategoryService {
    @Autowired
    private CategoryMapper categoryMapper;

    @Override
    public CategoryVo findCategoryById(Long id){
        Category category = categoryMapper.selectById(id);
        CategoryVo categoryVo = new CategoryVo();
        BeanUtils.copyProperties(category,categoryVo);
        return categoryVo;
    }
}

package com.mszlu.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.baseMapper;
import com.mszlu.blog.dao.pojo.ArticleBody;

public interface ArticleBodyMapper extends baseMapper {
}

package com.mszlu.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.baseMapper;
import com.mszlu.blog.dao.pojo.Category;

public interface CategoryMapper extends baseMapper {
}

2.5 测试 3. 使用线程池 更新阅读次数 3.1 线程池配置
package com.mszlu.blog.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class ThreadPoolConfig {

    @Bean("taskExecutor")
    public Executor asyncServiceExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(5);
        // 设置最大线程数
        executor.setMaxPoolSize(20);
        //配置队列大小
        executor.setQueueCapacity(Integer.MAX_VALUE);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("码神之路博客项目");
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //执行初始化
        executor.initialize();
        return executor;
    }
}

3.1 使用
package com.mszlu.blog.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.blog.dao.mapper.ArticleMapper;
import com.mszlu.blog.dao.pojo.Article;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class ThreadService {


    @Async("taskExecutor")
    public void updateViewCount(ArticleMapper articleMapper,Article article){

        Article articleUpdate = new Article();
        articleUpdate.setViewCounts(article.getViewCounts() + 1);
        LambdaQueryWrapper
queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Article::getId,article.getId()); //设置一个 为了在多线程的环境下 线程安全(改之前确认值有没有被其他线程抢先修改) queryWrapper.eq(Article::getViewCounts,article.getViewCounts()); //类似于 update article set view_count=100 where view_count=99 and id=11 articleMapper.update(articleUpdate,queryWrapper); try { //睡眠5秒 证明不会影响主线程的使用 Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
    @Autowired
    private ThreadService threadService;

    @Override
    public ArticleVo findArticleById(Long id) {
        Article article = articleMapper.selectById(id);
        threadService.updateViewCount(articleMapper,article);
        return copy(article,true,true,true,true);
    }

3.3 测试

睡眠 ThredService中的方法 5秒,不会影响主线程的使用,即文章详情会很快的显示出来,不受影响

Bug修正

之前Article中的commentCounts,viewCounts,weight 字段为int,会造成更新阅读次数的时候,将其余两个字段设为初始值0
mybatisplus但凡不是null就会生成到sql语句中进行更新

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 Integer commentCounts;

    private Integer viewCounts;

    
    private Long authorId;
    
    private Long bodyId;
    
    private Long categoryId;

    
    private Integer weight;


    
    private Long createDate;
}

1. 评论列表

id评论id
content评论内容
create_date评论时间
article_id评论文章
author_id谁评论的
parent_id盖楼功能对评论的评论进行回复
to_uid给谁评论
level评论的是第几层(1级表示最上层的评论,2表示对评论的评论)

CREATE TABLE `blog`.`ms_comment`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `create_date` bigint(0) NOT NULL,
  `article_id` int(0) NOT NULL,
  `author_id` bigint(0) NOT NULL,
  `parent_id` bigint(0) NOT NULL,
  `to_uid` bigint(0) NOT NULL,
  `level` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `article_id`(`article_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
package com.mszlu.blog.dao.pojo;

import lombok.Data;

@Data
public class Comment {

    private Long id;

    private String content;

    private Long createDate;

    private Long articleId;

    private Long authorId;

    private Long parentId;

    private Long toUid;

    private Integer level;
}

1.1 接口说明

接口url:/comments/article/{id}

请求方式:GET

请求参数:

参数名称参数类型说明
idlong文章id(路径参数)

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        {
            "id": 53,
            "author": {
                "nickname": "李四",
                "avatar": "http://localhost:8080/static/img/logo.b3a48c0.png",
                "id": 1
            },
            "content": "写的好",
            "childrens": [
                {
                    "id": 54,
                    "author": {
                        "nickname": "李四",
                        "avatar": "http://localhost:8080/static/img/logo.b3a48c0.png",
                        "id": 1
                    },
                    "content": "111",
                    "childrens": [],
                    "createDate": "1973-11-26 08:52",
                    "level": 2,
                    "toUser": {
                        "nickname": "李四",
                        "avatar": "http://localhost:8080/static/img/logo.b3a48c0.png",
                        "id": 1
                    }
                }
            ],
            "createDate": "1973-11-27 09:53",
            "level": 1,
            "toUser": null
        }
    ]
}
1.2 Controller
package com.mszlu.blog.controller;

import com.mszlu.blog.service.CommentsService;
import com.mszlu.blog.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("comments")
public class CommentsController {

    @Autowired
    private CommentsService commentsService;

    @GetMapping("article/{id}")
    public Result comments(@PathVariable("id") Long articleId){

        return commentsService.commentsByArticleId(articleId);

    }
}

1.3 Service
package com.mszlu.blog.service;

import com.mszlu.blog.vo.Result;

public interface CommentsService {


    Result commentsByArticleId(Long articleId);
}

package com.mszlu.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mszlu.blog.dao.mapper.CommentMapper;
import com.mszlu.blog.dao.pojo.Comment;
import com.mszlu.blog.service.CommentsService;
import com.mszlu.blog.service.SysUserService;
import com.mszlu.blog.vo.CommentVo;
import com.mszlu.blog.vo.Result;
import com.mszlu.blog.vo.UserVo;
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 CommentsServiceImpl implements CommentsService {

    @Autowired
    private CommentMapper commentMapper;
    @Autowired
    private SysUserService sysUserService;
    @Override
    public Result commentsByArticleId(Long articleId) {
            
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Comment::getArticleId,articleId);
        queryWrapper.eq(Comment::getLevel,1);
        List comments = commentMapper.selectList(queryWrapper);
        return Result.success(copyList(comments));
    }

    private CommentVo copy(Comment comment) {
        CommentVo commentVo = new CommentVo();
        // 相同属性copy
        BeanUtils.copyProperties(comment,commentVo);
        commentVo.setId(String.valueOf(comment.getId()));
        //作者信息
        Long authorId = comment.getAuthorId();
        UserVo userVo = this.sysUserService.findUserVoById(authorId);
        commentVo.setAuthor(userVo);
        //子评论
        Integer level = comment.getLevel();
        if (1 == level){
            Long id = comment.getId();
            List commentVoList = findCommentsByParentId(id);
            commentVo.setChildrens(commentVoList);
        }
        //to User 给谁评论
        if (level > 1){
            Long toUid = comment.getToUid();
            UserVo toUserVo = this.sysUserService.findUserVoById(toUid);
            commentVo.setToUser(toUserVo);
        }
        return commentVo;
    }

    private List findCommentsByParentId(Long id) {
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Comment::getParentId,id);
        queryWrapper.eq(Comment::getLevel,2);
        List comments = this.commentMapper.selectList(queryWrapper);
        return copyList(comments);
    }

    public List copyList(List commentList){
        List commentVoList = new ArrayList<>();
        for (Comment comment : commentList) {
            commentVoList.add(copy(comment));
        }
        return commentVoList;
    }


}

返回的数据:

package com.mszlu.blog.vo;

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

import java.util.List;

@Data
public class CommentVo  {

    private Long id;

    private UserVo author;

    private String content;

    private List childrens;

    private String createDate;

    private Integer level;

    private UserVo toUser;
}

package com.mszlu.blog.vo;

import lombok.Data;

@Data
public class UserVo {

    private String nickname;

    private String avatar;

    private Long id;
}

在SysUserService中提供 查询用户信息的服务:

  UserVo findUserVoById(Long id);
    @Override
    public UserVo findUserVoById(Long id) {
        SysUser sysUser = sysUserMapper.selectById(id);
        if (sysUser == null){
            sysUser = new SysUser();
            sysUser.setId(1L);
            sysUser.setAvatar("/static/img/logo.b3a48c0.png");
            sysUser.setNickname("码神之路");
        }
        UserVo userVo = new UserVo();
        userVo.setAvatar(sysUser.getAvatar());
        userVo.setNickname(sysUser.getNickname());
        userVo.setId(sysUser.getId());
        return userVo;
    }
2. 评论 2.1 接口说明

接口url:/comments/create/change

请求方式:POST

请求参数:

参数名称参数类型说明
articleIdlong文章id
contentstring评论内容
parentlong父评论id
toUserIdlong被评论的用户id

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": null
}
2.2 加入到登录拦截器中
@Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截test接口,后续实际遇到需要拦截的接口时,在配置为真正的拦截接口
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/test").addPathPatterns("/comments/create/change");
    }
2.3 Controller

构建评论参数对象:

package com.mszlu.blog.vo.params;

import lombok.Data;

@Data
public class CommentParam {

    private Long articleId;

    private String content;

    private Long parent;

    private Long toUserId;
}


    @PostMapping("create/change")
    public Result comment(@RequestBody CommentParam commentParam){
        return commentsService.comment(commentParam);
    }
2.4 Service
    Result comment(CommentParam commentParam);
 @Override
    public Result comment(CommentParam commentParam) {
        SysUser sysUser = UserThreadLocal.get();
        Comment comment = new Comment();
        comment.setArticleId(commentParam.getArticleId());
        comment.setAuthorId(sysUser.getId());
        comment.setContent(commentParam.getContent());
        comment.setCreateDate(System.currentTimeMillis());
        Long parent = commentParam.getParent();
        if (parent == null || parent == 0) {
            comment.setLevel(1);
        }else{
            comment.setLevel(2);
        }
        comment.setParentId(parent == null ? 0 : parent);
        Long toUserId = commentParam.getToUserId();
        comment.setToUid(toUserId == null ? 0 : toUserId);
        this.commentMapper.insert(comment);
        return Result.success(null);
    }
  //防止前端 精度损失 把id转为string
// 分布式id 比较长,传到前端 会有精度损失,必须转为string类型 进行传输,就不会有问题了
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/287034.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

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

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