秒杀系统Web实践——02登录模块(MD5加密+JSR303参数校验+全局异常处理+分布式Session)

第二章登录模块

目录

1.数据库设计

2.两次MD5加密

3. JSR303参数检验

4.全局异常处理器

5.分布式Session


1.数据库设计

设计用户表

CREATE TABLE `miaosha_user`(
`id` bigint(20) NOT NULL COMMENT '用户ID,手机号码',
`nickname` varchar(255) NOT NULL,
`password` varchar(32) DEFAULT NULL COMMENT 'MD5 (MDS (pass明文+固定salt) + salt)',
`salt` varchar(10) DEFAULT NULL,
`head` varchar(128) DEFAULT NULL COMMENT '头像,云存储的ID',
`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

2.两次MD5加密

原理

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

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

操作

1.引入依赖

        <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>

commons-codec:是Apache开源组织提供的用于摘要运算、编码解码的包。常见的编码解码工具Base64、MD5、Hex、SHA1、DES等。

commons-lang3:我们主要使用字符串操作类StringUtils

2.编写MD5Util类

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

public class MD5Util {
    public static String md5(String src) {
        return DigestUtils.md5Hex(src);
    }

    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);
    }

}

salt作用:对密码进行拼接增加安全性

前端的MD5加密

 var g_passsword_salt="1a2b3c4d"
 var inputPass = $("#password").val();
 var salt = g_passsword_salt;
 var str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
 var password = md5(str);

3. JSR303参数检验

普通参数校验

我们先看一下原来的参数校验代码

    @RequestMapping("/do_login")//异步
    @ResponseBody
    public Result<Boolean> doLogin( HttpServletResponse response, LoginVo loginVo, HttpSession session) throws IOException {

        //参数校验
        String password = loginVo.getPassword();
        String mobile = loginVo.getMobile();
        if (StringUtils.isEmpty(password)){
            return Result.error(CodeMsg.PASSWORD_EMPTY);
        }if (StringUtils.isEmpty(mobile)){
            return Result.error(CodeMsg.MOBILE_EMPTY);
        }if (!ValidatorUtil.isMobile(mobile)){
            return Result.error(CodeMsg.MOBILE_ERROR);
        }
        //登录
        ......

    }

通过正则表达式Matcher和Pattern来判断手机号的格式

public class ValidatorUtil {

    private static final Pattern mobile_pattern=Pattern.compile("1\\d{10}");

    public static boolean isMobile(String src){
        if (StringUtils.isEmpty(src)){
            return false;
        }
        Matcher m=mobile_pattern.matcher(src);
        return m.matches();
    }
}

通过LoginVo来接收前端传入的参数,并且进行参数校验

JSR303参数检验

1.自定义注解

使用的关键字@interface来自定义注解。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。

元注解:专门修饰注解的注解

注解里面定义的是:注解类型元素!

自定义注解参考文章

自定义注解类中用到了四种元注解,最后一个@Constraint指定了校验类,也就是接下来的IsMobileValidator类。值得一提的是除了自定义的message、require属性外,下面的groups和payload也是必须添加的。

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class})
public @interface IsMobile {

    boolean required() default true;

    String message() default "手机号码格式有误";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

2.校验类

需要实现ConstraintValidator接口。 接口使用了泛型,需要指定两个参数,第一个自定义注解类,第二个为需要校验的数据类型。 实现接口后要override两个方法,分别为initialize方法和isValid方法。其中initialize为初始化方法,可以在里面做一些初始化操作,isValid方法就是我们最终需要的校验方法了。可以在该方法中实现具体的校验步骤。本示例中进行了简单的手机号校验。 完成这几部分之后,一个简单的自定义校验注解就完成啦,不要忘记在使用的时候加上@Valid注解开启valid校验。 那么如何获取在注解中定义的message信息呢? 在valid校验中,如果校验不通过,会产生BindException异常,捕捉到异常后可以获取到defaultMessage也就是自定义注解中定义的内容,具体实现如下:

import org.apache.commons.lang3.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

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 s, ConstraintValidatorContext constraintValidatorContext) {
        if (required){
            return ValidatorUtil.isMobile(s);
        }else {
            if (StringUtils.isEmpty(s))
                return true;
            else
                return ValidatorUtil.isMobile(s);
        }
    }
}

把我们的注解加上去,并且使用@isValid开启valid校验

public class LoginVo {

    @NotNull
    @IsMobile
    private String mobile;
    @NotNull
    @Length(min = 32)
    private String password;

    //set和get方法
}
    @RequestMapping("/do_login")//异步
    @ResponseBody
    public Result<Boolean> doLogin( HttpServletResponse response, @isValid LoginVo loginVo, HttpSession session) throws IOException {

        //使用了自定义注解并开启@isValid
     
        //登录
        ......

    }

4.全局异常处理器

@ControllerAdvice :这是一个非常有用的注解,顾名思义,这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:

  1. 全局异常处理
  2. 全局数据绑定
  3. 全局数据预处理
import java.util.List;

/**
 * 全局异常处理器
 */
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
    @ExceptionHandler(value = Exception.class)
    public Result<String> exceptionHandler(HttpServletRequest request,Exception e){
        e.printStackTrace();
        if (e instanceof GlobalException){
            GlobalException ex=(GlobalException)e;
            return Result.error(ex.getCm());
        }else if (e instanceof BindException){
            BindException ex=(BindException)e;
            List<ObjectError> errors = ex.getAllErrors();
            ObjectError error=errors.get(0);
            String msg =error.getDefaultMessage();
            return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
        }else {
            return Result.error(CodeMsg.SERVER_ERROR);
        }
    }
}

5.分布式Session

1.在登录完成后生成token写进cookie并存到Session中,并且存到redis中<token,user>的形式

2.当在其他页面时,token随之传递,编写getByToken来得到我们存到Redis的user并且从新定义一个cookie,来更新cookie时效

    //生成cookie
    private void addCookie(HttpServletResponse response,String token,MiaoshaUser user){
        //System.out.println("生成cookie");

        redisService.set(MiaoshaUserKey.token,token,user);
        Cookie cookie=new Cookie(COOKI_NAME_TOKEN,token);
        cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
        cookie.setPath("/");
        response.addCookie(cookie);
    }


    public MiaoshaUser getByToken(HttpServletResponse response,String token) {
        if (StringUtils.isEmpty(token))
            return null;
        MiaoshaUser user= redisService.get(MiaoshaUserKey.token,token,MiaoshaUser.class);
        //延长有效期
        //生成cookie
        if (user!=null){
            addCookie(response,token,user);
        }
        return user;
    }

3.重写WebMvcConfigurerAdapter中的addArgumentResolvers是的controller可以识别user信息

未使用前

    @RequestMapping("/to_list")
    public String list(HttpServletResponse response, Model model,
                         @CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken,
                         @RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken){
        if (StringUtils.isEmpty(cookieToken)&&StringUtils.isEmpty(paramToken))
            return "login";
        String token=StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
        MiaoshaUser user=userService.getByToken(response,token);
        model.addAttribute("user",user);
        return "goods_list";
    }

使用后

    @RequestMapping("/to_list")
    public String list( Model model,MiaoshaUser user){
        System.out.println(user);
        model.addAttribute("user",user);
        return "goods_list";
    }

 

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    UserArgumentResolver userArgumentResolver;

    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(userArgumentResolver);
    }
}
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

    @Autowired
    MiaoshaUserService userService;

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        Class<?> clazz=methodParameter.getParameterType();
        return clazz== MiaoshaUser.class;
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        HttpServletRequest request=nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        HttpServletResponse response=nativeWebRequest.getNativeResponse(HttpServletResponse.class);

        String paramToken=request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN);
        String cookieToken=getCookieValue(request,MiaoshaUserService.COOKI_NAME_TOKEN);
        if (StringUtils.isEmpty(cookieToken)&&StringUtils.isEmpty(paramToken))
            return null;
        String token=StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
        System.out.println(token);
        MiaoshaUser user=userService.getByToken(response,token);
        return user;
    }

    private String getCookieValue(HttpServletRequest request, String cookiName) {
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie: cookies) {
            if (cookie.getName().equals(cookiName))
                return cookie.getValue();
        }
        return null;
    }
}

第二章就结束了


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章