文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

SpringCloud搭建企业级开发框架之实现多租户多平台短信通知服务(微服务实战)

2024-04-02 19:55

关注

目前系统集成短信似乎是必不可少的部分,由于各种云平台都提供了不同的短信通道,这里我们增加多租户多通道的短信验证码,并增加配置项,使系统可以支持多家云平台提供的短信服务。这里以阿里云和腾讯云为例,集成短信通知服务。

1、在GitEgg-Platform中新建gitegg-platform-sms基础工程,定义抽象方法和配置类
SmsSendService发送短信抽象接口:



public interface SmsSendService {

    
    default SmsResponse sendSms(SmsData smsData, String phoneNumber){
        if (StrUtil.isEmpty(phoneNumber)) {
            return new SmsResponse();
        }
        return this.sendSms(smsData, Collections.singletonList(phoneNumber));
    }

    
    SmsResponse sendSms(SmsData smsData, Collection<string> phoneNumbers);
    
}

SmsResultCodeEnum定义短信发送结果



@Getter
@AllArgsConstructor
public enum SmsResultCodeEnum {

    
    SUCCESS(200, "操作成功"),

    
    ERROR(429, "短信发送失败,请稍后重试"),

    
    PHONE_NUMBER_ERROR(500, "手机号错误");

    public int code;

    public String msg;
}

2、新建gitegg-platform-sms-aliyun工程,实现阿里云短信发送接口
AliyunSmsProperties配置类


@Data
@Component
@ConfigurationProperties(prefix = "sms.aliyun")
public class AliyunSmsProperties {

    
    private String product = "Dysmsapi";

    
    private String domain = "dysmsapi.aliyuncs.com";

    
    private String regionId = "cn-hangzhou";

    
    private String accessKeyId;

    
    private String accessKeySecret;

    
    private String signName;
}

AliyunSmsSendServiceImpl阿里云短信发送接口实现类



@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工程,实现腾讯云短信发送接口
TencentSmsProperties配置类


@Data
@Component
@ConfigurationProperties(prefix = "sms.tencent")
public class TencentSmsProperties {

    
    
    private String SmsSdkAppId;

    
    private String senderId;

    
    private String extendCode;

    
    private String signName;
}

TencentSmsSendServiceImpl腾讯云短信发送接口实现类



@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());
        
        request.setSign(properties.getSignName());
        
        if (!StringUtils.isEmpty(properties.getSenderId()))
        {
            request.setSenderId(properties.getSenderId());
        }
        request.setTemplateID(smsData.getTemplateId());
        
        String[] phoneNumbersArray = (String[]) phoneNumbers.toArray();
        request.setPhoneNumberSet(phoneNumbersArray);
        
        String[] templateParams = new String[]{};
        if (!CollectionUtils.isEmpty(smsData.getParams())) {
            templateParams = (String[]) smsData.getParams().values().toArray();
        }
        request.setTemplateParamSet(templateParams);
        try {
            
            SendSmsResponse sendSmsResponse = client.SendSms(request);
            //如果是批量发送,那么腾讯云短信会返回每条短信的发送状态,这里默认返回第一条短信的状态
            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中新建业务调用方法,这里要考虑到不同租户调用不同的短信配置进行短信发送,所以新建SmsFactory短信接口实例化工厂,根据不同的租户实例化不同的短信发送接口,这里以实例化com.gitegg.service.extension.sms.factory.SmsAliyunFactory类为例,进行实例化操作,实际使用中,这里需要配置和租户的对应关系,从租户的短信配置中获取。


@Component
public class SmsFactory {

    private final ISmsTemplateService smsTemplateService;

    
    private final Map<long, smssendservice=""> SmsSendServiceMap = new ConcurrentHashMap<>();

    public SmsFactory(ISmsTemplateService smsTemplateService) {
        this.smsTemplateService = smsTemplateService;
    }

    
    public SmsSendService getSmsSendService(SmsTemplateDTO smsTemplateDTO) {

        //根据channelId获取对应的发送短信服务接口,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;
    }
}


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);
    }

}


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());

        
        Credential cred = new Credential(sms.getSecretId(), sms.getSecretKey());
        // 实例化一个 http 选项,可选,无特殊需求时可以跳过
        HttpProfile httpProfile = new HttpProfile();
        // 设置代理
//        httpProfile.setProxyHost("host");
//        httpProfile.setProxyPort(port);
        
        httpProfile.setReqMethod("POST");
        
        httpProfile.setConnTimeout(60);
        
        if (!StringUtils.isEmpty(sms.getRegionId()))
        {
            httpProfile.setEndpoint(sms.getRegionId());
        }

        
        ClientProfile clientProfile = new ClientProfile();
        
        clientProfile.setSignMethod("HmacSHA256");
        clientProfile.setHttpProfile(httpProfile);
        
        SmsClient client = new SmsClient(cred, "",clientProfile);

        return new TencentSmsSendServiceImpl(tencentSmsProperties, client);
    }
}

5、定义短信发送接口及实现类
ISmsService业务短信发送接口定义



public interface ISmsService {


    
    SmsResponse sendSmsNormal(String smsCode, String smsData, String phoneNumbers);

    
    SmsResponse sendSmsVerificationCode( String smsCode, String phoneNumber);

    
    boolean checkSmsVerificationCode(String smsCode, String phoneNumber, String verificationCode);

}

SmsServiceImpl 短信发送接口实现类



@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的相关信息,租户信息会根据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("短信发送失败");
            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并设置过期时间为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类,供其他微服务调用发送短信



@RestController
@RequestMapping(value = "/feign/sms")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Api(value = "SmsFeign|提供微服务调用接口")
@RefreshScope
public class SmsFeign {

    private final ISmsService smsService;

    @GetMapping(value = "/send/normal")
    @ApiOperation(value = "发送普通短信", notes = "发送普通短信")
    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 = "发送短信验证码", notes = "发送短信验证码")
    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

到此这篇关于SpringCloud 搭建企业级开发框架之实现多租户多平台短信通知服务的文章就介绍到这了,更多相关SpringCloud 短信通知服务内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯