高并发项目-用户登录基本功能

文章目录

1.数据库表设计

1.IDEA连接数据库
2.修改application.yml中数据库的名称为seckill
3.IDEA创建数据库seckill
4.创建数据表 seckill_user
sql 复制代码
DROP TABLE IF EXISTS `seckill_user`;
CREATE TABLE `seckill_user`
(
    `id`              BIGINT(20)   NOT NULL COMMENT '用户 ID, 设为主键, 唯一 手机号',
    `nickname`        VARCHAR(255) NOT NULL DEFAULT '',
    `password`        VARCHAR(32)  NOT NULL DEFAULT '' COMMENT 'MD5(MD5(pass 明文+固定salt)+salt)',
    `slat`            VARCHAR(10)  NOT NULL DEFAULT '',
    `head`            VARCHAR(128) NOT NULL DEFAULT '' COMMENT '头像',
    `register_date`   DATETIME              DEFAULT NULL COMMENT '注册时间',
    `last_login_date` DATETIME              DEFAULT NULL COMMENT '最后一次登录时间',
    `login_count`     INT(11)               DEFAULT '0' COMMENT '登录次数',
    PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
select * from seckill_user;
5.密码加密分析
1.传统方式(不安全)
2.改进方式(两次加密加盐)

2.密码加密功能实现

1.pom.xml添加md5加密的依赖,刷新maven
xml 复制代码
    <!--md5 依赖-->
    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.15</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.11</version>
    </dependency>
2.编写工具类MD5Util.java,完成加密
java 复制代码
package com.sxs.seckill.utils;

import org.apache.commons.codec.digest.DigestUtils;

/**
 * Description: MD5加密工具类
 *
 * @Author sun
 * @Create 2024/5/5 14:23
 * @Version 1.0
 */
public class MD5Util {
    /**
     * 将一个字符串转换为MD5
     * @param src
     * @return
     */
    public static String md5(String src) {
        return DigestUtils.md5Hex(src);
    }

    // 固定的salt
    public static final String SALT = "dgeb2g4t";
    // 第一次加密加盐
    public static String inputPassToMidPass(String inputPass) {
        // 加盐
        String str = SALT.charAt(0) + inputPass + SALT.charAt(6);
        return md5(str);
    }

    /**
     * 第二次加密加盐
     * @param midPass
     * @param salt
     * @return
     */
    public static String midPassToDBPass(String midPass, String salt) {
        String str = salt.charAt(0) + midPass + salt.charAt(5);
        return md5(str);
    }

    /**
     * 两次加密
     * @param input
     * @param saltDB
     * @return
     */
    public static String inputPassToDBPass(String input, String saltDB) {
        String midPass = inputPassToMidPass(input);
        String dbPass = midPassToDBPass(midPass, saltDB);
        return dbPass;
    }
}
3.测试
1.引入jupiter 5.7.2依赖
xml 复制代码
    <!-- jupiter测试 -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.7.2</version>
      <scope>compile</scope>
    </dependency>
2.测试方法
java 复制代码
package com.sxs.seckill.utils;

/**
 * Description: 测试MD5加密方法
 *
 * @Author sun
 * @Create 2024/5/5 14:47
 * @Version 1.0
 */
public class MD5UtilTest {
    public static void main(String[] args) {
        String input = "123456";
        System.out.println("第一次加密:" + MD5Util.inputPassToMidPass(input));
        System.out.println("第二次加密:" + MD5Util.midPassToDBPass(MD5Util.inputPassToMidPass(input), MD5Util.SALT));
        System.out.println("两次加密:" + MD5Util.inputPassToDBPass(input, MD5Util.SALT));
    }
}

3.编写实体类映射表

1.创建pojo目录
2.User.java
java 复制代码
package com.sxs.seckill.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/5 14:59
 * @Version 1.0
 */
@Data
@TableName("seckill_user") // 指定表名
public class User implements Serializable { // 实现序列化接口
    private static final long serialVersionUID = 1L;
    /**
     * 用户 ID是手机号码
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID) // 指定主键,IdType.ASSIGN_ID表示自己指定ID,不自增
    private Long id;
    private String nickname;
    /**
     * MD5(MD5(pass 明文+固定 salt)+salt)
     */
    private String password;
    private String slat;
    /**
     * 头像
     */
    private String head;
    /**
     * 注册时间
     */
    private Date registerDate;
    /**
     * 最后一次登录时间
     */
    private Date lastLoginDate;
    /**
     * 登录次数
     */
    private Integer loginCount;
}

4.创建Mapper接口和Mapper.xml

1.创建UserMapper.java
java 复制代码
package com.sxs.seckill.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sxs.seckill.pojo.User;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/5 15:04
 * @Version 1.0
 */
public interface UserMapper extends BaseMapper<User> {
}
2.创建UserMapper.xml
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.sxs.seckill.mapper.UserMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.sxs.seckill.pojo.User">
        <id column="id" property="id"/>
        <result column="nickname" property="nickname"/>
        <result column="password" property="password"/>
        <result column="slat" property="slat"/>
        <result column="head" property="head"/>
        <result column="register_date" property="registerDate"/>
        <result column="last_login_date" property="lastLoginDate"/>
        <result column="login_count" property="loginCount"/>
    </resultMap>
</mapper>

5.响应信息类

1.创建vo目录
2.RespBeanEnum.java 响应信息枚举类
java 复制代码
package com.sxs.seckill.vo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

/**
 * Description: 响应枚举类
 *
 * @Author sun
 * @Create 2024/5/5 15:15
 * @Version 1.0
 */

@Getter
@AllArgsConstructor
@ToString
public enum RespBeanEnum {
    // 通用
    SUCCESS(200, "SUCCESS"),
    ERROR(500, "服务端异常"),
    //登录模块
    LOGIN_ERROR(500210, "用户名或者密码错误"),
    MOBILE_ERROR(500211, "手机号码格式不正确"), 
    BING_ERROR(500212, "参数绑定异常"), 
    MOBILE_NOT_EXIST(500213, "手机号码不存在"), 
    PASSWORD_UPDATE_FAIL(500214, "更新密码失败");

    // 响应码和响应信息
    private final Integer code;
    private final String message;

}
3.RespBean.java 响应信息的Bean
java 复制代码
package com.sxs.seckill.vo;

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

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/5 15:32
 * @Version 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {
    private long code;
    private String message;
    private Object obj;

    // 成功后
    public static RespBean success(Object obj) {
        return new RespBean(RespBeanEnum.SUCCESS.getCode(), RespBeanEnum.SUCCESS.getMessage(), obj);
    }

    public static RespBean success() {
        return new RespBean(RespBeanEnum.SUCCESS.getCode(), RespBeanEnum.SUCCESS.getMessage(), null);
    }

    // 失败各有不同
    public static RespBean error(RespBeanEnum respBeanEnum) {
        return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), null);
    }

    public static RespBean error(RespBeanEnum respBeanEnum, Object obj) {
        return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), obj);
    }
}

6.编写接受登录信息的vo以及手机号校验工具类

1.LoginVo.java
java 复制代码
package com.sxs.seckill.vo;

/**
 * Description: 登录实体类
 *
 * @Author sun
 * @Create 2024/5/5 15:43
 * @Version 1.0
 */
public class LoginVo {
    private String mobile;
    private String password;
}
2.ValidatorUtil.java
java 复制代码
package com.sxs.seckill.utils;

import org.junit.jupiter.api.Test;

/**
 * Description: 校验工具类
 *
 * @Author sun
 * @Create 2024/5/5 15:45
 * @Version 1.0
 */
public class ValidatorUtil {
    /**
     * 校验手机号是否有效
     * @param phoneNumber 手机号
     * @return 如果手机号有效返回 true,否则返回 false
     */
    public static boolean isMobile(String phoneNumber) {
        // 中国大陆的手机号码正则表达式,要求是11位数字,以1开头,第二位是3-9,后面是任意数字共11位
        String regex = "^1[3-9]\\d{9}$";
        return phoneNumber.matches(regex);
    }
    // 测试
    @Test
    public void t1() {
        System.out.println(isMobile("12345678901"));
        System.out.println(isMobile("1234567890"));
        // 正确的
        System.out.println(isMobile("13604969873"));
    }
}

7.编写service层

1.UserService.java
java 复制代码
package com.sxs.seckill.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.sxs.seckill.pojo.User;
import com.sxs.seckill.vo.LoginVo;
import com.sxs.seckill.vo.RespBean;

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

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/5 15:58
 * @Version 1.0
 */
public interface UserService extends IService<User> {
    /**
     * 用户登录校验
     * @param loginVo
     * @param request
     * @param response
     * @return
     */
    RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response);
}
2.UserServiceImpl.java
java 复制代码
package com.sxs.seckill.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sxs.seckill.mapper.UserMapper;
import com.sxs.seckill.pojo.User;
import com.sxs.seckill.service.UserService;
import com.sxs.seckill.utils.MD5Util;
import com.sxs.seckill.utils.ValidatorUtil;
import com.sxs.seckill.vo.LoginVo;
import com.sxs.seckill.vo.RespBean;
import com.sxs.seckill.vo.RespBeanEnum;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

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

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/5 15:59
 * @Version 1.0
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Resource
    private UserMapper userMapper;

    @Override
    public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
        // 获取用户输入的手机号和密码,此时的密码已经是midPass
        String mobile = loginVo.getMobile();
        String password = loginVo.getPassword();

        // 判断手机号和密码是否为空
        if (StringUtils.isBlank(mobile) || StringUtils.isBlank(password)) {
            return RespBean.error(RespBeanEnum.LOGIN_ERROR);
        }

        // 校验手机号是否正确
        if (!ValidatorUtil.isMobile(mobile)) {
            return RespBean.error(RespBeanEnum.MOBILE_ERROR);
        }

        // 根据手机号查询用户
        User user = userMapper.selectById(mobile);

        // 判断用户是否为空
        if (null == user) {
            return RespBean.error(RespBeanEnum.LOGIN_ERROR);
        }

        // 判断密码是否正确
        if (!MD5Util.midPassToDBPass(password, user.getSlat()).equals(user.getPassword())) {
            return RespBean.error(RespBeanEnum.LOGIN_ERROR);
        }

        // 返回成功
        return RespBean.success();
    }
}

8.编写controller层

1.引入校验的依赖
xml 复制代码
      <!--validation 参数校验-->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
        <version>2.4.5</version>
      </dependency>
2.LoginController.java
java 复制代码
package com.sxs.seckill.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Description:
 *
 * @Author sun
 * @Create 2024/5/5 16:41
 * @Version 1.0
 */
@Slf4j
@Controller
@RequestMapping("/login")
public class LoginController {

    /**
     * 访问/login/toLogin会跳转到templates/login.html
     * @return
     */
    @RequestMapping("/toLogin")
    public String toLogin() {
        return "login";
    }
    
}
3.引入静态资源和模板页
4.解析前端登录
1.点击登录,调用login
2.将密码加密加盐之后向后端 /login/doLogin 发送请求
5.测试访问首页
1.首先检查资源是否到了target目录,如果没到需要maven重新编译
2.测试访问 http://localhost:9092/seckill/login/toLogin
6.前端区分多环境
1.可以看到前端请求时使用的/其实只是本机的服务发现,也就是http://localhost:9092/,所以需要区分多环境
2.首先新增一个配置类 GlobalControllerAdvice.java 全局控制器通知,用于向前端传递全局变量
java 复制代码
package com.sxs.seckill.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;

/**
 * Description: 全局控制器通知,用于向前端传递全局变量
 *
 * @Author sun
 * @Create 2024/5/5 17:08
 * @Version 1.0
 */
@ControllerAdvice
public class GlobalControllerAdvice {
    // 从配置文件中读取baseUrl,从而使前端可以区分多环境
    @Value("${baseurl}")
    private String baseurl;

    @ModelAttribute("baseUrl")
    public String baseUrl() {
        // 这里可以根据环境变量或其他逻辑来动态确定 URL
        return baseurl;
    }
}
3.在application.yml和application-prod.yml编写baseUrl,区分多环境
4.login.html 读取baseUrl
1.在script标签内添加一行代码使其能够解析Thymeleaf
js 复制代码
th:inline="javascript"
2.修改请求,格式为 baseUrl + 资源路径
7.在数据库添加一条记录,可以模拟用户登录
1.假设密码是admin,在前端输出一下第一次加密加盐的结果
2.使用后端的工具类也来加密一下,比对结果,发现结果一致
3.在第二次加密的时候重新设置一个不同的盐为niubi666,模拟不同用户
4.将结果放到数据库中
5.此时的用户名和密码
  • 19588888888:195+八个八
  • admin
8.编写登录的controller并测试
1.LoginController.java
java 复制代码
    @ResponseBody
    @RequestMapping("/doLogin")
    public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
        RespBean respBean = userService.doLogin(loginVo, request, response);
        // 返回
        return respBean;
    }
2.测试,登录成功

相关推荐
Mos_x几秒前
SpringBoot】Spring Boot 项目的打包配置
java·后端
qianbailiulimeng4 分钟前
【Spring Boot】Spring Boot解决循环依赖
java·后端
何中应4 分钟前
Spring Boot解决循环依赖的几种办法
java·spring boot·后端
donotshow5 分钟前
SpringBoot】Spring Boot 项目的打包配置
java·后端
鬼火儿6 分钟前
Spring Boot 整合 ShedLock 处理定时任务重复
java·后端
王元_SmallA8 分钟前
【Spring Boot】Spring Boot解决循环依赖
java·后端
小圆53117 分钟前
java-learn(9):常见算法,collection框架
java·开发语言·算法
nbsaas-boot29 分钟前
SaaS 租户上下文传播架构
java·架构·saas
西岭千秋雪_1 小时前
Zookeeper监听机制
java·linux·服务器·spring·zookeeper
毕设源码-林学长1 小时前
计算机毕业设计java和Vue的安全教育科普平台设计与实现 安全知识普及与教育平台 安全教育信息化管理平台
java·开发语言·课程设计