阿里云短信发送(工厂模式实现)
文章目录
- 一、业务场景
- 1.1 验证码登录
- 1.2 手机号注册
- 1.3 忘记密码
- 1.4 其他场景.....
- 二. 准备工作
- 1.注册阿里云账号
- 2.阿里云申请签名
- 3.阿里云短信模版
- 4.阿里云短信控制台测试
- 三、功能规划
- 1. 工厂模式根据不同业务进行校验,提高代码的可扩展性
- 2.不同业务类型使用不同的缓存key
- 四、业务实现
- 1.引入jar
- 2.yam配置信息及对应配置类
- 3. 业务工厂: 获取对应业务模版
- 4. 发送短信模版:用来发送短信
- 5. 业务模版:用来校验
- 5.1 登录
- 5.2 注册
- 5.3 忘记密码
- 6.短信缓存redis
- 7. 请求入口:
- 8. 参数:SmsReqAo
提示:以下是本篇文章正文内容,下面案例可供参考
一、业务场景
每种业务场景对手机号校验方式可能都不相同
1.1 验证码登录
校验:手机号在系统是否存在,存在则发送短信
1.2 手机号注册
校验:手机号在系统是否已经注册,未注册则发送短信
1.3 忘记密码
校验:手机号在系统是否存在,存在则发送短信
1.4 其他场景…
二. 准备工作
1.注册阿里云账号
2.阿里云申请签名
3.阿里云短信模版
4.阿里云短信控制台测试
三、功能规划
1. 工厂模式根据不同业务进行校验,提高代码的可扩展性
2.不同业务类型使用不同的缓存key
四、业务实现
1.引入jar
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>3.1.0</version>
</dependency>
2.yam配置信息及对应配置类
@Configuration
@ConfigurationProperties(prefix = "aliyun.sms")
@Data
public class AliyunSmsConfig {
// 阿里云短信服务accessKeyId
private String accessKeyId;
// 阿里云短信服务accessKeySecret
private String accessKeySecret;
// 短信签名
private String signName;
// 短信发送地区
private String regionId;
// 短信模板
private Map<String, String> templates;
public String getTemplateCode(String key) {
String templateCode = templates.get(key);
ArgumentAssert.notBlank(templateCode, "短信模板不存在");
return templateCode;
}
}
3. 业务工厂: 获取对应业务模版
@Service
@AllArgsConstructor
@Slf4j
public class SmsTemplateFactory {
private final LoginSmsTemplate loginSmsTemplate;
private final RegisterSmsTemplate registerSmsTemplate;
private final ForgotPasswordSmsTemplate forgotPasswordSmsTemplate;
/**
* 根据场景类型获取对应的模板
*
* @param templateKey 场景类型(login/register/forgotPassword)
* @return 对应的模板实现类
*/
public SmsTemplate getTemplate(String templateKey) {
return switch (templateKey) {
case "login" -> loginSmsTemplate;
case "register" -> registerSmsTemplate;
case "forgotPassword" -> forgotPasswordSmsTemplate;
default -> throw new IllegalArgumentException("未知的场景类型:" + templateKey);
};
}
}
4. 发送短信模版:用来发送短信
@Slf4j
public abstract class SmsTemplate {
protected abstract void checkBiz(String phoneNumber);
/**
* 发送短信验证码
*
* @param templateKey 模板key
* @param phoneNumber 手机号
* @return 是否发送成功
*/
public boolean sendSms(String templateKey, String phoneNumber) {
checkBiz(phoneNumber);
SmsCodeCache smsCodeCache = SpringUtils.getBean(SmsCodeCache.class);
// 检查发送频率(60秒内只能发送一次)
if (!smsCodeCache.checkRateLimit(phoneNumber, 60)) {
throw new RuntimeException("发送频率过高,请稍后再试");
}
String code = this.generateCode(); // 生成验证码
boolean isSuccess = this.sendSmsToAliyun(templateKey, phoneNumber, code); // 调用阿里云发送短信
if (isSuccess) {
// 缓存验证码,有效期5分钟
smsCodeCache.cacheCode(templateKey, phoneNumber, code, 5);
}
return isSuccess;
}
/**
* 调用阿里云短信服务发送短信
*
* @param templateKey 模板key
* @param phoneNumber 手机号
* @param code 验证码
* @return 是否发送成功
*/
private boolean sendSmsToAliyun(String templateKey, String phoneNumber, String code) {
AliyunSmsConfig smsConfig = SpringUtils.getBean(AliyunSmsConfig.class);
String templateCode = smsConfig.getTemplateCode(templateKey);
try {
// 初始化客户端
Config config = new Config()
.setAccessKeyId(smsConfig.getAccessKeyId())
.setAccessKeySecret(smsConfig.getAccessKeySecret())
.setRegionId(smsConfig.getRegionId());
Client client = new Client(config);
// 构建请求
SendSmsRequest request = new SendSmsRequest()
.setPhoneNumbers(phoneNumber)
.setSignName(smsConfig.getSignName())
.setTemplateCode(templateCode)
.setTemplateParam("{\"code\":\"" + code + "\"}");
// 发送短信
SendSmsResponse response = client.sendSms(request);
return "OK".equals(response.getBody().getCode());
} catch (Exception e) {
log.error("发送短信验证码失败,模版:{},手机号:{},验证码:{},错误:{}", templateKey, phoneNumber, code, e.getMessage());
return false;
}
}
/**
* 生成6位随机验证码
*
* @return 验证码
*/
private String generateCode() {
Random random = new Random();
return String.format("%06d", random.nextInt(1000000));
}
}
5. 业务模版:用来校验
5.1 登录
@Component
@AllArgsConstructor
public class LoginSmsTemplate extends SmsTemplate {
private final BaseStaffService baseStaffService;
@Override
public void checkBiz(String phoneNumber) {
BaseStaff staff = baseStaffService.getStaffByTelephone(phoneNumber);
ArgumentAssert.notNull(staff, "该手机号不存在");
}
}
5.2 注册
@Component
@AllArgsConstructor
public class RegisterSmsTemplate extends SmsTemplate {
private final BaseStaffService baseStaffService;
@Override
public void checkBiz(String phoneNumber) {
BaseStaff staff = baseStaffService.getStaffByTelephone(phoneNumber);
ArgumentAssert.isNull(staff, "该手机号已注册");
}
}
5.3 忘记密码
@Component
@AllArgsConstructor
public class ForgotPasswordSmsTemplate extends SmsTemplate {
private final BaseStaffService baseStaffService;
@Override
public void checkBiz(String phoneNumber) {
BaseStaff staff = baseStaffService.getStaffByTelephone(phoneNumber);
ArgumentAssert.notNull(staff, "该手机号不存在");
}
}
6.短信缓存redis
@Component
@AllArgsConstructor
public class SmsCodeCache {
private final RedisTemplate<String, String> redisTemplate;
// 验证码缓存前缀
private static final String CODE_PREFIX = "sms:code:";
// 发送频率限制前缀
private static final String RATE_LIMIT_PREFIX = "sms:rate:";
/**
* 缓存验证码
*
* @param templateKey 模板key
* @param phoneNumber 手机号
* @param code 验证码
* @param expireTime 过期时间(分钟)
*/
public void cacheCode(String templateKey, String phoneNumber, String code, long expireTime) {
String key = CODE_PREFIX + templateKey + StrPool.COLON + phoneNumber;
redisTemplate.opsForValue().set(key, code, expireTime, TimeUnit.MINUTES);
}
/**
* 获取缓存的验证码
*
* @param templateKey 模板key
* @param phoneNumber 手机号
* @return 验证码
*/
public String getCachedCode(String templateKey, String phoneNumber) {
String key = CODE_PREFIX + templateKey + StrPool.COLON + phoneNumber;
return redisTemplate.opsForValue().get(key);
}
/**
* 删除缓存的验证码
*
* @param templateKey 模板key
* @param phoneNumber 手机号
*/
public void deleteCode(String templateKey, String phoneNumber) {
String key = CODE_PREFIX + templateKey + StrPool.COLON + phoneNumber;
redisTemplate.delete(key);
}
/**
* 检查发送频率
*
* @param phoneNumber 手机号
* @param interval 间隔时间(秒)
* @return 是否可以发送
*/
public boolean checkRateLimit(String phoneNumber, long interval) {
String key = RATE_LIMIT_PREFIX + phoneNumber;
Long lastSentTime = redisTemplate.opsForValue().getOperations().getExpire(key);
if (lastSentTime != null && lastSentTime > 0) {
return false; // 还在限制时间内
}
redisTemplate.opsForValue().set(key, "1", interval, TimeUnit.SECONDS);
return true;
}
}
7. 请求入口:
@PostMapping("getSmsCode")
@ApiOperation(value = "获取短信验证码")
public DataResponse<Boolean> getSmsCode(@RequestBody @Validated SmsReqAo ao) {
String templateKey = ao.getTemplateKey();
return DataResponse.builderSuccess(smsTemplateFactory.getTemplate(templateKey).sendSms(templateKey, ao.getTelephone()));
}
8. 参数:SmsReqAo
@Data
@ApiModel("短信求入参")
public class SmsReqAo implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank(message = "模版KEY不能为空")
@ApiModelProperty(value = "模版KEY", required = true)
String templateKey;
/**
* 手机号
*/
@ApiModelProperty(value = "手机号", required = true)
@NotBlank(message = "手机号不能为为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String telephone;
}