商城秒杀系统的实现(二)实现登录功能

文章目录

    • 数据库设计
    • 加密:两次MD5
      • pom.xml中添加依赖:
      • 编写login.html
      • 引入jquery.js、bootstrap、jquery-validation、layer.js
      • domain中创建对应的user
    • 参数校验
      • 自定义参数校验器
      • login部分代码解析
      • 全局异常处理器
    • 分布式Session
      • 原理
      • 登录流程:
      • 具体实现
        • redis中set方法的实现
        • addCookie方法的实现
        • 完整代码

数据库设计

CREATE TABLE miaosha_user (
  `id` bigint(20) NOT NULL COMMENT '用户ID,手机号码',
  `nickname` varchar(255) NOT NULL,
  `password` varchar(32) DEFAULT NULL COMMENT 'MD5(MD5(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


加密:两次MD5

引入MD5工具类,添加MD5Util

http是明文登录的,不安全

  • 首先客户端 md5 再传输给服务端
  • 服务端接受到之后+salt再md5,写入到数据库当中(防止数据库被盗)

pom.xml中添加依赖:

<dependency>
 <groupId>commons-codecgroupId>
 <artifactId>commons-codecartifactId>
dependency>
<dependency>
 <groupId>org.apache.commonsgroupId>
 <artifactId>commons-lang3artifactId>
 <version>3.6version>
dependency>

编写login.html

(代码可见github)

引入jquery.js、bootstrap、jquery-validation、layer.js

(文件见github)

domain中创建对应的user


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

参数校验

引入spring-boot-starter-validation

<dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-validationartifactId>
dependency>

自定义参数校验器

自己定义:

validator:

      @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})  
      @Retention(RetentionPolicy.RUNTIME)  
      @Documented  
      @Constraint(validatedBy = IsMobileValidator.class)//会去找ismobilexxx
      public @interface IsMobile {
          String message() default "手机号码格式有误";  
          Class<?>[] groups() default {};  
          Class<? extends Payload>[] payload() default {};  
          boolean required() default true;  //允许为空
      }
      public class IsMobileValidator implements ConstraintValidator<IsMobile, String>{
      }

IsMobileValidator

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

login部分代码解析

	public boolean 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();//获取数据库中人的salt
		String calcPass = MD5Util.formPass2DBPass(formpass, saltDB);//通过salt把输入的密码变成calc密码	
		if(!calcPass.equals(dbPass)) {//再比较这两个密码
			throw new GlobalException(CodeMsg.PASSWORD_ERROR);
		}
}

全局异常处理器

@ControllerAdvice
@ResponseBody

@ExceptionHandler(value=Excption.class)
public Result<String> excptionHandler(HttpServletRequest request ,Exeption e){
  if(e instanceof BindException){
		BindException ex = (BindException)e;
    List<ObjectError> errors = ex.getAllErrors();
    return Result.error(CodeMsg.BIND_EORROR.fillArgs());
  } 
} 

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
    @ExceptionHandler(value=Exception.class)  
    public Result<String> allExceptionHandler(HttpServletRequest request, Exception exception) throws Exception{  
    }
}

分布式Session

原理

实际实现中,不会只有一台服务器,如果第一次请求到达第一个服务器,第二个请求到了第二个服务器,第一次的session信息就丢失了,所以就把session信息存放在redis当中

登录流程:

登录之后,跳转到to_list页面
跳转的时候要把参数带上,用的就是cookie(或者还有request的参数)
然后我们在to_list当中,就要把cookie中的参数取出来
通过参数从redis中获取到user
展示这个user的货物界面

将用户信息存放在redis中时需要用token唯一标识
(登录成功之后啊,给这个用户生成一个类似于sessionID,标示这个用户,然后写到cookie当中传递给客户端,客户端在随后的访问都在cookie中传token,服务端拿到这个token之后,就用这个token来取到用户对应的session信息)

具体实现

redis中set方法的实现

	//设置对象
	//参数:前缀+key+value
	public <T> boolean set(KeyPrefix prefix, String key,  T value) {
		 Jedis jedis = null;
		 try {
			 jedis = jedisPool.getResource();
			 String str = beanToString(value);
			 if(str == null || str.length() <= 0) {
				 return false;
			 }
			//生成真正的key
			 String realKey = prefix.getPrefix() + key;
			 int seconds =  prefix.expireSeconds();
			 if(seconds <= 0) {
				 jedis.set(realKey, str);
			 }else {
				 jedis.setex(realKey, seconds, str);
			 }
			 return true;
		 }finally {
			  returnToPool(jedis);
		 }
	}

addCookie方法的实现

要写到cookie里面:token => cookie
但是要标示一下这个token对应着哪一个用户,所以就把这个用户信息写到redis当中(第三方缓存),这样只用知道token就可以得到用户信息

	//添加cookie到response中,redis中存储
	private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
		//1. redis中存储
		//用到了上面的set方法 参数:前缀+key+value
		//MiaoshaUserKey.token是一个前缀字符串,需要在MiaoshaUserKey中实现
		redisService.set(MiaoshaUserKey.token, token, user);
		//2.添加cookie到response中
		Cookie cookie = new Cookie(COOKIE_NAME_TOKEN, token);
		//再设置一下有效期和网站根目录
		cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
		cookie.setPath("/");
		response.addCookie(cookie);
	}

完整代码

就是上面的login加上最后的addCookie部分

	public boolean 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();//获取数据库中人的salt
		String calcPass = MD5Util.formPass2DBPass(formpass, saltDB);//通过salt把输入的密码变成calc密码	
		if(!calcPass.equals(dbPass)) {//再比较这两个密码
			throw new GlobalException(CodeMsg.PASSWORD_ERROR);
		}
		//生成一个token(随机的immutable universally unique identifier )
		String token = UUIDUtil.uuid();
		addCookie(response, token, user);
		return true;
	}

你可能感兴趣的:(商城秒杀系统的实现(二)实现登录功能)