-
pom.xml
<!-- https://mvnrepository.com/artifact/com.aliyun/dysmsapi20170525 --> <dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> <version>2.0.23</version> </dependency> <!-- https://mvnrepository.com/artifact/com.tencentcloudapi/tencentcloud-sdk-java-sms --> <dependency> <groupId>com.tencentcloudapi</groupId> <artifactId>tencentcloud-sdk-java-sms</artifactId> <version>3.1.810</version> </dependency> <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.20</version> </dependency>
-
application.yml
server: port: 8888 servlet: context-path: /sms --- # sms 短信 sms: enabled: true # 阿里云 dysmsapi.aliyuncs.com # 腾讯云 sms.tencentcloudapi.com endpoint: "dysmsapi.aliyuncs.com" # 阿里云 https://ram.console.aliyun.com/manage/ak # 腾讯云 https://console.cloud.tencent.com/cam/capi accessKeyId: xxxxxxxxx accessKeySecret: xxxxxxxxx # 阿里云 https://dysms.console.aliyun.com/domestic/text # 腾讯云 https://console.cloud.tencent.com/smsv2/csms-sign signName: xxxxxxxxx # 腾讯专用 https://console.cloud.tencent.com/cam/capi sdkAppId: xxxxxxxxx
-
属性类
@Data @Component @ConfigurationProperties(prefix = "sms") public class SmsProperties { private Boolean enabled; /** * 配置节点 * 阿里云 dysmsapi.aliyuncs.com * 腾讯云 sms.tencentcloudapi.com */ private String endpoint; /** * key */ private String accessKeyId; /** * 密匙 */ private String accessKeySecret; /* * 短信签名 */ private String signName; /** * 短信应用ID (腾讯专属) */ private String sdkAppId; }
-
配置类
@Configuration public class SmsConfig { @Configuration @ConditionalOnProperty(value = "sms.enabled", havingValue = "true") @ConditionalOnClass(com.aliyun.dysmsapi20170525.Client.class) static class AliyunSmsConfig { @Bean public SmsTemplate aliyunSmsTemplate(SmsProperties smsProperties) { return new AliyunSmsTemplate(smsProperties); } } @Configuration @ConditionalOnProperty(value = "sms.enabled", havingValue = "true") @ConditionalOnClass(com.tencentcloudapi.sms.v20190711.SmsClient.class) static class TencentSmsConfig { @Bean public SmsTemplate tencentSmsTemplate(SmsProperties smsProperties) { return new TencentSmsTemplate(smsProperties); } } }
-
接口类
public interface SmsTemplate { /** * 发送短信 * * @param phones 电话号(多个逗号分割) * @param templateId 模板id * @param param 模板对应参数 * 阿里 需使用 模板变量名称对应内容 例如: code=1234 * 腾讯 需使用 模板变量顺序对应内容 例如: 1=1234, 1为模板内第一个参数 */ SmsResult send(String phones, String templateId, Map<String, String> param); }
-
实现类(阿里云)
public class AliyunSmsTemplate implements SmsTemplate { private final SmsProperties properties; private final Client client; @SneakyThrows(Exception.class) public AliyunSmsTemplate(SmsProperties smsProperties) { this.properties = smsProperties; Config config = new Config() // 您的AccessKey ID .setAccessKeyId(smsProperties.getAccessKeyId()) // 您的AccessKey Secret .setAccessKeySecret(smsProperties.getAccessKeySecret()) // 访问的域名 .setEndpoint(smsProperties.getEndpoint()); this.client = new Client(config); } @Override public SmsResult send(String phones, String templateId, Map<String, String> param) { if (StringUtils.isBlank(phones)) { throw new SmsException("手机号不能为空"); } if (StringUtils.isBlank(templateId)) { throw new SmsException("模板ID不能为空"); } SendSmsRequest req = new SendSmsRequest() .setPhoneNumbers(phones) .setSignName(properties.getSignName()) .setTemplateCode(templateId) .setTemplateParam(JsonUtils.toJsonString(param)); try { SendSmsResponse resp = client.sendSms(req); return SmsResult.builder() .isSuccess("OK".equals(resp.getBody().getCode())) .message(resp.getBody().getMessage()) .response(JsonUtils.toJsonString(resp)) .build(); } catch (Exception e) { throw new SmsException(e.getMessage()); } } }
-
实现类(腾讯云)
public class TencentSmsTemplate implements SmsTemplate { private final SmsProperties properties; private final SmsClient client; @SneakyThrows(Exception.class) public TencentSmsTemplate(SmsProperties smsProperties) { this.properties = smsProperties; Credential credential = new Credential(smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret()); HttpProfile httpProfile = new HttpProfile(); httpProfile.setEndpoint(smsProperties.getEndpoint()); ClientProfile clientProfile = new ClientProfile(); clientProfile.setHttpProfile(httpProfile); this.client = new SmsClient(credential, "", clientProfile); } @Override public SmsResult send(String phones, String templateId, Map<String, String> param) { if (StringUtils.isBlank(phones)) { throw new SmsException("手机号不能为空"); } if (StringUtils.isBlank(templateId)) { throw new SmsException("模板ID不能为空"); } SendSmsRequest req = new SendSmsRequest(); Set<String> set = Arrays.stream(phones.split(",")).map(p -> "+86" + p).collect(Collectors.toSet()); req.setPhoneNumberSet(ArrayUtil.toArray(set, String.class)); if (CollUtil.isNotEmpty(param)) { req.setTemplateParamSet(ArrayUtil.toArray(param.values(), String.class)); } req.setTemplateID(templateId); req.setSign(properties.getSignName()); req.setSmsSdkAppid(properties.getSdkAppId()); try { SendSmsResponse resp = client.SendSms(req); SmsResult.SmsResultBuilder builder = SmsResult.builder() .isSuccess(true) .message("send success") .response(JsonUtils.toJsonString(resp)); for (SendStatus sendStatus : resp.getSendStatusSet()) { if (!"Ok".equals(sendStatus.getCode())) { builder.isSuccess(false).message(sendStatus.getMessage()); break; } } return builder.build(); } catch (Exception e) { throw new SmsException(e.getMessage()); } } }
-
控制器
@Validated @RequiredArgsConstructor @RestController @RequestMapping("/sms") public class SmsController { private final SmsProperties smsProperties; private AliyunSmsTemplate aliyunSmsTemplate; // 阿里云 private TencentSmsTemplate tencentSmsTemplate; // 腾讯云 /** * 发送短信Aliyun * * @param phones 电话号 * @param templateId 模板ID */ @GetMapping("/sendAliyun") public R<Object> sendAliyun(String phones, String templateId) { if (!smsProperties.getEnabled()) { return R.fail("当前系统没有开启短信功能!"); } if (!SpringUtils.containsBean("aliyunSmsTemplate")) { return R.fail("阿里云依赖未引入!"); } AliyunSmsTemplate aliyunSmsTemplate = SpringUtils.getBean(AliyunSmsTemplate.class); Map<String, String> map = new HashMap<>(1); map.put("code", "1234"); Object send = aliyunSmsTemplate.send(phones, templateId, map); return R.ok(send); } /** * 发送短信Tencent * * @param phones 电话号 * @param templateId 模板ID */ @GetMapping("/sendTencent") public R<Object> sendTencent(String phones, String templateId) { if (!smsProperties.getEnabled()) { return R.fail("当前系统没有开启短信功能!"); } if (!SpringUtils.containsBean("tencentSmsTemplate")) { return R.fail("腾讯云依赖未引入!"); } TencentSmsTemplate tencentSmsTemplate = SpringUtils.getBean(TencentSmsTemplate.class); Map<String, String> map = new HashMap<>(1); map.put("1", "1234"); Object send = tencentSmsTemplate.send(phones, templateId, map); return R.ok(send); } }
-
相关类
---------------------------------------结果类--------------------------------------- @Data @Builder public class SmsResult { /** * 是否成功 */ private boolean isSuccess; /** * 响应消息 */ private String message; /** * 实际响应体 * <p> * 可自行转换为 SDK 对应的 SendSmsResponse */ private String response; } ---------------------------------------异常类--------------------------------------- public class SmsException extends RuntimeException { private static final long serialVersionUID = 1L; public SmsException(String msg) { super(msg); } } ---------------------------------------工具类--------------------------------------- @NoArgsConstructor(access = AccessLevel.PRIVATE) public class JsonUtils { private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class); public static ObjectMapper getObjectMapper() { return OBJECT_MAPPER; } public static String toJsonString(Object object) { if (ObjectUtil.isNull(object)) { return null; } try { return OBJECT_MAPPER.writeValueAsString(object); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } public static <T> T parseObject(String text, Class<T> clazz) { if (StringUtils.isEmpty(text)) { return null; } try { return OBJECT_MAPPER.readValue(text, clazz); } catch (IOException e) { throw new RuntimeException(e); } } public static <T> T parseObject(byte[] bytes, Class<T> clazz) { if (ArrayUtil.isEmpty(bytes)) { return null; } try { return OBJECT_MAPPER.readValue(bytes, clazz); } catch (IOException e) { throw new RuntimeException(e); } } public static <T> T parseObject(String text, TypeReference<T> typeReference) { if (StringUtils.isBlank(text)) { return null; } try { return OBJECT_MAPPER.readValue(text, typeReference); } catch (IOException e) { throw new RuntimeException(e); } } public static Dict parseMap(String text) { if (StringUtils.isBlank(text)) { return null; } try { return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class)); } catch (MismatchedInputException e) { // 类型不匹配说明不是json return null; } catch (IOException e) { throw new RuntimeException(e); } } public static List<Dict> parseArrayMap(String text) { if (StringUtils.isBlank(text)) { return null; } try { return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class)); } catch (IOException e) { throw new RuntimeException(e); } } public static <T> List<T> parseArray(String text, Class<T> clazz) { if (StringUtils.isEmpty(text)) { return new ArrayList<>(); } try { return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz)); } catch (IOException e) { throw new RuntimeException(e); } } } ---------------------------------------工具类--------------------------------------- @Component public final class SpringUtils extends SpringUtil { /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * * @param name * @return boolean */ public static boolean containsBean(String name) { return getBeanFactory().containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * * @param name * @return boolean */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return getBeanFactory().isSingleton(name); } /** * @param name * @return Class 注册对象的类型 */ public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return getBeanFactory().getType(name); } /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 * * @param name */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return getBeanFactory().getAliases(name); } /** * 获取aop代理对象 * * @param invoker * @return */ @SuppressWarnings("unchecked") public static <T> T getAopProxy(T invoker) { return (T) AopContext.currentProxy(); } } ---------------------------------------响应类--------------------------------------- @Data @NoArgsConstructor public class R<T> implements Serializable { private static final long serialVersionUID = 1L; /** * 成功 */ public static final int SUCCESS = 200; /** * 失败 */ public static final int FAIL = 500; private int code; private String msg; private T data; public static <T> R<T> ok() { return restResult(null, SUCCESS, "操作成功"); } public static <T> R<T> ok(T data) { return restResult(data, SUCCESS, "操作成功"); } public static <T> R<T> ok(String msg) { return restResult(null, SUCCESS, msg); } public static <T> R<T> ok(String msg, T data) { return restResult(data, SUCCESS, msg); } public static <T> R<T> fail() { return restResult(null, FAIL, "操作失败"); } public static <T> R<T> fail(String msg) { return restResult(null, FAIL, msg); } public static <T> R<T> fail(T data) { return restResult(data, FAIL, "操作失败"); } public static <T> R<T> fail(String msg, T data) { return restResult(data, FAIL, msg); } public static <T> R<T> fail(int code, String msg) { return restResult(null, code, msg); } /** * 返回警告消息 * * @param msg 返回内容 * @return 警告消息 */ public static <T> R<T> warn(String msg) { return restResult(null, HttpStatus.WARN, msg); } /** * 返回警告消息 * * @param msg 返回内容 * @param data 数据对象 * @return 警告消息 */ public static <T> R<T> warn(String msg, T data) { return restResult(data, HttpStatus.WARN, msg); } private static <T> R<T> restResult(T data, int code, String msg) { R<T> r = new R<>(); r.setCode(code); r.setData(data); r.setMsg(msg); return r; } public static <T> Boolean isError(R<T> ret) { return !isSuccess(ret); } public static <T> Boolean isSuccess(R<T> ret) { return R.SUCCESS == ret.getCode(); } } ---------------------------------------状态类--------------------------------------- public interface HttpStatus { /** * 操作成功 */ int SUCCESS = 200; /** * 对象创建成功 */ int CREATED = 201; /** * 请求已经被接受 */ int ACCEPTED = 202; /** * 操作已经执行成功,但是没有返回数据 */ int NO_CONTENT = 204; /** * 资源已被移除 */ int MOVED_PERM = 301; /** * 重定向 */ int SEE_OTHER = 303; /** * 资源没有被修改 */ int NOT_MODIFIED = 304; /** * 参数列表错误(缺少,格式不匹配) */ int BAD_REQUEST = 400; /** * 未授权 */ int UNAUTHORIZED = 401; /** * 访问受限,授权过期 */ int FORBIDDEN = 403; /** * 资源,服务未找到 */ int NOT_FOUND = 404; /** * 不允许的http方法 */ int BAD_METHOD = 405; /** * 资源冲突,或者资源被锁 */ int CONFLICT = 409; /** * 不支持的数据,媒体类型 */ int UNSUPPORTED_TYPE = 415; /** * 系统内部错误 */ int ERROR = 500; /** * 接口未实现 */ int NOT_IMPLEMENTED = 501; /** * 系统警告消息 */ int WARN = 601; }
-
测试说明
测试未通过,报错如下,可能版本原因导致,代码仅供学习参考。 com.example.sms.exception.SmsException: Failed to connect to dysmsapi.aliyuncs.com/127.0.1.2:443 // 简易测试短信发送仍然报同样错误 @SpringBootApplication @RestController public class SmsApplication { public static void main(String[] args) { SpringApplication.run(SmsApplication.class, args); } @GetMapping("/sendSms") public void sendSms() throws Exception { // 发送短信 SendSmsResponse resp = new Client( new Config().setAccessKeyId("xxxxxxxxx") .setAccessKeySecret("xxxxxxxxx") .setEndpoint("dysmsapi.aliyuncs.com") ).sendSms( new SendSmsRequest().setSignName("xxxxxxxxx") .setTemplateCode("xxxxxxxxx") .setPhoneNumbers("xxxxxxxxx") .setTemplateParam("{\"code\":\"1234\"}") ); } }
SpringBoot+SMS(短信)
童心同萌2023-07-27 17:36
相关推荐
潘帕斯的雄鹰7 小时前
【1个月速成Java】基于Android平台开发个人记账app学习日记——第7天,申请阿里云SMS短信服务SDK东皋长歌21 小时前
SpringBoot+ClickHouse集成程序员徐师兄1 天前
基于 JavaWeb 的宠物商城系统(附源码,文档)果冻眼企鹅1 天前
常见用于从 HTTP 请求中提取数据的注解你白勺男孩TT2 天前
Vue项目中点击按钮后浏览器屏幕变黑,再次点击恢复的解决方法小云小白2 天前
springboot 传统应用程序,适配云原生改造IT界的奇葩2 天前
MQTT+Disruptor 提高物联网高并发null_173 天前
ruoyi若依vue分离版前端资源打包到jar中AshCode4 天前
Docker远程管理和应用容器远程部署爱吃土豆的程序员4 天前
深入理解跨域资源共享(CORS)安全问题原理及解决思路