1.数据库设计
2.两次MD5加密
3. JSR303参数检验
4.全局异常处理器
5.分布式Session
设计用户表
CREATE TABLE `miaosha_user`(
`id` bigint(20) NOT NULL COMMENT '用户ID,手机号码',
`nickname` varchar(255) NOT NULL,
`password` varchar(32) DEFAULT NULL COMMENT 'MD5 (MDS (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
1.用户端: PASS = MD5 (明文+固定Salt )
2.服务端: PASS = MD5 (用户输入+随机Salt )
1.引入依赖
commons-codec
commons-codec
org.apache.commons
commons-lang3
3.6
commons-codec:是Apache开源组织提供的用于摘要运算、编码解码的包。常见的编码解码工具Base64、MD5、Hex、SHA1、DES等。
commons-lang3:我们主要使用字符串操作类StringUtils
2.编写MD5Util类
import org.apache.commons.codec.digest.DigestUtils;
public class MD5Util {
public static String md5(String src) {
return DigestUtils.md5Hex(src);
}
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作用:对密码进行拼接增加安全性
前端的MD5加密
var g_passsword_salt="1a2b3c4d"
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);
我们先看一下原来的参数校验代码
@RequestMapping("/do_login")//异步
@ResponseBody
public Result doLogin( HttpServletResponse response, LoginVo loginVo, HttpSession session) throws IOException {
//参数校验
String password = loginVo.getPassword();
String mobile = loginVo.getMobile();
if (StringUtils.isEmpty(password)){
return Result.error(CodeMsg.PASSWORD_EMPTY);
}if (StringUtils.isEmpty(mobile)){
return Result.error(CodeMsg.MOBILE_EMPTY);
}if (!ValidatorUtil.isMobile(mobile)){
return Result.error(CodeMsg.MOBILE_ERROR);
}
//登录
......
}
通过正则表达式Matcher和Pattern来判断手机号的格式
public class ValidatorUtil {
private static final Pattern mobile_pattern=Pattern.compile("1\\d{10}");
public static boolean isMobile(String src){
if (StringUtils.isEmpty(src)){
return false;
}
Matcher m=mobile_pattern.matcher(src);
return m.matches();
}
}
通过LoginVo来接收前端传入的参数,并且进行参数校验
1.自定义注解
使用的关键字@interface
来自定义注解。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
元注解:专门修饰注解的注解
注解里面定义的是:注解类型元素!
自定义注解参考文章
该自定义注解类中用到了四种元注解,最后一个@Constraint指定了校验类,也就是接下来的IsMobileValidator类。值得一提的是除了自定义的message、require属性外,下面的groups和payload也是必须添加的。
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class})
public @interface IsMobile {
boolean required() default true;
String message() default "手机号码格式有误";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
2.校验类
需要实现ConstraintValidator接口。 接口使用了泛型,需要指定两个参数,第一个自定义注解类,第二个为需要校验的数据类型。 实现接口后要override两个方法,分别为initialize方法和isValid方法。其中initialize为初始化方法,可以在里面做一些初始化操作,isValid方法就是我们最终需要的校验方法了。可以在该方法中实现具体的校验步骤。本示例中进行了简单的手机号校验。 完成这几部分之后,一个简单的自定义校验注解就完成啦,不要忘记在使用的时候加上@Valid注解开启valid校验。 那么如何获取在注解中定义的message信息呢? 在valid校验中,如果校验不通过,会产生BindException异常,捕捉到异常后可以获取到defaultMessage也就是自定义注解中定义的内容,具体实现如下:
import org.apache.commons.lang3.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class IsMobileValidator implements ConstraintValidator {
private boolean required=false;
@Override
public void initialize(IsMobile constraintAnnotation) {
required=constraintAnnotation.required();
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (required){
return ValidatorUtil.isMobile(s);
}else {
if (StringUtils.isEmpty(s))
return true;
else
return ValidatorUtil.isMobile(s);
}
}
}
把我们的注解加上去,并且使用@isValid开启valid校验
public class LoginVo {
@NotNull
@IsMobile
private String mobile;
@NotNull
@Length(min = 32)
private String password;
//set和get方法
}
@RequestMapping("/do_login")//异步
@ResponseBody
public Result doLogin( HttpServletResponse response, @isValid LoginVo loginVo, HttpSession session) throws IOException {
//使用了自定义注解并开启@isValid
//登录
......
}
@ControllerAdvice :这是一个非常有用的注解,顾名思义,这是一个增强的 Controller。使用这个 Controller ,可以实现三个方面的功能:
import java.util.List;
/**
* 全局异常处理器
*/
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public Result 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 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);
}
}
}
1.在登录完成后生成token写进cookie并存到Session中,并且存到redis中
2.当在其他页面时,token随之传递,编写getByToken来得到我们存到Redis的user并且从新定义一个cookie,来更新cookie时效
//生成cookie
private void addCookie(HttpServletResponse response,String token,MiaoshaUser user){
//System.out.println("生成cookie");
redisService.set(MiaoshaUserKey.token,token,user);
Cookie cookie=new Cookie(COOKI_NAME_TOKEN,token);
cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
cookie.setPath("/");
response.addCookie(cookie);
}
public MiaoshaUser getByToken(HttpServletResponse response,String token) {
if (StringUtils.isEmpty(token))
return null;
MiaoshaUser user= redisService.get(MiaoshaUserKey.token,token,MiaoshaUser.class);
//延长有效期
//生成cookie
if (user!=null){
addCookie(response,token,user);
}
return user;
}
3.重写WebMvcConfigurerAdapter中的addArgumentResolvers是的controller可以识别user信息
未使用前
@RequestMapping("/to_list")
public String list(HttpServletResponse response, Model model,
@CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken,
@RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken){
if (StringUtils.isEmpty(cookieToken)&&StringUtils.isEmpty(paramToken))
return "login";
String token=StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
MiaoshaUser user=userService.getByToken(response,token);
model.addAttribute("user",user);
return "goods_list";
}
使用后
@RequestMapping("/to_list")
public String list( Model model,MiaoshaUser user){
System.out.println(user);
model.addAttribute("user",user);
return "goods_list";
}
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
UserArgumentResolver userArgumentResolver;
public void addArgumentResolvers(List argumentResolvers) {
argumentResolvers.add(userArgumentResolver);
}
}
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
MiaoshaUserService userService;
@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.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;
System.out.println(token);
MiaoshaUser user=userService.getByToken(response,token);
return user;
}
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;
}
}
第二章就结束了