diff --git a/pom.xml b/pom.xml index c786873..84213e8 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,8 @@ 0.9.1 1.16.20 3.3.2 + + 2.0.23 @@ -231,6 +233,11 @@ mapstruct 1.4.2.Final + + com.aliyun + dysmsapi20170525 + ${aliyun.sms.version} + diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 49f09fb..4a473df 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -91,6 +91,13 @@ mapstruct 1.4.2.Final + + + + com.aliyun + dysmsapi20170525 + true + diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index bbbed63..723505a 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -44,4 +44,14 @@ aliyun: bucketName: 你的bucketName # bucket 名称 wechat: appId: 你的微信服务号信息 - secret: 你的微信服务号信息 \ No newline at end of file + secret: 你的微信服务号信息 +sms: + enabled: true + # 阿里云 dysmsapi.aliyuncs.com + endpoint: dysmsapi.aliyuncs.com + accessKeyId: 你的accessKeyId #阿里云短信服务控制台查看 + accessKeySecret: 你的accessKeySecret #同上 + signName: 签名 + templateId: 模板id + # 腾讯专用 + sdkAppId: \ No newline at end of file diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 4afb743..4d43a46 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -130,6 +130,12 @@ javax.servlet-api + + + com.aliyun + dysmsapi20170525 + true + diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/SmsConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/SmsConfig.java new file mode 100644 index 0000000..b540ddf --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/SmsConfig.java @@ -0,0 +1,34 @@ +package com.ruoyi.common.config; + +import com.ruoyi.common.config.properties.SmsProperties; +import com.ruoyi.common.core.sms.AliyunSmsTemplate; +import com.ruoyi.common.core.sms.SmsTemplate; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +/** + * 短信配置类 + * + * @author Lion Li + * @version 4.2.0 + */ +@EnableConfigurationProperties(SmsProperties.class) +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); + } + + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/properties/SmsProperties.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/properties/SmsProperties.java new file mode 100644 index 0000000..2c93b39 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/properties/SmsProperties.java @@ -0,0 +1,46 @@ +package com.ruoyi.common.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * SMS短信 配置属性 + * + * @author Lion Li + * @version 4.2.0 + */ +@Data +@ConfigurationProperties(prefix = "sms") +@Component +public class SmsProperties { + + private Boolean enabled; + + /** + * 配置节点 + * 阿里云 dysmsapi.aliyuncs.com + */ + private String endpoint; + + /** + * key + */ + private String accessKeyId; + + /** + * 密匙 + */ + private String accessKeySecret; + + /* + * 短信签名 + */ + private String signName; + + /** + * 短信应用ID (腾讯专属) + */ + private String sdkAppId; + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsResult.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsResult.java new file mode 100644 index 0000000..7910299 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsResult.java @@ -0,0 +1,31 @@ +package com.ruoyi.common.core.domain.model; + +import lombok.Builder; +import lombok.Data; + +/** + * 上传返回体 + * + * @author Lion Li + */ +@Data +@Builder +public class SmsResult { + + /** + * 是否成功 + */ + private boolean isSuccess; + + /** + * 响应消息 + */ + private String message; + + /** + * 实际响应体 + *

+ * 可自行转换为 SDK 对应的 SendSmsResponse + */ + private String response; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/sms/AliyunSmsTemplate.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/sms/AliyunSmsTemplate.java new file mode 100644 index 0000000..a795ffa --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/sms/AliyunSmsTemplate.java @@ -0,0 +1,71 @@ +package com.ruoyi.common.core.sms; + +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.teaopenapi.models.Config; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ruoyi.common.config.properties.SmsProperties; +import com.ruoyi.common.core.domain.model.SmsResult; +import com.ruoyi.common.exception.sms.SmsException; +import com.ruoyi.common.utils.JsonUtils; +import com.ruoyi.common.utils.StringUtils; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * Aliyun 短信模板 + * + * @author Lion Li + * @version 4.2.0 + */ +public class AliyunSmsTemplate implements SmsTemplate { + + @Autowired + private SmsProperties properties; + + private 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 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()); + } + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/sms/SmsTemplate.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/sms/SmsTemplate.java new file mode 100644 index 0000000..ee6064b --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/sms/SmsTemplate.java @@ -0,0 +1,26 @@ +package com.ruoyi.common.core.sms; + +import com.ruoyi.common.core.domain.model.SmsResult; + +import java.util.Map; + +/** + * 短信模板 + * + * @author Lion Li + * @version 4.2.0 + */ +public interface SmsTemplate { + + /** + * 发送短信 + * + * @param phones 电话号(多个逗号分割) + * @param templateId 模板id + * @param param 模板对应参数 + * 阿里 需使用 模板变量名称对应内容 例如: code=1234 + * 腾讯 需使用 模板变量顺序对应内容 例如: 1=1234, 1为模板内第一个参数 + */ + SmsResult send(String phones, String templateId, Map param); + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/exception/sms/SmsException.java b/ruoyi-common/src/main/java/com/ruoyi/common/exception/sms/SmsException.java new file mode 100644 index 0000000..9d66156 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/exception/sms/SmsException.java @@ -0,0 +1,17 @@ +package com.ruoyi.common.exception.sms; + + +/** + * Sms异常类 + * + * @author Lion Li + */ +public class SmsException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public SmsException(String msg) { + super(msg); + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/JsonUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/JsonUtils.java new file mode 100644 index 0000000..e5c471e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/JsonUtils.java @@ -0,0 +1,112 @@ +package com.ruoyi.common.utils; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.ruoyi.common.utils.spring.SpringUtils; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * JSON 工具类 + * + * @author 芋道源码 + */ +@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 parseObject(String text, Class clazz) { + if (StringUtils.isEmpty(text)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(text, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(byte[] bytes, Class clazz) { + if (ArrayUtil.isEmpty(bytes)) { + return null; + } + try { + return OBJECT_MAPPER.readValue(bytes, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, TypeReference 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 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 List parseArray(String text, Class 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); + } + } + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/SmsUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SmsUtils.java new file mode 100644 index 0000000..beffc1e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SmsUtils.java @@ -0,0 +1,33 @@ +package com.ruoyi.common.utils; + +/** + * @Author: czc + * @Description: TODO + * @DateTime: 2023/6/16 17:50 + **/ +public class SmsUtils { + + public static String createRandom(boolean numberFlag, int length) { + String retStr = ""; + String strTable = numberFlag ? "1234567890" : "1234567890abcdefghijkmnpqrstuvwxyz"; + int len = strTable.length(); + boolean bDone = true; + do { + retStr = ""; + int count = 0; + for (int i = 0; i < length; i++) { + double dblR = Math.random() * len; + int intR = (int) Math.floor(dblR); + char c = strTable.charAt(intR); + if (('0' <= c) && (c <= '9')) { + count++; + } + retStr += strTable.charAt(intR); + } + if (count >= 2) { + bDone = false; + } + } while (bDone); + return retStr; + } +}