SpringCloud 搭建企業(yè)級開發(fā)框架之實現(xiàn)多租戶多平臺短信通知服務(wù)(微服務(wù)實戰(zhàn))
目前系統(tǒng)集成短信似乎是必不可少的部分,由于各種云平臺都提供了不同的短信通道,這里我們增加多租戶多通道的短信驗證碼,并增加配置項,使系統(tǒng)可以支持多家云平臺提供的短信服務(wù)。這里以阿里云和騰訊云為例,集成短信通知服務(wù)。
1、在GitEgg-Platform中新建gitegg-platform-sms基礎(chǔ)工程,定義抽象方法和配置類
SmsSendService發(fā)送短信抽象接口:
/** * 短信發(fā)送接口 */ public interface SmsSendService { /** * 發(fā)送單個短信 * @param smsData * @param phoneNumber * @return */ default SmsResponse sendSms(SmsData smsData, String phoneNumber){ if (StrUtil.isEmpty(phoneNumber)) { return new SmsResponse(); } return this.sendSms(smsData, Collections.singletonList(phoneNumber)); } /** * 群發(fā)發(fā)送短信 * @param smsData * @param phoneNumbers * @return */ SmsResponse sendSms(SmsData smsData, Collection<string> phoneNumbers); }
SmsResultCodeEnum定義短信發(fā)送結(jié)果
/** * @ClassName: ResultCodeEnum * @Description: 自定義返回碼枚舉 * @author GitEgg * @date 2020年09月19日 下午11:49:45 */ @Getter @AllArgsConstructor public enum SmsResultCodeEnum { /** * 成功 */ SUCCESS(200, "操作成功"), /** * 系統(tǒng)繁忙,請稍后重試 */ ERROR(429, "短信發(fā)送失敗,請稍后重試"), /** * 系統(tǒng)錯誤 */ PHONE_NUMBER_ERROR(500, "手機號錯誤"); public int code; public String msg; }
2、新建gitegg-platform-sms-aliyun工程,實現(xiàn)阿里云短信發(fā)送接口
AliyunSmsProperties配置類
@Data @Component @ConfigurationProperties(prefix = "sms.aliyun") public class AliyunSmsProperties { /** * product */ private String product = "Dysmsapi"; /** * domain */ private String domain = "dysmsapi.aliyuncs.com"; /** * regionId */ private String regionId = "cn-hangzhou"; /** * accessKeyId */ private String accessKeyId; /** * accessKeySecret */ private String accessKeySecret; /** * 短信簽名 */ private String signName; }
AliyunSmsSendServiceImpl阿里云短信發(fā)送接口實現(xiàn)類
/** * 阿里云短信發(fā)送 */ @Slf4j @AllArgsConstructor public class AliyunSmsSendServiceImpl implements SmsSendService { private static final String successCode = "OK"; private final AliyunSmsProperties properties; private final IAcsClient acsClient; @Override public SmsResponse sendSms(SmsData smsData, Collection<string> phoneNumbers) { SmsResponse smsResponse = new SmsResponse(); SendSmsRequest request = new SendSmsRequest(); request.setSysMethod(MethodType.POST); request.setPhoneNumbers(StrUtil.join(",", phoneNumbers)); request.setSignName(properties.getSignName()); request.setTemplateCode(smsData.getTemplateId()); request.setTemplateParam(JsonUtils.mapToJson(smsData.getParams())); try { SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request); if (null != sendSmsResponse && !StringUtils.isEmpty(sendSmsResponse.getCode())) { if (this.successCode.equals(sendSmsResponse.getCode())) { smsResponse.setSuccess(true); } else { log.error("Send Aliyun Sms Fail: [code={}, message={}]", sendSmsResponse.getCode(), sendSmsResponse.getMessage()); } smsResponse.setCode(sendSmsResponse.getCode()); smsResponse.setMessage(sendSmsResponse.getMessage()); } } catch (Exception e) { e.printStackTrace(); log.error("Send Aliyun Sms Fail: {}", e); smsResponse.setMessage("Send Aliyun Sms Fail!"); } return smsResponse; } }
3、新建gitegg-platform-sms-tencent工程,實現(xiàn)騰訊云短信發(fā)送接口
TencentSmsProperties配置類
@Data @Component @ConfigurationProperties(prefix = "sms.tencent") public class TencentSmsProperties { /* 填充請求參數(shù),這里 request 對象的成員變量即對應(yīng)接口的入?yún)? * 您可以通過官網(wǎng)接口文檔或跳轉(zhuǎn)到 request 對象的定義處查看請求參數(shù)的定義 * 基本類型的設(shè)置: * 幫助鏈接: * 短信控制臺:https://console.cloud.tencent.com/smsv2 * sms helper:https://cloud.tencent.com/document/product/382/3773 */ /* 短信應(yīng)用 ID: 在 [短信控制臺] 添加應(yīng)用后生成的實際 SDKAppID,例如1400006666 */ private String SmsSdkAppId; /* 國際/港澳臺短信 senderid: 國內(nèi)短信填空,默認未開通,如需開通請聯(lián)系 [sms helper] */ private String senderId; /* 短信碼號擴展號: 默認未開通,如需開通請聯(lián)系 [sms helper] */ private String extendCode; /** * 短信簽名 */ private String signName; }
TencentSmsSendServiceImpl騰訊云短信發(fā)送接口實現(xiàn)類
/** * 騰訊云短信發(fā)送 */ @Slf4j @AllArgsConstructor public class TencentSmsSendServiceImpl implements SmsSendService { private static final String successCode = "Ok"; private final TencentSmsProperties properties; private final SmsClient client; @Override public SmsResponse sendSms(SmsData smsData, Collection<string> phoneNumbers) { SmsResponse smsResponse = new SmsResponse(); SendSmsRequest request = new SendSmsRequest(); request.setSmsSdkAppid(properties.getSmsSdkAppId()); /* 短信簽名內(nèi)容: 使用 UTF-8 編碼,必須填寫已審核通過的簽名,可登錄 [短信控制臺] 查看簽名信息 */ request.setSign(properties.getSignName()); /* 國際/港澳臺短信 senderid: 國內(nèi)短信填空,默認未開通,如需開通請聯(lián)系 [sms helper] */ if (!StringUtils.isEmpty(properties.getSenderId())) { request.setSenderId(properties.getSenderId()); } request.setTemplateID(smsData.getTemplateId()); /* 下發(fā)手機號碼,采用 e.164 標準,+[國家或地區(qū)碼][手機號] * 例如+8613711112222, 其中前面有一個+號 ,86為國家碼,13711112222為手機號,最多不要超過200個手機號*/ String[] phoneNumbersArray = (String[]) phoneNumbers.toArray(); request.setPhoneNumberSet(phoneNumbersArray); /* 模板參數(shù): 若無模板參數(shù),則設(shè)置為空*/ String[] templateParams = new String[]{}; if (!CollectionUtils.isEmpty(smsData.getParams())) { templateParams = (String[]) smsData.getParams().values().toArray(); } request.setTemplateParamSet(templateParams); try { /* 通過 client 對象調(diào)用 SendSms 方法發(fā)起請求。注意請求方法名與請求對象是對應(yīng)的 * 返回的 res 是一個 SendSmsResponse 類的實例,與請求對象對應(yīng) */ SendSmsResponse sendSmsResponse = client.SendSms(request); //如果是批量發(fā)送,那么騰訊云短信會返回每條短信的發(fā)送狀態(tài),這里默認返回第一條短信的狀態(tài) if (null != sendSmsResponse && null != sendSmsResponse.getSendStatusSet()) { SendStatus sendStatus = sendSmsResponse.getSendStatusSet()[0]; if (this.successCode.equals(sendStatus.getCode())) { smsResponse.setSuccess(true); } else { smsResponse.setCode(sendStatus.getCode()); smsResponse.setMessage(sendStatus.getMessage()); } } } catch (Exception e) { e.printStackTrace(); log.error("Send Aliyun Sms Fail: {}", e); smsResponse.setMessage("Send Aliyun Sms Fail!"); } return smsResponse; } }
4、在GitEgg-Cloud中新建業(yè)務(wù)調(diào)用方法,這里要考慮到不同租戶調(diào)用不同的短信配置進行短信發(fā)送,所以新建SmsFactory短信接口實例化工廠,根據(jù)不同的租戶實例化不同的短信發(fā)送接口,這里以實例化com.gitegg.service.extension.sms.factory.SmsAliyunFactory類為例,進行實例化操作,實際使用中,這里需要配置和租戶的對應(yīng)關(guān)系,從租戶的短信配置中獲取。
@Component public class SmsFactory { private final ISmsTemplateService smsTemplateService; /** * SmsSendService 緩存 */ private final Map<long, smssendservice=""> SmsSendServiceMap = new ConcurrentHashMap<>(); public SmsFactory(ISmsTemplateService smsTemplateService) { this.smsTemplateService = smsTemplateService; } /** * 獲取 SmsSendService * * @param smsTemplateDTO 短信模板 * @return SmsSendService */ public SmsSendService getSmsSendService(SmsTemplateDTO smsTemplateDTO) { //根據(jù)channelId獲取對應(yīng)的發(fā)送短信服務(wù)接口,channelId是唯一的,每個租戶有其自有的channelId Long channelId = smsTemplateDTO.getChannelId(); SmsSendService smsSendService = SmsSendServiceMap.get(channelId); if (null == smsSendService) { Class cls = null; try { cls = Class.forName("com.gitegg.service.extension.sms.factory.SmsAliyunFactory"); Method staticMethod = cls.getDeclaredMethod("getSmsSendService", SmsTemplateDTO.class); smsSendService = (SmsSendService) staticMethod.invoke(cls,smsTemplateDTO); SmsSendServiceMap.put(channelId, smsSendService); } catch (ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } return smsSendService; } }
/** * 阿里云短信服務(wù)接口工廠類 */ public class SmsAliyunFactory { public static SmsSendService getSmsSendService(SmsTemplateDTO sms) { AliyunSmsProperties aliyunSmsProperties = new AliyunSmsProperties(); aliyunSmsProperties.setAccessKeyId(sms.getSecretId()); aliyunSmsProperties.setAccessKeySecret(sms.getSecretKey()); aliyunSmsProperties.setRegionId(sms.getRegionId()); aliyunSmsProperties.setSignName(sms.getSignName()); IClientProfile profile = DefaultProfile.getProfile(aliyunSmsProperties.getRegionId(), aliyunSmsProperties.getAccessKeyId(), aliyunSmsProperties.getAccessKeySecret()); IAcsClient acsClient = new DefaultAcsClient(profile); return new AliyunSmsSendServiceImpl(aliyunSmsProperties, acsClient); } }
/** * 騰訊云短信服務(wù)接口工廠類 */ public class SmsTencentFactory { public static SmsSendService getSmsSendService(SmsTemplateDTO sms) { TencentSmsProperties tencentSmsProperties = new TencentSmsProperties(); tencentSmsProperties.setSmsSdkAppId(sms.getSecretId()); tencentSmsProperties.setExtendCode(sms.getSecretKey()); tencentSmsProperties.setSenderId(sms.getRegionId()); tencentSmsProperties.setSignName(sms.getSignName()); /* 必要步驟: * 實例化一個認證對象,入?yún)⑿枰獋魅腧v訊云賬戶密鑰對 secretId 和 secretKey * 本示例采用從環(huán)境變量讀取的方式,需要預(yù)先在環(huán)境變量中設(shè)置這兩個值 * 您也可以直接在代碼中寫入密鑰對,但需謹防泄露,不要將代碼復(fù)制、上傳或者分享給他人 * CAM 密鑰查詢:https://console.cloud.tencent.com/cam/capi */ Credential cred = new Credential(sms.getSecretId(), sms.getSecretKey()); // 實例化一個 http 選項,可選,無特殊需求時可以跳過 HttpProfile httpProfile = new HttpProfile(); // 設(shè)置代理 // httpProfile.setProxyHost("host"); // httpProfile.setProxyPort(port); /* SDK 默認使用 POST 方法。 * 如需使用 GET 方法,可以在此處設(shè)置,但 GET 方法無法處理較大的請求 */ httpProfile.setReqMethod("POST"); /* SDK 有默認的超時時間,非必要請不要進行調(diào)整 * 如有需要請在代碼中查閱以獲取最新的默認值 */ httpProfile.setConnTimeout(60); /* SDK 會自動指定域名,通常無需指定域名,但訪問金融區(qū)的服務(wù)時必須手動指定域名 * 例如 SMS 的上海金融區(qū)域名為 sms.ap-shanghai-fsi.tencentcloudapi.com */ if (!StringUtils.isEmpty(sms.getRegionId())) { httpProfile.setEndpoint(sms.getRegionId()); } /* 非必要步驟: * 實例化一個客戶端配置對象,可以指定超時時間等配置 */ ClientProfile clientProfile = new ClientProfile(); /* SDK 默認用 TC3-HMAC-SHA256 進行簽名 * 非必要請不要修改該字段 */ clientProfile.setSignMethod("HmacSHA256"); clientProfile.setHttpProfile(httpProfile); /* 實例化 SMS 的 client 對象 * 第二個參數(shù)是地域信息,可以直接填寫字符串 ap-guangzhou,或者引用預(yù)設(shè)的常量 */ SmsClient client = new SmsClient(cred, "",clientProfile); return new TencentSmsSendServiceImpl(tencentSmsProperties, client); } }
5、定義短信發(fā)送接口及實現(xiàn)類
ISmsService業(yè)務(wù)短信發(fā)送接口定義
/** * <p> * 短信發(fā)送接口定義 * </p> * * @author GitEgg * @since 2021-01-25 */ public interface ISmsService { /** * 發(fā)送短信 * * @param smsCode * @param smsData * @param phoneNumbers * @return */ SmsResponse sendSmsNormal(String smsCode, String smsData, String phoneNumbers); /** * 發(fā)送短信驗證碼 * * @param smsCode * @param phoneNumber * @return */ SmsResponse sendSmsVerificationCode( String smsCode, String phoneNumber); /** * 校驗短信驗證碼 * * @param smsCode * @param phoneNumber * @return */ boolean checkSmsVerificationCode(String smsCode, String phoneNumber, String verificationCode); }
SmsServiceImpl 短信發(fā)送接口實現(xiàn)類
/** * <p> * 短信發(fā)送接口實現(xiàn)類 * </p> * * @author GitEgg * @since 2021-01-25 */ @Slf4j @Service @RequiredArgsConstructor(onConstructor_ = @Autowired) public class SmsServiceImpl implements ISmsService { private final SmsFactory smsFactory; private final ISmsTemplateService smsTemplateService; private final RedisTemplate redisTemplate; @Override public SmsResponse sendSmsNormal(String smsCode, String smsData, String phoneNumbers) { SmsResponse smsResponse = new SmsResponse(); try { QuerySmsTemplateDTO querySmsTemplateDTO = new QuerySmsTemplateDTO(); querySmsTemplateDTO.setSmsCode(smsCode); //獲取短信code的相關(guān)信息,租戶信息會根據(jù)mybatis plus插件獲取 SmsTemplateDTO smsTemplateDTO = smsTemplateService.querySmsTemplate(querySmsTemplateDTO); ObjectMapper mapper = new ObjectMapper(); Map smsDataMap = mapper.readValue(smsData, Map.class); List<string> phoneNumberList = JsonUtils.jsonToList(phoneNumbers, String.class); SmsData smsDataParam = new SmsData(); smsDataParam.setTemplateId(smsTemplateDTO.getTemplateId()); smsDataParam.setParams(smsDataMap); SmsSendService smsSendService = smsFactory.getSmsSendService(smsTemplateDTO); smsResponse = smsSendService.sendSms(smsDataParam, phoneNumberList); } catch (Exception e) { smsResponse.setMessage("短信發(fā)送失敗"); e.printStackTrace(); } return smsResponse; } @Override public SmsResponse sendSmsVerificationCode(String smsCode, String phoneNumber) { String verificationCode = RandomUtil.randomNumbers(6); Map<string, string=""> smsDataMap = new HashMap<>(); smsDataMap.put(SmsConstant.SMS_CAPTCHA_TEMPLATE_CODE, verificationCode); List<string> phoneNumbers = Arrays.asList(phoneNumber); SmsResponse smsResponse = this.sendSmsNormal(smsCode, JsonUtils.mapToJson(smsDataMap), JsonUtils.listToJson(phoneNumbers)); if (null != smsResponse && smsResponse.isSuccess()) { // 將短信驗證碼存入redis并設(shè)置過期時間為5分鐘 redisTemplate.opsForValue().set(SmsConstant.SMS_CAPTCHA_KEY + smsCode + phoneNumber, verificationCode, 30, TimeUnit.MINUTES); } return smsResponse; } @Override public boolean checkSmsVerificationCode(String smsCode, String phoneNumber, String verificationCode) { String verificationCodeRedis = (String) redisTemplate.opsForValue().get(SmsConstant.SMS_CAPTCHA_KEY + smsCode + phoneNumber); if (!StrUtil.isAllEmpty(verificationCodeRedis, verificationCode) && verificationCode.equalsIgnoreCase(verificationCodeRedis)) { return true; } return false; } }
6、新建SmsFeign類,供其他微服務(wù)調(diào)用發(fā)送短信
/** * @ClassName: SmsFeign * @Description: SmsFeign前端控制器 * @author gitegg * @date 2019年5月18日 下午4:03:58 */ @RestController @RequestMapping(value = "/feign/sms") @RequiredArgsConstructor(onConstructor_ = @Autowired) @Api(value = "SmsFeign|提供微服務(wù)調(diào)用接口") @RefreshScope public class SmsFeign { private final ISmsService smsService; @GetMapping(value = "/send/normal") @ApiOperation(value = "發(fā)送普通短信", notes = "發(fā)送普通短信") Result<object> sendSmsNormal(@RequestParam("smsCode") String smsCode, @RequestParam("smsData") String smsData, @RequestParam("phoneNumbers") String phoneNumbers) { SmsResponse smsResponse = smsService.sendSmsNormal(smsCode, smsData, phoneNumbers); return Result.data(smsResponse); } @GetMapping(value = "/send/verification/code") @ApiOperation(value = "發(fā)送短信驗證碼", notes = "發(fā)送短信驗證碼") Result<object> sendSmsVerificationCode(@RequestParam("smsCode") String smsCode, @RequestParam("phoneNumber") String phoneNumber) { SmsResponse smsResponse = smsService.sendSmsVerificationCode(smsCode, phoneNumber); return Result.data(smsResponse); } @GetMapping(value = "/check/verification/code") @ApiOperation(value = "校驗短信驗證碼", notes = "校驗短信驗證碼") Result<boolean> checkSmsVerificationCode(@RequestParam("smsCode") String smsCode, @RequestParam("phoneNumber") String phoneNumber, @RequestParam("verificationCode") String verificationCode) { boolean checkResult = smsService.checkSmsVerificationCode(smsCode, phoneNumber, verificationCode); return Result.data(checkResult); } }
項目源碼:
Gitee: https://gitee.com/wmz1930/GitEgg
GitHub: https://github.com/wmz1930/GitEgg
到此這篇關(guān)于SpringCloud 搭建企業(yè)級開發(fā)框架之實現(xiàn)多租戶多平臺短信通知服務(wù)的文章就介紹到這了,更多相關(guān)SpringCloud 短信通知服務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot整合Mybatis傳值的常用方式總結(jié)
今天給大家?guī)淼氖顷P(guān)于Springboot的相關(guān)知識,文章圍繞著Springboot整合Mybatis傳值的常用方式展開,文中有非常詳細的介紹及代碼示例,需要的朋友可以參考下2021-06-06Spring?Boot?教程之創(chuàng)建項目的三種方式
這篇文章主要分享了Spring?Boot?教程之創(chuàng)建項目的三種方式,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-05-05springboot+mybatis如何屏蔽掉mybatis日志
這篇文章主要介紹了springboot+mybatis如何屏蔽掉mybatis日志問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05Java 多線程synchronized關(guān)鍵字詳解(六)
這篇文章主要介紹了Java 多線程synchronized關(guān)鍵字詳解(六)的相關(guān)資料,需要的朋友可以參考下2015-12-12