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