houzhongjian
2024-10-16 7da8f196dee8e3c526c009a4bc7f5983ece6bb97
提交 | 用户 | 时间
e7c126 1 package com.iailab.module.system.service.sms;
H 2
3 import cn.hutool.core.date.LocalDateTimeUtil;
4 import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.map.MapUtil;
6 import com.iailab.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
7 import com.iailab.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
8 import com.iailab.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
9 import com.iailab.module.system.dal.dataobject.sms.SmsCodeDO;
10 import com.iailab.module.system.dal.mysql.sms.SmsCodeMapper;
11 import com.iailab.module.system.enums.sms.SmsSceneEnum;
12 import com.iailab.module.system.framework.sms.config.SmsCodeProperties;
13 import org.springframework.stereotype.Service;
14 import org.springframework.validation.annotation.Validated;
15
16 import javax.annotation.Resource;
17 import java.time.LocalDateTime;
18
19 import static cn.hutool.core.util.RandomUtil.randomInt;
20 import static com.iailab.framework.common.exception.util.ServiceExceptionUtil.exception;
21 import static com.iailab.framework.common.util.date.DateUtils.isToday;
22 import static com.iailab.module.system.enums.ErrorCodeConstants.*;
23
24 /**
25  * 短信验证码 Service 实现类
26  *
27  * @author iailab
28  */
29 @Service
30 @Validated
31 public class SmsCodeServiceImpl implements SmsCodeService {
32
33     @Resource
34     private SmsCodeProperties smsCodeProperties;
35
36     @Resource
37     private SmsCodeMapper smsCodeMapper;
38
39     @Resource
40     private SmsSendService smsSendService;
41
42     @Override
43     public void sendSmsCode(SmsCodeSendReqDTO reqDTO) {
44         SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());
45         Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
46         // 创建验证码
47         String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());
48         // 发送验证码
49         smsSendService.sendSingleSms(reqDTO.getMobile(), null, null,
50                 sceneEnum.getTemplateCode(), MapUtil.of("code", code));
51     }
52
53     private String createSmsCode(String mobile, Integer scene, String ip) {
54         // 校验是否可以发送验证码,不用筛选场景
55         SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null, null);
56         if (lastSmsCode != null) {
57             if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
58                     < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
59                 throw exception(SMS_CODE_SEND_TOO_FAST);
60             }
61             if (isToday(lastSmsCode.getCreateTime()) && // 必须是今天,才能计算超过当天的上限
62                     lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
63                 throw exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
64             }
65             // TODO iailab:提升,每个 IP 每天可发送数量
66             // TODO iailab:提升,每个 IP 每小时可发送数量
67         }
68
69         // 创建验证码记录
70         String code = String.format("%0" + smsCodeProperties.getEndCode().toString().length() + "d",
71                 randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
72         SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code).scene(scene)
73                 .todayIndex(lastSmsCode != null && isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1)
74                 .createIp(ip).used(false).build();
75         smsCodeMapper.insert(newSmsCode);
76         return code;
77     }
78
79     @Override
80     public void useSmsCode(SmsCodeUseReqDTO reqDTO) {
81         // 检测验证码是否有效
82         SmsCodeDO lastSmsCode = validateSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
83         // 使用验证码
84         smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId())
85                 .used(true).usedTime(LocalDateTime.now()).usedIp(reqDTO.getUsedIp()).build());
86     }
87
88     @Override
89     public void validateSmsCode(SmsCodeValidateReqDTO reqDTO) {
90         validateSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());
91     }
92
93     private SmsCodeDO validateSmsCode0(String mobile, String code, Integer scene) {
94         // 校验验证码
95         SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, code, scene);
96         // 若验证码不存在,抛出异常
97         if (lastSmsCode == null) {
98             throw exception(SMS_CODE_NOT_FOUND);
99         }
100         // 超过时间
101         if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
102                 >= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
103             throw exception(SMS_CODE_EXPIRED);
104         }
105         // 判断验证码是否已被使用
106         if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {
107             throw exception(SMS_CODE_USED);
108         }
109         return lastSmsCode;
110     }
111
112 }