阿里云短信发送服务
公众号开发学习记录,方便后续查阅 springboot后端
具体步骤
1.登录阿里云,开通短信服务,申请短信签名和短信模板(需要在项目代码中配置短信签名ID和模板ID)
个人账号一般只能申请用于测试的短信签名和模板,绑定5个测试手机号,要向任何人发送需要有企业认证
2.生成账号的AccessKey和AccessSecret(一般存到数据库表中,从数据库表中select,密钥最好不要放在代码中,防止上传到公共平台被恶意使用)
3.根据阿里云操作提示,导入相关依赖
<!--阿里云短信服务依赖-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.3.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibabacloud-dysmsapi20170525</artifactId>
<version>2.0.24</version>
</dependency>
4.复制阿里云官方demo进行改造
package demo;
import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import com.aliyun.core.http.HttpClient;
import com.aliyun.core.http.HttpMethod;
import com.aliyun.core.http.ProxyOptions;
import com.aliyun.httpcomponent.httpclient.ApacheAsyncHttpClientBuilder;
import com.aliyun.sdk.service.dysmsapi20170525.models.*;
import com.aliyun.sdk.service.dysmsapi20170525.*;
import com.google.gson.Gson;
import darabonba.core.RequestConfiguration;
import darabonba.core.client.ClientOverrideConfiguration;
import darabonba.core.utils.CommonUtil;
import darabonba.core.TeaPair;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;
public class SendSms {
public static void main(String[] args) throws Exception {
StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
.accessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"))
.accessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"))
.build());
// Configure the Client
AsyncClient client = AsyncClient.builder()
.region("cn-hangzhou") // Region ID
.credentialsProvider(provider)
.overrideConfiguration(
ClientOverrideConfiguration.create()
.setEndpointOverride("dysmsapi.aliyuncs.com")
)
.build();
// Parameter settings for API request
SendSmsRequest sendSmsRequest = SendSmsRequest.builder()
.build();
CompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);
SendSmsResponse resp = response.get();
System.out.println(new Gson().toJson(resp));
client.close();
}
}
结合具体业务逻辑进行改造
改造后的Service层
/**
* 短信发送服务类,包含短信发送验证码及验证码校验
*/
@Service
public class SendSmsImpl implements SendMsmService {
/**
* redis相关
*/
@Resource
RedisTemplate<String,String> redisTemplate;
/**
* 用于获取平台配置信息
*/
@Resource
PlatformConfigMapper platformConfigMapper;
/**
* 根据传入的短信验证码、手机号 调用阿里云短信服务的发送接口 完成信息发送
* 并返回短信发送结果(成功 or 失败)
*/
@Override
public boolean send(Map<String, Object> param, String phone) {
//手机号为空,返回发送短信失败的结果
if (Objects.isNull(phone)) {
return false;
}
// 从数据库中获取 阿里云短信发送接口的调用凭证 ACCESS_KEY_ID和ACCESS_KEY_SECRET
StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
.accessKeyId(platformConfigMapper.getByKey(AliConfigConstants.ACCESS_KEY_ID))
.accessKeySecret(platformConfigMapper.getByKey(AliConfigConstants.ACCESS_KEY_SECRET)).build());
// 配置客户端
AsyncClient client = AsyncClient.builder()
.region("cn-shenzhen") // Region ID
.credentialsProvider(provider)
.overrideConfiguration(
ClientOverrideConfiguration.create()
.setEndpointOverride("dysmsapi.aliyuncs.com"))
.build();
// API请求的参数设置
SendSmsRequest sendSmsRequest = SendSmsRequest.builder()
.phoneNumbers(phone)
.signName(SmsParam.SIG_NAME)
.templateCode(SmsParam.TEMPLATE_CODE)
.templateParam(JSONObject.toJSONString(param))
.build();
try {//最终发送
CompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);
SendSmsResponse resp = response.get();
boolean sendSuccess = resp.getBody().getCode().equals("OK")?true:false;
System.out.println(new Gson().toJson(resp));
return sendSuccess;
} catch (Exception e) {
e.printStackTrace();
return false;
}finally {
client.close();
}
}
/**
* 验证码校验
*/
@Override
public boolean check(String phone, String code) {
if(!Objects.isNull(phone)){ //传入的手机号不为空
if(!Objects.isNull(code)){ //传入的验证码不为空
String _code = redisTemplate.opsForValue().get(phone); //从redis中取出验证码与传入的验证码进行比对
if(code.equals(_code)){
return true;
}
else {
return false;
}
}
}
return false;
}
}
Controller层
@RestController
@RequestMapping("/msm")
public class MsmSendController {
@Resource
RedisTemplate<String,String> redisTemplate;
@Resource
private SendMsmService sendMsmService;
@PostMapping("/send")
public R sendMsm(String phone){
// 根据传入的手机号到redis中查找是否有对应的验证码 即验证码是否已经发送且在有效期内
// 如果已经发送且在有效期内,则不再发送验证码,并将验证码存在Result中返回
String code = redisTemplate.opsForValue().get(phone);
if(!Objects.isNull(code)){
return RUtils.success(new HashMap<String,String>().put("验证码",code));
}
//如果验证码未发送或已过期,则发送验证码
code = RandomUtil.getFourBitRandom();
HashMap<String, Object> param = new HashMap<>();
param.put("code",code);
boolean isSend = sendMsmService.send(param,phone);
//发送成功,将验证码和手机号存入redis中
if(isSend){
redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);
return RUtils.success();
}else {
return RUtils.Err(Renum.UNKNOWN_ERROR);
}
}
/**
* 登录
* */
@PostMapping("/login")
public R login(String phone,String code){
if(!Objects.isNull(phone)){ //传入的手机号不为空
if(!Objects.isNull(code)){ //传入的验证码不为空
String _code = redisTemplate.opsForValue().get(phone); //从redis中取出验证码与传入的验证码进行比对
if(code.equals(_code)){
return RUtils.success("登录成功");
}
else {
return RUtils.Err(Renum.VERIFICATION_CODE_FALSE);
}
}
}
return RUtils.Err(Renum.UNKNOWN_ERROR);
}
}
需要改进的地方:
1.作为公共组件供调用时,手机号验证码存入redis的代码不应该放在controller,而要放在service层,controller层要做到尽量不涉及业务逻辑(这块知识需要重新理解),这样调用者就无需进行设置redis缓存的逻辑
2.校验成功后需要把缓存删掉
3.后续考虑把发送记录存到数据库中,方便查看。