Commit 737ac45e authored by 王亮's avatar 王亮

temp commit(SMS).

parent 9c4481d5
......@@ -4,10 +4,13 @@ import cn.quantgroup.sms.MsgParams;
import cn.quantgroup.tech.util.TechEnvironment;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.captcha.CaptchaNewValidator;
import cn.quantgroup.xyqb.constant.UserConstant;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.model.sms.SmsMerchant;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.DateUtils;
import cn.quantgroup.xyqb.util.IpUtil;
import cn.quantgroup.xyqb.util.ValidationUtil;
......@@ -16,7 +19,6 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -44,19 +46,33 @@ public class SmsController implements IBaseController {
private static final String IMAGE_IP_COUNT = "image:ip";
private static final String IMAGE_PHONE_COUNT = "image:phone";
private static final String IMAGE_DEVICEID_COUNT = "image:deviceId:";
/** ip上限 */
/**
* ip上限
*/
private static final Long IP_MAX_PER_DAY = 5000L;
/** 手机号短信上限 */
/**
* 手机号短信上限
*/
private static final Long PHONE_MAX_PER_DAY = 20L;
/** 手机号语音上限 */
/**
* 手机号语音上限
*/
private static final Long PHONE_VOICE_MAX_PER_DAY = 5L;
/** 设备每天上限 */
/**
* 设备每天上限
*/
private static final Long DEVICE_MAX_PER_DAY = 20L;
/** 手机号语音上限-KEY-4 */
/**
* 手机号语音上限-KEY-4
*/
private static final String PHONE_VOICE_MAX_PER_DAY_KEY_4 = "4";
/** 手机号语音上限-KEY-5 */
/**
* 手机号语音上限-KEY-5
*/
private static final String PHONE_VOICE_MAX_PER_DAY_KEY_5 = "5";
/** 手机号语音上限-KEY-6 */
/**
* 手机号语音上限-KEY-6
*/
private static final String PHONE_VOICE_MAX_PER_DAY_KEY_6 = "6";
/**
......@@ -198,8 +214,13 @@ public class SmsController implements IBaseController {
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号格式有误", null);
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (sessionStruct != null && !UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" +phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
return JsonResult.buildErrorStateResult("1分钟内不能重复获取验证码", null);
......@@ -207,9 +228,6 @@ public class SmsController implements IBaseController {
String uniqueId = phoneNo + UUID.randomUUID().toString().replaceAll("-", "");
List<String> newList = new ArrayList<>();
newList.add(randomCode);
/*ConfirmableMsg confirmableMsg = new ConfirmableMsg(
uniqueId, newList, "1", "1", phoneNo
);*/
MsgParams message = new MsgParams.Builder()
.typeList(Collections.singletonList(2))
.phoneNo(phoneNo)
......@@ -220,7 +238,6 @@ public class SmsController implements IBaseController {
.channel(appName)
.build();
try {
//smsService.getSmsSender().sendConfirmableMessage(confirmableMsg);
smsService.getSmsSender().sendMsg(message);
redisTemplate.opsForValue().set(key, uniqueId + ":" + randomCode, EXPIRE_MINUTES, TimeUnit.MINUTES);
deleteRetSendCode(phoneNo);//删除用户重置密码,多次错误逻辑
......@@ -242,7 +259,13 @@ public class SmsController implements IBaseController {
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号格式有误", null);
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (sessionStruct != null && !UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" +phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
return JsonResult.buildErrorStateResult("1分钟内不能重复获取验证码", null);
......@@ -340,10 +363,10 @@ public class SmsController implements IBaseController {
}
public String getRandomCode(int count) {
if(TechEnvironment.isPro()) {
if (TechEnvironment.isPro()) {
return RandomStringUtils.random(count, RANDOM_CHARS);
}
switch (count){
switch (count) {
case 6:
return "000000";
default:
......@@ -398,7 +421,13 @@ public class SmsController implements IBaseController {
return JsonResult.buildErrorStateResult("您当前ip已经达到获取今天短信验证码上限", null);
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (sessionStruct != null && !UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" +phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
log.info("sendVerificationCode2New1分钟内不能重复获取验证码:phoneNo:{},deviceId:{},ip:{}", phoneNo, deviceId, clientIp);
......@@ -479,7 +508,13 @@ public class SmsController implements IBaseController {
return JsonResult.buildErrorStateResult("您当前ip已经达到获取今天语音验证码上限", null);
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (sessionStruct != null && !UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" +phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
return JsonResult.buildErrorStateResult("1分钟内不能重复获取验证码", null);
......
package cn.quantgroup.xyqb.controller.req.v2;
import lombok.Data;
import javax.validation.constraints.Size;
@Data
public class SMSReq {
/**
* 0:是短信,1是语音,默认值是0
*/
private Integer type = 0;
/**
* 手机号码,必填
*/
@Size(min = 11,max = 18)
private String phoneNo;
/**
* 场景类型,0:是普通默认短信,1是登录,2是重置密码
*/
private Integer sceneType = 0;
/**
* 指定验证码长度,可选,4-10位
*/
private Integer codeLength;
/**
* 指定模版参数,可选,需要自己指定模版时选用
*/
private String smsMerchant;
}
package cn.quantgroup.xyqb.controller.req.v2;
import lombok.Data;
import javax.validation.constraints.Size;
@Data
public class SMSVerifyReq {
/**
* 手机号码,必填
*/
@Size(min = 11,max = 18)
private String phoneNo;
/**
* 验证码,必填,4-10位
*/
@Size(min = 4,max = 10)
private String code;
}
package cn.quantgroup.xyqb.controller.v2;
import cn.quantgroup.xyqb.controller.req.v2.LoginReq;
import cn.quantgroup.xyqb.controller.req.v2.SMSReq;
import cn.quantgroup.xyqb.controller.req.v2.SMSVerifyReq;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.LoginBean;
import cn.quantgroup.xyqb.model.SMSCodeBean;
import cn.quantgroup.xyqb.service.v2.VerificationCodeContext;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -13,14 +14,20 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/v2/sms")
public class SMSV2controller {
private final VerificationCodeContext verificationCodeContext;
public SMSV2controller(VerificationCodeContext verificationCodeContext) {
this.verificationCodeContext = verificationCodeContext;
}
/**
* 统一获取短信验证码
* @return JsonResult<SMSCodeBean>
* @see <a href="http://yapi.quantgroups.com/project/17/interface/api/65709">统一获取短信验证码</a>
*/
@PostMapping("/code")
public JsonResult<SMSCodeBean> getCode(){
return null;
public JsonResult<SMSCodeBean> getCode(@RequestBody SMSReq smsReq){
return JsonResult.buildSuccessResultGeneric(verificationCodeContext.send(smsReq));
}
/**
......@@ -29,9 +36,9 @@ public class SMSV2controller {
* @see <a href="http://yapi.quantgroups.com/project/17/interface/api/65829">...</a>
*/
@PostMapping("/verify")
public JsonResult<Boolean> verify(){
public JsonResult<Boolean> verify(@RequestBody SMSVerifyReq smsVerifyReq){
return null;
return JsonResult.buildSuccessResultGeneric(verificationCodeContext.verify(smsVerifyReq));
}
......
......@@ -35,7 +35,13 @@ public enum BizExceptionEnum {
UN_EXIT_VERIFY_CODE("2004","验证模式下验证码参数不能为空"),
EX_GET_VERIFY_CODE("2005","获取验证码失败"),
UN_EXIT_GEETEST_LOG("2006","极验记录不存在"),
PHONE_MAX_PER_DAY("2007","今天已获取20次短信验证码,请使用语音验证码或明天再试"),
DEVICE_MAX_PER_DAY("2008","您设备已经达到获取今天验证码上限"),
IP_MAX_PER_DAY("2009","您当前ip已经达到获取今天短信验证码上限"),
DUPLICATE_MIN("2010","1分钟内不能重复获取验证码"),
PHONE_VOICE_MAX_PER_DAY("2011","今天已获取5次语音验证码,请使用短信验证码或明天再试"),
ERROR_USAGE("2022","参数校验失败,用户登录语音验证码usage不正确"),
ERROR_SEND_SMS("2023","验证码发送失败"),
//通用记录
ERROR_PARAM("4000","参数不错误");
......
......@@ -21,6 +21,8 @@ public class TokenInterceptor implements HandlerInterceptor {
public TokenInterceptor(ISessionService sessionService) {
this.sessionService = sessionService;
excludes.add("/v2/oauth/login");
excludes.add("/v2/sms/code");
excludes.add("/v2/sms/verify");
}
......
......@@ -4,4 +4,5 @@ import lombok.Data;
@Data
public class SMSCodeBean {
private String code;
}
package cn.quantgroup.xyqb.service.sms;
import cn.quantgroup.sms.SmsSender;
import cn.quantgroup.xyqb.controller.req.v2.SMSVerifyReq;
/**
* 短信发送服务
......@@ -25,6 +26,8 @@ public interface ISmsService {
*/
boolean verifyPhoneAndCode(String phoneNo, String verificationCode);
boolean verifyPhoneAndCode(SMSVerifyReq smsVerifyReq);
/**
* 是否需要重新发送短信验证码
*
......
......@@ -4,13 +4,18 @@ import cn.quantgroup.sms.MsgParams;
import cn.quantgroup.sms.SmsSender;
import cn.quantgroup.tech.util.TechEnvironment;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.constant.UserConstant;
import cn.quantgroup.xyqb.controller.req.v2.SMSVerifyReq;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
......@@ -98,6 +103,37 @@ public class SmsServiceImpl implements ISmsService {
return confirmSms(verificationCode, uniqueId, code);
}
public boolean verifyPhoneAndCode(SMSVerifyReq smsVerifyReq) {
// 非生产环境直接跳过验证码检验
if (!TechEnvironment.isPro()) {
return true;
}
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key;
if (sessionStruct == null) {
throw new BizException(BizExceptionEnum.UN_EXIT_TENANT_ID);
}
if (UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + smsVerifyReq.getPhoneNo();
} else {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" + smsVerifyReq.getPhoneNo();
}
String randomCode = stringRedisTemplate.opsForValue().get(key);
if (StringUtils.isBlank(randomCode)) {
return false;
}
String[] arr = randomCode.split(":");
if (arr.length != Constants.VERIFICATION_LEN) {
return false;
}
String uniqueId = arr[0];
String code = arr[1];
return confirmSms(smsVerifyReq.getCode(), uniqueId, code);
}
private boolean confirmSms(String smsVerificationCode, String unqiueId, String code) {
try {
MsgParams message = new MsgParams(Collections.singletonList(2), unqiueId);
......@@ -114,11 +150,11 @@ public class SmsServiceImpl implements ISmsService {
@Override
public boolean needResendCode(String phoneNo) {
return needResendCode(phoneNo, Constants.VERIFICATION_CODE_FINITE_COUNT,true);
return needResendCode(phoneNo, Constants.VERIFICATION_CODE_FINITE_COUNT, true);
}
@Override
public boolean needResendCode(String phoneNo, Long threshold,boolean isDelTryCount) {
public boolean needResendCode(String phoneNo, Long threshold, boolean isDelTryCount) {
String verificationCountKey = Constants.REDIS_VERIFICATION_COUNT + phoneNo;
Long getVerificationCount = stringRedisTemplate.opsForHash().increment(verificationCountKey, Constants.REDIS_VERIFICATION_COUNT, 1);
......@@ -143,7 +179,12 @@ public class SmsServiceImpl implements ISmsService {
@Override
public void deleteOnlyCodeFromCache(String phoneNo) {
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (sessionStruct != null && !UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" +phoneNo;
}
stringRedisTemplate.delete(key);
}
......
package cn.quantgroup.xyqb.service.v2;
import cn.quantgroup.sms.MsgParams;
import cn.quantgroup.tech.util.TechEnvironment;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.constant.UserConstant;
import cn.quantgroup.xyqb.controller.req.v2.SMSReq;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.SMSCodeBean;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.model.sms.SmsMerchant;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.DateUtils;
import cn.quantgroup.xyqb.util.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class SMSVerificationCodeStrategy implements VerificationCodeStrategy {
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
@Autowired
private ISmsService smsService;
private static final long EXPIRE_MINUTES = 10;
private static final Long PHONE_MAX_PER_DAY = 20L;
/**
* 设备每天上限
*/
private static final Long DEVICE_MAX_PER_DAY = 20L;
/**
* ip上限
*/
private static final Long IP_MAX_PER_DAY = 5000L;
@Override
public Integer getType() {
return 0;
}
@Override
public SMSCodeBean send(SMSReq smsReq) {
SMSCodeBean smsCodeBean = new SMSCodeBean();
int codeLength = Constants.SMS_CODE_LEN_4;
if (smsReq.getCodeLength() != null) {
codeLength = smsReq.getCodeLength();
}
String randomCode = getRandomCode(codeLength);
if (sendVerificationCode2New(smsReq.getPhoneNo(), randomCode, smsReq.getSmsMerchant())) {
smsCodeBean.setCode(randomCode);
return smsCodeBean;
} else {
throw new BizException(BizExceptionEnum.ERROR_SEND_SMS);
}
}
private final static String RANDOM_CHARS = "0123456789";
private String getRandomCode(int count) {
if (TechEnvironment.isPro()) {
return RandomStringUtils.random(count, RANDOM_CHARS);
}
if (count == 6) {
return "000000";
}
return Constants.SUCCESS_CODE;
}
/**
* 新版本短信验证码
*
* @param phoneNo 手机号码
* @param randomCode 随机码
* @param smsMerchant 模版
* @return true成功,false失败
*/
private boolean sendVerificationCode2New(String phoneNo, String randomCode, String smsMerchant) {
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String clientIp = sessionStruct.getIp();
// 手机号计数器
Long getPhoneVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_SMS_CODE_COUNT, phoneNo, 1);
redisTemplate.expire(Constants.REDIS_SMS_CODE_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
// 设备号计数器
Long getDeviceVerificationCount = 0L;
if (StringUtils.isNotBlank(sessionStruct.getScDeviceId())) {
getDeviceVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_SMS_DEVICE_COUNT, sessionStruct.getScDeviceId(), 1);
redisTemplate.expire(Constants.REDIS_SMS_DEVICE_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// IP计数器
Long getIPVerificationCount = 0L;
if (StringUtils.isNotBlank(clientIp)) {
getIPVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_SMS_IP_COUNT, clientIp, 1);
redisTemplate.expire(Constants.REDIS_SMS_IP_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// 手机号上限检查
if (getPhoneVerificationCount > PHONE_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.PHONE_MAX_PER_DAY);
}
// 设备号上限检查
if (getDeviceVerificationCount > DEVICE_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.DEVICE_MAX_PER_DAY);
}
// IP上限检查
if (!IpUtil.whiteOf(clientIp) && getIPVerificationCount > IP_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.IP_MAX_PER_DAY);
}
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (!UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" + phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
throw new BizException(BizExceptionEnum.DUPLICATE_MIN);
}
String uniqueId = phoneNo + UUID.randomUUID().toString().replaceAll("-", "");
MsgParams message = new MsgParams.Builder()
.typeList(Collections.singletonList(2))
.phoneNo(phoneNo)
.merchantId(SmsMerchant.of(smsMerchant).getMerchantId())
.contentId(SmsMerchant.of(smsMerchant).getContentId())
.uniqueId(uniqueId)
.contentArgs(Collections.singletonList(randomCode))
.channel("")
.build();
try {
smsService.getSmsSender().sendMsg(message);
redisTemplate.opsForValue().set(key, uniqueId + ":" + randomCode, EXPIRE_MINUTES, TimeUnit.MINUTES);
//删除用户重置密码,多次错误逻辑
deleteRetSendCode(phoneNo);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 删除用户重置密码时短信验证错误
*
* @param phoneNo 手机号码
*/
private void deleteRetSendCode(String phoneNo) {
String verificationCountKey = Constants.REDIS_VERIFICATION_COUNT + phoneNo;
redisTemplate.opsForHash().delete(verificationCountKey, Constants.REDIS_VERIFICATION_COUNT);
}
}
package cn.quantgroup.xyqb.service.v2;
import cn.quantgroup.sms.MsgParams;
import cn.quantgroup.tech.util.TechEnvironment;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.constant.UserConstant;
import cn.quantgroup.xyqb.controller.req.v2.SMSReq;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.SMSCodeBean;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.DateUtils;
import cn.quantgroup.xyqb.util.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class SMSVoiceVerificationCodeStrategy implements VerificationCodeStrategy {
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
@Autowired
private ISmsService smsService;
private static final long EXPIRE_MINUTES = 10;
/**
* 手机号语音上限
*/
private static final Long PHONE_VOICE_MAX_PER_DAY = 5L;
/**
* 设备每天上限
*/
private static final Long DEVICE_MAX_PER_DAY = 20L;
/**
* ip上限
*/
private static final Long IP_MAX_PER_DAY = 5000L;
@Override
public Integer getType() {
return 1;
}
@Override
public SMSCodeBean send(SMSReq smsReq) {
SMSCodeBean smsCodeBean = new SMSCodeBean();
String usage = null;
switch (smsReq.getSceneType()) {
case 0:
usage = "4";
break;
case 1:
usage = "6";
break;
case 2:
usage = "5";
break;
}
int codeLength = Constants.SMS_CODE_LEN_4;
if (smsReq.getCodeLength() != null) {
codeLength = smsReq.getCodeLength();
}
String randomCode = getRandomCode(codeLength);
if (usage == null) {
throw new BizException(BizExceptionEnum.ERROR_USAGE);
}
if (sendVerificationCode2VoiceNew(smsReq.getPhoneNo(), randomCode, usage)) {
smsCodeBean.setCode(randomCode);
return smsCodeBean;
} else {
throw new BizException(BizExceptionEnum.ERROR_SEND_SMS);
}
}
/**
* 新版本语音验证码
*/
private boolean sendVerificationCode2VoiceNew(String phoneNo, String randomCode, String usage) {
SessionStruct sessionStruct = XyqbSessionContextHolder.getXSession();
String clientIp = sessionStruct.getIp();
// 手机号计数器
String verificationCountKey = Constants.REDIS_VOICE_CODE_COUNT + usage;
Long getPhoneVerificationCount = redisTemplate.opsForHash().increment(verificationCountKey, phoneNo, 1);
redisTemplate.expire(verificationCountKey, DateUtils.getSeconds(), TimeUnit.SECONDS);
// 设备号计数器
Long getDeviceVerificationCount = 0L;
if (StringUtils.isNotBlank(sessionStruct.getScDeviceId())) {
String verificationDeviceCountKey = Constants.REDIS_VOICE_DEVICE_COUNT + sessionStruct.getScDeviceId();
getDeviceVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_VOICE_DEVICE_COUNT, verificationDeviceCountKey, 1);
redisTemplate.expire(Constants.REDIS_VOICE_DEVICE_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// IP计数器
Long getIPVerificationCount = 0L;
if (StringUtils.isNotBlank(clientIp)) {
String verificationIPCountKey = Constants.REDIS_VOICE_IP_COUNT + clientIp;
getIPVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_VOICE_IP_COUNT, verificationIPCountKey, 1);
redisTemplate.expire(Constants.REDIS_VOICE_IP_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// 手机号上限检查
if (getPhoneVerificationCount > PHONE_VOICE_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.PHONE_VOICE_MAX_PER_DAY);
}
// 设备号上限检查
if (getDeviceVerificationCount > DEVICE_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.DEVICE_MAX_PER_DAY);
}
// IP上限检查
if (!IpUtil.whiteOf(clientIp) && getIPVerificationCount > IP_MAX_PER_DAY) {
throw new BizException(BizExceptionEnum.IP_MAX_PER_DAY);
}
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
if (!UserConstant.defaultTenantId.equals(sessionStruct.getTenantId())) {
key = Constants.REDIS_PREFIX_VERIFICATION_CODE + sessionStruct.getTenantId() + "_" + phoneNo;
}
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
throw new BizException(BizExceptionEnum.DUPLICATE_MIN);
}
String uniqueId = phoneNo + UUID.randomUUID().toString().replaceAll("-", "");
MsgParams message = new MsgParams(Collections.singletonList(4), phoneNo, "1", "4", Collections.singletonList(randomCode), uniqueId);
try {
smsService.getSmsSender().sendMsg(message);
redisTemplate.opsForValue().set(key, uniqueId + ":" + randomCode, EXPIRE_MINUTES, TimeUnit.MINUTES);
//删除用户重置密码,多次错误逻辑
deleteRetSendCode(phoneNo);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 删除用户重置密码时短信验证错误
*
* @param phoneNo 手机号码
*/
private void deleteRetSendCode(String phoneNo) {
String verificationCountKey = Constants.REDIS_VERIFICATION_COUNT + phoneNo;
redisTemplate.opsForHash().delete(verificationCountKey, Constants.REDIS_VERIFICATION_COUNT);
}
private final static String RANDOM_CHARS = "0123456789";
private String getRandomCode(int count) {
if (TechEnvironment.isPro()) {
return RandomStringUtils.random(count, RANDOM_CHARS);
}
if (count == 6) {
return "000000";
}
return Constants.SUCCESS_CODE;
}
}
package cn.quantgroup.xyqb.service.v2;
import cn.quantgroup.xyqb.controller.req.v2.SMSReq;
import cn.quantgroup.xyqb.controller.req.v2.SMSVerifyReq;
import cn.quantgroup.xyqb.exception.BizException;
import cn.quantgroup.xyqb.exception.BizExceptionEnum;
import cn.quantgroup.xyqb.model.SMSCodeBean;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class VerificationCodeContext {
private final ISmsService smsService;
private final Map<Integer, VerificationCodeStrategy> verificationCodeStrategyHashMap = new HashMap<>();
public VerificationCodeContext(List<VerificationCodeStrategy> verificationCodeStrategyList, ISmsService smsService) {
this.smsService = smsService;
verificationCodeStrategyList.forEach(i -> verificationCodeStrategyHashMap.put(i.getType(), i));
}
public SMSCodeBean send(SMSReq smsReq) {
VerificationCodeStrategy verificationCodeStrategy = verificationCodeStrategyHashMap.get(smsReq.getType());
if (verificationCodeStrategy == null) {
throw new BizException(BizExceptionEnum.UN_EXIT_VERIFY_TYPE);
}
return verificationCodeStrategy.send(smsReq);
}
public boolean verify(SMSVerifyReq smsVerifyReq) {
return smsService.verifyPhoneAndCode(smsVerifyReq);
}
}
package cn.quantgroup.xyqb.service.v2;
import cn.quantgroup.xyqb.controller.req.v2.SMSReq;
import cn.quantgroup.xyqb.model.SMSCodeBean;
public interface VerificationCodeStrategy {
Integer getType();
SMSCodeBean send(SMSReq smsReq);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment