谷粒商城微服务分布式高级篇(十四) ——接入短信验证码和验证码防刷校验

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){
		.......
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值