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

基于springboot + vue 的个人博客搭建过程

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

基于springboot + vue 的个人博客搭建过程

前言

码神之路网站所使用的博客,项目简单,需求明确,容易上手,非常适合作为练手几项目。
成品预览: blog.mszlu.com
项目搭建思路:
1. 提供前端工程,只需要实现后端接口即可
2. 项目以单体架构入手,先快速开发,不考虑项目优化,降低开发负担
3. 开发完成后,开始优化项目,提升编程思维能力(页面静态化,缓存,云存储,日志等等…)
4. docker部署上线
5. 云服务器购买,域名购买,域名备案等…

项目使用技术:springboot + mybatisplus + redis + mysql

工程搭建 1. 前端工程

用vscode打开,然后编译启动,具体命令如下:

npm install
npm run build
npm run dev

BUT,我们在这里不用这种方式,而是采用另外一种方式去启动前端。
下面给出具体命令:

yarn
yarn start

看在这里,我们可能会有一个大写的问号:npm和yarn有什么区别呢?

npm和yarn的区别请点这里

总的来说就是,yarn更快,更简洁(真是忘金油式的回答啊!)
总结一下为什么yarn比npm更好吧:

  1. 速度快: 采用并行安装和离线安装(如果之前安装过yarn,安装时就可以从缓存中离线安装)
  2. 安装版本统一: (确保每次拉取项目使用的依赖版本都是相同的)
  3. 更简洁的输出
  4. 多注册来源处理
  5. 更好的语义化(命令更简洁)
2. 新建maven工程(父框架) 2.1 新建blog-parent父框架, 删掉src,pom.xml引入依赖


    4.0.0

    com.raxcl
    blog-parent
    1.0-SNAPSHOT
    
        blog-api
    
    pom
    
        org.springframework.boot
        spring-boot-starter-parent
        2.5.0
        
    

    
        8
        8
    
    
        
            
                com.alibaba
                fastjson
                1.2.76
            

            
                commons-collections
                commons-collections
                3.2.2
            

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

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


2.2 maven依赖飘红问题

过程中可能会有一些依赖飘红,推测是由于版本号和父项目不一致导致的,先不用管(有强迫症的孩子可以尝试解决) 。 是由于maven版本过低导致的,maven官网推荐大家更新至3.8.3版本。飘红问题即可解决(如果还飘红,可以先输入版本号,刷新pom.xml文件后再删除版本号即可)

3. 新建子模块blog-api

3.1 pom.xml添加依赖


        
            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
        
    
3.2 配置信息

application.properties

#server
server.port=8888
spring.application.name=raxcl

# datasource
spring.datasource.url=jdbc:mysql://106.54.170.191: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_

MybatisPlusConfig

package com.raxcl.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.beans.factory.annotation.Configurable;

@Configuration
@MapperScan("com.raxcl.blog.mapper")
public class MybatisPlusConfig {

    //分页插件
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

WebMVCConfig

package com.raxcl.config;

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry){
        //跨域配置
        registry.addMapping("
    private int commentCounts;

    
    private Long createDate;

    
    private String summary;

    
    private String title;

    
    private  int viewCounts;

    
    private int weight;

    
    private Long authorId;

    
    private Long bodyId;

    
    private int category_id;
}

package com.raxcl.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.raxcl.blog.dao.pojo;

import lombok.Data;

@Data
public class Tag {
    private Long id;

    private String avatar;

    private String tagName;
}
1.2.2.2 mapper接口
package com.raxcl.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.baseMapper;
import com.raxcl.blog.dao.pojo.Article;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ArticleMapper extends baseMapper
{ }
package com.raxcl.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.baseMapper;
import com.raxcl.blog.dao.pojo.SysUser;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SysUserMapper extends baseMapper {
}

package com.raxcl.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.baseMapper;
import com.raxcl.blog.dao.pojo.Tag;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface TagMapper extends baseMapper {
    
    List findTagByArticleId(Long articleId);
}

1.2.3 vo层
package com.raxcl.blog.vo.param;

import lombok.Data;

@Data
public class PageParams {
    private int page = 1;
    private int pageSize = 10;
}

package com.raxcl.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 List categorys;




    
    private Long authorId;

    
    private Long bodyId;

    
    private int category_id;
}

package com.raxcl.blog.vo;

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

@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.raxcl.blog.vo;

import lombok.Data;

@Data
public class TagVo {
    private Long id;

    private String tagName;
}

1.2.4 controller层
package com.raxcl.blog.api;

import com.raxcl.blog.service.ArticleService;
import com.raxcl.blog.vo.param.PageParams;
import com.raxcl.blog.vo.Result;
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("articles")
public class ArticleController {

    private final ArticleService articleService;


    public ArticleController(ArticleService articleService) {
        this.articleService = articleService;
    }

    //Result是统一结果返回(VO类)
    @PostMapping
    public Result articles(@RequestBody PageParams pageParams){
        return articleService.listArticle(pageParams);
    }
}

1.2.5 service层
package com.raxcl.blog.service;

import com.raxcl.blog.vo.param.PageParams;
import com.raxcl.blog.vo.Result;
import org.springframework.stereotype.Service;


public interface ArticleService {

    
    Result listArticle(PageParams pageParams);
}

package com.raxcl.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.raxcl.blog.dao.mapper.ArticleMapper;
import com.raxcl.blog.dao.pojo.Article;
import com.raxcl.blog.service.ArticleService;
import com.raxcl.blog.service.SysUserService;
import com.raxcl.blog.service.TagService;
import com.raxcl.blog.vo.ArticleVo;
import com.raxcl.blog.vo.param.PageParams;
import com.raxcl.blog.vo.Result;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

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

@Service
public class ArticleServiceImpl implements ArticleService {
    private final ArticleMapper articleMapper;
    private final TagService tagService;
    private final SysUserService sysUserService;

    public ArticleServiceImpl(ArticleMapper articleMapper, TagService tagService, SysUserService sysUserService) {
        this.articleMapper = articleMapper;
        this.tagService = tagService;
        this.sysUserService = sysUserService;
    }

    @Override
    public Result listArticle(PageParams pageParams) {
        
        Page
page = new Page<>(pageParams.getPage(),pageParams.getPageSize()); LambdaQueryWrapper
queryWrapper = new LambdaQueryWrapper<>(); //是否置顶进行排序 queryWrapper.orderByDesc(Article::getWeight, Article::getCreateDate); Page
articlePage = articleMapper.selectPage(page, queryWrapper); //能直接返回吗? 很明显不能 List
records = articlePage.getRecords(); 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; } private ArticleVo copy(Article article, boolean isTag, boolean isAuthor) { ArticleVo articleVo = new ArticleVo(); BeanUtils.copyProperties(article, articleVo); articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm")); if (isTag){ articleVo.setTags(tagService.findTagByArticleId(article.getId())); } if (isAuthor){ articleVo.setAuthor(sysUserService.findUserById(article.getId()).getNickname()); } return articleVo; } }
package com.raxcl.blog.service;

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

public interface SysUserService {
    SysUser findUserById(Long id);
}

package com.raxcl.blog.service.impl;

import com.raxcl.blog.dao.mapper.SysUserMapper;
import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.service.SysUserService;
import org.springframework.stereotype.Service;

import java.util.Objects;

@Service
public class SysUserServiceImpl implements SysUserService {
    private final SysUserMapper sysUserMapper;

    public SysUserServiceImpl(SysUserMapper sysUserMapper) {
        this.sysUserMapper = sysUserMapper;
    }

    @Override
    public SysUser findUserById(Long id) {
        SysUser sysUser = sysUserMapper.selectById(id);
        if (Objects.isNull(sysUser)){
            sysUser = new SysUser();
            sysUser.setNickname("raxcl学博客");
        }
        return sysUser;
    }
}

package com.raxcl.blog.service;

import com.raxcl.blog.vo.TagVo;

import java.util.List;

public interface TagService {
    List findTagByArticleId(Long articleId);
}

package com.raxcl.blog.service.impl;

import com.raxcl.blog.dao.mapper.TagMapper;
import com.raxcl.blog.dao.pojo.Article;
import com.raxcl.blog.dao.pojo.Tag;
import com.raxcl.blog.service.TagService;
import com.raxcl.blog.vo.ArticleVo;
import com.raxcl.blog.vo.TagVo;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

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

@Service
public class TagServiceImpl implements TagService {

    private final TagMapper tagMapper;

    public TagServiceImpl(TagMapper tagMapper) {
        this.tagMapper = tagMapper;
    }

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

    private List copyList(List records) {
        List tagVoList = new ArrayList<>();
        for (Tag record : records){
            tagVoList.add(copy(record));
        }
        return  tagVoList;
    }

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

1.2.6 mapper实现层






    
        select tag_id
        from ms_article_tag
        group by tag_id
        order by count(1) desc limit #{size}
    

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

6. 登录功能 6.1 接口说明

接口url:/login

请求方式:POST

请求参数:

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

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": "token"
}
6.2 pom.xml
        
        
            commons-codec
            commons-codec
        
6.3 vo层
package com.raxcl.blog.vo.param;

import lombok.Data;

@Data
public class LoginParam {
    private String account;

    private String password;
}
package com.raxcl.blog.vo;

public enum ErrorCode {


    PARAMS_ERROR(10001,"参数有误"),

    ACCOUNT_PWD_NOT_EXIST (10002,"用户名或密码不存在"),;

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

6.4 utils层
package com.raxcl.blog.utils;

import io.jsonwebtoken.*;

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


}

6.5 controller层
package com.raxcl.blog.controller;

import com.raxcl.blog.service.LoginService;
import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.param.LoginParam;
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 {
    private final LoginService loginService;

    public LoginController(LoginService loginService) {
        this.loginService = loginService;
    }

    @PostMapping
    public Result login(@RequestBody LoginParam loginParam){
        return loginService.login(loginParam);
    }
}

6.6 service层
package com.raxcl.blog.service;

import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.param.LoginParam;

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


package com.raxcl.blog.service.impl;

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

import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements LoginService {

    private static final String slat = "raxcl!@#";

    private final SysUserService sysUserService;
    private final RedisTemplate redisTemplate;

    public LoginServiceImpl(SysUserService sysUserService, RedisTemplate redisTemplate) {
        this.sysUserService = sysUserService;
        this.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 (Objects.isNull(sysUser)){
            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);
    }

}

package com.raxcl.blog.service;

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

public interface SysUserService {

    SysUser findUser(String account, String pwd);
}

package com.raxcl.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.raxcl.blog.dao.mapper.SysUserMapper;
import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.service.SysUserService;
import org.springframework.stereotype.Service;

import java.util.Objects;

@Service
public class SysUserServiceImpl implements SysUserService {
    @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;
    }
}

7. 获取用户信息 7.1 接口说明

接口url:/users/currentUser

请求方式:GET

请求参数:

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

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": {
        "id":1,
        "account":"1",
        "nickaname":"1",
        "avatar":"ss"
    }
}
7.2 controller层
package com.raxcl.blog.controller;

import com.raxcl.blog.service.SysUserService;
import com.raxcl.blog.vo.Result;
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 {
    private final SysUserService sysUserService;

    public UserController(SysUserService sysUserService) {
        this.sysUserService = sysUserService;
    }

    @GetMapping("currentUser")
    public Result currentUser (@RequestHeader("Authorization") String token){
        return sysUserService.getUserInfoByToken(token);
    }
}

7.3 vo层
package com.raxcl.blog.vo;

import lombok.Data;

@Data
public class LoginUserVo {
    private Long id;

    private String account;

    private  String nickname;

    private String avatar;
}

7.4 service层
package com.raxcl.blog.service;

import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.vo.Result;

public interface SysUserService {
    Result getUserInfoByToken(String token);
}

package com.raxcl.blog.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.raxcl.blog.dao.mapper.SysUserMapper;
import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.service.SysUserService;
import com.raxcl.blog.utils.JWTUtils;
import com.raxcl.blog.vo.ErrorCode;
import com.raxcl.blog.vo.LoginUserVo;
import com.raxcl.blog.vo.Result;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Objects;

@Service
public class SysUserServiceImpl implements SysUserService {
    private final SysUserMapper sysUserMapper;
    private final RedisTemplate redisTemplate;

    public SysUserServiceImpl(SysUserMapper sysUserMapper, RedisTemplate redisTemplate) {
        this.sysUserMapper = sysUserMapper;
        this.redisTemplate = redisTemplate;

    }
    
    @Override
    public Result getUserInfoByToken(String token) {
        Map map = JWTUtils.checkToken((token));
        if (Objects.isNull(map)){
            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);
    }
}
8. 登出

接口url:/logout

请求方式:GET

请求参数:

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

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": null
}
8.1 controller层
package com.raxcl.blog.controller;

import com.raxcl.blog.service.LoginService;
import com.raxcl.blog.vo.Result;
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("logout")
public class LogoutController {
    private final LoginService loginService;

    public LogoutController(LoginService loginService) {
        this.loginService = loginService;
    }

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

8.2 service层
package com.raxcl.blog.service;

import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.param.LoginParam;

public interface LoginService {

    Result logout(String token);
}

package com.raxcl.blog.service.impl;

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

import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements LoginService {
    @Override
    public Result logout(String token) {
        redisTemplate.delete("TOLEN_"+token);
        return Result.success(null);
    }

}

9. 注册 9.1 接口说明

接口url:/register

请求方式:POST

请求参数:

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

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": "token"
}
9.2 vo层
package com.raxcl.blog.vo.param;

import lombok.Data;

@Data
public class LoginParam {
    private String account;

    private String password;

    private String nickname;
}

package com.raxcl.blog.vo;

public enum ErrorCode {


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

9.3 controller层
package com.raxcl.blog.controller;

import com.raxcl.blog.service.LoginService;
import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.param.LoginParam;
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 {
    private final LoginService loginService;

    public RegisterController(LoginService loginService) {
        this.loginService = loginService;
    }

    @PostMapping
    public Result register(@RequestBody LoginParam loginParam){
        return loginService.register(loginParam);
    }
}


9.4 service层
package com.raxcl.blog.service;

import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.param.LoginParam;

public interface LoginService {

    Result register(LoginParam loginParam);
}


package com.raxcl.blog.service.impl;

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

import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
@Transactional
public class LoginServiceImpl implements LoginService {

    private static final String slat = "raxcl!@#";

    private final SysUserService sysUserService;
    private final RedisTemplate redisTemplate;

    public LoginServiceImpl(SysUserService sysUserService, RedisTemplate redisTemplate) {
        this.sysUserService = sysUserService;
        this.redisTemplate = redisTemplate;
    }

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

}
package com.raxcl.blog.service;

import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.vo.Result;

public interface SysUserService {

    SysUser findUserByAccount(String account);

    void save(SysUser sysUser);
}

package com.raxcl.blog.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.raxcl.blog.dao.mapper.SysUserMapper;
import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.service.SysUserService;
import com.raxcl.blog.utils.JWTUtils;
import com.raxcl.blog.vo.ErrorCode;
import com.raxcl.blog.vo.LoginUserVo;
import com.raxcl.blog.vo.Result;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Objects;

@Service
public class SysUserServiceImpl implements SysUserService {
    private final SysUserMapper sysUserMapper;
    private final RedisTemplate redisTemplate;

    public SysUserServiceImpl(SysUserMapper sysUserMapper, RedisTemplate redisTemplate) {
        this.sysUserMapper = sysUserMapper;
        this.redisTemplate = redisTemplate;

    }
    @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 采用了雪花算法
        this.sysUserMapper.insert(sysUser);
    }
}
10. 登录拦截器

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

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

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

10.1 hander层
package com.raxcl.blog.handler;

import com.alibaba.fastjson.JSON;
import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.service.LoginService;
import com.raxcl.blog.vo.ErrorCode;
import com.raxcl.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
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;
import java.io.IOException;

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

    public LoginInterceptor(LoginService loginService) {
        this.loginService = loginService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(!(handler instanceof HandlerMethod)){
            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;
        }
        //是登录状态,放行
        return true;
    }

}

10.2 service层
package com.raxcl.blog.service;

import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.vo.Result;
import com.raxcl.blog.vo.param.LoginParam;

public interface LoginService {
    SysUser checkToken(String token);
}

package com.raxcl.blog.service.impl;

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

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
@Transactional
public class LoginServiceImpl implements LoginService {
    @Override
    public SysUser checkToken(String token) {
        if (StringUtils.isBlank(token)){
            return null;
        }
        Map stringObjectMap = JWTUtils.checkToken(token);
        if (stringObjectMap == null){
            return null;
        }
        String userJson = redisTemplate.opsForValue().get("TOKEN_"+ token);
        if (StringUtils.isBlank(userJson)){
            return null;
        }
        SysUser sysUser = JSON.parseObject(userJson, SysUser.class);
        return sysUser;
    }


}

10.3 config层(使拦截器生效)
package com.raxcl.blog.config;

import com.raxcl.blog.handler.LoginInterceptor;
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 {

    private final LoginInterceptor loginInterceptor;

    public WebMVCConfig(LoginInterceptor loginInterceptor) {
        this.loginInterceptor = loginInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry){
        //拦截test接口,后续实际遇到需要拦截的接口时,在配置为真正的拦截接口
        registry.addInterceptor(loginInterceptor).addPathPatterns("/test");
    }


}

10.4 测试层(controller)
package com.raxcl.blog.controller;

import com.raxcl.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(){
        return Result.success(null);
    }
}

11. ThreadLocal保存用户信息 11.1 utils层
package com.raxcl.blog.utils;

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

public class UserThreadLocal {
    private UserThreadLocal(){}

    private static final ThreadLocal LOCAL = new ThreadLocal<>();

    public static void put(SysUser sysUser){
        LOCAL.set(sysUser);
    }
    public static SysUser get(){
        return LOCAL.get();
    }
    public static void remove(){
        LOCAL.remove();
    }

}

11.2 handler层
package com.raxcl.blog.handler;

import com.alibaba.fastjson.JSON;
import com.raxcl.blog.dao.pojo.SysUser;
import com.raxcl.blog.service.LoginService;
import com.raxcl.blog.utils.UserThreadLocal;
import com.raxcl.blog.vo.ErrorCode;
import com.raxcl.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
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;
import java.io.IOException;

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

    public LoginInterceptor(LoginService loginService) {
        this.loginService = loginService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //在执行controller方法之前进行执行
        //在执行controller方法(Handler)之前进行执行
        
        if(!(handler instanceof HandlerMethod)){
            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;
        }
        //是登录状态,放行
        UserThreadLocal.put(sysUser);
        return true;
    }

    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex){
        UserThreadLocal.remove();
    }

}

11.3 controller层
package com.raxcl.blog.controller;

import com.raxcl.blog.utils.UserThreadLocal;
import com.raxcl.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(){
        System.out.println("哈哈哈哈哈哈");
        System.out.println(UserThreadLocal.get());
        return Result.success(null);
    }
}

12. ThreadLocal内存泄漏

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

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

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

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

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

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

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

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