spring boot3登录开发-3(账密登录逻辑实现)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: spring boot3登录开发-3(账密登录逻辑实现)

前置条件

本文衔接上文,请从上文开始spring boot3登录开发-2(1图形验证码接口实现) 用户表设计如下:

create table user
(
    id           bigint auto_increment comment '主键'
        primary key,
    user_name    varchar(32)                            null comment '用户昵称',
    password     varchar(256)                           null comment '密码',
    user_account varchar(64)                            null comment '账号',
    user_role    varchar(256) default 'user'            null comment '用户角色:user / admin',
    avatar       varchar(1024)                          null comment '头像',
    create_time  datetime     default (now())           null comment '创建时间',
    update_time  datetime     default CURRENT_TIMESTAMP null comment '更新时间',
    is_delete    tinyint(1)   default 0                 null comment '逻辑删除:1删除/0存在',
    gender       tinyint(1)                             null comment '性别',
    status       tinyint(1)   default 1                 not null comment '状态:1正常0禁用'
)
    comment '用户表';
 
INSERT INTO `user` VALUES (1,'蒾酒','e10adc3949ba59abbe56e057f20f883e','admin','admin',NULL,'2024-02-02 18:54:44','2024-02-02 18:54:44',0,NULL,1);

内容简介

上文我们已经实现了图形验证码接口,本文我们实现登录逻辑

  • 通过用户登录DTO(数据传输对象)接收用户登录填写信息
  • 通过注解@NotNull、@Valid进行参数非空校验
  • 通过redis缓存的验证码信息与用户提交的比对验证
  • 通过全局异常处理处理参数为空、用户不存在、密码错误、验证码错误、用户被封禁等业务异常

用户登录逻辑实现

作者习惯于将业务代码全部放在service层里面,controller层只用于暴漏接口封装返回结果。

创建交互对象

1.创建用户登录DTO

说白了就是用户登录表单提交发起post请求,请求体负载的登录对象后端需要有个对象跟表单对象字段对应接收:请求体json->接收对象。这一过程也叫反序列化。

通过@NotNull做非空校验

import jakarta.validation.constraints.NotNull;
import lombok.Data;
 
/**
 * @author mijiupro
 */
@Data
public class UserLoginDTO {
    @NotNull(message = "账号不能为空")
    private String userAccount;//用户账号
    @NotNull(message = "密码不能为空")
    private String password;//密码
    @NotNull(message = "验证码id不能为空")
    private String captchaId;//验证码id
    @NotNull(message = "验证码内容不能为空")
    private String captcha;//验证码内容
}

2.创建用户登录VO

通俗来说就是用户登录成功后返回给前端一个合法令牌token,以及一些非敏感信息方便前端展示,这些信息包装成一个对象,展示对象->json。这一过程又叫序列化。

import lombok.Builder;
import lombok.Data;
 
import java.io.Serializable;
 
 
/**
 * @author mijiupro
 */
@Data
@Builder
public class UserLoginVO implements Serializable {
    private String token;//令牌
    private String userName;//用户名
    private String avatar;//头像
}

创建自定义登录业务异常

说白了就是登录代码可能会判断账号是否存在密码是否正确,当账号不存在或密码错误需要返回对应提示信息,这种类似情况多了你的代码就会很多if-return,代码就会很难看;那么通过自定义异常去到异常处理的方法里面写对应返回提示以及其他逻辑,这样直接抛出对应异常AOP拦截到该异常走对应异常处理逻辑即可。(一句话概括就是:把处理特殊业务异常情况的代码逻辑抽取出来放到别的类里面写,可以使代码更加清晰和可维护)

1.创建验证码错误异常

import com.mijiu.commom.enumerate.ResultEnum;
import lombok.Getter;
 
/**
 * @author mijiupro
 */
@Getter
public class CaptchaErrorException extends RuntimeException {
    private final ResultEnum resultEnum;//返回提示信息枚举(code,message)
 
    public CaptchaErrorException(ResultEnum resultEnum) {
        this.resultEnum = resultEnum;
    }
}

2.创建用户不存在异常

import com.mijiu.commom.enumerate.ResultEnum;
import lombok.Getter;
 
/**
 * 账户不存在异常
 *
 * @author mijiupro
 */
@Getter
public class AccountNotFoundException extends RuntimeException {
    private final ResultEnum resultEnum;
 
    public AccountNotFoundException(ResultEnum resultEnum) {
        this.resultEnum = resultEnum;
    }
}

3.创建密码错误异常

import com.mijiu.commom.enumerate.ResultEnum;
import lombok.Getter;
 
/**
 * 密码错误异常
 *
 * @author mijiupro
 */
@Getter
public class PasswordErrorException extends RuntimeException {
    private final ResultEnum resultEnum;
 
    public PasswordErrorException(ResultEnum resultEnum) {
        this.resultEnum = resultEnum;
    }
 
}

4.创建用户被封禁异常

import com.mijiu.commom.enumerate.ResultEnum;
import lombok.Getter;
 
/**
 * @author mijiupro
 */
@Getter
public class AccountForbiddenException extends RuntimeException {
    private final ResultEnum resultEnum;
 
    public AccountForbiddenException(ResultEnum resultEnum) {
        this.resultEnum = resultEnum;
    }
}

2.登录业务逻辑实现

代码逻辑:参数校验(使用注解方式校验)----验证码校验----账号存在检验----密码校验----用户状态判断

import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.mijiu.commom.enumerate.ResultEnum;
import com.mijiu.commom.exception.AccountForbiddenException;
import com.mijiu.commom.exception.AccountNotFoundException;
import com.mijiu.commom.exception.CaptchaErrorException;
import com.mijiu.commom.exception.PasswordErrorException;
import com.mijiu.commom.model.dto.UserLoginDTO;
import com.mijiu.commom.model.vo.UserLoginVO;
import com.mijiu.commom.util.JwtUtils;
import com.mijiu.entity.User;
import com.mijiu.mapper.UserMapper;
import com.mijiu.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
 
 
import java.util.Map;
 
/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author 蒾酒
 * @since 2024-02-03
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
 
    private final UserMapper userMapper;
    private final JwtUtils jwtUtils;
    private final StringRedisTemplate stringRedisTemplate;
 
    public UserServiceImpl(UserMapper userMapper, JwtUtils jwtUtils, StringRedisTemplate stringRedisTemplate) {
        this.userMapper = userMapper;
        this.jwtUtils = jwtUtils;
        this.stringRedisTemplate = stringRedisTemplate;
    }
    @Override
    public UserLoginVO login(@Valid UserLoginDTO userLoginDTO) {
        // 获取验证码id
        String captchaId = userLoginDTO.getCaptchaId();
        // 获取用户提交验证码
        String userCaptcha = userLoginDTO.getCaptcha();
        // 获取缓存验证码
        String cacheCaptcha = stringRedisTemplate.opsForValue().get("login:captcha:" + captchaId);
        // 比较验证码是否正确
        if (cacheCaptcha == null || !cacheCaptcha.equalsIgnoreCase(userCaptcha)) {
            throw new CaptchaErrorException(ResultEnum.USER_CAPTCHA_ERROR);
        }
        // 判断用户是否存在
        User loginUser = new LambdaQueryChainWrapper<>(userMapper)
                .select(User::getId, User::getUserAccount, User::getPassword,
                        User::getUserName, User::getUserRole,
                        User::getAvatar, User::getStatus)
                .eq(User::getUserAccount, userLoginDTO.getUserAccount())
                .one();
        if (loginUser == null) {
            throw new AccountNotFoundException(ResultEnum.USER_NOT_EXIST);
        }
        log.info("loginUser: {}", loginUser);
        // 判断密码是否正确
        String md5Password = DigestUtils.md5DigestAsHex(userLoginDTO.getPassword().getBytes());
        if (!md5Password.equals(loginUser.getPassword())) {
            throw new PasswordErrorException(ResultEnum.USER_PASSWORD_ERROR);
        }
        // 判断用户状态是否正常
        if (!loginUser.getStatus()) {
            throw new AccountForbiddenException(ResultEnum.USER_ACCOUNT_FORBIDDEN);
        }
        // 生成token
        String token = jwtUtils.generateToken(Map.of("userId", loginUser.getId(),
                        "userRole",loginUser.getUserRole()),
                "user");
        //构建响应对象
        return UserLoginVO.builder()
                .userName(loginUser.getUserName())
                .avatar(loginUser.getAvatar())
                .token(token)
                .build();
    }
}

这里要说一下的是通常数据库不放密码明文,这样做可以防止别人获取数据库直接得到账密登录违规操作风险,代码中使用MD5加密是很容易被暴力破解的所以可以用MD5加盐策略或者其他安全加密算法

3.测试接口

测试之前记得把图形验证码接口中redis缓存验证码的过期时间设置的长一点。

先生成一个验证码

日志打印图形验证码文本

正常测试

验证码错误测试

用户不存在测试

密码错误测试

账号被封禁测试

字段status修改为0代表被禁用

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7天前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
24 0
|
1月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
42 4
|
4天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
14 2
|
28天前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
35 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
25天前
|
XML Java 数据格式
提升效率!Spring Boot 开发中的常见失误轻松规避
本文深入探讨了在 Spring Boot 开发中常见的失误,包括不当使用注解、不良异常处理、低效日志记录等,提供了有效的规避策略,帮助开发者提升代码质量和系统性能,构建更健壮、高效的应用程序。
|
9天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
21 0
|
1月前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
26 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
18天前
|
JavaScript 前端开发 Java
SpringBoot_web开发-webjars&静态资源映射规则
https://www.91chuli.com/ 举例:jquery前端框架
15 0
|
1月前
|
开发框架 Java API
「SpringBrick快速入门指南」:一款基于Spring Boot的高级插件化开发框架
「SpringBrick快速入门指南」:一款基于Spring Boot的高级插件化开发框架
52 0
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
46 0