diff --git a/accompany-base/accompany-common/src/main/java/com/accompany/common/constant/Constant.java b/accompany-base/accompany-common/src/main/java/com/accompany/common/constant/Constant.java index 846b4fbd7..e489cacf2 100644 --- a/accompany-base/accompany-common/src/main/java/com/accompany/common/constant/Constant.java +++ b/accompany-base/accompany-common/src/main/java/com/accompany/common/constant/Constant.java @@ -1817,6 +1817,11 @@ public class Constant { */ public static final String INCOME_METHOD = "income_method"; + /** + * 短信发送方式(阿里 aliyun 腾讯 tencent) + */ + public static final String SMS_SDK_TYPE = "sms_sdk_type"; + } public static class ActiveMq { diff --git a/accompany-base/accompany-common/src/main/java/com/accompany/common/constant/SmsConstant.java b/accompany-base/accompany-common/src/main/java/com/accompany/common/constant/SmsConstant.java index 8ea5f67f5..63b99b96a 100644 --- a/accompany-base/accompany-common/src/main/java/com/accompany/common/constant/SmsConstant.java +++ b/accompany-base/accompany-common/src/main/java/com/accompany/common/constant/SmsConstant.java @@ -5,6 +5,8 @@ package com.accompany.common.constant; */ public class SmsConstant { + public static final int SMS_EXPIRE_MINUTE = 3; + /** * 短信过期时间60秒 */ @@ -20,30 +22,37 @@ public class SmsConstant { */ public static final int SMS_SEND_INTERVAL_SECONDS = 60; - /** - * 验证码短信类型 - */ - public static class SmsType { - public static final Byte REGISTER = 1;//注册 - public static final Byte LOGIN = 2; //登录 - public static final Byte RESET_LOGIN_PASSWORD = 3;//忘记登录密码 - public static final Byte BINDING_PHONE = 4; //绑定手机 - public static final Byte BINDING_ALIPAY = 5; //绑定支付宝 - public static final Byte RESET_PAY_PASSWORD = 6; //重置支付密码 - public static final Byte UNBINDING_PHONE = 7; //更换绑定手机号码 - public static final Byte CERTIFICATION = 8; //实名认证 - public static final Byte H5_BINDING_ALIPAY = 9; //h5绑定支付宝 - public static final Byte SUPER_ADMIN_LOGIN = 10; //超管登录 - public static final Byte BINDING_BANK_CARD = 11; //绑定提现银行卡 - public static final Byte H5_BINDING_BANK_CARD = 12; //h5 绑定提现银行卡 - public static final Byte AUTH_CODE = 13; // 获取手机授权码 - } + /** + * 验证码短信类型 + */ + public static class SmsType { + public static final Byte REGISTER = 1;//注册 + public static final Byte LOGIN = 2; //登录 + public static final Byte RESET_LOGIN_PASSWORD = 3;//忘记登录密码 + public static final Byte BINDING_PHONE = 4; //绑定手机 + public static final Byte BINDING_ALIPAY = 5; //绑定支付宝 + public static final Byte RESET_PAY_PASSWORD = 6; //重置支付密码 + public static final Byte UNBINDING_PHONE = 7; //更换绑定手机号码 + public static final Byte CERTIFICATION = 8; //实名认证 + public static final Byte H5_BINDING_ALIPAY = 9; //h5绑定支付宝 + public static final Byte SUPER_ADMIN_LOGIN = 10; //超管登录 + public static final Byte BINDING_BANK_CARD = 11; //绑定提现银行卡 + public static final Byte H5_BINDING_BANK_CARD = 12; //h5 绑定提现银行卡 + public static final Byte AUTH_CODE = 13; // 获取手机授权码 + } - /** - * 阿里云短信返回码 - */ - public static class AliyunSmsResponseCode { - public static final String OK = "OK"; - public static final String BUSINESS_LIMIT_CONTROL = "isv.BUSINESS_LIMIT_CONTROL"; - } + /** + * 阿里云短信返回码 + */ + public static class AliyunSmsResponseCode { + public static final String OK = "OK"; + public static final String BUSINESS_LIMIT_CONTROL = "isv.BUSINESS_LIMIT_CONTROL"; + } + + public interface ResCode { + + String OK = "Ok"; + + String LimitExceeded = "LimitExceeded"; + } } diff --git a/accompany-base/accompany-core/src/main/java/com/accompany/core/base/SpringContextHolder.java b/accompany-base/accompany-core/src/main/java/com/accompany/core/base/SpringContextHolder.java index 1f7277f53..575466249 100644 --- a/accompany-base/accompany-core/src/main/java/com/accompany/core/base/SpringContextHolder.java +++ b/accompany-base/accompany-core/src/main/java/com/accompany/core/base/SpringContextHolder.java @@ -45,4 +45,12 @@ public class SpringContextHolder { public static T getBean(Class requiredType) { return applicationContext.getBean(requiredType); } + + @SuppressWarnings("unchecked") + public static T getBeanOfClass(Class requiredType) { + String simpleName = requiredType.getSimpleName(); + char[] ch = simpleName.toCharArray(); + ch[0] += 32; + return (T) applicationContext.getBean(new String(ch)); + } } diff --git a/accompany-base/accompany-sms/pom.xml b/accompany-base/accompany-sms/pom.xml index f66865b31..45226c48e 100644 --- a/accompany-base/accompany-sms/pom.xml +++ b/accompany-base/accompany-sms/pom.xml @@ -24,6 +24,12 @@ accompany-core ${revision} + + + com.tencentcloudapi + tencentcloud-sdk-java + ${tencentcloud-sdk-java.version} + diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/config/TencentSmsConfig.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/config/TencentSmsConfig.java new file mode 100644 index 000000000..0d96fe451 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/config/TencentSmsConfig.java @@ -0,0 +1,46 @@ +package com.accompany.sms.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; + +/** + * @author: liaozetao + * @date: 2023/8/14 11:43 + * @description: + */ +@Data +@RefreshScope +@Configuration +@ConfigurationProperties(prefix = "tencent.sms") +public class TencentSmsConfig { + + private String secretId; + + private String secretKey; + + private String appId; + + private String appKey; + + private Map apps; + + + @Data + public static class AppConfig { + + /** + * 签名ID + */ + private String signId; + + /** + * 模版ID + */ + private String templateId; + } + +} diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/config/TencentSmsConfiguration.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/config/TencentSmsConfiguration.java new file mode 100644 index 000000000..b1f4696c2 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/config/TencentSmsConfiguration.java @@ -0,0 +1,65 @@ +package com.accompany.sms.config; + +import com.tencentcloudapi.common.Credential; +import com.tencentcloudapi.common.profile.ClientProfile; +import com.tencentcloudapi.common.profile.HttpProfile; +import com.tencentcloudapi.sms.v20210111.SmsClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author: liaozetao + * @date: 2023/8/14 11:55 + * @description: + */ +@Configuration +public class TencentSmsConfiguration { + + @Autowired + private TencentSmsConfig tencentSmsConfig; + + @Bean + public Credential credential() { + /* 必要步骤: + * 实例化一个认证对象,入参需要传入腾讯云账户密钥对secretId,secretKey。 + * 这里采用的是从环境变量读取的方式,需要在环境变量中先设置这两个值。 + * 你也可以直接在代码中写死密钥对,但是小心不要将代码复制、上传或者分享给他人, + * 以免泄露密钥对危及你的财产安全。 + * SecretId、SecretKey 查询: https://console.cloud.tencent.com/cam/capi */ + return new Credential(tencentSmsConfig.getSecretId(), tencentSmsConfig.getSecretKey()); + } + + @Bean + public HttpProfile httpProfile() { + // 实例化一个http选项,可选,没有特殊需求可以跳过 + HttpProfile httpProfile = new HttpProfile(); + // 设置代理(无需要直接忽略) + // httpProfile.setProxyHost("真实代理ip"); + // httpProfile.setProxyPort(真实代理端口); + /* SDK默认使用POST方法。 + * 如果你一定要使用GET方法,可以在这里设置。GET方法无法处理一些较大的请求 */ + httpProfile.setReqMethod("POST"); + /* SDK有默认的超时时间,非必要请不要进行调整 + * 如有需要请在代码中查阅以获取最新的默认值 */ + httpProfile.setConnTimeout(60); + /* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */ + httpProfile.setEndpoint("sms.tencentcloudapi.com"); + return httpProfile; + } + + @Bean + public SmsClient smsClient(Credential credential, HttpProfile httpProfile) { + /* 非必要步骤: + * 实例化一个客户端配置对象,可以指定超时时间等配置 */ + ClientProfile clientProfile = new ClientProfile(); + /* SDK默认用TC3-HMAC-SHA256进行签名 + * 非必要请不要修改这个字段 */ + clientProfile.setSignMethod("HmacSHA256"); + clientProfile.setHttpProfile(httpProfile); + /* 实例化要请求产品(以sms为例)的client对象 + * 第二个参数是地域信息,可以直接填写字符串ap-guangzhou,支持的地域列表参考 https://cloud.tencent.com/document/api/382/52071#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 */ + return new SmsClient(credential, "ap-guangzhou", clientProfile); + } + +} diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/enums/SmsTypeEnum.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/enums/SmsTypeEnum.java new file mode 100644 index 000000000..8e4c393b7 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/enums/SmsTypeEnum.java @@ -0,0 +1,13 @@ +package com.accompany.sms.enums; + +/** + * @author: liaozetao + * @date: 2023/8/14 15:14 + * @description: + */ +public enum SmsTypeEnum { + + ALIYUN, + + TENCENT; +} diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/AliyunSmsService.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/AliyunSmsService.java index 087cd4d69..0d3d71e2a 100644 --- a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/AliyunSmsService.java +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/AliyunSmsService.java @@ -1,7 +1,15 @@ package com.accompany.sms.service; +import cn.hutool.core.util.StrUtil; import com.accompany.common.constant.Constant; +import com.accompany.common.constant.SmsConstant; import com.accompany.common.device.DeviceInfo; +import com.accompany.common.status.BusiStatus; +import com.accompany.common.utils.StringUtils; +import com.accompany.core.exception.ServiceException; +import com.accompany.sms.common.BeanMapper; +import com.accompany.sms.config.AliyunSmsConfig; +import com.accompany.sms.result.AliyunSmsRet; import com.alibaba.fastjson.JSONObject; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; @@ -10,13 +18,9 @@ import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.profile.IClientProfile; -import com.accompany.sms.config.AliyunSmsConfig; -import com.accompany.sms.result.AliyunSmsRet; -import com.accompany.sms.common.BeanMapper; import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -41,41 +45,44 @@ public class AliyunSmsService implements InitializingBean { public AliyunSmsRet sendSms(String mobile, String code, DeviceInfo deviceInfo) throws ClientException { + return sendSms(mobile, (deviceInfo != null ? deviceInfo.getApp() : null), code); + } + + public AliyunSmsRet sendSms(String mobile, String app, String code) throws ClientException { //组装请求对象-具体描述见控制台-文档部分内容 SendSmsRequest request = new SendSmsRequest(); //必填:待发送手机号 request.setPhoneNumbers(mobile); //必填:短信签名-可在短信控制台中找到 // 根据app参数选择不同的签名,如果没有配置特殊的,则使用默认的 - if (aliyunSmsConfig.getAppSignNameDic() != null && deviceInfo != null && aliyunSmsConfig.getAppSignNameDic().containsKey(deviceInfo.getApp())) { - String signName = aliyunSmsConfig.getAppSignNameDic().get(deviceInfo.getApp()); - log.info("{} use signName {}", deviceInfo.getApp(), signName); + if (aliyunSmsConfig.getAppSignNameDic() != null && StrUtil.isNotEmpty(app) && aliyunSmsConfig.getAppSignNameDic().containsKey(app)) { + String signName = aliyunSmsConfig.getAppSignNameDic().get(app); + log.info("{} use signName {}", app, signName); request.setSignName(signName); } else { request.setSignName(aliyunSmsConfig.getSignName()); } request.setTemplateCode(aliyunSmsConfig.getTemplateCode()); - // 国际区分 if (!mobile.startsWith(Constant.CHINA_MAINLAND_PHONE_AREA_CODE)) { request.setSignName(aliyunSmsConfig.getIntlSignName()); request.setTemplateCode(aliyunSmsConfig.getIntlTemplateCode()); } - //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 request.setTemplateParam(gson.toJson(ImmutableMap.of("code", code))); //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者 //hint 此处可能会抛出异常,注意catch SendSmsResponse acsResponse = acsClient.getAcsResponse(request); - AliyunSmsRet smsRet = BeanMapper.map(acsResponse, AliyunSmsRet.class); - return smsRet; + log.info("acsResponse : {}", JSONObject.toJSON(acsResponse)); + return BeanMapper.map(acsResponse, AliyunSmsRet.class); } /** * 发送短信 - * @param mobile 手机号 + * + * @param mobile 手机号 * @param templateId 模板id - * @param param 参数 + * @param param 参数 * @return * @throws ClientException */ @@ -86,10 +93,11 @@ public class AliyunSmsService implements InitializingBean { /** * 发送短信 - * @param mobile 手机号 + * + * @param mobile 手机号 * @param templateId 模板id - * @param signName 签名 - * @param param 参数 + * @param signName 签名 + * @param param 参数 * @return * @throws ClientException */ @@ -105,10 +113,24 @@ public class AliyunSmsService implements InitializingBean { //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 request.setTemplateParam(gson.toJson(param)); SendSmsResponse acsResponse = acsClient.getAcsResponse(request); - AliyunSmsRet smsRet = BeanMapper.map(acsResponse, AliyunSmsRet.class); - return smsRet; + return BeanMapper.map(acsResponse, AliyunSmsRet.class); } + /** + * 校验 + * + * @param resCode + */ + public void validResponseCode(String resCode) { + if (StringUtils.isNotEmpty(resCode) && + resCode.equalsIgnoreCase(SmsConstant.AliyunSmsResponseCode.BUSINESS_LIMIT_CONTROL)) { + throw new ServiceException(BusiStatus.SMS_SENDING_FREQUENCY_TOO_HIGH); + } + if (StringUtils.isEmpty(resCode) || + (!resCode.equalsIgnoreCase(SmsConstant.AliyunSmsResponseCode.OK))) { + throw new ServiceException(BusiStatus.SMS_SEND_ERROR); + } + } @Override diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/SmsService.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/SmsService.java index ef0448cc2..3ff1e9bb5 100644 --- a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/SmsService.java +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/SmsService.java @@ -11,6 +11,7 @@ import com.accompany.common.utils.StringUtils; import com.accompany.core.exception.ServiceException; import com.accompany.core.service.SysConfService; import com.accompany.core.service.base.BaseService; +import com.accompany.sms.enums.SmsTypeEnum; import com.accompany.sms.result.AliyunSmsRet; import com.aliyuncs.exceptions.ClientException; import com.google.i18n.phonenumbers.NumberParseException; @@ -29,36 +30,34 @@ import org.springframework.util.Assert; @Service public class SmsService extends BaseService { - @Autowired - private SmsRecordService smsRecordService; + @Autowired + private SmsRecordService smsRecordService; - @Autowired - private AliyunSmsService aliyunSmsService; + @Autowired + private AliyunSmsService aliyunSmsService; @Autowired private SysConfService sysConfService; - - public BusiResult sendSmsCode(String mobile, Integer type, DeviceInfo deviceInfo, String ip, String code) { - Boolean exits = jedisService.exits(getSmsIntervalKey(mobile,type)); + public BusiResult sendSmsCode(String mobile, Integer type, DeviceInfo deviceInfo, String ip, String code) { + Boolean exits = jedisService.exits(getSmsIntervalKey(mobile, type)); if (exits != null && exits) { - return new BusiResult(BusiStatus.SMS_NOT_EXPIRED); + return new BusiResult<>(BusiStatus.SMS_NOT_EXPIRED); } if (StringUtils.isBlank(code)) { code = String.format("%d", RandomUtil.getFiveRandomNumber()); } try { // 格式化手机号 - String phone = "+" + mobile; + String phone = "+" + mobile; PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); Phonenumber.PhoneNumber swissNumberProto = phoneUtil.parse(phone, null); - String realPhone = String.valueOf(swissNumberProto.getCountryCode()) + String.valueOf(swissNumberProto.getNationalNumber()); - + String realPhone = swissNumberProto.getCountryCode() + String.valueOf(swissNumberProto.getNationalNumber()); AliyunSmsRet smsRet = aliyunSmsService.sendSms(realPhone, code, deviceInfo); - log.info("sendSmsCode to {}, response msg:{}", realPhone, gson.toJson(smsRet)); - + String smsType = sysConfService.getDefaultSysConfValueById(Constant.SysConfId.SMS_SDK_TYPE, SmsTypeEnum.ALIYUN.name().toLowerCase()); + log.info("sendSmsCode to {}, response msg : {}", realPhone, gson.toJson(smsRet)); // 写入短信发送频率 - jedisService.setex(getSmsIntervalKey(mobile,type), SmsConstant.SMS_SEND_INTERVAL_SECONDS, ""); + jedisService.setex(getSmsIntervalKey(mobile, type), SmsConstant.SMS_SEND_INTERVAL_SECONDS, ""); // 写入缓存 jedisService.setex(getSmsKey(mobile), SmsConstant.SMS_EXPIRE_SECONDS, code); // 新增短信记录 @@ -72,18 +71,17 @@ public class SmsService extends BaseService { (!resCode.equalsIgnoreCase(SmsConstant.AliyunSmsResponseCode.OK))) { throw new ServiceException(BusiStatus.SMS_SEND_ERROR); } - } catch (ClientException | NumberParseException e) { + } catch (RuntimeException | ClientException | NumberParseException e) { log.error("sendSmsCode error. mobile:{}", mobile, e); throw new ServiceException(BusiStatus.SMS_SEND_ERROR); } - return new BusiResult(BusiStatus.SMS_SEND_SUCCESS); + return new BusiResult<>(BusiStatus.SMS_SEND_SUCCESS); } public boolean verifySmsCode(String mobile, String code) { logger.info("verifySmsCode mobile : {}, code : {}", mobile, code); // 是否关掉验证码验证 - Boolean checkSms = Boolean.parseBoolean( - sysConfService.getDefaultSysConfValueById(Constant.SysConfId.VERIFY_SMS_CODE, "true")); + boolean checkSms = Boolean.parseBoolean(sysConfService.getDefaultSysConfValueById(Constant.SysConfId.VERIFY_SMS_CODE, "true")); if (!checkSms) { return true; } @@ -93,7 +91,7 @@ public class SmsService extends BaseService { String smsKey = getSmsKey(mobile); String codeCache = jedisService.get(smsKey); logger.info("verifySmsCode smsKey : {}, codeCache : {}", smsKey, codeCache); - if (org.apache.commons.lang3.StringUtils.equalsIgnoreCase(codeCache, code)) { + if (StringUtils.equalsIgnoreCase(codeCache, code)) { jedisService.del(getSmsKey(mobile)); return true; } @@ -102,6 +100,7 @@ public class SmsService extends BaseService { /** * 获取短信验证码的RedisKey + * * @param mobile * @return */ @@ -111,11 +110,12 @@ public class SmsService extends BaseService { /** * 短信验证码发送频率RedisKey + * * @param mobile * @param type * @return */ private String getSmsIntervalKey(String mobile, Integer type) { - return RedisKey.sms_send_interval.getKey(mobile+"_"+type); + return RedisKey.sms_send_interval.getKey(mobile + "_" + type); } } diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/TencentSmsService.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/TencentSmsService.java new file mode 100644 index 000000000..e78b609d4 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/TencentSmsService.java @@ -0,0 +1,30 @@ +package com.accompany.sms.service; + +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import com.tencentcloudapi.sms.v20210111.models.SendStatus; + +/** + * @author: liaozetao + * @date: 2023/8/14 11:52 + * @description: + */ +public interface TencentSmsService { + + /** + * 发送短信 + * + * @param mobile + * @param app + * @param code + * @return + * @throws TencentCloudSDKException + */ + SendStatus sendSms(String mobile, String app, String code) throws TencentCloudSDKException; + + /** + * 校验返回结果 + * + * @param resCode + */ + void validResponseCode(String resCode); +} diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/impl/TencentSmsServiceImpl.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/impl/TencentSmsServiceImpl.java new file mode 100644 index 000000000..ac99885c8 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/service/impl/TencentSmsServiceImpl.java @@ -0,0 +1,105 @@ +package com.accompany.sms.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.accompany.common.constant.SmsConstant; +import com.accompany.common.exception.ApiException; +import com.accompany.common.status.BusiStatus; +import com.accompany.common.utils.StringUtils; +import com.accompany.sms.config.TencentSmsConfig; +import com.accompany.sms.service.TencentSmsService; +import com.alibaba.fastjson.JSONObject; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import com.tencentcloudapi.sms.v20210111.SmsClient; +import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest; +import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; +import com.tencentcloudapi.sms.v20210111.models.SendStatus; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * @author: liaozetao + * @date: 2023/8/14 11:52 + * @description: + */ +@Slf4j +@Service +public class TencentSmsServiceImpl implements TencentSmsService { + + @Autowired + private TencentSmsConfig tencentSmsConfig; + + @Autowired + private SmsClient smsClient; + + @Override + public SendStatus sendSms(String mobile, String app, String code) throws TencentCloudSDKException { + log.info("mobile : {}, app : {}, code : {}", mobile, app, code); + String appId = tencentSmsConfig.getAppId(); + Map apps = tencentSmsConfig.getApps(); + if (CollectionUtil.isEmpty(apps) || !apps.containsKey(app)) { + log.info("sms config app : {}", app); + return null; + } + String signId = apps.get(app).getSignId(); + String templateId = apps.get(app).getTemplateId(); + /* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数 + * 你可以直接查询SDK源码确定接口有哪些属性可以设置 + * 属性可能是基本类型,也可能引用了另一个数据结构 + * 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */ + SendSmsRequest req = new SendSmsRequest(); + /* 填充请求参数,这里request对象的成员变量即对应接口的入参 + * 你可以通过官网接口文档或跳转到request对象的定义处查看请求参数的定义 + * 基本类型的设置: + * 帮助链接: + * 短信控制台: https://console.cloud.tencent.com/smsv2 + * 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */ + /* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */ + // 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看 + req.setSmsSdkAppId(appId); + /* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */ + // 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看 + /* 模板 ID: 必须填写已审核通过的模板 ID */ + // 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看 + req.setSignName(signId); + req.setTemplateId(templateId); + /* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空 */ + String[] templateParamSet = {code, String.valueOf(SmsConstant.SMS_EXPIRE_MINUTE)}; + req.setTemplateParamSet(templateParamSet); + /* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号] + * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号 */ + mobile = "+" + mobile; + String[] phoneNumberSet = {mobile}; + req.setPhoneNumberSet(phoneNumberSet); + /* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的 + * 返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应 */ + SendSmsResponse res = smsClient.SendSms(req); + if (res != null) { + log.info("SendSmsResponse : {}", JSONObject.toJSON(res)); + /* 当出现以下错误码时,快速解决方案参考 + * [FailedOperation.SignatureIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.signatureincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [FailedOperation.TemplateIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.templateincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [UnauthorizedOperation.SmsSdkAppIdVerifyFail](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunauthorizedoperation.smssdkappidverifyfail-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * [UnsupportedOperation.ContainDomesticAndInternationalPhoneNumber](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Aunsupportedoperation.containdomesticandinternationalphonenumber-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F) + * 更多错误,可咨询[腾讯云助手](https://tccc.qcloud.com/web/im/index.html#/chat?webAppId=8fa15978f85cb41f7e2ea36920cb3ae1&title=Sms) + */ + return res.getSendStatusSet()[0]; + } + return null; + } + + @Override + public void validResponseCode(String resCode) { + if (SmsConstant.ResCode.OK.equals(resCode)) { + return; + } + if (StringUtils.isNotEmpty(resCode) && resCode.startsWith(SmsConstant.ResCode.LimitExceeded)) { + throw new ApiException(BusiStatus.SMS_SENDING_FREQUENCY_TOO_HIGH); + } + + throw new ApiException(BusiStatus.SMS_SEND_ERROR); + } + +} diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/AliYunSmsStrategy.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/AliYunSmsStrategy.java new file mode 100644 index 000000000..e9c18a100 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/AliYunSmsStrategy.java @@ -0,0 +1,49 @@ +package com.accompany.sms.strategy; + +import com.accompany.core.base.SpringContextHolder; +import com.accompany.sms.enums.SmsTypeEnum; +import com.accompany.sms.result.AliyunSmsRet; +import com.accompany.sms.service.AliyunSmsService; +import lombok.extern.slf4j.Slf4j; + +/** + * @author: liaozetao + * @date: 2023/8/14 15:17 + * @description: + */ +@Slf4j +public class AliYunSmsStrategy extends SmsStrategy { + + private final AliyunSmsService aliyunSmsService; + + public AliYunSmsStrategy() { + aliyunSmsService = SpringContextHolder.getBeanOfClass(AliyunSmsService.class); + } + + @Override + String type() { + return SmsTypeEnum.ALIYUN.name().toLowerCase(); + } + + @Override + SmsResponse doSend(SmsContext context) { + SmsResponse response = new SmsResponse(); + String mobile = context.getMobile(); + String app = context.getApp(); + String code = context.getCode(); + try { + AliyunSmsRet aliyunSmsRet = aliyunSmsService.sendSms(mobile, app, code); + response.setCode(aliyunSmsRet.getCode()); + response.setMessage(aliyunSmsRet.getMessage()); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage()); + } + return response; + } + + @Override + public void validResCode(String resCode) { + aliyunSmsService.validResponseCode(resCode); + } +} diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsContext.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsContext.java new file mode 100644 index 000000000..8d7960026 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsContext.java @@ -0,0 +1,27 @@ +package com.accompany.sms.strategy; + +import lombok.Data; + +/** + * @author: liaozetao + * @date: 2023/8/14 14:57 + * @description: + */ +@Data +public class SmsContext { + + /** + * 手机号码 + */ + private String mobile; + + /** + * 应用 + */ + private String app; + + /** + * 验证码 + */ + private String code; +} diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsFactory.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsFactory.java new file mode 100644 index 000000000..790df04f3 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsFactory.java @@ -0,0 +1,27 @@ +package com.accompany.sms.strategy; + +import com.accompany.sms.enums.SmsTypeEnum; + +/** + * @author: liaozetao + * @date: 2023/8/14 16:05 + * @description: + */ +public class SmsFactory { + + public SmsResponse send(String type, String mobile, String app, String code) { + SmsContext context = new SmsContext(); + context.setMobile(mobile); + context.setApp(app); + context.setCode(code); + SmsStrategy smsStrategy = null; + if (SmsTypeEnum.ALIYUN.name().toLowerCase().equalsIgnoreCase(type)) { + smsStrategy = new AliYunSmsStrategy(); + } else if (SmsTypeEnum.TENCENT.name().toLowerCase().equalsIgnoreCase(type)) { + smsStrategy = new TencentSmsStrategy(); + } else { + smsStrategy = new AliYunSmsStrategy(); + } + return smsStrategy.send(context); + } +} diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsResponse.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsResponse.java new file mode 100644 index 000000000..951ea8032 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsResponse.java @@ -0,0 +1,22 @@ +package com.accompany.sms.strategy; + +import lombok.Data; + +/** + * @author: liaozetao + * @date: 2023/8/14 15:06 + * @description: + */ +@Data +public class SmsResponse { + + /** + * 异常编码 + */ + private String code; + + /** + * 异常信息 + */ + private String message; +} diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsStrategy.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsStrategy.java new file mode 100644 index 000000000..34ad8ab49 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/SmsStrategy.java @@ -0,0 +1,42 @@ +package com.accompany.sms.strategy; + +/** + * @author: liaozetao + * @date: 2023/8/14 14:59 + * @description: + */ +public abstract class SmsStrategy { + + /** + * 类型 + * + * @return + */ + abstract String type(); + + + /** + * 发送验证码 + * + * @param context + * @return + */ + public SmsResponse send(SmsContext context) { + return doSend(context); + } + + /** + * 发送验证码 + * + * @param context + * @return + */ + abstract SmsResponse doSend(SmsContext context); + + /** + * 校验返回结果 + * + * @param resCode + */ + public abstract void validResCode(String resCode); +} diff --git a/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/TencentSmsStrategy.java b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/TencentSmsStrategy.java new file mode 100644 index 000000000..8e9bcf1b9 --- /dev/null +++ b/accompany-base/accompany-sms/src/main/java/com/accompany/sms/strategy/TencentSmsStrategy.java @@ -0,0 +1,50 @@ +package com.accompany.sms.strategy; + +import com.accompany.core.base.SpringContextHolder; +import com.accompany.sms.enums.SmsTypeEnum; +import com.accompany.sms.service.TencentSmsService; +import com.accompany.sms.service.impl.TencentSmsServiceImpl; +import com.tencentcloudapi.sms.v20210111.models.SendStatus; +import lombok.extern.slf4j.Slf4j; + +/** + * @author: liaozetao + * @date: 2023/8/14 15:04 + * @description: + */ +@Slf4j +public class TencentSmsStrategy extends SmsStrategy { + + private final TencentSmsService tencentSmsService; + + public TencentSmsStrategy() { + tencentSmsService = SpringContextHolder.getBeanOfClass(TencentSmsServiceImpl.class); + } + + @Override + public String type() { + return SmsTypeEnum.TENCENT.name().toLowerCase(); + } + + @Override + public SmsResponse doSend(SmsContext context) { + SmsResponse response = new SmsResponse(); + String mobile = context.getMobile(); + String app = context.getApp(); + String code = context.getCode(); + try { + SendStatus sendStatus = tencentSmsService.sendSms(mobile, app, code); + response.setCode(sendStatus.getCode()); + response.setMessage(sendStatus.getMessage()); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage()); + } + return response; + } + + @Override + public void validResCode(String resCode) { + tencentSmsService.validResponseCode(resCode); + } +} diff --git a/pom.xml b/pom.xml index 6be60eb60..40114c578 100644 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,7 @@ 2.7.0 1.7.2 2.2.3 + 3.1.781 @@ -966,6 +967,12 @@ 1.5.0 + + com.tencentcloudapi + tencentcloud-sdk-java + ${tencentcloud-sdk-java.version} + +