• 谷粒商城 高级篇 (十五) --------- 登录与注册



    一、环境搭建

    A、新建 gulimall-auth-server 项目

    在这里插入图片描述

    B、添加依赖模板

    在这里插入图片描述

    C、创建完项目后,我们需要降一下版本,方便处理
    在这里插入图片描述

    D、导入 common 工具类,同时排除 mp 的依赖

    在这里插入图片描述

    E、application.properties 配置服务的注册与发现

    在这里插入图片描述

    F、主启动类上加上注解开启服务注册与发现及远程调用功能

    在这里插入图片描述

    G、导入前端模板

    在这里插入图片描述

    H、配置域名映射
    在这里插入图片描述

    I、虚拟机中上传静态资源,完成动静分离
    在这里插入图片描述

    H、网关服务中配置路由转发
    在这里插入图片描述

    二、登录实现

    在 gulimall-auth-controller 中的 LoginController 中实现对登录请求处理的接口

    @Autowired
    private MemberFeignService memberFeignService;
    
    @GetMapping(value = "/login.html")
    public String loginPage(HttpSession session) {
    
        // 从 session 先取出来用户信息, 判断用户是否已经登录过了
        Object attribute = session.getAttribute(AuthServerConstant.LOGIN_USER);
        // 如果用户没登陆那就跳转到登录页面
    
        if (attribute == null) {
            return "login";
        } else {
            return "redirect:http://gulimall.com";
        }
    
    }
    
    @PostMapping(value = "/login")
    public String login(UserLoginVo vo, RedirectAttributes attributes, HttpSession session) {
    
        // 远程登录
        R login = memberFeignService.login(vo);
    
        if (login.getCode() == 0) {
            MemberResponseVo data = login.getData("data", new TypeReference<MemberResponseVo>(){});
            session.setAttribute(AuthServerConstant.LOGIN_USER,data);
            return "redirect:http://gulimall.com";
        } else {
            Map<String,String> errors = new HashMap<>();
            errors.put("msg",login.getData("msg",new TypeReference<String>(){}));
            attributes.addFlashAttribute("errors",errors);
            return "redirect:http://auth.gulimall.com/login.html";
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    这里是远程调用 member 服务下的接口。。。

    在这里插入图片描述
    对应的 MemberController 中:

    @PostMapping(value = "/login")
    public R login(@RequestBody MemberUserLoginVo vo) {
    
        MemberEntity memberEntity = memberService.login(vo);
    
        if (memberEntity != null) {
            return R.ok().setData(memberEntity);
        } else {
            return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMessage());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    MemberServiceImpl 中 login 方法的实现:

    @Override
    public MemberEntity login(MemberUserLoginVo vo) {
        String loginacct = vo.getLoginacct();
        String password = vo.getPassword();
    
        //1、去数据库查询 SELECT * FROM ums_member WHERE username = ? OR mobile = ?
        MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>()
                .eq("username", loginacct).or().eq("mobile", loginacct));
    
        if (memberEntity == null) {
            //登录失败
            return null;
        } else {
            //获取到数据库里的password
            String password1 = memberEntity.getPassword();
            BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
            //进行密码匹配
            boolean matches = passwordEncoder.matches(password, password1);
            if (matches) {
                //登录成功
                return memberEntity;
            }
        }
    
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    密码匹配是 MD5 算法自带的匹配方法。。。,比对成功完成账号密码的登录功能。。

    三、注册功能

    注册功能中一个重要功能的实现是获取短信验证码。。

    首先我们先在前端实现验证码倒计时的功能:

    $(function () {
       $("#sendCode").click(function () {
           //2、倒计时
           if ($(this).hasClass("disabled")) {
               //正在倒计时。
           } else {
               //1、给指定手机号发送验证码
               $.get("/sms/sendcode?phone=" + $("#phoneNum").val(), function (data) {
                   if (data.code != 0) {
                       alert(data.msg);
                   }
               });
               timeoutChangeStyle();
           }
       });
    })
    var num = 60;
    
    function timeoutChangeStyle() {
       $("#sendCode").attr("class", "disabled");
       if (num == 0) {
           $("#sendCode").text("发送验证码");
           num = 60;
           $("#sendCode").attr("class", "");
       } else {
           var str = num + "s 后再次发送";
           $("#sendCode").text(str);
           setTimeout("timeoutChangeStyle()", 1000);
       }
       num--;
    }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    gulimall-third-party 第三方服务模块整合短信验证码功能:

    package com.fancy.gulimall.thirdparty.controller;
    import com.fancy.common.utils.R;
    import com.fancy.gulimall.thirdparty.component.SmsComponent;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/sms")
    public class SmsSendController {
    
    
        @Autowired
        SmsComponent smsComponent;
    
        /**
         * 提供给别的服务进行调用
         * @param phone
         * @param code
         * @return
         */
        @GetMapping("/sendcode")
        public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code){
            smsComponent.sendSmsCode(phone,code);
            return R.ok();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    sendSmsCode 方法实现:这里主要是配置阿里云密钥、短信签名、手机号等

    package com.fancy.gulimall.thirdparty.component;
    
    import com.fancy.gulimall.thirdparty.utils.HttpUtils;
    import lombok.Data;
    import org.apache.http.HttpResponse;
    import org.apache.http.util.EntityUtils;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
    @Data
    @Component
    public class SmsComponent {
    
        private String host;
        private String path;
        private String skin;
        private String sign;
        private String appcode;
    
        public void sendSmsCode(String phone,String code){
            String method = "GET";
            String appcode = "93b7e19861a24c519a7548b17dc16d75";
            Map<String, String> headers = new HashMap<String, String>();
            //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
            headers.put("Authorization", "APPCODE " + appcode);
            Map<String, String> querys = new HashMap<String, String>();
            querys.put("code", code);
            querys.put("phone", phone);
            querys.put("skin", skin);
            querys.put("sign", sign);
            //JDK 1.8示例代码请在这里下载:  http://code.fegine.com/Tools.zip
    
            try {
                HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);
                //System.out.println(response.toString());如不输出json, 请打开这行代码,打印调试头部状态码。
                //状态码: 200 正常;400 URL无效;401 appCode错误; 403 次数用完; 500 API网管错误
                //获取response的body
                System.out.println(EntityUtils.toString(response.getEntity()));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    在 gulimall-auth-server 模块下实现第三方服务的短信服务的远程调用。。。

    在这里插入图片描述

    实现短信发送验证功能:这里将短信验证码存入 Redis 中,并设置过期时间为 3 分钟,实现三分钟内验证码有效的功能,同时实现接口防刷的功能。

    @ResponseBody
    @GetMapping(value = "/sms/sendCode")
    public R sendCode(@RequestParam("phone") String phone) {
        //1、接口防刷
        String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
        if (!StringUtils.isEmpty(redisCode)) {
            //活动存入redis的时间,用当前时间减去存入redis的时间,判断用户手机号是否在60s内发送验证码
            long currentTime = Long.parseLong(redisCode.split("_")[1]);
            if (System.currentTimeMillis() - currentTime < 60000) {
                //60s内不能再发
                return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMessage());
            }
        }
    
        //2、验证码的再次效验 redis.存key-phone,value-code
        int code = (int) ((Math.random() * 9 + 1) * 100000);
        String codeNum = String.valueOf(code);
        String redisStorage = codeNum + "_" + System.currentTimeMillis();
    
        //存入redis,防止同一个手机号在60秒内再次发送验证码
        stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+phone,
                redisStorage,10, TimeUnit.MINUTES);
    
        thirdPartFeignService.sendCode(phone, codeNum);
    
        return R.ok();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    注册接口的实现:从Redis 中根据手机号取出验证码进行验证,进行注册是调用 远程方法

    @PostMapping(value = "/register")
    public String register(@Valid UserRegisterVo vos, BindingResult result,
                           RedirectAttributes attributes) {
    
        //如果有错误回到注册页面
        if (result.hasErrors()) {
            Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
            attributes.addFlashAttribute("errors",errors);
    
            //效验出错回到注册页面
            return "redirect:http://auth.gulimall.com/reg.html";
        }
    
        //1、效验验证码
        String code = vos.getCode();
    
        //获取存入Redis里的验证码
        String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vos.getPhone());
        if (!StringUtils.isEmpty(redisCode)) {
            //截取字符串
            if (code.equals(redisCode.split("_")[0])) {
                //删除验证码;令牌机制
                stringRedisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX+vos.getPhone());
                //验证码通过,真正注册,调用远程服务进行注册
                R register = memberFeignService.register(vos);
                if (register.getCode() == 0) {
                    //成功
                    return "redirect:http://auth.gulimall.com/login.html";
                } else {
                    //失败
                    Map<String, String> errors = new HashMap<>();
                    errors.put("msg", register.getData("msg",new TypeReference<String>(){}));
                    attributes.addFlashAttribute("errors",errors);
                    return "redirect:http://auth.gulimall.com/reg.html";
                }
    
    
            } else {
                //效验出错回到注册页面
                Map<String, String> errors = new HashMap<>();
                errors.put("code","验证码错误");
                attributes.addFlashAttribute("errors",errors);
                return "redirect:http://auth.gulimall.com/reg.html";
            }
        } else {
            //效验出错回到注册页面
            Map<String, String> errors = new HashMap<>();
            errors.put("code","验证码错误");
            attributes.addFlashAttribute("errors",errors);
            return "redirect:http://auth.gulimall.com/reg.html";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    远程方法 register

    在这里插入图片描述

    @PostMapping(value = "/register")
    public R register(@RequestBody MemberUserRegisterVo vo) {
    
        try {
            memberService.register(vo);
        } catch (PhoneException e) {
            return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnum.PHONE_EXIST_EXCEPTION.getMessage());
        } catch (UsernameException e) {
            return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(),BizCodeEnum.USER_EXIST_EXCEPTION.getMessage());
        }
    
        return R.ok();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    memberService.register 方法实现:

    @Override
    public void register(MemberUserRegisterVo vo) {
        MemberEntity memberEntity = new MemberEntity();
    
        //设置默认等级
        MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
        memberEntity.setLevelId(levelEntity.getId());
    
        //设置其它的默认信息
        //检查用户名和手机号是否唯一。感知异常,异常机制
        checkPhoneUnique(vo.getPhone());
        checkUserNameUnique(vo.getUserName());
    
        memberEntity.setNickname(vo.getUserName());
        memberEntity.setUsername(vo.getUserName());
        //密码进行MD5加密
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode(vo.getPassword());
        memberEntity.setPassword(encode);
        memberEntity.setMobile(vo.getPhone());
        memberEntity.setGender(0);
        memberEntity.setCreateTime(new Date());
    
        //保存数据
        this.baseMapper.insert(memberEntity);
    }
    
    @Override
    public void checkPhoneUnique(String phone) throws PhoneException {
    
        Integer phoneCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
    
        if (phoneCount > 0) {
            throw new PhoneException();
        }
    
    }
    
    @Override
    public void checkUserNameUnique(String userName) throws UsernameException {
    
        Integer usernameCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
    
        if (usernameCount > 0) {
            throw new UsernameException();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    注意:这里的加密方式 是 MD5 + salt 的盐值加密,更难以被破解。。。

    这里如果用户名或手机号有重复,会抛出相应的异常,这里是用到了异常感知的处理方式。。。

    在这里插入图片描述

  • 相关阅读:
    C: . 与 -> 的区别
    Chrome插件开发教程
    OLED屏简介
    面向移动支付过程中网络安全的研究与分析
    redisTemplate、jedis、lettuce、redission的对比
    加入网关后,输入网址报404
    你知道HTTP与HTTPS有什么区别吗?
    freertos初体验 - 在stm32上移植
    剑指Offer 09.用两个栈实现队列
    spring-cloud-alibaba - nacos配置中心&注册中心 实战
  • 原文地址:https://blog.csdn.net/m0_51111980/article/details/126898347