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");
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值