2.java秒杀项目第二课-后端登录功能

实现登录功能

MD5配置

xml 复制代码
<dependency>
  <groupId>commons-codec</groupId>
  <artifactId>commons-codec</artifactId>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.6</version>
</dependency>

创建util包,MD5Util

java 复制代码
package com.yxl.seckill.util;

import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Component;

@Component
public class MD5Util
{
    public static String md5(String src) {
        return DigestUtils.md5Hex(src);
    }
    private static final String salt = "1a2b3c4d";
    public static String inputPassToFormPass(String inputPass) {
        System.out.println(salt.charAt(0));
        System.out.println(salt.charAt(2));
        System.out.println(salt.charAt(5));
        System.out.println(salt.charAt(4));
        System.out.println(""+salt.charAt(0)+salt.charAt(2));
        String str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) +
            salt.charAt(4);
         System.out.println(str);
        return md5(str);
    }
    public static String formPassToDBPass(String formPass, String salt) {
        String str = ""+salt.charAt(0)+salt.charAt(2) + formPass +salt.charAt(5) +
            salt.charAt(4);
        return md5(str);
    }
    public static String inputPassToDbPass(String inputPass, String saltDB) {
        String formPass = inputPassToFormPass(inputPass);
        String dbPass = formPassToDBPass(formPass, saltDB);
        return dbPass;
    }

    public static void main(String[] args) {
        System.out.println(inputPassToFormPass("123456"));//d3b1294a61a07da9b49b6e22b2cbd7f9
        System.out.println(formPassToDBPass(inputPassToFormPass("123456"),
            "1a2b3c4d"));
        System.out.println(inputPassToDbPass("123456", "1a2b3c4d"));
    }

}

点击 Run运行

在测试控制器中Demo2Controller,增加对用户的List进行数据初始化,使其有一条供测试的数据信息 属性id赋值为1,name赋值为"张三",password为123456进行md5加密后处理的数据(需要引入MD5Util)。创建登录接口"/login",对前端输入的用户信息进行用户与密码信息的校验。

1.增加对用户的List进行数据初始化

java 复制代码
  // 静态User列表用于存储数据
    private static List<User> userList = new ArrayList<>();
    
    {
        User user1 = new User();
        user1.setId(1L);
        user1.setName("张三");
        user1.setPassword("d3b1294a61a07da9b49b6e22b2cbd7f9");
        userList.add(user1);
       
    }

2.创建登录接口"/login"

3.在接口中实现用户信息进行用户与密码信息的校验逻辑(需要引入)

java 复制代码
  // POST /demo2/login - 登录
    @GetMapping("/login")
    public CommonResult<User> login(@RequestParam String name, @RequestParam String password) {
        System.out.println("参数:"+name);
        System.out.println("参数:"+password);
        String inputPassToFormPass = MD5Util.inputPassToFormPass(password);
        System.out.println(inputPassToFormPass);

        for (User user : userList) {
            System.out.println(user.getName());
            System.out.println(user.getPassword());
            if (user.getName().equals(name) && user.getPassword().equals(inputPassToFormPass)) {
                return CommonResult.success(user);
            }
        }
        return CommonResult.error("User not found with name: " + name);
    }

调用结果:失败与成功的结果反馈

用户端:PASS=MD5(明文+固定Salt)

服务端:PASS=MD5(用户输入+随机Salt)

用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双

重保险。

逆向工程配置

MyBatis-Plus 官方文档 https://baomidou.com/guides/new-code-generator/

新建一个工程,只选择spring web,generator

进行配置

xml 复制代码
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.14</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.14</version>
        </dependency>
        <!-- freemarke 模板 -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.28</version>
            <scope>compile</scope>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.19</version>
        </dependency>
    </dependencies>

将代码生成器的模板 放在此目录下

加入生成代码

java 复制代码
package com.yxl.generator;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.sql.Types;
import java.util.Collections;

public class CodeGenerator
{
    public static void main(String[] args) {
        FastAutoGenerator.create("url", "user", "pwd")
            .globalConfig(builder -> {
                builder.author("yxl") // 设置作者
                    .enableSwagger() // 开启 swagger 模式
                    .outputDir("G://java"); // 指定输出目录
            })
            .dataSourceConfig(builder ->
                builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
                    int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                    if (typeCode == Types.SMALLINT) {
                        // 自定义类型转换
                        return DbColumnType.INTEGER;
                    }
                    return typeRegistry.getColumnType(metaInfo);
                })
            )
            .packageConfig(builder ->
                builder.parent("com.yxl.seckill") // 设置父包名
                    .moduleName("") // 设置父包模块名
                    .pathInfo(Collections.singletonMap(OutputFile.xml, "G://java//com//yxl//seckill")) // 设置mapperXml生成路径
            )
            .strategyConfig(builder ->
                builder.addInclude("t_user") // 设置需要生成的表名
                    .entityBuilder()
                    .enableLombok()
                    .controllerBuilder()
                    .enableRestStyle() // 启用 REST 风格
//                    .addTablePrefix("t_", "c_") // 设置过滤表前缀
            )
            .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
            .execute();
}

生成结果

在控制器类中对用户表进行增删改查的实现。
springboot 整合mybatis-plus

配置pom.xml

xml 复制代码
  <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.6.2</version>
        </dependency>
<!--mysql 依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <!-- MyBatis-Plus 依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.14</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-jsqlparser -->
        <!--mybatis-plus 分页插件依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-jsqlparser</artifactId>
            <version>3.5.14</version>
        </dependency>

Mybits-plus的yaml配置

java 复制代码
# 数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/seckill?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&useSSL=false
    username: root
    password: 123456
    hikari:
      # 连接池名
      pool-name: DateHikariCP
      # 最小空闲连接数
      minimum-idle: 5
      # 空闲连接存活最大时间,默认600000(10分钟)
      idle-timeout: 180000
      # 最大连接数,默认10
      maximum-pool-size: 10
      # 从连接池返回的连接的自动提交
      auto-commit: true
      # 连接最大存活时间,0表示永久存活,默认1800000(30分钟)
      max-lifetime: 1800000
      # 连接超时时间,默认30000(30秒)
      connection-timeout: 30000
      # 测试连接是否可用的查询语句
      connection-test-query: SELECT 1
# Mybatis-plus配置
mybatis-plus:
  #配置Mapper映射文件
  mapper-locations: classpath*:/mapper/*Mapper.xml
  # 配置MyBatis数据返回类型别名(默认别名是类名)
  type-aliases-package: com.yxl.seckill.entity
  ## Mybatis SQL 打印(方法接口所在的包,不是Mapper.xml所在的包)
Spring Boot 工程
  • 配置 MapperScan 注解
java 复制代码
package com.yxl.seckill;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.yxl.seckill.mapper")
public class SeckillApplication
{

    public static void main(String[] args)
    {
        SpringApplication.run(SeckillApplication.class, args);
    }

}

实现登录接口

创建登录参数消息体vo ,创建vo包

java 复制代码
package com.yxl.seckill.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.hibernate.validator.constraints.Length;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginVo
{
    private String mobile;
    private String password;
}
创建登录接口 loginController
java 复制代码
package com.yxl.seckill.controller;

import com.yxl.seckill.service.ITUserService;
import com.yxl.seckill.vo.CommonResult;
import com.yxl.seckill.vo.LoginVo;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/")
@Slf4j
public class LoginController
{

    private static final Logger log = LoggerFactory.getLogger(LoginController.class);
    @Autowired
    private ITUserService userService;
    @RequestMapping("/login")
    public CommonResult<String> login(HttpServletRequest request, HttpServletResponse response, @RequestBody LoginVo loginVo)
    {

        log.info(loginVo.toString());
        CommonResult<String> login = userService.login(request, response, loginVo);
        return login;
    }

    
}

创建uuid,UUID(Universally Unique Identifier,通用唯一识别码)是一种在软件构建中用于确保全局唯一性的标准。它是一个128位的数字,通常表示为32个十六进制数字,并通过特定的算法生成,以确保在不同的系统和时间中都是唯一的。

java 复制代码
package com.yxl.seckill.util;

import java.util.UUID;

public class UUIDUtil
{
    public static String uuid() {
        return UUID.randomUUID().toString().replace("-", "");
    }

}

TUserServiceImpl 实现登录

java 复制代码
 @Override
    public CommonResult<String> login(HttpServletRequest request, HttpServletResponse response, LoginVo loginVo)
    {
        try
        {

            String mobile = loginVo.getMobile();
            String password = loginVo.getPassword();
            TUser user = userMapper.selectById(mobile);
            if (user == null)
            {
                return CommonResult.error(ResponseCode.LOGINVO_ERROR);
            }
            //校验密码
            if (!MD5Util.formPassToDBPass(password, user.getSlat()).equals(user.getPassword()))
            {

                return CommonResult.error(ResponseCode.LOGINVO_ERROR);
            }
            //生成cookie
            String ticket = UUIDUtil.uuid();
            request.getSession().setAttribute(ticket,user);
            CookieUtil.setCookie(request,response,"userTicket",ticket);

            return CommonResult.success(ticket);

        }
        catch (Exception e)
        {
            e.printStackTrace();
            return CommonResult.error(ResponseCode.ERROR);
        }
    }

CookieUtil 代码实现:Cookie 是浏览器存储在用户设备上的小型文本数据 ,它用于记录用户会话、偏好等信息。它的核心作用是:让服务器识别用户身份https://juejin.cn/post/7522770423504306202

java 复制代码
package com.yxl.seckill.util;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

public class CookieUtil
{
    /**
     * 得到Cookie的值, 不编码
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName)
    {
        return getCookieValue(request, cookieName, false);
    }
    /**
     * 得到Cookie的值,
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName,
                                        boolean isDecoder) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    if (isDecoder) {
                        retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
                    } else {
                        retValue = cookieList[i].getValue();
                    }
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }
    /**
     * 得到Cookie的值,
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String cookieName,
                                        String encodeString) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    retValue = URLDecoder.decode(cookieList[i].getValue(),
                        encodeString);
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }
    /**
     * 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse
        response, String cookieName,
                                 String cookieValue) {
        setCookie(request, response, cookieName, cookieValue, -1);
    }
    /**
     * 设置Cookie的值 在指定时间内生效,但不编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse
        response, String cookieName,
                                 String cookieValue, int cookieMaxage) {
        setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
    }
    /**
     * 设置Cookie的值 不设置生效时间,但编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse
        response, String cookieName,
                                 String cookieValue, boolean isEncode) {
        setCookie(request, response, cookieName, cookieValue, -1, isEncode);
    }
    /**
     * 设置Cookie的值 在指定时间内生效, 编码参数
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse
        response, String cookieName,
                                 String cookieValue, int cookieMaxage, boolean
                                     isEncode) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage,
            isEncode);
    }
    /**
     * 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse
        response, String cookieName,
                                 String cookieValue, int cookieMaxage, String
                                     encodeString) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage,
            encodeString);
    }
    /**
     * 删除Cookie带cookie域名
     */
    public static void deleteCookie(HttpServletRequest request, HttpServletResponse
        response,
                                    String cookieName) {
        doSetCookie(request, response, cookieName, "", -1, false);
    }
    /**
     * 设置Cookie的值,并使其在指定时间内生效
     *
     * @param cookieMaxage cookie生效的最大秒数
     */
    private static final void doSetCookie(HttpServletRequest request,
                                          HttpServletResponse response,
                                          String cookieName, String cookieValue, int
                                              cookieMaxage, boolean isEncode) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else if (isEncode) {
                cookieValue = URLEncoder.encode(cookieValue, "utf-8");
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0)
                cookie.setMaxAge(cookieMaxage);
            if (null != request) {// 设置域名的cookie
                String domainName = getDomainName(request);
                System.out.println(domainName);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 设置Cookie的值,并使其在指定时间内生效
     *
     * @param cookieMaxage cookie生效的最大秒数
     */
    private static final void doSetCookie(HttpServletRequest request,
                                          HttpServletResponse response,
                                          String cookieName, String cookieValue, int
                                              cookieMaxage, String encodeString) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else {
                cookieValue = URLEncoder.encode(cookieValue, encodeString);
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0) {
                cookie.setMaxAge(cookieMaxage);
            }
            if (null != request) {// 设置域名的cookie
                String domainName = getDomainName(request);
                System.out.println(domainName);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 得到cookie的域名
     */
    private static final String getDomainName(HttpServletRequest request) {
        String domainName = null;
// 通过request对象获取访问的url地址
        String serverName = request.getRequestURL().toString();
        if (serverName == null || serverName.equals("")) {
            domainName = "";
        } else {
// 将url地下转换为小写
            serverName = serverName.toLowerCase();
// 如果url地址是以http://开头 将http://截取
            if (serverName.startsWith("http://")) {
                serverName = serverName.substring(7);
            }
            int end = serverName.length();
// 判断url地址是否包含"/"
            if (serverName.contains("/")) {
//得到第一个"/"出现的位置
                end = serverName.indexOf("/");
            }
// 截取
            serverName = serverName.substring(0, end);
// 根据"."进行分割
            final String[] domains = serverName.split("\\.");
            int len = domains.length;
            if (len > 3) {
// www.xxx.com.cn
                domainName = domains[len - 3] + "." + domains[len - 2] + "." +
                    domains[len - 1];
            } else if (len <= 3 && len > 1) {
// xxx.com or xxx.cn
                domainName = domains[len - 2] + "." + domains[len - 1];
            } else {
                domainName = serverName;
            }
        }
        if (domainName != null && domainName.indexOf(":") > 0) {
            String[] ary = domainName.split("\\:");
            domainName = ary[0];
        }
        return domainName;
    }

}

登录测试 :http://localhost:8080/login?mobile=18501345678&password=d3b1294a61a07da9b49b6e22b2cbd7f9

配置验证注解pom.xml

xml 复制代码
       <dependency>
            <groupId>org.jetbrains</groupId>
            <artifactId>annotations</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.2.5.Final</version>
        </dependency>
注解的应用
java 复制代码
package com.yxl.seckill.vo;

import lombok.Data;
import org.jetbrains.annotations.NotNull;
import org.hibernate.validator.constraints.Length;

@Data
public class LoginVo
{
    @NotNull
    private String mobile;
    @NotNull
    @Length(min = 32)
    private String password;
}
自定义注解 手机号验证

IsMobile 是一个自定义的 Java 验证注解,用于验证手机号码格式是否正确。它包含以下四个元注解:

@Documented:表示该注解应该被 javadoc 工具记录,使注解信息出现在生成的文档中。

@Retention(RetentionPolicy.RUNTIME):指定注解的保留策略为运行时保留,这意味着注解信息会在编译后的 class 文件中存在,并且在运行时可以通过反射获取。

RetentionPolicy 的三种策略

  1. RetentionPolicy.SOURCE:注解只保留在源文件中,当 Java 文件编译成 class 文件时,注解被丢弃。
  2. RetentionPolicy.CLASS:注解被保留到 class 文件中,但 JVM 加载 class 文件时,注解不会被加载到内存中,这是默认的生命周期。
  3. RetentionPolicy.RUNTIME:注解不仅被保留到 class 文件中,JVM 加载 class 文件后,注解仍然存在。这使得我们可以在运行时通过反射机制读取注解信息。

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE}):定义了注解可以应用的目标元素类型,包括方法、字段、参数、局部变量和类型使用。

@Constraint(validatedBy = {IsMobileValidator.class}):这是最重要的部分,它将注解标记为一个约束(验证规则),并指定了验证器类 IsMobileValidator 来执行实际的验证逻辑。

此外,该注解还定义了几个属性:

required():指定该字段是否必须填写,默认为 true

message():验证失败时的默认错误消息,这里是"手机号码格式错误"

groups():验证分组,默认为空

payload():有效载荷,默认为空

当使用该注解时,会调用 IsMobileValidator 类进行验证。验证器会根据 required 属性的值来决定验证逻辑:

如果 required 为 true,则必须验证手机号码格式

如果 required 为 false,则允许为空,但如果填写了则必须符合手机号码格式

具体的手机号码格式验证由 ValidatorUtil.isMobile() 方法完成,使用正则表达式 1 (3-9) 0-9{9}$ 来验证手机号码是否符合中国手机号码的格式。

java 复制代码
package com.yxl.seckill.validator;


import com.yxl.seckill.util.ValidatorUtil;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.util.StringUtils;

public class IsMobileValidator implements ConstraintValidator<IsMobile, String>
{

    private boolean required = false;
    @Override
    public void initialize(IsMobile constraintAnnotation) {
        required = constraintAnnotation.required();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        if (required){
            return ValidatorUtil.isMobile(value);
        }else {
            if (StringUtils.isEmpty(value)){
                return true;
            }else {
                return ValidatorUtil.isMobile(value);
            }
        }

    }
}

ConstraintValidator 是一个泛型接口,专门用于实现自定义约束验证逻辑。在我们的项目中,IsMobileValidator 类实现了这个接口来验证手机号码格式。

  1. 基本结构

ConstraintValidator 接口有两个泛型参数:

第一个参数:对应的注解类型(在我们项目中是 IsMobile)

第二个参数:被验证的数据类型(在我们项目中是 String)

  1. 核心方法

ConstraintValidator 接口定义了两个重要方法:

initialize() - 初始化方法:

在验证开始前执行一次

用于获取注解中的配置参数

在我们的例子中,获取 required 属性值

isValid() - 验证方法:

执行实际的验证逻辑

返回 true 表示验证通过,false 表示验证失败

在我们的例子中,根据 required 值和手机号格式进行验证

浏览器会话

HttpSession ,通常称为session ,是JavaWeb中用于表示会话的机制,它是一个存储在Web服务器端的Java对象,代表用户和Web服务器的一次会话。会话的本质是从session对象 的创建到最终超时销毁的过程。HttpSession 对象可以将会话状态保存在服务器端,而Cookie则可以将会话状态保存在客户端浏览器中。

JSESSIONID 是一种用于在客户端和服务器之间维持会话状态的机制。由于 HTTP 协议是无状态的,每次请求都是独立的,因此需要一种方法来识别同一个用户的多个请求。JSESSIONID 就是用来解决这个问题的。


存在问题

之前的代码在我们之后一台应用系统,所有操作都在一台Tomcat上,没有什么问题。当我们部署多台系统,配合Nginx的时候会出现用户登录的问题。

Web 服务器: 一台 2 核 4G 的服务器可能能够处理几千个并发连接,具体取决于 Web 应用程序的复杂程度和请求类型。 数据库服务器: 一台 2 核 4G 的服务器可能能够处理几百个并发连接,具体取决于数据库类型和查询复杂程度。

当部署在多台服务器上时,由于 Nginx 使用默认负载均衡策略(轮询),请求将会按照时间顺序逐一分发到后端应用上。也就是说刚开始我们在 Tomcat1 登录之后,用户信息放在 Tomcat1 的 Session 里。过了一会,请求又被 Nginx 分发到了 Tomcat2 上,这时 Tomcat2 上 Session 里还没有用户信息,于是又要登录。

使用Redis进行解决

使用Redis中间件进行session缓存。

Redis安装 https://download.csdn.net/blog/column/10939729/115372992

安装完之后 进行访问 _redis-cli.exe -h 127.0.0.1 -p 6379 _

查看键值 keys *

Redis需要进行Config配置, \xAC\xED\x00\x05t\x00是Redis键值对中出现的前缀,它是由于RedisTemplate的配置问题导致的。不影响程序运行。

java 复制代码
package com.yxl.seckill.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig
{
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        //key序列器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //value序列器
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //Hash类型 key序列器
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //Hash类型 value序列器
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }

}
Springboot整合Redis
  • 添加全局pom.xml配置
json 复制代码
   <!-- spring data redis 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  • 引入hutool工具
json 复制代码
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.38</version>
</dependency>
  • 进行application.yml文件配置
json 复制代码
  redis:
      #超时时间
    timeout: 10000ms
      #服务器地址
    host: 127.0.0.1
      #服务器端口
    port: 6379
      #数据库
    database: 0
      #密码
      #    password: root
    lettuce:
      pool:
          #最大连接数,默认8
        max-active: 1024
          #最大连接阻塞等待时间,默认-1
        max-wait: 10000ms
          #最大空闲连接
        max-idle: 200
          #最小空闲连接
        min-idle: 5

在TUserServiceImpl注入 使用

json 复制代码
    @Autowired
    private RedisTemplate redisTemplate;

在TUserServiceImpl 进行登录操作改写,将userTiket从session缓存改到redis中

java 复制代码
package com.yxl.seckill.service.impl;

import com.yxl.seckill.entity.TUser;
import com.yxl.seckill.mapper.TUserMapper;
import com.yxl.seckill.service.ITUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yxl.seckill.util.CookieUtil;
import com.yxl.seckill.util.JsonUtil;
import com.yxl.seckill.util.MD5Util;
import com.yxl.seckill.util.UUIDUtil;
import com.yxl.seckill.vo.CommonResult;
import com.yxl.seckill.vo.LoginVo;
import com.yxl.seckill.vo.ResponseCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author yxl
 * @since 2025-09-23
 */
@Service
public class TUserServiceImpl extends ServiceImpl<TUserMapper, TUser> implements ITUserService {
    @Autowired
    private TUserMapper userMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public CommonResult<String> login(HttpServletRequest request, HttpServletResponse response, LoginVo loginVo)
    {
        try
        {

            String mobile = loginVo.getMobile();
            String password = loginVo.getPassword();
            TUser user = userMapper.selectById(mobile);
            if (user == null)
            {
                return CommonResult.error(ResponseCode.LOGINVO_ERROR);
            }
            //校验密码
            if (!MD5Util.formPassToDBPass(password, user.getSlat()).equals(user.getPassword()))
            {

                return CommonResult.error(ResponseCode.LOGINVO_ERROR);
            }
            //生成cookie
            String ticket = UUIDUtil.uuid();
            // request.getSession().setAttribute(ticket,user); 
//            redisTemplate.opsForValue().set("user:" + ticket,
//                JsonUtil.object2JsonStr(user), 60 * 2, java.util.concurrent.TimeUnit.SECONDS);
            redisTemplate.opsForValue().set("user:" + ticket,
                JsonUtil.object2JsonStr(user));
            CookieUtil.setCookie(request,response,"userTicket",ticket);

            return CommonResult.success(ticket);

        }
        catch (Exception e)
        {
            e.printStackTrace();
            return CommonResult.error(ResponseCode.ERROR);
        }
    }

    @Override
    public TUser getByUserTicket(String userTicket, HttpServletRequest request, HttpServletResponse response)
    {
        if (!StringUtils.hasText(userTicket)) {
            return null;
        }
        String userJson = (String) redisTemplate.opsForValue().get("user:" +
            userTicket);
        if (!StringUtils.hasText(userJson)) {
            return null;
        }
        TUser user = JsonUtil.jsonStr2Object(userJson, TUser.class);
        if (null != user) {
            CookieUtil.setCookie(request,response,"userTicket",userTicket);
        }
        return user;

    }
}

getByUserTicket 改写

json 复制代码
    @Override
    public TUser getByUserTicket(String userTicket, HttpServletRequest request, HttpServletResponse response)
    {
        if (!StringUtils.hasText(userTicket)) {
            return null;
        }
        String userJson = (String) redisTemplate.opsForValue().get("user:" +
            userTicket);
        if (!StringUtils.hasText(userJson)) {
            return null;
        }
        TUser user = JsonUtil.jsonStr2Object(userJson, TUser.class);
        if (null != user) {
            CookieUtil.setCookie(request,response,"userTicket",userTicket);
        }
        return user;

    }

MD5Util.javaUUIDUtil.javaValidatorUtil.javaCookieUtil.javaJsonUtil.java

课下练习

Nginx web服务器入门

https://www.bilibili.com/video/BV1TZ421b7SD/?spm_id_from=333.337.search-card.all.click&vd_source=b756dc9ebc6bb806e3db30e17f165947

\[2.1 springboot 快速整合mybits\]

\[2.2 redis 安装\]

相关推荐
cndes10 分钟前
给Miniconda换源,让包下载更迅速
开发语言·python
乐观的Terry18 分钟前
1、为什么要自己造发布系统
java·spring boot·后端·spring cloud·架构
LuminousCPP29 分钟前
C 语言集中实践全记录:顺序表 + 单链表原理实现与通讯录项目实战【附可运行源码】
c语言·开发语言·数据结构·经验分享·笔记
兰令水31 分钟前
hot100【acm版】【2026.7.13打卡-java版本】
java·开发语言·数据结构·算法·leetcode·面试
铅笔侠_小龙虾1 小时前
Rust 学习(4)-函数与注释
开发语言·学习·rust
名字还没想好☜1 小时前
用 Redis + Redisson 实现分布式锁:从踩坑到生产可用
java·redis·分布式·junit·分布式锁·redisson
tiana_1 小时前
写了个零依赖的 Go 版本管理器,curl | bash 完事
开发语言·golang·bash
谙忆1 小时前
图片CDN与边缘优化实战:缓存策略、图片处理型CDN与动态裁剪
java·spring·缓存
嘘嘘出差1 小时前
Python 爬虫入门实战:爬取豆瓣电影 Top 250
开发语言·爬虫·python
爱写代码的倒霉蛋1 小时前
实现协程的三种方式
开发语言·python