时间:2025-05-24 09:15
人气:
作者:admin
@
目录用户登录 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;
MD5 的加密的依赖包:
这里我们解读一下密码的设计!!:
登录为例讲解:
传统方式:
客户端——> password 明文——>后端(md5(password 明文)) == db 中存放的 password 是否一致) :这种传统方式存在的问题:

传统方式改进的方式 1:客户端——> md5(password 明文)——>后端(md5(md5(password 明文)) ) == db 中存放的 password 是否一致) 。这样就算黑客拦截到了我们前端发送的信息,也是被加密的,所以无妨。
我们可以在传统方式的基础上,在 进行一个加盐上的处理。让密码更加安全一些。
传统方式改进的方式 2:客户端——> md5(password 明文+salt1(固定的盐))——>后端(md5(md5(password 明文+salt1(固定的盐)+salt2(从数据库当中获取的盐,不同用户对应的盐也不同))) ) == db 中存放的 password 是否一致) 。
注意:是对称加密的。
Md5 加密所需的相关依赖 。
<!--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>
Junit Jupiter: Junit Jupiter 提供了 JUnit5 的新的编程模型,是 JUnit5 新特性的核心。内部 包含了一个测试引擎,用于在 Junit Platform 上运行
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.2</version>
<scope>compile</scope>
</dependency>
加密密码工具类编写
package com.rainbowsea.seckill.utill;
import org.apache.commons.codec.digest.DigestUtils;
/**
* MD5 加密工具类,根据前面密码设计方案提供相应的方法
*/
public class MD5Util {
/**
* 第一次加密所需的盐。
*/
private static final String SALT = "UCmP7xHA";
/**
* MD5 加密
*
* @param src 要加密的字符串
* @return String
*/
public static String md5(String src) {
return DigestUtils.md5Hex(src);
}
/**
* 加密加盐,完成的是 md5(pass+salt1)
*
* @param inputPass 输入的密码
* @return String
*/
public static String inputPassToMidPass(String inputPass) {
String str = "" + SALT.charAt(0) + inputPass + SALT.charAt(6);
return md5(str);
}
/**
* 这个盐随机生成,成的是 md5( md5(pass+salt1)+salt2)
*
* @param midPass 加密的密码
* @param salt 从数据库获取到不同用户加密的盐
* @return String
*/
public static String midPassToDBPass(String midPass, String salt) {
String str = salt.charAt(1) + midPass + salt.charAt(5);
return md5(str);
}
/**
* 进行两次加密加盐 最后存到数据库的 md5( md5(pass+salt1)+salt2)
* salt1是前端进行的salt2 是后端进行的随机生成
*/
public static String inputPassToDBPass(String inputPass, String salt) {
String midPass = inputPassToMidPass(inputPass);
String dbPass = midPassToDBPass(midPass, salt);
return dbPass;
}
}

validation参数校验
<!--validation参数校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.4.5</version>
</dependency>

用户登录逻辑编写:
package com.rainbowsea.seckill.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rainbowsea.seckill.mapper.UserMapper;
import com.rainbowsea.seckill.pojo.User;
import com.rainbowsea.seckill.service.UserService;
import com.rainbowsea.seckill.utill.MD5Util;
import com.rainbowsea.seckill.utill.ValidatorUtil;
import com.rainbowsea.seckill.vo.LoginVo;
import com.rainbowsea.seckill.vo.RespBean;
import com.rainbowsea.seckill.vo.RespBeanEnum;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author huo
* @description 针对表【seckill_user】的数据库操作Service实现
* @createDate 2025-04-24 15:38:01
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
@Resource
private UserMapper userMapper;
/**
* 登录校验
*
* @param loginVo 登录时发送的信息
* @param request request
* @param response response
* @return RespBean
*/
@Override
public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
String mobile = loginVo.getMobile();
String password = loginVo.getPassword();
// 判断手机号/id,和密码是否为空
if (!StringUtils.hasText(mobile) || !StringUtils.hasText(password)) {
return RespBean.error(RespBeanEnum.LOGIN_ERROR);
}
// 判断手机号是否合格
if (!ValidatorUtil.isMobile(mobile)) {
return RespBean.error(RespBeanEnum.MOBILE_ERROR);
}
// 查询DB,判断用户是否存在
User user = userMapper.selectById(mobile);
if (null == user) {
return RespBean.error(RespBeanEnum.LOGIN_ERROR);
}
// 如果用户存在,则对比密码!
// 注意:我们从 LoginVo 取出的密码是中间密码(即客户端经过一次加密加盐处理的密码)
if (!MD5Util.midPassToDBPass(password, user.getSlat()).equals(user.getPassword())) {
return RespBean.error(RespBeanEnum.LOGIN_ERROR);
}
// 登录成功
return RespBean.success(user);
}
}

package com.rainbowsea.seckill.controller;
import com.rainbowsea.seckill.service.UserService;
import com.rainbowsea.seckill.vo.LoginVo;
import com.rainbowsea.seckill.vo.RespBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@Slf4j
@Controller
@RequestMapping("/login")
public class LoginController {
@Resource
private UserService userService;
/**
* 用户登录
*
* @return 返回登录页面
*/
@RequestMapping("/toLogin")
public String toLogin() {
return "login"; // 到templates/login.html
}
/**
* 登录功能
*/
@RequestMapping("/doLogin")
@ResponseBody
public RespBean doLogin
(@Valid LoginVo loginVo, HttpServletRequest request,
HttpServletResponse response) {
log.info("{}", loginVo);
return userService.doLogin(loginVo, request, response);
}
}

自定义校验器:

package com.rainbowsea.seckill.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 开发一个自定义的注解:替换如下,登录校验时的代码
* <p>
* <p>
* // 判断手机号/id,和密码是否为空
* if (!StringUtils.hasText(mobile) || !StringUtils.hasText(password)) {
* return RespBean.error(RespBeanEnum.LOGIN_ERROR);
* }
* <p>
* // 判断手机号是否合格
* if (!ValidatorUtil.isMobile(mobile)) {
* return RespBean.error(RespBeanEnum.MOBILE_ERROR);
* }
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER,
TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class})
public @interface IsMobile {
String message() default "手机号码格式错误";
boolean required() default true;
Class<?>[] groups() default {}; // 默认参数
Class<? extends Payload>[] payload() default {}; //默认参数
}
package com.rainbowsea.seckill.validator;
import com.rainbowsea.seckill.utill.ValidatorUtil;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 我们自拟定注解 IsMobile(手机号是否正确) 的校验规则
*/
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.hasText(value)) {
return true;
} else {
return ValidatorUtil.isMobile(value);
}
}
}
}

package com.rainbowsea.seckill.vo;
import com.rainbowsea.seckill.validator.IsMobile;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
/**
* 接收用户登录时,发送的信息(mobile,password)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginVo {
// 添加 validation 组件后使用
@NotNull
@IsMobile //自拟定注解
private String mobile;
@Length(min = 32)
@NotNull
private String password;
}



package com.rainbowsea.seckill.exception;
import com.rainbowsea.seckill.vo.RespBeanEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 全局异常处理类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GlobalException extends RuntimeException {
private RespBeanEnum respBeanEnum;
}

package com.rainbowsea.seckill.exception;
import com.rainbowsea.seckill.vo.RespBean;
import com.rainbowsea.seckill.vo.RespBeanEnum;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.validation.BindException;
/**
* 全局异常处理定义
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理所有的异常
*
* @param e 异常对象
* @return RespBean
*/
@ExceptionHandler(Exception.class)
public RespBean ExceptionHandler(Exception e) {
//如果是全局异常,正常处理
if (e instanceof GlobalException) {
GlobalException ex = (GlobalException) e;
return RespBean.error(ex.getRespBeanEnum());
} else if (e instanceof BindException) { // BindException 绑定异常
// 如果是绑定异常 :由于我们自定义的注解只会在控制台打印错误信息,想让改信息传给前端。
// 需要获取改异常 BindException,进行打印
BindException ex = (BindException) e;
RespBean respBean = RespBean.error(RespBeanEnum.BING_ERROR);
respBean.setMessage(" 参 数 校 验 异 常 ~ : " +
ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return respBean;
}
return RespBean.error(RespBeanEnum.ERROR);
}
}

package com.rainbowsea.seckill.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rainbowsea.seckill.exception.GlobalException;
import com.rainbowsea.seckill.mapper.UserMapper;
import com.rainbowsea.seckill.pojo.User;
import com.rainbowsea.seckill.service.UserService;
import com.rainbowsea.seckill.utill.MD5Util;
import com.rainbowsea.seckill.utill.ValidatorUtil;
import com.rainbowsea.seckill.vo.LoginVo;
import com.rainbowsea.seckill.vo.RespBean;
import com.rainbowsea.seckill.vo.RespBeanEnum;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author huo
* @description 针对表【seckill_user】的数据库操作Service实现
* @createDate 2025-04-24 15:38:01
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
@Resource
private UserMapper userMapper;
/**
* 登录校验
*
* @param loginVo 登录时发送的信息
* @param request request
* @param response response
* @return RespBean
*/
@Override
public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
String mobile = loginVo.getMobile();
String password = loginVo.getPassword();
// 判断手机号/id,和密码是否为空
//if (!StringUtils.hasText(mobile) || !StringUtils.hasText(password)) {
// return RespBean.error(RespBeanEnum.LOGIN_ERROR);
/
【从0到1构建一个ClaudeAgent】协作-Agent团队