|
@@ -0,0 +1,154 @@
|
|
|
+package org.jeecg.modules.shop.captcha.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.lang.Assert;
|
|
|
+import cn.hutool.core.util.IdUtil;
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.jeecg.modules.shop.captcha.entity.SendSmsCaptchaDTO;
|
|
|
+import org.jeecg.modules.shop.captcha.entity.VerifySmsCaptchaDTO;
|
|
|
+import org.jeecg.modules.shop.captcha.enums.CaptchaKeyEnum;
|
|
|
+import org.jeecg.modules.shop.captcha.enums.CaptchaTypeEnum;
|
|
|
+import org.jeecg.modules.shop.captcha.service.ICaptchaService;
|
|
|
+import org.jeecg.modules.shop.captcha.util.EmailUtil;
|
|
|
+import org.jeecg.modules.shop.captcha.util.SmsUtil;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.context.annotation.Lazy;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.concurrent.ThreadLocalRandom;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class CaptchaServiceImpl implements ICaptchaService {
|
|
|
+
|
|
|
+
|
|
|
+ private final RedisTemplate<String, String> redisTemplate;
|
|
|
+
|
|
|
+ private final SmsUtil smsUtil;
|
|
|
+
|
|
|
+ private final EmailUtil emailUtil;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void sendSmsCaptcha(SendSmsCaptchaDTO dto) {
|
|
|
+ // ************************************************
|
|
|
+ // * 流控和限额都在阿里云那边配置了,这边不再重复校验
|
|
|
+ // ************************************************
|
|
|
+ Assert.isTrue(CaptchaTypeEnum.isValidType(dto.getType()), "不是有效的验证码类型");
|
|
|
+ String captcha = generateCaptcha();
|
|
|
+// if (StrUtil.equalsAny(dto.getType(), CaptchaTypeEnum.SUPPLIER_LOGIN.getType(), CaptchaTypeEnum.SUPPLIER_FORGOT_PASSWORD.getType())) {
|
|
|
+// Assert.isTrue(customerService.count(new LambdaQueryWrapper<S003Customer>().eq(S003Customer::getEmail, dto.getAccount()))>0, "邮箱未注册");
|
|
|
+// } else if (StrUtil.equals(dto.getType(), CaptchaTypeEnum.SUPPLIER_REGISTER.getType())) {
|
|
|
+// Assert.isFalse(customerService.count(new LambdaQueryWrapper<S003Customer>().eq(S003Customer::getEmail, dto.getAccount()))<=1, "邮箱已注册");
|
|
|
+// }
|
|
|
+ Assert.isTrue(tryLock(dto.getAccount()), "流控");
|
|
|
+ emailUtil.sendRegisterEmail(dto.getAccount(),captcha);
|
|
|
+ String cacheKey = CaptchaKeyEnum.generateEmailCaptchaKey(dto.getType(), dto.getAccount());
|
|
|
+ redisTemplate.opsForValue().set(cacheKey, captcha, 10, TimeUnit.MINUTES);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void sendNotifiedCaptcha(SendSmsCaptchaDTO dto) {
|
|
|
+ Assert.isTrue(CaptchaTypeEnum.isValidType(dto.getType()), "不是有效的验证码类型");
|
|
|
+ //分为手机号和邮箱的情况
|
|
|
+ if (StrUtil.containsAny(dto.getAccount(), "@")) {
|
|
|
+ Assert.isTrue(tryLock(dto.getAccount()), "流控");
|
|
|
+ emailUtil.sendNotifiedEmail(dto.getAccount(), dto.getName());
|
|
|
+ }else {
|
|
|
+ try {
|
|
|
+ Assert.isTrue(tryLock(dto.getAccount()), "流控");
|
|
|
+ smsUtil.sendSmsNotified(dto.getAccount(), dto.getName());
|
|
|
+ } catch (Exception e) {
|
|
|
+ String errMsg = "验证码发送失败";
|
|
|
+ if (StrUtil.contains(e.getMessage(), "流控")) {
|
|
|
+ errMsg += ",触发流控限制";
|
|
|
+ }
|
|
|
+ throw new RuntimeException(errMsg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String verifySmsCaptcha(VerifySmsCaptchaDTO dto) {
|
|
|
+ Assert.isTrue(CaptchaTypeEnum.isValidType(dto.getType()), "参数错误");
|
|
|
+ int maxAccessCount = 5;
|
|
|
+ String captchaKey = "";
|
|
|
+ String captcha = "";
|
|
|
+ //分手机号和邮件两张情况
|
|
|
+ if (StrUtil.containsAny(dto.getAccount(), "@")){
|
|
|
+ captchaKey = CaptchaKeyEnum.generateEmailCaptchaKey(dto.getType(), dto.getAccount());
|
|
|
+ captcha = getCaptcha(captchaKey, maxAccessCount);
|
|
|
+ }else{
|
|
|
+ captchaKey = CaptchaKeyEnum.generateSmsCaptchaKey(dto.getType(), dto.getAccount());
|
|
|
+ captcha = getCaptcha(captchaKey, maxAccessCount);
|
|
|
+ }
|
|
|
+ Assert.notBlank(captcha, "验证码错误");
|
|
|
+ Assert.isTrue(StrUtil.equals(dto.getCaptcha(), captcha), "验证码错误");
|
|
|
+ String token = IdUtil.simpleUUID();
|
|
|
+ String tokenKey = CaptchaKeyEnum.generateCaptchaTokenKey(captchaKey);
|
|
|
+ redisTemplate.opsForValue().set(tokenKey, token, 10, TimeUnit.MINUTES);
|
|
|
+ clearCaptcha(captchaKey);
|
|
|
+ return token;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void verifyToken(String captchaKey, String token) {
|
|
|
+ Assert.isTrue(StrUtil.isAllNotEmpty(captchaKey, token), "参数错误");
|
|
|
+ String tokenKey = CaptchaKeyEnum.generateCaptchaTokenKey(captchaKey);
|
|
|
+ String _token = redisTemplate.opsForValue().get(tokenKey);
|
|
|
+ Assert.isTrue(StrUtil.equals(_token, token), "token无效");
|
|
|
+ redisTemplate.delete(tokenKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getCaptcha(String captchaKey, int maxAccessCount) {
|
|
|
+ Assert.notEmpty(captchaKey, "captchaKey 不能为空");
|
|
|
+ Assert.isTrue(maxAccessCount > 0, "maxAccessCount 必须大于 0");
|
|
|
+ String script = "local captchaKey = KEYS[1]\n" +
|
|
|
+ "local countKey = KEYS[2]\n" +
|
|
|
+ "local captcha = redis.call('GET', captchaKey)\n" +
|
|
|
+ "if not captcha then\n" +
|
|
|
+ " return nil\n" +
|
|
|
+ "end\n" +
|
|
|
+ "local currentCount = tonumber(redis.call('GET', countKey))\n" +
|
|
|
+ "if not currentCount or currentCount < tonumber(ARGV[1]) then\n" +
|
|
|
+ " redis.call('INCR', countKey)\n" +
|
|
|
+ " local captchaKeyTTL = redis.call('TTL', captchaKey)\n" +
|
|
|
+ " if captchaKeyTTL < 0 then\n" +
|
|
|
+ " captchaKeyTTL = 600\n" +
|
|
|
+ " end\n" +
|
|
|
+ " redis.call('EXPIRE', countKey, captchaKeyTTL)\n" +
|
|
|
+ " return captcha\n" +
|
|
|
+ "else\n" +
|
|
|
+ " redis.call('DEL', captchaKey)\n" +
|
|
|
+ " redis.call('DEL', countKey)\n" +
|
|
|
+ " return nil\n" +
|
|
|
+ "end";
|
|
|
+ String countKey = CaptchaKeyEnum.generateCaptchaAccessCountKey(captchaKey);
|
|
|
+ DefaultRedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
|
|
|
+ return redisTemplate.execute(redisScript, Arrays.asList(captchaKey, countKey), String.valueOf(maxAccessCount));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void clearCaptcha(String captchaKey) {
|
|
|
+ String countKey = CaptchaKeyEnum.generateCaptchaAccessCountKey(captchaKey);
|
|
|
+ redisTemplate.delete(Arrays.asList(captchaKey, countKey));
|
|
|
+ }
|
|
|
+
|
|
|
+ private String generateCaptcha() {
|
|
|
+ return String.valueOf(ThreadLocalRandom.current().nextInt(100000, 999999));
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean tryLock(String receiver) {
|
|
|
+ Boolean isLock = redisTemplate.opsForValue().setIfAbsent(CaptchaKeyEnum.generateCaptchaSendLockKey(receiver), "1", 1, TimeUnit.MINUTES);
|
|
|
+ return Boolean.TRUE.equals(isLock);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|