1. 程式人生 > >Java秒殺系統方案優化 2 --第2章 實現使用者登入以及分散式session功能

Java秒殺系統方案優化 2 --第2章 實現使用者登入以及分散式session功能

第2章 實現使用者登入以及分散式session功能

1. 明文密碼兩次md5入庫 分別使用簽名如1a2b3c4d,分別用簽名和密碼使用MD5加密兩次後(一次是最原始密碼加密,一次是加密後再使用MD5和簽名加密)才存入資料庫,每個使用者對應都有一個欄位,例如本案例中的salt,存放簽名, 如何驗證?密碼是否一致? 首先在前端JS 需要把input框輸入的最原始的密碼加密,加密的方法和第一次相同,然後用form表單傳入後臺驗證,後臺就是使用使用者的簽名進行第二次加密,加密後和使用者密碼匹配 (PS,如果在JS中使用加密,如何保證JS使用的MD5加密和後臺系統使用的MD5加密相同?,這裡我有點不明白,我看了登入頁面引用的是一個md5.min.js。不過看了下不太像開源的JS,有點想自己寫的一個JS,所以這裡我不太懂,請大佬輕噴)

2·自定義註解引數驗證


public class LoginVo {

    @NotNull
    @IsMobile
    private String mobile;

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

如何寫一個 @IsMobile註解來判斷是否一個手機號碼,首先建立一個IsMobile註解類,程式碼如下:


@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class })
public
@interface IsMobile { boolean required() default true; String message() default "手機號碼格式錯誤"; Class<?>
[] groups() default { }; Class<? extends Payload>[] payload() default { }; }

驗證的方式由IsMobileValidator來實現程式碼如下,初始化的時候獲取該註解是否必須驗。


public class IsMobileValidator implements
ConstraintValidator<IsMobile, String> {
private boolean required = false; public void initialize(IsMobile constraintAnnotation) { required = constraintAnnotation.required(); } public boolean isValid(String value, ConstraintValidatorContext context) { if(required) { return ValidatorUtil.isMobile(value); }else { if(StringUtils.isEmpty(value)) { return true; }else { return ValidatorUtil.isMobile(value); } } } }

Java註解之Retention、Documented、Target介紹可參考網上的文章, 其他博主寫的 其他博主寫的2

3·系統通用異常處理

  1. 定義系統通用返回類 系統的返回只有兩種結果,失敗,成功。失敗帶有錯誤資訊,成功帶有資料資訊(也有可能沒有),另外也需要一個錯誤碼,具體程式碼如下:
public class Result<T> {

    private int code;
    private String msg;
    private T data;

    /**
     *  成功時候的呼叫
     * */
    public static  <T> Result<T> success(T data){
        return new Result<T>(data);
    }

    /**
     *  失敗時候的呼叫
     * */
    public static  <T> Result<T> error(CodeMsg codeMsg){
        return new Result<T>(codeMsg);
    }

    private Result(T data) {
        this.data = data;
    }

    private Result(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private Result(CodeMsg codeMsg) {
        if(codeMsg != null) {
            this.code = codeMsg.getCode();
            this.msg = codeMsg.getMsg();
        }
    }
        public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }

}
  1. 封裝錯誤資訊類 由於在第一步的時候我們在呼叫失敗的時候傳入的是一個類,所以這裡就需要封裝這裡類,需要有錯誤碼和錯誤資訊 具體程式碼如下:

public class CodeMsg {

    private int code;
    private String msg;

    //通用的錯誤碼
    public static CodeMsg SUCCESS = new CodeMsg(0, "success");
    //商品模組 5003XX

    //訂單模組 5004XX

    //秒殺模組 5005XX

    private CodeMsg( ) {
    }

    private CodeMsg( int code,String msg ) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }

    public CodeMsg fillArgs(Object... args) {
        int code = this.code;
        String message = String.format(this.msg, args);
        return new CodeMsg(code, message);
    }

    @Override
    public String toString() {
        return "CodeMsg [code=" + code + ", msg=" + msg + "]";
    }


}
每個系統定義的通用返回類和錯誤資訊類都不一樣,這裡也只是一種模式而已,在專案中要結合使用
PS(如果日後系統越來越龐大,錯誤資訊越來越多了,在CodeMsg類中的資訊就會越來越多,這裡就需要重新分解了)

3.定義全域性異常類 全域性異常類就是在處理業務的時候丟擲來的,然後通過相關的處理機制捕獲到這個異常,這個全域性異常類也是和錯誤資訊類相關的,具體程式碼如下:

public class GlobalException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    private CodeMsg cm;

    public GlobalException(CodeMsg cm) {
        super(cm.toString());
        this.cm = cm;
    }

    public CodeMsg getCm() {
        return cm;
    }

}

在處理業務的時候就可以丟擲異常了,具體程式碼如下:

if(user == null) {
    throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);    
}

4 捕獲全域性異常類(重點) 在第三點的時候說過要捕獲到全部異常,就是在這裡處理的了,通過捕獲到異常資訊,獲取錯誤資訊,然後把錯誤資訊封裝給通用返回類,然後返回這個類的資訊,具體程式碼如下:

@ControllerAdvice 註解定義全域性異常處理類 @ExceptionHandler 註解宣告異常處理方法 參考其他部落格的資訊

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

4· 分散式session 這裡其實說的就是Cookie,通過Redis快取Token 具體程式碼如下:

private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
        redisService.set(MiaoshaUserKey.token, token, user);
        Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token);
        cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
        cookie.setPath("/");
        response.addCookie(cookie);
    }

Redis的key值在系統中使用方式的是介面+抽象類+實現類 介面定義行為,具體程式碼如下:

public interface KeyPrefix {

    /**
     * 過期時間
     * @return
     */
    public int expireSeconds();
    /**
     * 定義字首
     * @return
     */
    public String getPrefix();

}

抽象類實現介面,定義通用的屬性,具體程式碼如下:

public abstract class BasePrefix implements KeyPrefix{

    private int expireSeconds;

    private String prefix;

    public BasePrefix(String prefix) {//0代表永不過期
        this(0, prefix);
    }

    public BasePrefix( int expireSeconds, String prefix) {
        this.expireSeconds = expireSeconds;
        this.prefix = prefix;
    }

    public int expireSeconds() {//預設0代表永不過期
        return expireSeconds;
    }

    public String getPrefix() {
        String className = getClass().getSimpleName();
        return className+":" + prefix;
    }

}

實現類整合抽象類,定義私人化的屬性,具體程式碼如下:

public class UserKey extends BasePrefix{

    private UserKey(String prefix) {
        super(prefix);
    }
    public static UserKey getById = new UserKey("id");
    public static UserKey getByName = new UserKey("name");
}