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; } }