signed

QiShunwang

“诚信为本、客户至上”

Java电商秒杀系统及其优化(二)

2021/1/28 15:55:15   来源:

登录界面处理

  • 两次MD5实现
  • 数据库录入
  • JSR303校验
  • 全局异常处理
  • 分布式Session
  • 两次MD5实现
    • 第一次MD5
  • 构建数据库
  • JSR303校验
  • 全局异常处理
  • 分布式Session
    • 为什么要实现分布式Session
    • 过程
    • 问题
      • WebMvcConfigurerAdapter
  • 分布式Session总结

两次MD5实现

数据库录入

JSR303校验

全局异常处理

分布式Session


两次MD5实现

创建了一个MD5的工具类,方便调用。

用户端:pass= MD5(明文+固定salt)
服务端:pass=MD5(用户输入+随机salt)

1、为什么实现两次MD5
第一次因为http传输是明文的,为了防止用户的密码直接在网络上进行传输,所以进行第一次,而第二次防止数据库被盗之后,通过一些方法反破解,所以再加一道MD5以及随机salt的拼接。

第一次MD5

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

	private static final String salt = "1a2b3c4d";

	public static String inputPassToFormPass(String inputPass) {
		String str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
		System.out.println(str);
		return md5(str);
	}

第二次md5

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的值!![在这里插入图片描述]在这里插入图片描述

JSR303校验

spring中已经集成的validation
@NotNull:不能为空
@Email:必须为email格式
@Length():规定长度

自定义校验器@IsMobile,根据@NotNull仿写

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

Constraint一个限制,系统看到IsMobile这个注解后会调用IsMobileValidator.class这个类进行校验,需继承ConstraintValidator接口,初始化方法拿到注解,然后isvalid方法中进行判断格式,是否为空等条件

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

}

全局异常处理

1.为什么要进行全局异常处理
刚开始我们对账号密码进行判断时,返回的时codemsg,但是真实业务中,我们应该就是要么登陆成功,或者失败,不应把这些信息给到用户,所以我们定义异常,把这些信息抛出,最后返回true or false。

public String login(HttpServletResponse response, LoginVo loginVo) {
		if(loginVo == null) {
			throw new GlobalException(CodeMsg.SERVER_ERROR);
		}
		String mobile = loginVo.getMobile();
		String formPass = loginVo.getPassword();
		//判断手机号是否存在
		MiaoshaUser user = getById(Long.parseLong(mobile));
		if(user == null) {
			throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
		}
		//验证密码
		String dbPass = user.getPassword();
		String saltDB = user.getSalt();
		String calcPass = MD5Util.formPassToDBPass(formPass, saltDB);
		if(!calcPass.equals(dbPass)) {
			throw new GlobalException(CodeMsg.PASSWORD_ERROR);
		}

分布式Session

为什么要实现分布式Session

因为我们是一个秒杀系统,肯定会有多台服务器,用户的请求有可能随机落入到不同的服务器中,这样的结果将会导致用户的session丢失。传统做法时进行Session同步,将用户的session的信息同步到每一台服务器上,但这样的方式弊端也很明显,就是占用内存,以及如果服务器数量特别庞大,那么操作难度和成本也会变得相当高。

我们的做法是,用户登陆成功后给这个用户加上一个token(UUID,累死sessionId)来标识这个用户,写到cookie当中传给客户端,随后客户端在登录时都会上传这个token,然后服务端通过这个token来取到用户对应的session信息。跟原生的容器实现session的原理是一样的。

过程

1.我们先通过UUID创建一个token,但我们要知道token对应的是哪一个用户,所以我们要把token和用户信息存入redis缓存中

String token= UUIDUtil.uuid();
		addCookie(response, token, user);
		return token

2.生成Cookie,我们创建了一个addcookie方法,就是把token信息存入cookie,然后把cookie方法写入客户端,这样客户端每次可以通过cookie中的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);
	}

3.两个注解使用
@CookieValue:这个注解能够根据参数value在Cookie中获取值
@RequestParam:该注解让我们在Request中能获取参数,解决的主要是,移动手机端不使用Cookie存值的问题

4.延长有效期,在从缓存中取用户信息时,再重新设置一个cookie

问题

我们登陆进去之后,还要进入商品详情页,秒杀页面,这时都要判断用户的session即用户是否登陆,这样十分繁琐,所以我们要把这个方式剥离出来。
思路:直接将user对象注入到我们的controller方法中。这样直接由用户信息了,不需要进行判断了,如下解决

WebMvcConfigurerAdapter

1.这个项目中我们使用了WebMvcConfigurerAdapter,去重写里面的addAgumentReslover方法

@Configuration
public class WebConfig  extends WebMvcConfigurerAdapter{
	
	@Autowired
	UserArgumentResolver userArgumentResolver;
	
	@Autowired
	AccessInterceptor accessInterceptor;
	
	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		argumentResolvers.add(userArgumentResolver);
	}
  1. 然后在UserArgumentResolver中实现我们的参数添加逻辑,继承HandlerMethodArgumentResolver 重写supportsParameter()和resolveArgument()方法,我们要实现输入MiaoshaUser。
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

    @Autowired
    MiaoShaUserService miaoShaUserService;

    @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.COOKIE_NAME_TOKEN);
        String cookieToken = getCookieValue(request, miaoShaUserService.COOKIE_NAME_TOKEN);
        if(StringUtils.isEmpty(paramToken) && StringUtils.isEmpty(cookieToken)){
            return null;
        }
        String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;

        return miaoShaUserService.getByToken(response,token);
    }

    private String getCookieValue(HttpServletRequest request,String cookieName){
        Cookie[] cookies = request.getCookies();

        for(Cookie cookie : cookies){
            if(cookie.getName().equals(cookieName)){
                return cookie.getValue();
            }
        }
        return null;
    }
}

先判断参数类型,然后对参数的处理一是从request中获取token值,二是从cookie中拿取token值,根据token值来获取到对应的user。
这样就实现了代码的简化。

分布式Session总结

此图转载自https://blog.csdn.net/qq_46225886/article/details/107256719,方便理解此图转载自https://blog.csdn.net/qq_46225886/article/details/107256719,方便理解