▶【SecKill】U2 实现登录功能

▶【SecKill】U2 实现登录功能

一、数据库设计

1、新建数据库表miaosha_user`

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;

【注】字符集采用的是utf8mb4(most bytes 4)。简单来说,utf8mb4是utf8的超集,能够用4个字节存储更多的字符。标准UTF-8字符集编码可以用1~4个字节取编码21位字符,但是在MySQL中,utf8最多使用3个字节,像一些表情emoji和不常用的字符如“墅”需要用4个字节才能表示出来。用utf8mb4能解决以上问题。数据库中存储了"动态"salt值

 

二、明文密码两次MD5处理:安全

1、为什么密码要用两次MD5?

2、在pom.xml添加MD5依赖

<!-- 8.添加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>

 

3、新建com.kirin.miaosha.util包,作为MD5工具类

4、新建MD5Util工具类

 

(1)(测试)做一次MD5

package com.kirin.miaosha.util;

public class MD5Util {
    
    public static String md5(String src) {
        return DigestUtils.md5Hex(src); //调用DigestUtils,实现md5处理
    }
    
    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 void main(String[] args) {
        System.out.println(inputPassToFormPass("123456"));
    }

}

 

(2)★(完整)做2次MD5

 

(3)做2次MD5后,得到加密密码b7797cce01b4b131b433b6acf4add449,将其输入数据库表中作为一条记录

 

5、新建LoginController类,直接复制DemoController.java

 

6、新建登录页面

(1)在 src/main/resources/templates 中新建login.html

(2)导入静态CSS模板

 

(3)login.html:

<!DOCTYPE HTML>
<!-- 定义一个th -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>登录</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <!-- jquery -->
    <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
    <!-- bootstrap -->
    <link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" />
    <script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
    <!-- jquery-validator -->
    <script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script>
    <script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script>
    <!-- layer -->
    <script type="text/javascript" th:src="@{/layer/layer.js}"></script>
    <!-- md5.js -->
    <script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
    <!-- common.js -->
    <script type="text/javascript" th:src="@{/js/common.js}"></script>
</head>

<body>
    <form name="loginForm" id="loginForm" method="post" style="width:50%; margin:0 auto">
        <h2 style="text-align:center; margin-bottom: 20px">用户登录</h2>
        <div class="form-group">
               <div class="row">
                   <label class="form-label col-md-4">手机号码(11位):</label>
                <div class="col-md-5">
                    <input id="mobile" name = "mobile" class="form-control" type="text" placeholder="手机号码" required="true"  minlength="11" maxlength="11" />
                </div>
                <div class="col-md-1">
                </div>
            </div>
        </div>
        
        <div class="form-group">
            <div class="row">
                <label class="form-label col-md-4">密码:</label>
                <div class="col-md-5">
                       <input id="password" name="password" class="form-control" type="password"  placeholder="密码" required="true" minlength="6" maxlength="16" />
                </div>
               </div>
        </div>
        
        <div class="row">
            <div class="col-md-5">
                <button class="btn btn-primary btn-block" type="reset" onclick="reset()">重置</button>
            </div>
            <div class="col-md-5">
                <button class="btn btn-primary btn-block" type="submit" onclick="login()">登录</button>
            </div>
        </div>
    </form>
</body>

<script>
    //登录
    function login(){
        $("#loginForm").validate({ //参数校验:取Form表单的id=loginForm
            submitHandler:function(form){ //若验证通过,则回调doLogin()方法
                 doLogin();
            }    
        });
    }
    
    function doLogin(){
        g_showLoading();
        
        var inputPass = $("#password").val(); //获取用户输入的明文密码
        var salt = g_passsword_salt; //静态的salt,用于第一次MD5,在输入的密码后进行拼接(common.js)
        var str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4); //转换、拼接
        var password = md5(str); //做一次MD5
        
        //提交
        $.ajax({
            url: "/login/do_login",
            type: "POST",
            data:{
                mobile:$("#mobile").val(), //获取用户输入的手机号
                password: password  //计算2次MD5后的加密密码
            },
            //提交成功后的回调方法
            success:function(data){ //成功后会返回data
                layer.closeAll();
                //console.log(data);
                if(data.code == 0){
                    layer.msg("成功");
                    window.location.href="/goods/to_list";
                }else{
                    layer.msg(data.msg);
                }
            },
            //提交失败后的回调方法
            error:function(){
                layer.closeAll(); //关闭Loading...框
            }
        });
    }
</script>
</html>

 

7、新建com.kirin.miaosha.vo包:接收参数

新建LoginVo.java:接收参数

 

LoginController.java:

 

LoginController.java:参数校验

 

CodeMsg.java:设置登录模块的报错弹窗

//登录模块 5002XX
public static CodeMsg SESSION_ERROR = new CodeMsg(500210, "Session不存在或者已经失效");
public static CodeMsg PASSWORD_EMPTY = new CodeMsg(500211, "登录密码不能为空");
public static CodeMsg MOBILE_EMPTY = new CodeMsg(500212, "手机号不能为空");
public static CodeMsg MOBILE_ERROR = new CodeMsg(500213, "手机号格式错误");
public static CodeMsg MOBILE_NOT_EXIST = new CodeMsg(500214, "手机号不存在");
public static CodeMsg PASSWORD_ERROR = new CodeMsg(500215, "密码错误");

 

在com.kirin.miaosha.util包中,新建ValidatorUtil.java,用于判断用户登录时手机号的格式

package com.kirin.miaosha.util;

//用于判断用户登录时输入的格式
public class ValidatorUtil {
    
    private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}"); //以1开头,后跟10个数字
    
    public static boolean isMobile(String src) {
        if(StringUtils.isEmpty(src)) {
            return false;
        }
        Matcher m = mobile_pattern.matcher(src);
        return m.matches();
    }
}

 

8、在com.kirin.miaosha.domain包中,新建MiaoshaUser.java,对应miaosha_user数据库表

package com.kirin.miaosha.domain;

import java.util.Date;

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;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getNickname() {
        return nickname;
    }
    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getSalt() {
        return salt;
    }
    public void setSalt(String salt) {
        this.salt = salt;
    }
    public String getHead() {
        return head;
    }
    public void setHead(String head) {
        this.head = head;
    }
    public Date getRegisterDate() {
        return registerDate;
    }
    public void setRegisterDate(Date registerDate) {
        this.registerDate = registerDate;
    }
    public Date getLastLoginDate() {
        return lastLoginDate;
    }
    public void setLastLoginDate(Date lastLoginDate) {
        this.lastLoginDate = lastLoginDate;
    }
    public Integer getLoginCount() {
        return loginCount;
    }
    public void setLoginCount(Integer loginCount) {
        this.loginCount = loginCount;
    }
}

 

9、在com.kirin.miaosha.dao包中,新建MiaoshaUserDao.java,对应MiaoshaUser.java的接口方法

package com.kirin.miaosha.dao;

@Mapper
public interface MiaoshaUserDao {
    
    @Select("select * from miaosha_user where id = #{id}")
    public MiaoshaUser getById(@Param("id")long id);
}

 

10、在com.kirin.miaosha.service包中,新建MiaoshaUserService.java,对应MiaoshaUser.java的接口方法

 

8、完整代码:

com.kirin.miaosha.util / MD5Util.java:

package com.kirin.miaosha.util;

public class MD5Util {
    
    //做一次MD5
    public static String md5(String src) {
        return DigestUtils.md5Hex(src); //调用DigestUtils,实现md5处理
    }
    
    //1.做第一次MD5
    //静态的salt,用于第一次MD5,在输入的密码后进行拼接
    private static final String salt = "1a2b3c4d";
    
    //1.做第一次MD5:把用户输入的明文密码转换成Form表单格式
    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
    }
    
    //2.做第二次MD5:把Form表单格式的密码转换成DB格式的密码
    public static String formPassToDBPass(String formPass, String salt) { //动态的salt,取随机值
        String str = ""+salt.charAt(0)+salt.charAt(2) + formPass +salt.charAt(5) + salt.charAt(4);
        return md5(str); //做一次MD5
    }
    
    //3.合并第一和第二次转换(MD5):把用户输入的明文密码转换成DB格式的密码
    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) {
        //1.做第一次MD5
        //System.out.println(inputPassToFormPass("123456")); //输出:d3b1294a61a07da9b49b6e22b2cbd7f9 ——> 破解后12123456c3
        //2.做第二次MD5
        //System.out.println(formPassToDBPass(inputPassToFormPass("123456"), "1a2b3c4d")); //设定动态salt的值为1a2b3c4d
        System.out.println(inputPassToDbPass("123456", "1a2b3c4d")); //b7797cce01b4b131b433b6acf4add449
    }

}

 

com.kirin.miaosha.util / ValidatorUtil.java:

package com.kirin.miaosha.util;

//用于判断用户登录时输入的格式
public class ValidatorUtil {
    
    private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}"); //以1开头,后跟10个数字
    
    public static boolean isMobile(String src) {
        if(StringUtils.isEmpty(src)) {
            return false;
        }
        Matcher m = mobile_pattern.matcher(src);
        return m.matches();
    }
}

 

com.kirin.miaosha.vo / LoginVo.java:

package com.imooc.miaosha.vo;

public class LoginVo {
    
    private String mobile;private String password;
    
    public String getMobile() {
        return mobile;
    }
    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "LoginVo [mobile=" + mobile + ", password=" + password + "]";
    }
}

 

com.kirin.miaosha.domain / MiaoshaUser.java:

package com.kirin.miaosha.domain;

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;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getNickname() {
        return nickname;
    }
    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getSalt() {
        return salt;
    }
    public void setSalt(String salt) {
        this.salt = salt;
    }
    public String getHead() {
        return head;
    }
    public void setHead(String head) {
        this.head = head;
    }
    public Date getRegisterDate() {
        return registerDate;
    }
    public void setRegisterDate(Date registerDate) {
        this.registerDate = registerDate;
    }
    public Date getLastLoginDate() {
        return lastLoginDate;
    }
    public void setLastLoginDate(Date lastLoginDate) {
        this.lastLoginDate = lastLoginDate;
    }
    public Integer getLoginCount() {
        return loginCount;
    }
    public void setLoginCount(Integer loginCount) {
        this.loginCount = loginCount;
    }
}

 

com.kirin.miaosha.service / MiaoshaUserService.java:

package com.kirin.miaosha.service;

@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.SERVER_ERROR;
        }
        String mobile = loginVo.getMobile();
        String formPass = loginVo.getPassword();
        //判断手机号是否存在
        MiaoshaUser user = getById(Long.parseLong(mobile));
        if(user == null) {
            return CodeMsg.MOBILE_NOT_EXIST;
        }
        //验证密码
        String dbPass = user.getPassword(); //获取数据库中的密码
        String saltDB = user.getSalt(); //获取数据库中的静态salt
        String calcPass = MD5Util.formPassToDBPass(formPass, saltDB); //做2次MD5
        if(!calcPass.equals(dbPass)) { //判断计算出来的MD5加密密码和数据库中的密码是否一致
            return CodeMsg.PASSWORD_ERROR;
        }
        return CodeMsg.SUCCESS;
    }
}

 

com.kirin.miaosha.controller / LoginController.java:

package com.kirin.miaosha.controller;

@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(LoginVo loginVo) {
        log.info(loginVo.toString()); //在LoginVo.java中生成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);
        }
        if(!ValidatorUtil.isMobile(mobile)) { //验证mobile的输入的格式,是否是以1开头,后跟10个数字
            return Result.error(CodeMsg.MOBILE_ERROR);
        }
        //登录
        CodeMsg cm = userService.login(loginVo);
        if(cm.getCode() == 0) {
            return Result.success(true);
        }else {
            return Result.error(cm);
        }
    }
}

 

 

三、JSR303参数检验+全局异常处理器

1、添加JSR参数校验依赖

<!-- 9.添加JSR参数校验依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

 

2、

① com.kirin.miaosha.controller / LoginController.java:

 

② 新建com.kirin.miaosha.validator包

新建IsMobile类,作为自定义验证器

package com.kirin.miaosha.validator;

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) //能够标注的范围
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class }) //这个注解帮助我们处理逻辑,其中IsMobileValidator.class是真正处理逻辑的类
public @interface IsMobile { //根据已有注解@NotNull,仿写而来
    
    boolean required() default true;
    
    String message() default "手机号码格式错误"; //添加错误信息

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
}

 

新建IsMobileValidator类

先看类的声明部分,public class IsMobileValidator implements ConstraintValidator<IsMobile, String>,它有两个泛型,第一个是自定义的注解类,第二个是要验证的参数类型,另外实现该接口的逻辑类,被spring管理成bean,可以在需要的地方进行装配

其中有一个initialize,初始化方法,它调用的是我们自定义注解中写的required()方法,默认需要有值

另一个方法isValid,则对逻辑进行验证,true验证通过,false验证失败

package com.kirin.miaosha.validator;

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

 

3、全局异常处理器

(1)新建com.kirin.miaosha.exception包

(2)新建GlobalExceptionHandler类

在CodeMsg.java中,添加一条异常定义

package com.kirin.miaosha.result;

public class CodeMsg {
    private int code;
    private String msg;
    
    //定义通用异常
    public static CodeMsg SUCCESS = new CodeMsg(0, "success");
    public static CodeMsg SERVER_ERROR = new CodeMsg(500100, "服务端异常");
    public static CodeMsg BIND_ERROR = new CodeMsg(500101, "参数校验异常:%s"); //带参数
    
    //登录模块 5002XX
      public static CodeMsg SESSION_ERROR = new CodeMsg(500210, "Session不存在或者已经失效");
      public static CodeMsg PASSWORD_EMPTY = new CodeMsg(500211, "登录密码不能为空");
      public static CodeMsg MOBILE_EMPTY = new CodeMsg(500212, "手机号不能为空");
      public static CodeMsg MOBILE_ERROR = new CodeMsg(500213, "手机号格式错误");
      public static CodeMsg MOBILE_NOT_EXIST = new CodeMsg(500214, "手机号不存在");
      public static CodeMsg PASSWORD_ERROR = new CodeMsg(500215, "密码错误");

    //商品模块5003XX

    //订单模块5004XX

    //秒杀模块5005XX

      private CodeMsg( ) {
    }
            
    private CodeMsg( int code,String msg ) {
        this.code = code;
        this.msg = msg;
    }
    
    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    
    //定义带参数的CodeMsg
    public CodeMsg fillArgs(Object... args) {
        int code = this.code;
        String message = String.format(this.msg, args); //把原始的message拼接上参数
        return new CodeMsg(code, message);
    }

    @Override
    public String toString() {
        return "CodeMsg [code=" + code + ", msg=" + msg + "]";
    }
}

其中String.format()能够根据传入的字符串格式,比如"参数校验异常:%s",其中%s,能被第二个传入的参数进行替换,从而形成动态的字符串

 

GlobalExceptionHandler.java:

package com.kirin.miaosha.exception;

//全局异常处理器:处理登录异常没有显示在页面上的问题
@ControllerAdvice //它是增强的Controller,能够实现全局异常处理和全局数据绑定
@ResponseBody
public class GlobalExceptionHandler {
    //配合@ExceptionHandler(value = Exception.class),它能够实现对所有异常的接受,而在方法中,对不同的异常进行处理
    @ExceptionHandler(value=Exception.class) //拦截所有的异常
    public Result<String> exceptionHandler(HttpServletRequest request, Exception e){
        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));
        }else {
            return Result.error(CodeMsg.SERVER_ERROR);
        }
    }
}

运行:出现提示框,则成功!

 

(3)自动新建GlobalException类:全局异常

 

修改com.kirin.miaosha.service / MiaoshaUserService.java代码:

 

修改com.kirin.miaosha.exception / GlobalExceptionHandler.java代码:

 

修改com.kirin.miaosha.controller / LoginController.java代码:

package com.kirin.miaosha.controller;

@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) {
    public Result<Boolean> doLogin(@Valid LoginVo loginVo) { //2.JSR303参数检验
        log.info(loginVo.toString()); //在LoginVo.java中生成toString()
        //1.自己写的参数校验
//        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);
//        }
//        if(!ValidatorUtil.isMobile(mobile)) { //验证mobile的输入的格式,是否是以1开头,后跟10个数字
//            return Result.error(CodeMsg.MOBILE_ERROR);
//        }
        
        //登录
        userService.login(loginVo);
        return Result.success(true);
    }
}

 

 

四、分布式Session

(1)com.kirin.miaosha.service / MiaoshaUserService.java:登录成功后,生成cookie

封装addCookie():

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

 

(2)在com.kirin.miaosha.util包中,新建UUIDUtil.java,UUID的工具类

package com.kirin.miaosha.util;

import java.util.UUID;

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

 

(3)新建商品列表页goods_list.html

(4)在com.kirin.miaosha.controller包中,新建GoodsController.java,使用户登录后跳转到商品列表页goods_list.html

package com.kirin.miaosha.controller;

@Controller
@RequestMapping("/goods")
public class GoodsController {

    @Autowired
    MiaoshaUserService userService;
    
    @Autowired
    RedisService redisService;
    
    @RequestMapping("/to_list")
    public String toList(HttpServletResponse response,Model model,
        @CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken, //根据参数value在Cookie中获取值
        @RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken){ //让我们在Request中能获取参数,解决的主要是,移动手机端不使用Cookie存值的问题
        //参数校验
        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";
    }
}

 

(5)在(4)中,每次获取参数都要写这么一大段,简化方法:

  @RequestMapping("/to_list") //跳转到商品列表页goods_list.html
    public String toList(HttpServletResponse response,Model model,MiaoshaUser user){
        model.addAttribute("user",user);
        return "goods_list";
    }

【原理】WebMvcConfigurerAdapter

采用的是继承WebMvcConfigurerAdapter,重写其中addArgumentResolvers()方法,该方法实现的是参数解析的功能

【解决方法】

新建com.kirin.miaosha.config包,新建WebConfig.java:

package com.kirin.miaosha.config;

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
    
    @Autowired
    UserArgumentResolver userArgumentResolver;
    
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(userArgumentResolver);
    }
}

 

新建UserArgumentResolver.java:

package com.kirin.miaosha.config;

@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
//实现HandlerMethodArgumentResolver接口,必须重写其中的两个方法,supportsParameter()和resolveArgument()

    @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 {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        HttpServletResponse response = webRequest.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;
        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;
    }
}

实现HandlerMethodArgumentResolver接口,必须重写其中的两个方法,supportsParameter()和resolveArgument()

 

posted @ 2022-02-12 16:16  淇凌  阅读(129)  评论(0编辑  收藏  举报