1.数据库设计

建立如下字段

java实现用户界面功能的包 java用户模块怎么实现的_System

2.明文密码两次MD5处理

原因:如果不对密码做任何处理直接明文传输,很容易被黑客截获密码,这样很不安全,于是通过一次MD5加密,这样即使黑客截获密码也看不出真正的密码是什么。但是现在有彩虹表这个反查MD5的技术,为了安全起见可以再MD5一次,并且在两次MD5加密的时候在原数值后加上Salt尾缀,增加破解难度,使黑客无法获取到用户真正的密码。大体步骤如下
(1)用户端:PASS = MD5 (明文+固定Salt)
(2)服务端:PASS = MD5(用户输入+随机Salt)
明文密码做MD5传给服务端,服务端加入随机Salt与用户输入的密码拼接起来,然后再一次MD5,把MD5和Salt同时写入数据库当中。第一次MD5是为了防止用户的密码在网络上明文传输,服务端的MD5是为了防止反查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>

创建util包(存放MD5相关的类)
创建MD5util类,拼接Salt做MD5加密,第一次MD5后到服务端拼接随机Salt并将Salt和第一次MD5后的用户密码存入数据库。

import org.apache.commons.codec.digest.DigestUtils;


public class MD5Util {

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

    private static final String salt = "1a2b3c4d";
    //拼接Salt并MD5加密,第一个Salt如果随机,服务端无法知道是什么
    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,生成随机Salt
    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
    }

}

3.JSR303参数校验和全局异常处理器

(1)在登录的controller中每一次登录都要做参数校验,
加入JSR303校验的依赖

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

使用方法,第一步在传递loginVo的参数前加@Valid注解

@RequestMapping("/do_login")
    @ResponseBody
    public Result<Boolean> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {
    	log.info(loginVo.toString());
    	//登录
    	userService.login(response, loginVo);
    	return Result.success(true);
    }

第二步在LoginVo类的每一个需要校验的参数前加@NotNull不允许为空,在手机号前还需加入@IsMobile自定义校验器,在密码参数前还需加入第二个注解@Length(min=32)限制密码长度。
LoginController类如下

@Controller
@RequestMapping("/login")
public class LoginController {

	private static Logger log = LoggerFactory.getLogger(LoginController.class);
	
	@Autowired
	MiaoshaUserService userService;
	
	@Autowired
	RedisService redisService;
	
    @RequestMapping("/to_login")
    public String toLogin() {
        return "login";
    }
    
    @RequestMapping("/do_login")
    @ResponseBody
    public Result<Boolean> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {
    	log.info(loginVo.toString());
    	//登录
    	userService.login(response, loginVo);
    	return Result.success(true);
    }
}

建立自定义校验器的包validator,在包中建立IsMobile注解和IsMobileValidator类
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);
			}
		}
	}
}

全局异常拦截器,新建exception包,建立GlobalException类和GlobalExceptionHandler类。
GlobalExceptionHandler类

@ControllerAdvice//类似xml的实现
@ResponseBody
//相当于controller
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());//获取并返回CodeMsg错误信息
		}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));//将msg传入绑定参数中,使错误信息带出去
		}else {
			return Result.error(CodeMsg.SERVER_ERROR);//CodeMag为自定义的错误信息,返回带参数的错误码
		}
	}
}

GlobalException类(全局异常类)在MiaoshaUserService类中判断出现异常后,抛出此异常(if(loginVo == null){ throw new GlobalException(CodeMsg.Server_error); })抛完之后由异常处理器处理。

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

}

4.分布式Session

秒杀功能,实际运行的时候有多台服务器,此时会有用户的session处理的问题,第一个请求在第一台服务器上,第二个请求在第二个服务器上,用户的session信息就丢失了。原生的session会同步session,但是同步消耗了过多资源。登录成功后给用户生成一个类似于sessionID的东西(takon)绑定用户的session,写到cookie中传递到客户端,然后客户端在之后的访问中上传这个token,服务端拿到token之后,根据token来取到用户的session信息。token是UUID生成的通用唯一识别码复制的。