05Gulimall - perfect mailbox registration and registration functions
Use mailbox verification code in project gulimall
It is divided into two services. One is the third-party service gulimall third party, which is used to really send verification codes. The second is the auth authentication service, which calls the third-party service to send the verification code.
1.gulimall-third-party
1.0 introducing dependencies
<!--SMS verification code--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
New package: component
New class: MyEmail
1.1 code of 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; /** * Send verification code * * @param email */ public void sendMail(String email, String code) { MimeMessage mimeMessage = javaMailSender.createMimeMessage(); try { MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); // Set sender mimeMessageHelper.setFrom(from); // Set recipient mimeMessageHelper.setTo(email); // Set message subject mimeMessageHelper.setSubject("XXXXX Verification code for"); //Generate random number // String random = randomInteger(); //Place random numbers into the session // session.setAttribute("email",email); // session.setAttribute("code",random); // Set the style of the verification code mimeMessageHelper.setText("Hello, welcome to register XXXX Mall, your verification code is:<font style='color:green'>"+code+"</font>",true); javaMailSender.send(mimeMessage); } catch (MessagingException e) { e.printStackTrace(); } } /** * Generate random verification code * * @return */ private String randomInteger() { Random random = new Random(); StringBuffer stringBuffer = new StringBuffer(); //Generate 6-bit random numbers for (int i = 0;i<6;i++){ int i1 = random.nextInt(10); stringBuffer.append(i1); } return stringBuffer.toString(); } }
1.2 modify the configuration file of 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: your email address
- password: after you open PO3/SMTP in your mailbox, a string of passwords will be generated. Please specify Baidu
1.3 controller.SmsSendController
@RestController @RequestMapping("/sms") public class SmsSendController { @Autowired MyEmail myEmail; /** * Provided to other service calls * @param email * @param code * @return */ @GetMapping("/sendCode") public R sendCode(@RequestParam("email") String email,@RequestParam("code") String code){ myEmail.sendMail(email,code); return R.ok(); } }
2.gulimall-auth-server
Certification services
2.1 feign.ThirdPartFeignService
Call the service of remote 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("Verification code sending error!"); } }
2.3 Read timed out executing GET
This exception occurred:
Because the default timeout of Feign call is one minute, an exception will be thrown if the interface cannot return in one minute
Add the configuration in the application.yml file of gulimall auth server:
feign: client: config: default: connectTimeout: 10000 readTimeout: 600000 spring: cloud: loadbalancer: retry: enabled: true
3. Front end reference
$(function () { $("#sendCode").click(function () { //2. Countdown if($(this).hasClass("disabled")) { //Countdown in progress } else { //1. Send verification code to specified mobile phone number $.get("/sms/sendCode?email=" + $("#phoneNum").val(),function (data) { if(data.code != 0) { alert(data.msg); } }); timeoutChangeStyle(); } }); });
Mainly sending requests:
$.get("/sms/sendCode?email=" + $("#phoneNum").val()
4. SMS sending interface anti brushing
4.1 further improve sending verification code
gulimall-auth-server
com.atguigu.gulimall.auth.controller.LoginController. Modify the sendCode method
To determine whether to send repeatedly within 60s, a system time can be spliced behind the verification code, such as underlined segmentation. Store it in redis.
Before sending each time, you need to determine whether redis contains this key? Even if this key is included, continue to judge whether the current system time minus the time stored in redis is less than 60000ms;
Finally, before sending the third-party service, restore the code to a 5-digit verification code to ensure that there is a 5-digit verification code in the email.
@ResponseBody @GetMapping("/sms/sendCode") public R sendCode(@RequestParam("email")String email){ //TODO interface anti brush //First query whether redis contains 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) { //It cannot be sent again in 60 seconds return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMsg()); } } //Mobile phone verification code verification, plus the current system time, is to verify whether it exceeds 60 seconds String code = MyEmail.randomInteger() + "_" + System.currentTimeMillis(); String[] codeSplit = code.split("_"); //Cache verification code redisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+email, code, 1,//1 minute expiration time TimeUnit.MINUTES); //Restore code to 5 bits code = codeSplit[0]; R r = thirdPartFeignService.sendCode(email, code); if (r.getCode() == 0) { return R.ok(); }else { return R.error("Verification code sending error!"); } }
4.2 improve the registration logic
Register method of com.atguigu.gulimall.auth.controller.LoginController
@PostMapping("/register") public String regist(@Valid UserRegistVo userRegistVo, BindingResult bindingResult, RedirectAttributes redirectAttributes) { //Pre check if (bindingResult.hasErrors()) { Map<String, String> collect = bindingResult.getFieldErrors().stream() .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage)); redirectAttributes.addFlashAttribute("errors", collect); return "redirect:http://auth.zuckmall.com/reg.html"; } //Verification code //Verification code from the front end String codeFromUser = userRegistVo.getCode(); //The verification code obtained from redis. Here is getPhone. I'm too lazy to change it to email String codeFromRedis = redisTemplate.opsForValue() .get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + userRegistVo.getPhone()); if (!StringUtils.isEmpty(codeFromRedis)) { //check String[] codeSplit = codeFromRedis.split("_"); String codeFromRedisSplit = codeSplit[0]; if (codeFromRedisSplit.equals(codeFromUser)) { //Verification code comparison succeeded //Delete verification code, token mechanism redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + userRegistVo.getPhone()); //TODO calls remote service registration }else { //The verification code is wrong and stored in the error set map Map<String, String> errors = new HashMap<>(); errors.put("code","Verification code error"); redirectAttributes.addFlashAttribute("errors", errors); //Verification error, return to the registration page return "redirect:http://auth.zuckmall.com/reg.html"; } }else{ //The verification code has expired and is stored in the error set map Map<String, String> errors = new HashMap<>(); errors.put("code","Verification code error"); redirectAttributes.addFlashAttribute("errors", errors); //Verification error, return to the registration page return "redirect:http://auth.zuckmall.com/reg.html"; } }
The main codes of verification code are as follows:
1. Obtain the verification code from redis
2. Judge whether it exists?
2.1 check on existence
2.1.1 split string
2.1.2 comparison
1) successful comparison: call remote service registration
2) comparison failure: an error prompt is returned (returned in the map)
2.2 if it does not exist, it means that the verification code has expired and will be prompted
//Verification code //Verification code from the front end String codeFromUser = userRegistVo.getCode(); //The verification code obtained from redis. Here is getPhone. I'm too lazy to change it to email String codeFromRedis = redisTemplate.opsForValue() .get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + userRegistVo.getPhone()); if (!StringUtils.isEmpty(codeFromRedis)) { //check String[] codeSplit = codeFromRedis.split("_"); String codeFromRedisSplit = codeSplit[0]; if (codeFromRedisSplit.equals(codeFromUser)) { //Verification code comparison succeeded //Delete verification code, token mechanism redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + userRegistVo.getPhone()); //TODO calls remote service registration }else { //The verification code is wrong and stored in the error set map Map<String, String> errors = new HashMap<>(); errors.put("code","Verification code error"); redirectAttributes.addFlashAttribute("errors", errors); //Verification error, return to the registration page return "redirect:http://auth.zuckmall.com/reg.html"; } }else{ //The verification code has expired and is stored in the error set map Map<String, String> errors = new HashMap<>(); errors.put("code","Verification code error"); redirectAttributes.addFlashAttribute("errors", errors); //Verification error, return to the registration page return "redirect:http://auth.zuckmall.com/reg.html"; }
4.3 calling remote service registration
1. Previous code: TODO calls the code in the remote registration place
..... //Call remote service registration R r = memberFeignService.regist(userRegistVo); if (r.getCode() == 0) { //success System.out.println("login was successful"); return "redirect:http://auth.zuckmall.com/login.html"; }else{ System.err.println("There is a problem with the remote service"); Map<String, String> 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
/** * register * @return */ @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(); //Set default level MemberLevelEntity level = memberLevelDao.getDefaultLevel(); member.setLevelId(level.getId()); //Mobile phone, user name //First check whether it is unique and let the Controller perceive the exception mechanism checkPhoneUnique(vo.getPhone()); checkUsernameUnique(vo.getUserName()); member.setMobile(vo.getPhone()); member.setUsername(vo.getUserName()); //Password encrypted storage BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String encode = encoder.encode(vo.getPassword()); member.setPassword(encode); //preservation baseMapper.insert(member); } @Override public void checkPhoneUnique(String phone) throws PhoneExistException { Integer count = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone)); if (count > 0) { throw new PhoneExistException(); } } @Override public void checkUsernameUnique(String username) throws UsernameExistException { Integer count = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", username)); if (count > 0) { throw new UsernameExistException(); } }
5,com.atguigu.member.dao.MemberLevelDao
@Mapper public interface MemberLevelDao extends BaseMapper<MemberLevelEntity> { MemberLevelEntity getDefaultLevel(); }
6.MemberLevelDao.xml
<select id="getDefaultLevel" resultType="com.atguigu.member.entity.MemberLevelEntity"> SELECT * FROM ums_member_level WHERE default_status = 1 </select>