1.gulimall-third-party 1.0 引入依赖在项目gulimall中使用邮箱验证码
分为两个服务,一个是第三方服务gulimall-third-party,用于真正发送验证码。第二个是auth认证服务,调用第三方服务发送验证码。
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: xxxx@163.com
password: xxx
default-encoding: utf-8
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
- username: 你的发件邮箱
- password:你的邮箱开启PO3/SMTP后会生成一串密码,具体百度
@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



