Description or Example
# BUG
## feign调用微服务的时候总是405
> 这是因为参数污染的问题, ~~405一定和参数有关~~, 而且这个参数必要转为json, 这就考虑参数污染, 可以考虑加上@RequestParam
> 注意加入Redis配置和依赖
# 核心源码
```java
@Controller
public class LoginRegisterController {
@Autowired
private SMSService smsService;
@GetMapping("/sms/sendCode")
@ResponseBody
public R getPhoneCode(String phone) {
return smsService.sendPhoneSMS(phone);
}
}
```
```java
@FeignClient("bitmall-thirdpart")
public interface SMSService {
@RequestMapping("/thirdpart/sms/sendCode")
R sendPhoneSMS(@RequestParam("phone") String phone);
}
```
```java
@RestController
@RequestMapping("thirdpart/sms")
public class SMSController {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private SMSComponent smsComponent;
/**
* 给指定的手机号发送纯数字验证码
* @param phone 接受验证码的手机号
* @return
*/
@RequestMapping("/sendCode")
public R sendPhoneSMS(@RequestParam("phone") String phone) {
// 1. 获取Redis里面的缓存起来的验证码
String key = PHONE_SMS_CODE + phone;
String code = redisTemplate.opsForValue().get(key);
// 判断验证码是否存在
if (StringUtils.isNotBlank(code)) {
// 继续判断是否超过防刷时限
Long createTimeMillis = Long.valueOf(code.split("_")[1]);
Long curTimeMillis = System.currentTimeMillis();
long minutes = (curTimeMillis - createTimeMillis) / 60000; // 距今多少分钟
// 这里做了一个规定, 三分钟内只能获取一个验证码
if (minutes < 4) {
return R.error(SMS_FAILURE.getCode(), SMS_FAILURE.getMessage());
}
}
// 到这里说明没有获取过验证码或者验证码需要再次获取
code = RandomUtil.randomNumbers(6);
// 保存到redis
// 过期时间默认是15分钟
redisTemplate.opsForValue().set(key, code + "_" + System.currentTimeMillis(), 15, TimeUnit.MINUTES);
try {
smsComponent.sendPhoneCode(phone, code);
} catch (ExecutionException | InterruptedException e) {
return R.error(SMS_GET_FAILURE.getCode(), SMS_GET_FAILURE.getMessage());
}
return R.ok();
}
}
```
# 知识点
## 为什么给后端发请求, 而不是像OSS一样直接发请求给ailyunSMS服务器?
### 旧版API
> 对于旧版API而言, 如果直接给SMS服务器发送请求, 有一个致命的缺陷, 即请求头必须要有用户的APIKEY, 如果有不法分子拿到了APIKEY, 就可以根据官方文档猛发请求, 消耗公司的验证码, 造成财产损失
### 新版API
> 当前我用的接口架构不支持获取签名, 必须拿到用户的ACCESSID和SEUCRKEY才行, 这些给前端发送, 极大几率被截获, 因此, 为了安全, 统一由后端发请求 即便牺牲一定的性能
### 架构图
[架构图](https://www.processon.com/embed/64dca3c9039d400ab5105bb7)
## 为什么验证码存储在Reids里面?
1. 因为验证码具有时效性, 即验证码不可能永久有效的, 因此不需要持久化, 放在Redis加上过期时间即可
2. 放在Redis里面解决分布式下会话不共享问题
## 为什么给每一个code加上一个时间戳?
> 因为前端的缘故, 页面很有可能被扒, 里面的请求验证码的地址暴露, 一旦这样, 如果有心之人频繁发该请求, 会大量消耗验证码, 造成财产损失, 因此加上时间戳, 判断是否在3min内, 若在则不让其获取, 保证财产安全
## 如何实现防刷和限时校验
> 防刷可以通过加上时间戳, 通过当前时间戳和存储起来的时间戳比较, 从来防刷
> 限时校验可以给Redis的缓存加上过期时间, 若存在说明code还在有效期, 可以校验, 否则无法校验
> 注意: 防刷时间一定要比过期时间短, 否则过期了, 防刷时间没了, 防刷没意义了
## 架构
> 整体的架构是, 先调用auth微服务的获取验证码controller, 然后该controller调用第三方微服务获取验证码