秒杀项目学习第二章

主要内容

  1. 数据库设计
  2. 明文密码两次MD5处理
  3. JSR303参数检验+全局异常处理
  4. 分布式Session

一、数据库设计

在这里插入图片描述

二、两次MD5

两次MD5的原因:

  • HTTP在网络上是明文传输的,为了防止不法分子截取数据包,那么就可能获得我们的登陆密码

两次MD5的设计方案

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

导包

//用于MD5
<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>

封装一个MD5加密工具类

public class MD5Util {
    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);
    }

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

    public static String inputPassToDbPass(String inputPass, String saltDB) {
        String formPass = inputPassToFormPass(inputPass);
        String dbPass = formPassToDBPass(formPass, saltDB);
        return dbPass;
    }

    public static void main(String[] args) {
        System.out.println(inputPassToFormPass("123456"));//d3b1294a61a07da9b49b6e22b2cbd7f9
		System.out.println(formPassToDBPass(inputPassToFormPass("123456"), "1a2b3c4d"));
		System.out.println(inputPassToDbPass("123456", "1a2b3c4d"));//b7797cce01b4b131b433b6acf4add449
    }
}

三、登陆功能的实现

登录页面的展现

1.LoginController

 @RequestMapping("/to_login")
    public String toLogin() {
        return "login";
    }

2.处理页面
在这里插入图片描述

完成登陆功能

1.完成上图中login()方法

<script>
function login(){
	/*参数校验,验证通过会回调doLogin方法,可以翻阅相关jquery-validator的内容*/
	$("#loginForm").validate({
        submitHandler:function(form){
             doLogin();
        }    
    });
}
function doLogin(){
	//展示转圈
	g_showLoading();
	//第一次MD5
	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);

	$.ajax({
		url: "/login/do_login",
	    type: "POST",
	    data:{
	    	mobile:$("#mobile").val(),
	    	password: password
	    },
	    success:function(data){
	    	layer.closeAll();
			//layer.msg(data.msg);
	    	if(data.code == 0){
	    		layer.msg("成功");
	    	}else{
	    		layer.msg(data.msg);
	    	}
	    },
	    error:function(){
	    	layer.closeAll();
	    }
	});
}
</script>

2.为方便接收参数新建LoginVo

public class LoginVo {
    private String mobile;
    private String password;

3.controller包中的LoginController增加方法

@RequestMapping("/do_login")
    @ResponseBody
    public Result doLogin(LoginVo loginVo) {
        //输出到idea控制台
        log.info(loginVo.toString());
        //参数校验
        String passInput = loginVo.getPassword();
        String mobile = loginVo.getMobile();
        //密码是否为空
        if(StringUtils.isEmpty(passInput)){
            return Result.error(CodeMsg.PASSWORD_EMPTY);
        }
        //手机号是否为空
        if(StringUtils.isEmpty(mobile)){
            return Result.error(CodeMsg.MOBILE_EMPTY);
        }
        //手机号格式见4
        if(ValidatorUtil.isMobile(mobile)){
            return Result.error(CodeMsg.MOBILE_ERROR);
        }
        //登录见5
        CodeMsg cm = userService.login(loginVo);
        return Result.success(true);
    }

4.为了验证手机号格式写一个ValidatorUtil类

/**
 * 手机号验证
 */
public class ValidatorUtil {
    private static final Pattern mobile_pattern = Pattern.compile("^[1](([3|5|8][\\d])|([4][4,5,6,7,8,9])|([6][2,5,6,7])|([7][^9])|([9][1,8,9]))[\\d]{8}$");
    public static boolean isMobile(String src){
        if(StringUtils.isEmpty(src)){
            return false;
        }
        Matcher matcher = mobile_pattern.matcher(src);
       // System.out.println(matcher.matches());
        return matcher.matches();
    }

    public static void main(String[] args) {
        System.out.println(isMobile("110"));
        System.out.println(isMobile("13626527917"));
    }
}

5.服务层MiaoshaUserService

@Service
public class MiaoshaUserService {
	@Autowired
	MiaoshaUserDao miaoshaUserDao;
	
	public MiaoshaUser getById(long id) {
		return miaoshaUserDao.getByid(id);
	}

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

6.domain包中MiaoshaUser

public class MiaoshaUser {
    private Long id;
    private String nickname;
    private String password;
    private String salt;
    private String head;
    private Date registerDate;
    private Date lastLoginDate;
    private Integer loginCount;

7.数据层MiaoshaUserDao

@Repository
public interface MiaoshaUserDao {
    @Select("select * from miaosha_user where id=#{id}")
    public MiaoshaUser getByid(@Param("id") long id);
    @Update("update miaosha_user set password=#{password} where id = #{id}")
    public void update(MiaoshaUser toBeUpdate);
}

至此初步登陆功能实现了

四、JSR303参数校验

使用原因:如下图这么写就很烦
在这里插入图片描述
JSR303很强大可以找文档学习

导包

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

使用注解

1.@Validated注解
在这里插入图片描述
2.处理LoginVo类
在这里插入图片描述
3.实现自定义注解
IsMobile

//复制别的注解的
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@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

//<IsMobile, String> 校验谁,注解修饰什么类型
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 value, ConstraintValidatorContext context) {
        if(required){
            return ValidatorUtil.isMobile(value);
        }else{
            //如果不是必须的,且value为空可以返回true
            if(StringUtils.isEmpty(value)){
                return true;
            }else{
                //不为空验证格式
                return ValidatorUtil.isMobile(value);
            }
        }
    }
}

显示错误信息

使用注解后如果没有通过校验会在控制台报错,把错误信息输出可以先拦截异常然后根据异常信息输出异常

如下参数校验异常时:
在这里插入图片描述

1.GlobalExceptionHandler
但是这么写不好啊,可以设置成就拦截GlobalException和BindException
牛客那个项目返回json都转成字符串到js中再转成对象,这里直接返回对象

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
    @ExceptionHandler(value = Exception.class)
    public Result<String> exceptionHandler(HttpServletRequest request, Exception e) {
    	//见2
        if (e instanceof GlobalException) {
            GlobalException ex = (GlobalException) e;
            return Result.error(ex.getCodeMsg());
        } else if (e instanceof BindException) {
            BindException ex = (BindException) e;
            List<ObjectError> errors = ex.getAllErrors();
            ObjectError error = errors.get(0);
            String msg = error.getDefaultMessage();
            //见3
            return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
        } else {
            return Result.error(CodeMsg.SERVER_ERROR);
        }
    }
}

2.GlobalException

public class GlobalException extends RuntimeException {
    private CodeMsg codeMsg;
    public GlobalException(CodeMsg codeMsg){
        super();
        this.codeMsg = codeMsg;
    }

    public CodeMsg getCodeMsg() {
        return codeMsg;
    }
}

3.CodeMsg增加如下代码

public static CodeMsg BIND_ERROR = new CodeMsg(500101, "参数校验异常:%s");
public CodeMsg fillArgs(Object... args) {
        int code = this.code;
        String message = String.format(this.msg, args);
        return new CodeMsg(code, message);
}

4.修改MiaoshaUserService
遇到问题就抛出去就行了

public boolean login(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);
		}
		return true;
	}

4.修改MiaoshaUserService

@RequestMapping(path="/do_login",method = RequestMethod.POST)
    @ResponseBody
    public Result doLogin(@Validated LoginVo loginVo) {
        //输出到idea控制台
        log.info(loginVo.toString());
        //登录
        userService.login(loginVo);
        return Result.success(true);
    }

五、分布式Session

把一个客户端传来的token映射一个User

  • 生成一个名为token的Cookie传给客户端
  • 客户端每次访问时携带此token
  • 服务端根据此token识别用户

生成Cookie

1.写一个生成uuid的工具类

public class UUIDUtil {
	public static String uuid() {
		return UUID.randomUUID().toString().replace("-", "");
	}
}

2.修改MiaoshaUserService增加添加Cookie的代码

public boolean login(HttpServletResponse response,LoginVo loginVo) {
		。。。忽略代码
		//生成cookie
		String token = UUIDUtil.uuid();
		addCookie(response, token, user);
		return true;
	}
	public static final String COOKI_NAME_TOKEN = "token";
	private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
		//见3
		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.MiaoshaUserKey

public class MiaoshaUserKey extends BasePrefix{

	public static final int TOKEN_EXPIRE = 3600*24 * 2;
	private MiaoshaUserKey(int expireSeconds, String prefix) {
		super(expireSeconds, prefix);
	}
	public static MiaoshaUserKey token = new MiaoshaUserKey(TOKEN_EXPIRE, "tk");
}

从Cookie中读取token转换成User

1.MiaoshaUserService中增加getByToken方法

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

2.为了方便使用User避免多次重复写从redis中根据token获取对象的操作,写一个配置类
作用是当读取到controller中方法参数包含MiaoshaUser时自动注入。

3.新建config.WebConfig

@Configuration
public class WebConfig implements WebMvcConfigurer {
	//见4
	@Autowired
	UserArgumentResolver userArgumentResolver;
	//这个方法用于管理能往controller中参数中注入值的对象
	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		argumentResolvers.add(userArgumentResolver);
	}
}

4.UserArgumentResolver

@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

	@Autowired
	MiaoshaUserService userService;
	//设置能处理的对象类型
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> clazz = parameter.getParameterType();
		return clazz== MiaoshaUser.class;
	}
	//要注入的对象是什么
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		//获取request和response
		HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
		HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
		//从参数中取值和从cookie中取值,为了兼容不同情况
		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;
		return userService.getByToken(response, token);
	}

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

5.使用一波
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值