项目实战:仿若依权限管理系统——登录功能

本文详细描述了一个基于SpringBoot的项目中,前端通过Ajax发送POST请求实现登录功能的过程。涉及前端数据封装、Controller层的处理(包括AjaxResult返回和调用Service层进行验证),以及Service层的业务逻辑验证和JWTtoken的生成。
摘要由CSDN通过智能技术生成

项目实战:仿若依权限管理系统——登录功能

第一步:前端发送ajax请求

前端:用户在登录页面输入账号、密码和验证码,然后点击登录按钮

发送POST形式的Ajax请求到Controller层

export function login(username, password, code, uuid) {
  const data = {
    username,
    password,
    code,
    uuid
  }
  return request({
    url: '/login',
    headers: {
      isToken: false
    },
    method: 'post',
    data: data
  })
}

第二步:Controller层

SysLoginController接受到了来自前端的POST请求,执行对应的AjaxResult方法

package com.fs.system.web.controller.common;

import java.util.Objects;
import com.fs.common.constant.Constants;
import com.fs.common.core.vo.AjaxResult;
import com.fs.common.core.vo.LoginBody;
import com.fs.common.core.vo.LoginUser;
import com.fs.common.util.ServletUtils;
import com.fs.system.service.impl.SysLoginService;
import com.fs.system.service.impl.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

// 2.3 @RestController 注解
/*
* @RestController注解是@Controller和@ResponseBody的结合
* @Controller:标识这个类是控制器类,负责处理来自客户端的HTTP请求
* @ResponseBody:把数据以JSON字符串的形式返回给客户端
* */
/**
 * @author: JackX
 * @createTime: * @param: null * @return: null
 * @description: 登录验证的Controller
2024/1/4
9:07
 */
@RestController
public class SysLoginController {
    @Autowired
    private SysLoginService loginService;

    // 2.1 @PostMapping 注解
    /*
    * 代表该方法接受Post请求
    * 这个注解会把Http请求映射到Controller上
    * */
    @PostMapping("/login")
    // 2.2 AjaxResult 返回值类型
    /*
    * AjaxResult是一种常用的返回值类型
    * 主要用于把JSON格式的数据返回给浏览器
    * */
    public AjaxResult login(@RequestBody LoginBody loginBody) throws Exception {
        // 2.4 @RequestBody 注解
        /*
        * @RequestBody:接受前端传递给后端的JSON字符串中的数据
        * 表明这个数据是从前端发来的
        * 这里前端发来了一个data
            const data = {
                username,
                password,
                code,
                uuid
            }
        * 包含 用户名,密码,验证码,唯一标识
        * LoginBody(username=admin, password=123456, code=3, uuid=c6729bc11c7a404993d8a701c986bc3d)
        * 你问我怎么看到的?debug啊
        * */

        // 调用.success()方法
        AjaxResult ajax = AjaxResult.success();
        /*
        * 既然成功接受到了人家的ajax请求,那肯定要返回成功
        * 所以调用.success()方法
        * 该方法返回2个值
        * 第一个是msg="操作成功"
        * 第二个是code=200
        * 200是HTTP常见的状态码之一,成功的状态码,HTTP 200
        * */

        // 生成令牌
        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());
        /*
         * Controller层连接前端和Service层
         * 接受到请求后就要去调用Service层的方法了
         * 这里调用了SysLoginService类的login方法
         * 传入了前端发过来的四个参数 用户名,密码,验证码,唯一标识
         * */
        /*
         * 这个LoginBody也是我们创建的一个数据类
         * 用来创建一个专门储存数据的java对象
         * ctrl+B快捷键点击LoginBody即可跳转过去
         * */

        // 添加token
        ajax.put(Constants.TOKEN, token);
        /*
         * .put()方法
         * 将键值对添加到一个对象中
         * 这里是设置ajax的token值
         * */
        /*
         * Constans是专门的一个常量类
         * 里面有一个名为TOKEN的常量
         * 默认值为"token"
         * */

        // 把AjaxResult返回给浏览器
        return ajax;
    }
}

2.1 @PostMapping 注解

@GetMapping和@PostMapping就是@RequestMapping已经指定请求方式的简写版(源码中可以看出来),GetMapping就是Get请求,PostMapping就是Post请求

@RequestMapping是一个用来处理请求地址到处理器controller功能方法映射规则的注解,这个注解会将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法controller上,可用于类或方法上。注解在类上,表示类中的所有响应请求的方法都是以该地址作为父路径(模块路径)。

2.2 AjaxResult 返回值类型

AjaxResult是一个常用的返回值类型,用于在前后端分离的Web应用中进行数据交互。它主要用于返回浏览器需要的JSON格式数据,以便于浏览器端进行异步处理。它通常包含以下属性:

public class AjaxResult {
    // 状态码,0表示请求成功,其他表示请求失败
    private int code;
    // 消息提示
    private String message;
    // 返回数据
    private Object data;
}

2.3 @RestController 注解

@RestController 是@Controller 和@ResponseBody 的结合

@Controller 将当前修饰的类注入SpringBoot IOC容器,使得从该类所在的项目跑起来的过程中,这个类就被实例化。

@Controller 用于标识一个类是控制器组件。在 Spring MVC(Model-View-Controller)架构中扮演重要角色,负责处理来自客户端的HTTP请求,协调业务逻辑的处理,并根据请求返回适当的视图或数据。
@ResponseBody 它的作用简短截说就是指该类中所有的API接口返回的数据,都会以JSON字符串的形式返回给客户端

2.4 @RequestBody 注解

@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);而最常用的使用请求体传参的无疑是POST请求了,所以使用@RequestBody接收数据时,一般都用POST方式进行提交。在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。

2.5 @Data

@Data 是一个 Lombok 提供的注解,它可以自动为类生成常用的方法,包括 getter、setter、equals、hashCode 和 toString 等。使用 @Data 注解可以简化代码,使代码更加简洁易读。

debug到这里之后,在FliterChainProxy过滤器链里面走了很久很久很久很久很久

第三步:Service层

Service层主要用于处理业务逻辑部分

也就是全部和登录相关的验证等

SysLoginService实现类:

package com.fs.system.service.impl;

import cn.hutool.core.util.StrUtil;
import com.fs.common.constant.CacheConstants;
import com.fs.common.constant.UserConstants;
import com.fs.common.core.pojo.SysUser;
import com.fs.common.core.vo.LoginUser;
import com.fs.common.enums.UserStatus;
import com.fs.common.exception.ServiceException;
import com.fs.common.exception.user.*;
import com.fs.common.util.DateUtils;
import com.fs.common.util.RedisCache;
import com.fs.common.util.ip.IpUtils;
import com.fs.system.service.ISysConfigService;
import com.fs.system.service.ISysLoginService;
import com.fs.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.misc.MessageUtils;

import java.util.Objects;

/**
 * @author: JackX
 * @createTime: * @param: null * @return: null
 * @description:
 2024/1/4
 14:51
*/
/*
    登录业务流程:
    1.验证码校验
    2.后台对前台传递参数的校验
    3.根据用户名查询数据库SysUser
    4.进行密码输入次数校验
    5.记录成功登录的用户信息日志
    6.创建token,保存到redis,并返回JWT
* */
@Service
@Slf4j
public class SysLoginService implements ISysLoginService {
    @Autowired
    private TokenService tokenService;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysConfigService configService;

    @Autowired
    private SysPasswordService passwordService;

    /**
     * @author: JackX
     * @param: username 用户名
     * @param: password 密码
     * @param: code 验证码
     * @param: uuid 唯一标识
     * @return: java.lang.String 结果
     * @description: 登录
     * @createTime:
     2024/1/4
     15:17
    */
    @Override
    /*
    * 3.1 @Override 注解
    * 表明该方法是重写方法
    * */
    public String login(String username, String password, String code, String uuid) {
        // 1.验证码校验
        /*
        * 调用validateCaptcha方法
        * 传入 用户名 验证码 唯一标识
        * 判断是否为空,判断验证码是否匹配
        * */
        validateCaptcha(username, code, uuid);

        // 2.登录前置校验
        /*
        * 传入 用户名 密码
        * 判断是否为空,判断长度是否匹配,判断IP黑名单
        * */
        loginPreCheck(username, password);

        // 3.用户验证(这里去mapper层)
        /*
        * 调用ISysUserService中的selectUserByUserName方法
        * 传入 用户名
        * 目的是从数据库查询对应用户名的用户
        * */
        SysUser user = userService.selectUserByUserName(username);

        // 4.判断数据库查询结果
        /*
        * 如果为空,抛异常
        * UserStatus 用户状态码类
        * 0正常 1停用 2删除
        * 如果用户状态码为1停用,抛异常
        * 如果用户状态码为2删除,抛异常
        * */
        if (Objects.isNull(user)) {
            log.info("登录用户:{} 不存在.", username);
            throw new ServiceException("用户不存在/密码错误");
        }
        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            log.info("登录用户:{} 已被删除.", username);
            throw new ServiceException("对不起,您的账号已被删除");
        }
        else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户:{} 已被停用.", username);
            throw new ServiceException("用户已封禁,请联系管理员");
        }

        // 5.验证账号密码错误次数
        /*
        * 如果次数超出,抛异常
        * */
        passwordService.validate(user,password);

        // 能走到这一步,说明之前的5个验证都通过了
        // 保存用户信息
        LoginUser loginUser = new LoginUser();
        loginUser.setUserId(user.getUserId());
        loginUser.setUser(user);

        // 数据库记录该用户信息(和数据库有关的都去mapper层)
        recordLoginInfo(user.getUserId());

        // 生成token
        return tokenService.createToken(loginUser);
    }

    /**
     * @author: JackX
     * @param: username 用户名
     * @param: code 验证码
     * @param: uuid 唯一表示
     * @description: 验证码校验
     * 如果验证码为空,抛异常。
     * 如果验证码不匹配,抛异常
     * @createTime:
     2024/1/4
     15:12
    */
    @Override
    public void validateCaptcha(String username, String code, String uuid) {
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        if (captchaEnabled) {
            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
            String captcha = redisCache.getCacheObject(verifyKey);
            if (captcha == null) {
                throw new CaptchaExpireException();
            }
            if (!code.equalsIgnoreCase(captcha)) {
                throw new CaptchaException();
            }
        }
    }

    /**
     * @author: JackX
     * @param: username 用户名
     * @param: password 用户密码
     * @description: 登录前置校验
     * 如果用户名或者密码为空,抛异常。
     * 如果用户名或密码长度不在范围内,抛异常。
     * 如果IP在黑名单中,抛异常。
     * @createTime:
     2024/1/4
     15:16
    */
    public void loginPreCheck(String username, String password) {
        // 用户名或密码为空 错误
        if (StrUtil.isEmpty(username) || StrUtil.isEmpty(password)) {
            throw new UserNotExistsException();
        }

        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
            throw new UserPasswordNotMatchException();
        }

        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
            throw new UserPasswordNotMatchException();
        }

        // IP黑名单校验
        String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
            throw new BlackListException();
        }
    }

    /**
     * @author: JackX
     * @param: userId 用户id
     * @description: 记录登录信息
     * @createTime:
     2024/1/4
     15:16
    */
    @Override
    public void recordLoginInfo(Long userId) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr());
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }
}

3.1 @Override

表明该方法是重写方法,保证正确重写父类的方法

第四步:Mapper层

Mapper层主要用于和数据库进行交互

会自动调用resources目录下的同名的Mapper.xml文件

并执行里面的SQL语句

调用mapper层的service类

SysUserServiceImpl实现类:

package com.fs.system.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.fs.common.constant.UserConstants;
import com.fs.common.core.pojo.*;
import com.fs.common.exception.ServiceException;
import com.fs.system.mapper.*;
import com.fs.system.service.ISysConfigService;
import com.fs.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

/*
* 用户 业务层处理
* */
@Service
@Slf4j
public class SysUserServiceImpl implements ISysUserService{

    @Autowired
    private SysUserMapper userMapper;

    /*
    * 通过用户名查询用户
    * 输入 用户名
    * 返回 用户
    * */
    @Override
    public SysUser selectUserByUserName(String userName) {
        return userMapper.selectUserByUserName(userName);
    }

    /*
    * 修改用户基本信息
    * 输入 用户
    * 返回 int
    * */
    @Override
    public int updateUserProfile(SysUser user) {
        return userMapper.updateUser(user);
    }
}

mapper层接口和Mapper.xml文件配合食用

SysUserMapper接口:

/**
     * 通过用户名查询用户
     * 
     * @param userName 用户名
     * @return 用户对象信息
     */
    public SysUser selectUserByUserName(String userName);

Mapper.xml配置文件

SysUserMapper.xml

查询用户名相同、未被删除的用户

<select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult">
	    <include refid="selectUserVo"/>
		where u.user_name = #{userName} and u.del_flag = '0'
</select>
  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值