秒殺系統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;
    }
}

第二章就結束了


 

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