05Gulimall-邮箱注册、注册功能

05Gulimall-邮箱注册、注册功能,第1张

05Gulimall-邮箱注册、注册功能 05Gulimall-邮箱注册、注册功能完善

在项目gulimall中使用邮箱验证码

分为两个服务,一个是第三方服务gulimall-third-party,用于真正发送验证码。第二个是auth认证服务,调用第三方服务发送验证码。

1.gulimall-third-party 1.0 引入依赖

    org.springframework.boot
    spring-boot-starter-mail

新建包:component

新建类:MyEmail

1.1 MyEmail的代码
package com.atguigu.thirdparty.component;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.Random;

@Component
public class MyEmail {
    @Value("${spring.mail.username}")
    private String from;

    @Resource
    JavaMailSender javaMailSender;

    
    public void sendMail(String email, String code) {

        MimeMessage mimeMessage = javaMailSender.createMimeMessage();

        try {
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
//            设置发件人
            mimeMessageHelper.setFrom(from);
//            设置收件人
            mimeMessageHelper.setTo(email);
//            设置邮件主题
            mimeMessageHelper.setSubject("XXXXX的验证码");
            //生成随机数
//            String random = randomInteger();
            //将随机数放置到session中
//            session.setAttribute("email",email);
//            session.setAttribute("code",random);

//            设置验证码的样式
            mimeMessageHelper.setText("你好,欢迎注册XXXX商城,您的验证码是:"+code+"",true);

            javaMailSender.send(mimeMessage);

        } catch (MessagingException e) {
            e.printStackTrace();
        }


    }

    
    private String randomInteger() {
        Random random = new Random();
        StringBuffer stringBuffer = new StringBuffer();
        //生成6位的随机数
        for (int i = 0;i<6;i++){
            int i1 = random.nextInt(10);
            stringBuffer.append(i1);
        }
        return stringBuffer.toString();
    }
}

1.2 修改gulimall-third-party的配置文件
spring:
  mail:
    host: smtp.163.com
    username: [email protected]
    password: xxx
    default-encoding: utf-8
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
  • username: 你的发件邮箱
  • password:你的邮箱开启PO3/SMTP后会生成一串密码,具体百度
1.3 controller.SmsSendController
@RestController
@RequestMapping("/sms")
public class SmsSendController {
    @Autowired
    MyEmail myEmail;
    
    @GetMapping("/sendCode")
    public R sendCode(@RequestParam("email") String email,@RequestParam("code") String code){
        myEmail.sendMail(email,code);
        return R.ok();
    }
}
2.gulimall-auth-server

认证服务

2.1 feign.ThirdPartFeignService

调用远程gulimall-third-party的服务

@FeignClient("gulimall-third-party")
public interface ThirdPartFeignService {
    @GetMapping("/sms/sendCode")
    R sendCode(@RequestParam("email") String email, @RequestParam("code") String code);
}
2.2 controller.LoginController
@ResponseBody
@GetMapping("/sms/sendCode")
public R sendCode(@RequestParam("email")String email){
    String code = MyEmail.randomInteger();
    R r = thirdPartFeignService.sendCode(email, code);
    if (r.getCode() == 0) {
        return R.ok();
    }else {
        return R.error("验证码发送错误!");
    }

}
2.3 Read timed out executing GET

出现这个异常:

因为Feign调用默认的超时时间为一分钟,一分钟接口不能返回就会抛出异常

在gulimall-auth-server的application.yml文件加入配置:

feign:
  client:
    config:
      default:
        connectTimeout: 10000
        readTimeout: 600000
        
spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true        
3.前端参考
$(function () {
   $("#sendCode").click(function () {
   //2、倒计时
   if($(this).hasClass("disabled")) {
      //正在倒计时中
   } else {
      //1、给指定手机号发送验证码
      $.get("/sms/sendCode?email=" + $("#phoneNum").val(),function (data) {
         if(data.code != 0) {
            alert(data.msg);
         }
      });
      timeoutChangeStyle();
   }
});
  });

主要是发送请求:

$.get("/sms/sendCode?email=" + $("#phoneNum").val()

4.短信发送接口防刷 4.1 进一步完善发送验证码

gulimall-auth-server

com.atguigu.gulimall.auth.controller.LoginController,修改这个sendCode方法

判断是否在60s内重复发送,可以在验证码后面拼接一个系统时间,比如说用下划线分割。将其存储在redis中。

每次发送前需要判断redis中是否含有这个key?即便是含有这个key也要继续判断,当前系统时间减去redis中存储的时间是否小于60000ms;

最后注意,发送第三方服务前,把code还原成5位验证码,保证邮件里是5位验证码。

@ResponseBody
@GetMapping("/sms/sendCode")
public R sendCode(@RequestParam("email")String email){
    //TODO 接口防刷

    //先查询redis中是否含有
    String redisCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + email);
    if (!StringUtils.isEmpty(redisCode)) {
        String[] split = redisCode.split("_");
        long timeFromRedis = Long.parseLong(split[1]);
        if (System.currentTimeMillis() - timeFromRedis <= 60000) {
            //60秒不可再发
            return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMsg());
        }
    }

    //手机验证码校验,加上系统当前时间是为了验证是否超过60秒
    String code = MyEmail.randomInteger() + "_" + System.currentTimeMillis();
    String[] codeSplit = code.split("_");
    //缓存验证码
    redisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+email,
            code,
            1,//1分钟过期时间
            TimeUnit.MINUTES);

    //将code还原成5位
    code = codeSplit[0];
    R r = thirdPartFeignService.sendCode(email, code);
    if (r.getCode() == 0) {
        return R.ok();
    }else {
        return R.error("验证码发送错误!");
    }

}
4.2 完善注册逻辑

com.atguigu.gulimall.auth.controller.LoginController的regist方法

@PostMapping("/register")
public String regist(@Valid UserRegistVo userRegistVo,
                     BindingResult bindingResult,
                     RedirectAttributes redirectAttributes) {

    //前置校验
    if (bindingResult.hasErrors()) {
        Map collect = bindingResult.getFieldErrors().stream()
                .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
        redirectAttributes.addFlashAttribute("errors", collect);
        return "redirect:http://auth.zuckmall.com/reg.html";
    }
    //校验验证码
    //前端传来的验证码
    String codeFromUser = userRegistVo.getCode();
    //从redis中获取的验证码,这里是getPhone,懒得改成email了
    String codeFromRedis = redisTemplate.opsForValue()
            .get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + userRegistVo.getPhone());
    if (!StringUtils.isEmpty(codeFromRedis)) {
        //校验
        String[] codeSplit = codeFromRedis.split("_");
        String codeFromRedisSplit = codeSplit[0];
        if (codeFromRedisSplit.equals(codeFromUser)) {
            //验证码对比成功
            //删除验证码,令牌机制
            redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + userRegistVo.getPhone());

            //TODO 调用远程服务注册

        }else {
            //验证码错误,存入错误集合map中
            Map errors = new HashMap<>();
            errors.put("code","验证码错误");
            redirectAttributes.addFlashAttribute("errors", errors);
            //校验出错,回到注册页
            return "redirect:http://auth.zuckmall.com/reg.html";
        }

    }else{
        //验证码过期了,存入错误集合map中
        Map errors = new HashMap<>();
        errors.put("code","验证码错误");
        redirectAttributes.addFlashAttribute("errors", errors);
        //校验出错,回到注册页
        return "redirect:http://auth.zuckmall.com/reg.html";
    }


}

校验验证码的主要代码如下:

1.从redis中获取验证码

2.判断是否存在?

​ 2.1 存在就校验

​ 2.1.1 分割字符串

​ 2.1.2 比对

​ 1)比对成功:调用远程服务注册

​ 2)比对失败:返回错误提示(map中返回)

​ 2.2 不存在就说明验证码过期,给予提示

	//校验验证码
    //前端传来的验证码
    String codeFromUser = userRegistVo.getCode();
    //从redis中获取的验证码,这里是getPhone,懒得改成email了
    String codeFromRedis = redisTemplate.opsForValue()
            .get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + userRegistVo.getPhone());
    if (!StringUtils.isEmpty(codeFromRedis)) {
        //校验
        String[] codeSplit = codeFromRedis.split("_");
        String codeFromRedisSplit = codeSplit[0];
        if (codeFromRedisSplit.equals(codeFromUser)) {
            //验证码对比成功
            //删除验证码,令牌机制
            redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + userRegistVo.getPhone());

            //TODO 调用远程服务注册

        }else {
            //验证码错误,存入错误集合map中
            Map errors = new HashMap<>();
            errors.put("code","验证码错误");
            redirectAttributes.addFlashAttribute("errors", errors);
            //校验出错,回到注册页
            return "redirect:http://auth.zuckmall.com/reg.html";
        }

    }else{
        //验证码过期了,存入错误集合map中
        Map errors = new HashMap<>();
        errors.put("code","验证码错误");
        redirectAttributes.addFlashAttribute("errors", errors);
        //校验出错,回到注册页
        return "redirect:http://auth.zuckmall.com/reg.html";
    }
4.3 调用远程服务注册

1.上一个代码:TODO调用远程注册地方的代码完善

    .....
				//调用远程服务注册
                R r = memberFeignService.regist(userRegistVo);
                if (r.getCode() == 0) {
                    //成功
                    System.out.println("注册成功");
                    return "redirect:http://auth.zuckmall.com/login.html";
                }else{
                    System.err.println("远程服务存在问题");
                    Map errors = new HashMap<>();
                    errors.put("msg", String.valueOf(r.get("msg")));
                    redirectAttributes.addFlashAttribute("errors", errors);
                    return "redirect:http://auth.zuckmall.com/reg.html";
                }
    .....

2.MemberFeignService

@FeignClient("gulimall-member")
public interface MemberFeignService {
    @PostMapping("/member/member/regist")
    R regist(@RequestBody UserRegistVo vo);
}

3.com.atguigu.member.controller.MemberController

	
    @PostMapping("/regist")
    public R regist(@RequestBody MemberRegistVo vo){
        try {
            memberService.regist(vo);
        } catch (PhoneExistException e) {
            e.printStackTrace();
            return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(),
                    BizCodeEnum.PHONE_EXIST_EXCEPTION.getMsg());
        } catch (UsernameExistException e){
            e.printStackTrace();
            return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(),
                    BizCodeEnum.USER_EXIST_EXCEPTION.getMsg());
        }
        return R.ok();
    }

	

4.com.atguigu.member.service.impl.MemberServiceImpl

	@Override
    public void regist(MemberRegistVo vo) {
        MemberEntity member = new MemberEntity();
        //设置默认等级
        MemberLevelEntity level = memberLevelDao.getDefaultLevel();
        member.setLevelId(level.getId());
        //手机,用户名
        //先检查是否唯一,让Controller感知异常,异常机制
        checkPhoneUnique(vo.getPhone());
        checkUsernameUnique(vo.getUserName());
        member.setMobile(vo.getPhone());
        member.setUsername(vo.getUserName());
        //密码加密存储
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        String encode = encoder.encode(vo.getPassword());
        member.setPassword(encode);
        //保存
        baseMapper.insert(member);
    }

	@Override
    public void checkPhoneUnique(String phone) throws PhoneExistException {
        Integer count = baseMapper.selectCount(new QueryWrapper().eq("mobile", phone));
        if (count > 0) {
            throw new PhoneExistException();
        }
    }

    @Override
    public void checkUsernameUnique(String username) throws UsernameExistException {
        Integer count = baseMapper.selectCount(new QueryWrapper().eq("username", username));
        if (count > 0) {
            throw new UsernameExistException();
        }
    }

5、com.atguigu.member.dao.MemberLevelDao

@Mapper
public interface MemberLevelDao extends baseMapper {
    MemberLevelEntity getDefaultLevel();
}

6.MemberLevelDao.xml


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5637194.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存