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


一、环境搭建

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

}

这里是远程调用 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());
    }
}

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

密码匹配是 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>

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

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

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

注册接口的实现:从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";
    }
}

远程方法 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();
}

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

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

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

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在森林中麋了鹿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值