1、先上阿里云购买短信服务,购买后可在已购买服务中找到AppCode。
阿里云短信
2、在第三方项目中,加入发送验证码服务,在yml文件中配置发送验证码所需配置
code:
sendCodeUrl: https://gyytz.market.alicloudapi.com/sms/smsSend
appcode: 已购买服务中找到AppCodeAppCode
smsSignId: 2e65b1bb3d054466b82f0c9d125465e2
templateId: f5e68c3ad6b6474faa8cd178b21d3377
@ConfigurationProperties(prefix = "code")
@Data
@Component
public class SendCodeComponent {
Logger logger = LoggerFactory.getLogger(SendCodeComponent.class);
private String sendCodeUrl;
private String appCode;
private String smsSignId;
private String templateId;
public Integer sendCode(String code, String mobile) {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "APPCODE " + appCode);
Map<String, String> querys = new HashMap<>();//不同商家请求方式,请求参数不同,具体见商家示例
querys.put("mobile", mobile);
querys.put("param", code);
querys.put("smsSignId", smsSignId);
querys.put("templateId", templateId);
Integer resultCode = 0;
try {
HttpClientResult clientResult = HttpClientUtils.doPost(sendCodeUrl, headers, querys);
resultCode = clientResult.getCode();
} catch (Exception e) {
logger.debug(mobile + ":" + "发送验证码失败:" + e);
}
return resultCode;
}
}
@RestController
@RequestMapping("/send")
public class SendCodeController {
@Autowired
private SendCodeComponent sendCodeComponent;
@GetMapping("/code")
public R sendCode(@RequestParam("mobile") String mobile, @RequestParam("code") String code) {
Integer integer = sendCodeComponent.sendCode(code, mobile);
return integer == 200 ? R.ok() : R.error();
}
}
3、在auth项目中feign调用feign调用
@FeignClient("mall-third-party")
public interface FeignService {
@GetMapping("send/code")
public R sendCode(@RequestParam("mobile") String mobile, @RequestParam("code") String code);
}
@RestController
public class LoginController {
@Autowired
FeignService feignService;
@Autowired
StringRedisTemplate redisTemplate;
@RateLimit
@RequestMapping("/send/code")
public R sendCode(@RequestParam("phone") String phone) {
//TODO 接口防刷
String redisValue = redisTemplate.opsForValue().get("sms:code:" + phone);
if (StringUtils.isNotBlank(redisValue)) {
long l = Long.parseLong(redisValue.split("_")[1]);
if (System.currentTimeMillis() - l < 60000)
return R.error(10002, "验证码获取频率太高");
}
String code = UUID.randomUUID().toString().substring(0, 5);
String redisCode = code + "_" + System.currentTimeMillis();
//验证码的再次校验 redis:sms:code:phone->code
redisTemplate.opsForValue().set("sms:code:" + phone, redisCode, 10, TimeUnit.MINUTES);
R r = feignService.sendCode(phone, code);
return r;
}
}
接口注解防刷校验
1、创建注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimit {
/**
* 周期,单位是秒
*/
int cycle() default 60;
/**
* 请求次数
*/
int number() default 1;
/**
* 默认提示信息
*/
String msg() default "请勿重复点击";
}
2、注入redis进行校验
@Configuration
public class AccessLimitBeanConfig {
@Autowired
private StringRedisTemplate redisTemplate;
@Bean
@ConditionalOnMissingBean(AccessLimitService.class)
public AccessLimitService rateLimitService() {
AccessLimitServiceImpl accessLimitServiceImpl = new AccessLimitServiceImpl();
accessLimitServiceImpl.setRedisTemplate(redisTemplate);
return accessLimitServiceImpl;
}
}
3、拦截校验接口
public interface AccessLimitService {
/**
* 接口频次限制校验
*
* @param ip 客户端IP
* @param uri 请求接口名
* @param accessLimit 限制频次信息
*/
Boolean limit(String ip, String uri, AccessLimit accessLimit);
}
4、校验接口实现类
@Slf4j
public class AccessLimitServiceImpl implements AccessLimitService {
@Autowired
private StringRedisTemplate redisTemplate;
public void setRedisTemplate(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public Boolean limit(String ip, String uri, AccessLimit accessLimit) {
log.info("默认的实现,请自定义实现类覆盖当前实现");
String key = "rate:" + ip + ":" + uri;
// 缓存中存在key,在限定访问周期内已经调用过当前接口
if (redisTemplate.hasKey(key)) {
// 访问次数自增1
redisTemplate.opsForValue().increment(key, 1);
// 超出访问次数限制
String s = redisTemplate.opsForValue().get(key);
if (Integer.parseInt(s) > accessLimit.number()) {
return false;
}
// 未超出访问次数限制,不进行任何操作,返回true
} else {
// 第一次设置数据,过期时间为注解确定的访问周期
redisTemplate.opsForValue().set(key, "1", accessLimit.cycle(), TimeUnit.SECONDS);
}
return true;
}
}
5、创建注解拦截器
@Component
public class AccessLimitInterceptor extends HandlerInterceptorAdapter {
private AccessLimitService accessLimitService;
public void setAccessLimitService(AccessLimitService accessLimitService) {
this.accessLimitService = accessLimitService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 判断请求是否属于方法的请求
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取方法中的注解,看是否有该注解
AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
if (accessLimit == null) {
return true;
}
// 请求IP地址
String ip = request.getRemoteAddr();
// 请求url路径
String uri = request.getRequestURI();
return accessLimitService.limit(ip, uri, accessLimit);
}
return true;
}
}
6、拦截器注册
@Slf4j
@Configuration
public class MallWebConfig implements WebMvcConfigurer {
@Autowired
private AccessLimitService accessLimitService;
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.debug("加入------>AccessLimitInterceptor");
AccessLimitInterceptor accessLimitInterceptor = new AccessLimitInterceptor();
accessLimitInterceptor.setAccessLimitService(accessLimitService);
registry.addInterceptor(accessLimitInterceptor);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
log.debug("加入------>ViewControllers");
registry.addViewController("/login.html").setViewName("/login");
registry.addViewController("/reg.html").setViewName("/reg");
}
}
7、在controller访问层加上@AccessLimit注解
@AccessLimit
@RequestMapping("/send/code")
public R sendCode(@RequestParam("phone") String phone){
.......
}