package com.iailab.module.system.service.sms;
|
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.map.MapUtil;
|
import com.iailab.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
|
import com.iailab.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
import com.iailab.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
|
import com.iailab.module.system.dal.dataobject.sms.SmsCodeDO;
|
import com.iailab.module.system.dal.mysql.sms.SmsCodeMapper;
|
import com.iailab.module.system.enums.sms.SmsSceneEnum;
|
import com.iailab.module.system.framework.sms.config.SmsCodeProperties;
|
import org.springframework.stereotype.Service;
|
import org.springframework.validation.annotation.Validated;
|
|
import javax.annotation.Resource;
|
import java.time.LocalDateTime;
|
|
import static cn.hutool.core.util.RandomUtil.randomInt;
|
import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static com.iailab.framework.common.util.date.DateUtils.isToday;
|
import static com.iailab.module.system.enums.ErrorCodeConstants.*;
|
|
/**
|
* 短信验证码 Service 实现类
|
*
|
* @author iailab
|
*/
|
@Service
|
@Validated
|
public class SmsCodeServiceImpl implements SmsCodeService {
|
|
@Resource
|
private SmsCodeProperties smsCodeProperties;
|
|
@Resource
|
private SmsCodeMapper smsCodeMapper;
|
|
@Resource
|
private SmsSendService smsSendService;
|
|
@Override
|
public void sendSmsCode(SmsCodeSendReqDTO reqDTO) {
|
SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
|
Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
|
// 创建验证码
|
String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
|
// 发送验证码
|
smsSendService.sendSingleSms(reqDTO.getMobile(), null, null,
|
sceneEnum.getTemplateCode(), MapUtil.of("code", code));
|
}
|
|
private String createSmsCode(String mobile, Integer scene, String ip) {
|
// 校验是否可以发送验证码,不用筛选场景
|
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null, null);
|
if (lastSmsCode != null) {
|
if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
|
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
|
throw exception(SMS_CODE_SEND_TOO_FAST);
|
}
|
if (isToday(lastSmsCode.getCreateTime()) && // 必须是今天,才能计算超过当天的上限
|
lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
|
throw exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
|
}
|
// TODO iailab:提升,每个 IP 每天可发送数量
|
// TODO iailab:提升,每个 IP 每小时可发送数量
|
}
|
|
// 创建验证码记录
|
String code = String.format("%0" + smsCodeProperties.getEndCode().toString().length() + "d",
|
randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
|
SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code).scene(scene)
|
.todayIndex(lastSmsCode != null && isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1)
|
.createIp(ip).used(false).build();
|
smsCodeMapper.insert(newSmsCode);
|
return code;
|
}
|
|
@Override
|
public void useSmsCode(SmsCodeUseReqDTO reqDTO) {
|
// 检测验证码是否有效
|
SmsCodeDO lastSmsCode = validateSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
|
// 使用验证码
|
smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId())
|
.used(true).usedTime(LocalDateTime.now()).usedIp(reqDTO.getUsedIp()).build());
|
}
|
|
@Override
|
public void validateSmsCode(SmsCodeValidateReqDTO reqDTO) {
|
validateSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
|
}
|
|
private SmsCodeDO validateSmsCode0(String mobile, String code, Integer scene) {
|
// 校验验证码
|
SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, code, scene);
|
// 若验证码不存在,抛出异常
|
if (lastSmsCode == null) {
|
throw exception(SMS_CODE_NOT_FOUND);
|
}
|
// 超过时间
|
if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
|
>= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
|
throw exception(SMS_CODE_EXPIRED);
|
}
|
// 判断验证码是否已被使用
|
if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {
|
throw exception(SMS_CODE_USED);
|
}
|
return lastSmsCode;
|
}
|
|
}
|