总结:
- 配置好阿里云短信服务API调用所需配置项信息
- 按业务场景自定义标识码,做到正确发送不同场景的短信验证码
- 有一点需要特别注意:写这边文章时,偶然发现阿里云短信服务API,<u>在遇到以数字0开头的随机数码时,发送的验证码会忽略数字0,导致验证码长度不匹配</u>。因此,建议在生成随机数码时,使用递归思想提前将 以数字0开头的随机数码过滤掉!
/**
* @Description 阿里云短信服务 控制层
* @Author blake
* @Date 2018/12/6 下午5:06
* @Version 1.0
*/
@Api(tags = "05\. 阿里云短信服务", description = "阿里云短信验证码")
@RestController
@RequestMapping("/api/common/sms")
public class AliyunSmsController extends BaseController {
@Autowired
private AliyunSmsService aliyunSmsService;
/**
* 1)短信防刷 借助Redis
* 2)异步接收短信发送状态,更新本地数据库的短信发送 成功与否标识位
*/
@ApiOperation(value = "发送短信验证码", response = Boolean.class)
@PostMapping("/verification/code")
public Response sendSms(@RequestBody @Valid SmsVerifyCodeRequest request)
throws ClientException {
return JsonSend.success(aliyunSmsService.sendSms(request));
}
}
/**
* @Description 阿里云短信服务 业务逻辑层
* @Author blake
* @Date 2018/12/6 下午5:24
* @Version 1.0
*/
@Service
public class AliyunSmsServiceImpl implements AliyunSmsService {
private static final Logger logger = LoggerFactory.getLogger(AliyunSmsServiceImpl.class);
@Autowired
private AliyunSmsProperties aliyunSmsProperties;
@Autowired
private BeePlusUserAPIs beePlusUserAPIs;
@Autowired
private RedissonClient redissonClient;
@Autowired
private SmsSendLogDAO smsSendLogDAO;
/**
* @return java.lang.Boolean
* @throws
* @description 发送短信验证码
* @params [request]
*/
@Override
public Boolean sendSms(SmsVerifyCodeRequest request)
throws ClientException {
String phone = request.getPhone();
Boolean isCheckBusiness = request.getIsCheckBusiness();
if (Objects.nonNull(isCheckBusiness) && isCheckBusiness) {
// 调用Bee+方API,判断是否为会员身份,若不是会员,则提示"手机号码不存在,请移步前台办理会员!";若为会员,
// 业务逻辑继续发送验证码,完成会员信息绑定
// BeePlus方会员身份校验API
String toDetectMemberApi = beePlusUserAPIs.getToDetectMemberApi();
if (StringUtils.isBlank(toDetectMemberApi)) {
throw new CommonBusinessException("toDetectMemberApi配置有误,请核查!");
}
MemberDetectReqDTO memberDetectReqDTO = new MemberDetectReqDTO();
memberDetectReqDTO.setPhone(phone);
String jsonPhone = JacksonUtil.toJSon(memberDetectReqDTO);
// 校验手机号码为phone的会员是否存在
String exists = HttpUtils.doPost(toDetectMemberApi, Objects.requireNonNull(jsonPhone));
if (IntegerUtil.toInt(exists) != 1) {
throw new CommonBusinessException("手机号码不存在,请移步前台办理会员!");
}
}
// 从Redis取出用户发送短信验证码的间隔时效标识
RBucket<Object> bucketDuration = redissonClient.getBucket(Constants.COUNT_DOWN_SEND_SMS_PREFIX + phone);
if (bucketDuration.isExists()) {
logger.info("AliyunSmsServiceImpl.sendSms ========== 短信验证码发送频繁,限定每{}分钟发送一次 ========== ",
Constants.VERIFY_CODE_SEND_DURATION);
throw new CommonBusinessException("短信验证码发送频繁,请稍候再试!");
}
Integer codeType = request.getCodeType();
// 可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
String accessKeyId = aliyunSmsProperties.getAccessKeyId();
String accessKeySecret = aliyunSmsProperties.getAccessKeySecret();
String product = aliyunSmsProperties.getProduct();
String domain = aliyunSmsProperties.getDomain();
// 短信签名:形如【蜜蜂科技】
String smsSign = Constants.SMS_SING;
// 初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
// 组装请求对象-具体描述见控制台-文档部分内容
SendSmsRequest smsRequest = new SendSmsRequest();
// 必填:待发送手机号
smsRequest.setPhoneNumbers(phone);
// 必填:短信签名-可在短信控制台中找到
smsRequest.setSignName(smsSign);
// 6位长度随机码
String randomCode = RandomUtils.getRandomCode();
// 短信调试开关,万能短信验证码 = "1234"
if (aliyunSmsProperties.getSimulate()) {
randomCode = "1234";
}
// 使用redis存储,借助其key可设置过期时间
// 无论key是否存在,key=value的键始终存在
RBucket<Object> randomCodeBucket = redissonClient.getBucket(Constants.VERIFY_CODE_SESSION_PREFIX + phone);
// 设置value的同时配置key存活时间
randomCodeBucket.set(randomCode, Constants.VERIFY_CODE_VALID_SECONDS, TimeUnit.SECONDS);
// 设置短信验证码发送间隔
bucketDuration.set(1, Constants.VERIFY_CODE_SEND_DURATION, TimeUnit.MINUTES);
// 短信调试开关
if (aliyunSmsProperties.getSimulate()) {
return true;
}
// 匹配消息模板
chooseSmsTemplate(codeType, smsRequest, randomCode);
// 选填-上行短信扩展码(无特殊需求用户请忽略此字段)
// smsRequest.setSmsUpExtendCode("90997");
// 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
// smsRequest.setOutId("yourOutId");
// hint 此处可能会抛出异常,注意catch
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(smsRequest);
logger.info("AliyunSmsServiceImpl.sendSms =========== sendSmsResponse:{} ===========",
JacksonUtil.toJSon(sendSmsResponse));
// 短信内容
String msgContent = "";
//查明细
if (sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) {
QuerySendDetailsResponse querySendDetailsResponse = querySendDetails(sendSmsResponse.getBizId(), phone);
List<QuerySendDetailsResponse.SmsSendDetailDTO> smsSendDetailDTOs = querySendDetailsResponse.getSmsSendDetailDTOs();
if (CollectionUtil.isNotEmpty(smsSendDetailDTOs)) {
logger.debug("AliyunSmsServiceImpl.sendSms =========== 短信明细查询接口返回数据 ===========");
logger.debug("AliyunSmsServiceImpl.sendSms =========== Code:[{}] ===========",
querySendDetailsResponse.getCode());
logger.debug("AliyunSmsServiceImpl.sendSms =========== Message:[{}] ===========",
querySendDetailsResponse.getMessage());
for (QuerySendDetailsResponse.SmsSendDetailDTO smsSendDetailDTO : smsSendDetailDTOs) {
logger.info("AliyunSmsServiceImpl.sendSms =========== smsSendDetailDTO:{} ===========",
JacksonUtil.toJSon(smsSendDetailDTO));
msgContent = smsSendDetailDTO.getContent();
}
}
// 保存短信验证码发送记录
smsSendLogDAO.insertSmsSendLog(phone, codeType, randomCode, msgContent, true);
return true;
} else {
smsSendLogDAO.insertSmsSendLog(phone, codeType, randomCode, msgContent, false);
return false;
}
}
/**
* @return com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse
* @throws
* @description 查询短信发送记录
* @params [bizId, phone]
*/
private QuerySendDetailsResponse querySendDetails(String bizId, String phone) throws ClientException {
// 可自助调整超时时间
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
String accessKeyId = aliyunSmsProperties.getAccessKeyId();
String accessKeySecret = aliyunSmsProperties.getAccessKeySecret();
String product = aliyunSmsProperties.getProduct();
String domain = aliyunSmsProperties.getDomain();
//初始化acsClient,暂不支持region化
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
//组装请求对象
QuerySendDetailsRequest request = new QuerySendDetailsRequest();
//必填-号码
request.setPhoneNumber(phone);
//可选-流水号
request.setBizId(bizId);
//必填-发送日期 支持30天内记录查询,格式yyyyMMdd
SimpleDateFormat ft = new SimpleDateFormat("yyyyMMdd");
request.setSendDate(ft.format(new Date()));
//必填-页大小
request.setPageSize(10L);
//必填-当前页码从1开始计数
request.setCurrentPage(1L);
//hint 此处可能会抛出异常,注意catch
QuerySendDetailsResponse querySendDetailsResponse = acsClient.getAcsResponse(request);
return querySendDetailsResponse;
}
/**
* @return void
* @throws
* @description 根据短信验证码类型,匹配消息模板
* @params [codeType, smsRequest, randomCode]
*/
private void chooseSmsTemplate(Integer codeType, SendSmsRequest smsRequest, String randomCode) {
switch (codeType) {
case Constants.VerifyCodeType.VERIFY_IDENTITY_CODE:
//必填:短信模板-可在短信控制台中找到
smsRequest.setTemplateCode("SMS_152380369");
smsRequest.setTemplateParam("{\"code\":" + randomCode + "}");
break;
case Constants.VerifyCodeType.LOGIN_CONFIRM_CODE:
smsRequest.setTemplateCode("SMS_152380368");
smsRequest.setTemplateParam("{\"code\":" + randomCode + "}");
break;
case Constants.VerifyCodeType.LOGIN_UN_NORMAL_CODE:
smsRequest.setTemplateCode("SMS_152380367");
smsRequest.setTemplateParam("{\"code\":" + randomCode + "}");
break;
case Constants.VerifyCodeType.USER_REGISTER_CODE:
smsRequest.setTemplateCode("SMS_152380366");
smsRequest.setTemplateParam("{\"code\":" + randomCode + "}");
break;
case Constants.VerifyCodeType.MODIFY_PASSWORD_CODE:
smsRequest.setTemplateCode("SMS_152380365");
smsRequest.setTemplateParam("{\"code\":" + randomCode + "}");
break;
case Constants.VerifyCodeType.INFORMATION_CHANGE_CODE:
smsRequest.setTemplateCode("SMS_152380364");
smsRequest.setTemplateParam("{\"code\":" + randomCode + "}");
break;
}
}
}
public class Constants {
/**
* 短信验证类型标识码
*/
public interface VerifyCodeType {
// 验证类型1=> 身份验证
int VERIFY_IDENTITY_CODE = 1;
// 验证类型2=> 登录确认
int LOGIN_CONFIRM_CODE = 2;
// 验证类型3=> 登录异常
int LOGIN_UN_NORMAL_CODE = 3;
// 验证类型4=> 用户注册
int USER_REGISTER_CODE = 4;
// 验证类型5=> 修改密码
int MODIFY_PASSWORD_CODE = 5;
// 验证类型6=> 信息变更
int INFORMATION_CHANGE_CODE = 6;
}
}