起因:
短信發送的應用場景非常多,在較大的系統中,短信一般作爲單獨的服務獨立運行,
而短信發送任務的觸發基本有兩種方式。1、定時獲取Redis中短信發送任務,2、消息中間件訂閱短息任務隊列。
RabbitMQ訂閱短信:
短信服務:
短信服務爲獨立工程。將阿里雲 SmsServer抽取爲工具類。
1、pom依賴:
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- 阿里sms短信服務 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>${aliyun-java-sdk-core.version}</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>${aliyun-java-sdk-dysmsapi.version}</version>
</dependency>
<!-- 阿里sms短信服務 -->
<!-- 極光推送 -->
<dependency>
<groupId>cn.jpush.api</groupId>
<artifactId>jpush-client</artifactId>
<version>3.2.17</version>
</dependency>
<dependency>
<groupId>cn.jpush.api</groupId>
<artifactId>jiguang-common</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
<scope>compile</scope>
</dependency>
<!-- 極光推送 -->
2、啓動類:
@SpringBootApplication
@EnableAsync
public class SmsServerApplication {
public static void main(String[] args) {
SpringApplication.run(SmsServerApplication.class, args);
}
}
3、SmsQueueConfig類
@Configuration
public class SmsQueueConfig {
/**
* 消息隊列
* @return
*/
@Bean
public Queue smsQueue(){
return new Queue("sms");
}
}
4、JGuangConfig類
@Configuration
public class JGuangConfig {
@Value("${jpush.master.jpushAppKey}")
private String jpushAppKey;
@Value("${jpush.master.secret}")
private String jpushMasterSecret;
@Bean
public JPushClient getJPushClient() {
return new JPushClient(jpushMasterSecret, jpushAppKey);
}
}
5、極光推送服務
@Log4j
@Service
public class JpushService {
@Autowired
private JPushClient jPushClient;
/**
* 給手機推送異常提醒
*/
@Async
public void sendPush() {
sendRemind("registrationId", "驗證碼");
}
/**
* 通過極光的註冊編號推送
* @param registrationId
* @param title
*/
private void sendRemind(String registrationId, String title) {
Builder builder = PushPayload.newBuilder();
builder.setPlatform(Platform.all());
builder.setAudience(Audience.registrationId(registrationId));
builder.setNotification(Notification.newBuilder().setAlert(title).build());
builder.setOptions(Options.newBuilder().setApnsProduction(true).build());
try {
PushResult pushResult = jPushClient.sendPush(builder.build());
log.info("推送成功,misId:("+ pushResult.msg_id +")");
} catch (APIConnectionException | APIRequestException e) {
e.printStackTrace();
log.info("推送失敗~~~,錯誤原因:"+ e);
}
}
}
6、ALiYunSmsUtil類
public class ALiYunSmsUtil {
public static BaseVo<Void> sendPhoneCode(String phone, String code) {
IAcsClient acsClient = getIAcsClient();
// 組裝請求對象
SendSmsRequest smsRequest = new SendSmsRequest();
//使用post提交
smsRequest.setMethod(MethodType.POST);
//必填:待發送手機號。支持以逗號分隔的形式進行批量調用,批量上限爲1000個手機號碼,批量調用相對於單條調用及時性稍有延遲,驗證碼類型的短信推薦使用單條調用的方式;發送國際/港澳臺消息時,接收號碼格式爲國際區號+號碼,如“85200000000”
smsRequest.setPhoneNumbers(phone);
//必填:短信簽名-可在短信控制檯中找到
smsRequest.setSignName("阿里雲");
//必填:短信模板-可在短信控制檯中找到,發送國際/港澳臺消息時,請使用國際/港澳臺短信模版
smsRequest.setTemplateCode("SMS_1500000000");
//可選:模板中的變量替換JSON串,如模板內容爲"親愛的${name},您的驗證碼爲${code}"時,此處的值爲
//友情提示:如果JSON中需要帶換行符,請參照標準的JSON協議對換行符的要求,比如短信內容中包含\r\n的情況在JSON中需要表示成\\r\\n,否則會導致JSON在服務端解析失敗
smsRequest.setTemplateParam("{\"code\":\""+ code +"\"}");
//可選-上行短信擴展碼(擴展碼字段控制在7位或以下,無特殊需求用戶請忽略此字段)
//request.setSmsUpExtendCode("90997");
//可選:outId爲提供給業務方擴展字段,最終在短信回執消息中將此值帶回給調用者
//smsRequest.setOutId("");
BaseVo<Void> baseVo = new BaseVo<>();
try {
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(smsRequest);
if (sendSmsResponse == null) {
baseVo.setCode(2);
baseVo.setMessage("sendSmsResponse爲空,驗證碼發送失敗");
} else if (!StringUtils.isEmpty(sendSmsResponse.getCode()) && sendSmsResponse.getCode().equals("OK")) {
baseVo.setCode(1);
baseVo.setMessage("驗證碼發送成功!");
} else {
baseVo.setCode(2);
baseVo.setMessage(StringUtils.isEmpty(sendSmsResponse.getMessage()) ? "請稍後重試" : sendSmsResponse.getMessage());
}
} catch (ClientException e) {
e.printStackTrace();
baseVo.setCode(2);
baseVo.setMessage("調用阿里雲sms發生錯誤");
}
return baseVo;
}
private static IAcsClient getIAcsClient() {
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
//初始化ascClient需要的幾個參數
final String product = "Dysmsapi";//短信API產品名稱(短信產品名固定,無需修改)
final String domain = "dysmsapi.aliyuncs.com";//短信API產品域名(接口地址固定,無需修改)
//替換成你的AK
final String accessKeyId = "accessKeyId";//你的accessKeyId,參考本文檔步驟2
final String accessKeySecret = "accessKeySecret";//你的accessKeySecret,參考本文檔步驟2
//初始化ascClient,暫時不支持多region(請勿修改)
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId,
accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", product, domain);
return new DefaultAcsClient(profile);
}
}
7、mq監聽sms隊列:
@Log4j
@Component
@RabbitListener(queues="sms")
public class SmsListener {
@Autowired
private JpushService jpushService;
@RabbitHandler
public void sendSms(Map<String,String> map) {
log.info("發送手機號:" + map.get("phone"));
log.info("發送手機號:" + map.get("code"));
BaseVo<Void> baseVo = ALiYunSmsUtil.sendPhoneCode(map.get("phone"), map.get("code"));
if (baseVo.getCode() != 1) {
log.info("發送短信驗證碼失敗,失敗原因:" + baseVo.getMessage());
// 進行推送處理
jpushService.sendPush();
}
}
}
8、配置文件:
server.port=8013
#log日誌信息
logging.level.root=info
logging.path=../logs/info/SmsServer
#rabbit配置
spring.rabbitmq.host=192.168.41.26
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
#極光推送配置
jpush.master.secret=yourAccessKeySecret
jpush.master.jpushAppKey=yourAccessKeyId
用戶發送短信服務:
1、添加pom依賴
<!-- 集成redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
2、添加RabbitMQ配置:
# Redis數據庫索引(默認爲0)
spring.redis.database=0
# Redis服務器地址
spring.redis.host=192.168.41.26
# Redis服務器連接端口
spring.redis.port=6379
# Redis服務器連接密碼(默認爲空)
spring.redis.password=shuo456
# 連接超時時間(毫秒)
spring.redis.timeout=20000
#日誌信息
logging.level.root=info
logging.path=../logs/info/serverMonitor
#rabbit配置
spring.rabbitmq.host=192.168.41.26
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
3、ALiYunSmsService類
@Slf4j
@Service
public class ALiYunSmsService extends BaseServer{
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RedisService redisService;
// 緩存時間
private long cathTime = 60 * 3;
public void senSms(String phone){
if (StringUtils.isBlank(phone)) {
return;
}
// 1、生成4位短信驗證碼,阿帕奇提供的工具類
String code = RandomStringUtils.randomNumeric(4);
log.info(phone+ "收到的驗證碼是:" + code);
// 2、存入reids數據庫,當用戶輸入驗證碼後,後臺可進行校驗,有效時間爲 3分鐘
String redisKey = getKey(phone+ "_" +code);
redisService.setex(redisKey, code, cathTime);
// 3、將驗證碼和手機號發送到 rebbitMQ中
Map<String,String> map = new HashMap<>();
map.put("phone", phone);
map.put("code", code);
rabbitTemplate.convertAndSend("sms", map);
}
}
4、testController,測試接口
@RestController
public class TestController {
@Autowired
private ALiYunSmsService aLiYunSmsService;
@RequestMapping("/getUser")
public String getUser(int i){
int j = 1/i;
System.out.println(j);
aLiYunSmsService.senSms("1300000000");
return "success";
}
}
測試:
1、啓動RabbitMQ、短信服務、用戶服務。
2、測試接口
優點
1、mq爲實時監聽訂閱,有任務就立即觸發發送短息。
2、緩解服務器壓力
3、系統解耦合
4、異步調用
5、流量削峯
參考資料:cp026la寫的不錯