Browse Source

提交短信、存储模块代码

pangqijun 1 year ago
parent
commit
3a62e80db1
35 changed files with 1872 additions and 3 deletions
  1. 12 0
      blade-core-oss/pom.xml
  2. 173 0
      blade-core-oss/src/main/java/org/springblade/core/oss/HuaweiObsTemplate.java
  3. 2 1
      blade-core-oss/src/main/java/org/springblade/core/oss/OssTemplate.java
  4. 239 0
      blade-core-oss/src/main/java/org/springblade/core/oss/TencentCosTemplate.java
  5. 33 0
      blade-core-oss/src/main/java/org/springblade/core/oss/enums/OssEnum.java
  6. 18 0
      blade-core-oss/src/main/java/org/springblade/core/oss/enums/OssStatusEnum.java
  7. 1 0
      blade-core-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java
  8. 6 1
      blade-core-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java
  9. 48 0
      blade-core-redis/pom.xml
  10. 405 0
      blade-core-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java
  11. 38 0
      blade-core-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java
  12. 34 0
      blade-core-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java
  13. 56 0
      blade-core-sms/pom.xml
  14. 99 0
      blade-core-sms/src/main/java/org/springblade/core/sms/AliSmsTemplate.java
  15. 134 0
      blade-core-sms/src/main/java/org/springblade/core/sms/HuaweiSmsTemplate.java
  16. 73 0
      blade-core-sms/src/main/java/org/springblade/core/sms/QiniuSmsTemplate.java
  17. 40 0
      blade-core-sms/src/main/java/org/springblade/core/sms/SmsTemplate.java
  18. 78 0
      blade-core-sms/src/main/java/org/springblade/core/sms/TencentSmsTemplate.java
  19. 74 0
      blade-core-sms/src/main/java/org/springblade/core/sms/YunpianSmsTemplate.java
  20. 38 0
      blade-core-sms/src/main/java/org/springblade/core/sms/config/AliSmsConfiguration.java
  21. 10 0
      blade-core-sms/src/main/java/org/springblade/core/sms/constant/SmsConstant.java
  22. 22 0
      blade-core-sms/src/main/java/org/springblade/core/sms/menus/SmsEnum.java
  23. 18 0
      blade-core-sms/src/main/java/org/springblade/core/sms/menus/SmsStatusEnum.java
  24. 43 0
      blade-core-sms/src/main/java/org/springblade/core/sms/model/SmsCode.java
  25. 32 0
      blade-core-sms/src/main/java/org/springblade/core/sms/model/SmsData.java
  26. 18 0
      blade-core-sms/src/main/java/org/springblade/core/sms/model/SmsInfo.java
  27. 22 0
      blade-core-sms/src/main/java/org/springblade/core/sms/model/SmsResponse.java
  28. 21 0
      blade-core-sms/src/main/java/org/springblade/core/sms/props/SmsProperties.java
  29. 5 0
      blade-core-tool/pom.xml
  30. 22 0
      blade-core-tool/src/main/java/org/springblade/core/tool/constant/CacheConstant.java
  31. 17 0
      blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java
  32. 9 0
      blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java
  33. 18 0
      blade-core-tool/src/main/java/org/springblade/core/tool/utils/CollectionUtil.java
  34. 5 1
      blade-core-tool/src/main/java/org/springblade/core/tool/utils/WebUtil.java
  35. 9 0
      pom.xml

+ 12 - 0
blade-core-oss/pom.xml

@@ -39,6 +39,18 @@
             <artifactId>qiniu-java-sdk</artifactId>
             <version>7.9.4</version>
         </dependency>
+        <!--腾讯COS-->
+        <dependency>
+            <groupId>com.qcloud</groupId>
+            <artifactId>cos_api</artifactId>
+            <version>5.6.36</version>
+        </dependency>
+        <!--华为云Obs-->
+        <dependency>
+            <groupId>com.huaweicloud</groupId>
+            <artifactId>esdk-obs-java</artifactId>
+            <version>3.19.7</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 173 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/HuaweiObsTemplate.java

@@ -0,0 +1,173 @@
+package org.springblade.core.oss;
+
+import com.obs.services.ObsClient;
+import com.obs.services.model.ObjectMetadata;
+import com.obs.services.model.PutObjectResult;
+import lombok.SneakyThrows;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.oss.model.OssFile;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/25
+ * Description
+ */
+public class HuaweiObsTemplate implements OssTemplate {
+	private final ObsClient obsClient;
+	private final OssProperties ossProperties;
+	private final OssRule ossRule;
+
+	public void makeBucket(String bucketName) {
+		if (!this.bucketExists(bucketName)) {
+			this.obsClient.createBucket(this.getBucketName(bucketName));
+		}
+
+	}
+
+	public void removeBucket(String bucketName) {
+		this.obsClient.deleteBucket(this.getBucketName(bucketName));
+	}
+
+	public boolean bucketExists(String bucketName) {
+		return this.obsClient.headBucket(this.getBucketName(bucketName));
+	}
+
+	public void copyFile(String bucketName, String fileName, String destBucketName) {
+		this.obsClient.copyObject(this.getBucketName(bucketName), fileName, this.getBucketName(destBucketName), fileName);
+	}
+
+	public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
+		this.obsClient.copyObject(this.getBucketName(bucketName), fileName, this.getBucketName(destBucketName), destFileName);
+	}
+
+	public OssFile statFile(String fileName) {
+		return this.statFile(this.ossProperties.getBucketName(), fileName);
+	}
+
+	public OssFile statFile(String bucketName, String fileName) {
+		ObjectMetadata stat = this.obsClient.getObjectMetadata(this.getBucketName(bucketName), fileName);
+		OssFile ossFile = new OssFile();
+		ossFile.setName(fileName);
+		ossFile.setLink(this.fileLink(ossFile.getName()));
+		ossFile.setHash(stat.getContentMd5());
+		ossFile.setLength(stat.getContentLength());
+		ossFile.setPutTime(stat.getLastModified());
+		ossFile.setContentType(stat.getContentType());
+		return ossFile;
+	}
+
+	public String filePath(String fileName) {
+		return this.getOssHost(this.getBucketName()).concat("/").concat(fileName);
+	}
+
+	public String filePath(String bucketName, String fileName) {
+		return this.getOssHost(this.getBucketName(bucketName)).concat("/").concat(fileName);
+	}
+
+	public String fileLink(String fileName) {
+		return this.getOssHost().concat("/").concat(fileName);
+	}
+
+	public String fileLink(String bucketName, String fileName) {
+		return this.getOssHost(this.getBucketName(bucketName)).concat("/").concat(fileName);
+	}
+
+	public BladeFile putFile(MultipartFile file) {
+		return this.putFile(this.ossProperties.getBucketName(), file.getOriginalFilename(), file);
+	}
+
+	public BladeFile putFile(String fileName, MultipartFile file) {
+		return this.putFile(this.ossProperties.getBucketName(), fileName, file);
+	}
+
+	@SneakyThrows
+	public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
+		return this.putFile(bucketName, fileName, file.getInputStream());
+	}
+
+	public BladeFile putFile(String fileName, InputStream stream) {
+		return this.putFile(this.ossProperties.getBucketName(), fileName, stream);
+	}
+
+	public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
+		return this.put(bucketName, stream, fileName, false);
+	}
+
+	public void removeFile(String fileName) {
+		this.obsClient.deleteObject(this.getBucketName(), fileName);
+	}
+
+	public void removeFile(String bucketName, String fileName) {
+		this.obsClient.deleteObject(this.getBucketName(bucketName), fileName);
+	}
+
+	public void removeFiles(List<String> fileNames) {
+		fileNames.forEach(this::removeFile);
+	}
+
+	public void removeFiles(String bucketName, List<String> fileNames) {
+		fileNames.forEach((fileName) -> {
+			this.removeFile(this.getBucketName(bucketName), fileName);
+		});
+	}
+
+	public BladeFile put(String bucketName, InputStream stream, String key, boolean cover) {
+		try {
+			this.makeBucket(bucketName);
+			String originalName = key;
+			key = this.getFileName(key);
+			if (cover) {
+				this.obsClient.putObject(this.getBucketName(bucketName), key, stream);
+			} else {
+				PutObjectResult response = this.obsClient.putObject(this.getBucketName(bucketName), key, stream);
+				int retry = 0;
+
+				for(byte retryCount = 5; StringUtils.isEmpty(response.getEtag()) && retry < retryCount; ++retry) {
+					response = this.obsClient.putObject(this.getBucketName(bucketName), key, stream);
+				}
+			}
+
+			BladeFile file = new BladeFile();
+			file.setOriginalName(originalName);
+			file.setName(key);
+			file.setLink(this.fileLink(bucketName, key));
+			return file;
+		} catch (Throwable var9) {
+			throw var9;
+		}
+	}
+
+	private String getFileName(String originalFilename) {
+		return this.ossRule.fileName(originalFilename);
+	}
+
+	private String getBucketName() {
+		return this.getBucketName(this.ossProperties.getBucketName());
+	}
+
+	private String getBucketName(String bucketName) {
+		return this.ossRule.bucketName(bucketName);
+	}
+
+	public String getOssHost(String bucketName) {
+		String prefix = this.ossProperties.getEndpoint().contains("https://") ? "https://" : "http://";
+		return prefix + this.getBucketName(bucketName) + "." + this.ossProperties.getEndpoint().replaceFirst(prefix, "");
+	}
+
+	public String getOssHost() {
+		return this.getOssHost(this.ossProperties.getBucketName());
+	}
+
+	public HuaweiObsTemplate(final ObsClient obsClient, final OssProperties ossProperties, final OssRule ossRule) {
+		this.obsClient = obsClient;
+		this.ossProperties = ossProperties;
+		this.ossRule = ossRule;
+	}
+}

+ 2 - 1
blade-core-oss/src/main/java/org/springblade/core/oss/OssTemplate.java

@@ -20,6 +20,7 @@ import org.springblade.core.oss.model.BladeFile;
 import org.springblade.core.oss.model.OssFile;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
 
@@ -128,7 +129,7 @@ public interface OssTemplate {
 	 * @param file 上传文件类
 	 * @return BladeFile
 	 */
-	BladeFile putFile(MultipartFile file);
+	BladeFile putFile(MultipartFile file) throws IOException;
 
 	/**
 	 * 上传文件

+ 239 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/TencentCosTemplate.java

@@ -0,0 +1,239 @@
+package org.springblade.core.oss;
+
+import com.qcloud.cos.COSClient;
+import com.qcloud.cos.model.CannedAccessControlList;
+import com.qcloud.cos.model.ObjectMetadata;
+import com.qcloud.cos.model.PutObjectResult;
+import lombok.SneakyThrows;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.oss.model.OssFile;
+import org.springblade.core.oss.props.OssProperties;
+import org.springblade.core.oss.rule.OssRule;
+import org.springframework.util.StringUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description
+ */
+public class TencentCosTemplate implements OssTemplate {
+	private final COSClient cosClient;
+	private final OssProperties ossProperties;
+	private final OssRule ossRule;
+
+	public void makeBucket(String bucketName) {
+		try {
+			if (!this.bucketExists(bucketName)) {
+				this.cosClient.createBucket(this.getBucketName(bucketName));
+				this.cosClient.setBucketAcl(this.getBucketName(bucketName), CannedAccessControlList.PublicRead);
+			}
+		} catch (Throwable var3) {
+			throw var3;
+		}
+	}
+
+	public void removeBucket(String bucketName) {
+		try {
+			this.cosClient.deleteBucket(this.getBucketName(bucketName));
+		} catch (Throwable var3) {
+			throw var3;
+		}
+	}
+
+	public boolean bucketExists(String bucketName) {
+		try {
+			return this.cosClient.doesBucketExist(this.getBucketName(bucketName));
+		} catch (Throwable var3) {
+			throw var3;
+		}
+	}
+
+	public void copyFile(String bucketName, String fileName, String destBucketName) {
+		try {
+			this.cosClient.copyObject(this.getBucketName(bucketName), fileName, this.getBucketName(destBucketName), fileName);
+		} catch (Throwable var5) {
+			throw var5;
+		}
+	}
+
+	public void copyFile(String bucketName, String fileName, String destBucketName, String destFileName) {
+		try {
+			this.cosClient.copyObject(this.getBucketName(bucketName), fileName, this.getBucketName(destBucketName), destFileName);
+		} catch (Throwable var6) {
+			throw var6;
+		}
+	}
+
+	public OssFile statFile(String fileName) {
+		try {
+			return this.statFile(this.ossProperties.getBucketName(), fileName);
+		} catch (Throwable var3) {
+			throw var3;
+		}
+	}
+
+	public OssFile statFile(String bucketName, String fileName) {
+		try {
+			ObjectMetadata stat = this.cosClient.getObjectMetadata(this.getBucketName(bucketName), fileName);
+			OssFile ossFile = new OssFile();
+			ossFile.setName(fileName);
+			ossFile.setLink(this.fileLink(ossFile.getName()));
+			ossFile.setHash(stat.getContentMD5());
+			ossFile.setLength(stat.getContentLength());
+			ossFile.setPutTime(stat.getLastModified());
+			ossFile.setContentType(stat.getContentType());
+			return ossFile;
+		} catch (Throwable var5) {
+			throw var5;
+		}
+	}
+
+	public String filePath(String fileName) {
+		try {
+			return this.getOssHost().concat("/").concat(fileName);
+		} catch (Throwable var3) {
+			throw var3;
+		}
+	}
+
+	public String filePath(String bucketName, String fileName) {
+		try {
+			return this.getOssHost(bucketName).concat("/").concat(fileName);
+		} catch (Throwable var4) {
+			throw var4;
+		}
+	}
+
+	public String fileLink(String fileName) {
+		try {
+			return this.getOssHost().concat("/").concat(fileName);
+		} catch (Throwable var3) {
+			throw var3;
+		}
+	}
+
+	public String fileLink(String bucketName, String fileName) {
+		try {
+			return this.getOssHost(bucketName).concat("/").concat(fileName);
+		} catch (Throwable var4) {
+			throw var4;
+		}
+	}
+
+	@SneakyThrows
+	public BladeFile putFile(MultipartFile file) {
+		return this.putFile(this.ossProperties.getBucketName(), file.getOriginalFilename(), file);
+	}
+
+	@SneakyThrows
+	public BladeFile putFile(String fileName, MultipartFile file) {
+		return this.putFile(this.ossProperties.getBucketName(), fileName, file);
+	}
+
+	@SneakyThrows
+	public BladeFile putFile(String bucketName, String fileName, MultipartFile file) {
+		return this.putFile(bucketName, fileName, file.getInputStream());
+	}
+
+	@SneakyThrows
+	public BladeFile putFile(String fileName, InputStream stream) {
+		return this.putFile(this.ossProperties.getBucketName(), fileName, stream);
+	}
+
+	@SneakyThrows
+	public BladeFile putFile(String bucketName, String fileName, InputStream stream) {
+		return this.put(bucketName, stream, fileName, false);
+	}
+
+	public BladeFile put(String bucketName, InputStream stream, String key, boolean cover) {
+		try {
+			this.makeBucket(bucketName);
+			String originalName = key;
+			key = this.getFileName(key);
+			if (cover) {
+				this.cosClient.putObject(this.getBucketName(bucketName), key, stream, (ObjectMetadata)null);
+			} else {
+				PutObjectResult response = this.cosClient.putObject(this.getBucketName(bucketName), key, stream, (ObjectMetadata)null);
+				int retry = 0;
+
+				for(byte retryCount = 5; StringUtils.isEmpty(response.getETag()) && retry < retryCount; ++retry) {
+					response = this.cosClient.putObject(this.getBucketName(bucketName), key, stream, (ObjectMetadata)null);
+				}
+			}
+
+			BladeFile file = new BladeFile();
+			file.setOriginalName(originalName);
+			file.setName(key);
+			file.setDomain(this.getOssHost(bucketName));
+			file.setLink(this.fileLink(bucketName, key));
+			return file;
+		} catch (Throwable var9) {
+			throw var9;
+		}
+	}
+
+	public void removeFile(String fileName) {
+		try {
+			this.cosClient.deleteObject(this.getBucketName(), fileName);
+		} catch (Throwable var3) {
+			throw var3;
+		}
+	}
+
+	public void removeFile(String bucketName, String fileName) {
+		try {
+			this.cosClient.deleteObject(this.getBucketName(bucketName), fileName);
+		} catch (Throwable var4) {
+			throw var4;
+		}
+	}
+
+	public void removeFiles(List<String> fileNames) {
+		try {
+			fileNames.forEach(this::removeFile);
+		} catch (Throwable var3) {
+			throw var3;
+		}
+	}
+
+	public void removeFiles(String bucketName, List<String> fileNames) {
+		try {
+			fileNames.forEach((fileName) -> {
+				this.removeFile(this.getBucketName(bucketName), fileName);
+			});
+		} catch (Throwable var4) {
+			throw var4;
+		}
+	}
+
+	private String getBucketName() {
+		return this.getBucketName(this.ossProperties.getBucketName());
+	}
+
+	private String getBucketName(String bucketName) {
+		return this.ossRule.bucketName(bucketName).concat("-").concat(this.ossProperties.getAppId());
+	}
+
+	private String getFileName(String originalFilename) {
+		return this.ossRule.fileName(originalFilename);
+	}
+
+	public String getOssHost(String bucketName) {
+		return "http://" + this.cosClient.getClientConfig().getEndpointBuilder().buildGeneralApiEndpoint(this.getBucketName(bucketName));
+	}
+
+	public String getOssHost() {
+		return this.getOssHost(this.ossProperties.getBucketName());
+	}
+
+	public TencentCosTemplate(final COSClient cosClient, final OssProperties ossProperties, final OssRule ossRule) {
+		this.cosClient = cosClient;
+		this.ossProperties = ossProperties;
+		this.ossRule = ossRule;
+	}
+}

+ 33 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/enums/OssEnum.java

@@ -0,0 +1,33 @@
+package org.springblade.core.oss.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description
+ */
+@Getter
+@AllArgsConstructor
+public enum OssEnum {
+	MINIO("minio", 1),
+	QINIU("qiniu", 2),
+	ALI("ali", 3),
+	TENCENT("tencent", 4),
+	HUAWEI("huawei", 5);
+
+	final String name;
+	final int category;
+
+	public static OssEnum of(String name) {
+		if (name != null) {
+			for (OssEnum value : OssEnum.values()) {
+				if (value.name.equals(name)) {
+					return value;
+				}
+			}
+		}
+		return null;
+	}
+}

+ 18 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/enums/OssStatusEnum.java

@@ -0,0 +1,18 @@
+package org.springblade.core.oss.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description 存储状态枚举类
+ */
+@Getter
+@AllArgsConstructor
+public enum OssStatusEnum {
+	DISABLE(1),
+	ENABLE(2);
+
+	private final int num;
+}

+ 1 - 0
blade-core-oss/src/main/java/org/springblade/core/oss/model/BladeFile.java

@@ -24,6 +24,7 @@ import lombok.Data;
  */
 @Data
 public class BladeFile {
+	private Long attachId;
 	/**
 	 * 文件地址
 	 */

+ 6 - 1
blade-core-oss/src/main/java/org/springblade/core/oss/props/OssProperties.java

@@ -20,7 +20,7 @@ import org.springblade.core.tool.support.Kv;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 
 /**
- * Minio参数配置类
+ * Oss参数配置类
  *
  * @author Chill
  */
@@ -68,4 +68,9 @@ public class OssProperties {
 	 */
 	private Kv args;
 
+	private String appId;
+
+
+	private String region;
+
 }

+ 48 - 0
blade-core-redis/pom.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>blade-tool</artifactId>
+        <groupId>org.springblade</groupId>
+        <version>4.0.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>blade-core-redis</artifactId>
+    <name>${project.artifactId}</name>
+    <version>${blade.tool.version}</version>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <!--Blade-->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-core-tool</artifactId>
+            <version>${blade.tool.version}</version>
+        </dependency>
+        <!--Redis-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+            <version>3.11.6</version>
+        </dependency>
+        <!-- protostuff -->
+        <dependency>
+            <groupId>io.protostuff</groupId>
+            <artifactId>protostuff-core</artifactId>
+            <version>1.6.0</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>io.protostuff</groupId>
+            <artifactId>protostuff-runtime</artifactId>
+            <version>1.6.0</version>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+</project>

+ 405 - 0
blade-core-redis/src/main/java/org/springblade/core/redis/cache/BladeRedis.java

@@ -0,0 +1,405 @@
+//package org.springblade.core.redis.cache;
+//
+//import org.springblade.core.tool.utils.CollectionUtil;
+//import org.springframework.data.redis.core.*;
+//import org.springframework.lang.Nullable;
+//import org.springframework.util.Assert;
+//
+//import java.time.Duration;
+//import java.util.*;
+//import java.util.concurrent.TimeUnit;
+//import java.util.function.Supplier;
+//
+///**
+// * Author pangqijun
+// * Date 2023/10/24
+// * Description
+// */
+//public class BladeRedis {
+//	private final RedisTemplate redisTemplate;
+//	private final ValueOperations valueOps;
+//	private final HashOperations<String, Object, Object> hashOps;
+//	private final ListOperations<String, Object> listOps;
+//	private final SetOperations<String, Object> setOps;
+//	private final ZSetOperations<String, Object> zSetOps;
+//
+//	public BladeRedis(RedisTemplate redisTemplate) {
+//		this.redisTemplate = redisTemplate;
+//		Assert.notNull(redisTemplate, "redisTemplate is null");
+//		this.valueOps = redisTemplate.opsForValue();
+//		this.hashOps = redisTemplate.opsForHash();
+//		this.listOps = redisTemplate.opsForList();
+//		this.setOps = redisTemplate.opsForSet();
+//		this.zSetOps = redisTemplate.opsForZSet();
+//	}
+//
+//	public void set(CacheKey cacheKey, Object value) {
+//		String key = cacheKey.getKey();
+//		Duration expire = cacheKey.getExpire();
+//		if (expire == null) {
+//			this.set(key, value);
+//		} else {
+//			this.setEx(key, value, expire);
+//		}
+//
+//	}
+//
+//	public void set(String key, Object value) {
+//		this.valueOps.set(key, value);
+//	}
+//
+//	public void setEx(String key, Object value, Duration timeout) {
+//		this.valueOps.set(key, value, timeout);
+//	}
+//
+//	public void setEx(String key, Object value, Long seconds) {
+//		this.valueOps.set(key, value, seconds, TimeUnit.SECONDS);
+//	}
+//
+//	@Nullable
+//	public <T> T get(String key) {
+//		ValueOperations<String, T> operation = this.valueOps.get(key);
+//		ValueOperations<String, T> o = this.valueOps.get(key);
+//		return this.valueOps.get(key);
+//	}
+//
+//
+//	@Nullable
+//	public Object get(CacheKey cacheKey) {
+//		return this.valueOps.get(cacheKey.getKey());
+//	}
+//
+//
+//
+//	public Boolean del(String key) {
+//		return this.redisTemplate.delete(key);
+//	}
+//
+//	public Boolean del(CacheKey key) {
+//		return this.redisTemplate.delete(key.getKey());
+//	}
+//
+//	public Long del(String... keys) {
+//		return this.del((Collection)Arrays.asList(keys));
+//	}
+//
+//	public Long del(Collection<String> keys) {
+//		return this.redisTemplate.delete(keys);
+//	}
+//
+//	public Set<String> keys(String pattern) {
+//		return this.redisTemplate.keys(pattern);
+//	}
+//
+//	public void mSet(Object... keysValues) {
+//		this.valueOps.multiSet(CollectionUtil.toMap(keysValues));
+//	}
+//
+//	public List<Object> mGet(String... keys) {
+//		return this.mGet((Collection)Arrays.asList(keys));
+//	}
+//
+//	public List<Object> mGet(Collection<String> keys) {
+//		return this.valueOps.multiGet(keys);
+//	}
+//
+//	public Long decr(String key) {
+//		return this.valueOps.decrement(key);
+//	}
+//
+//	public Long decrBy(String key, long longValue) {
+//		return this.valueOps.decrement(key, longValue);
+//	}
+//
+//	public Long incr(String key) {
+//		return this.valueOps.increment(key);
+//	}
+//
+//	public Long incrBy(String key, long longValue) {
+//		return this.valueOps.increment(key, longValue);
+//	}
+//
+//	public Long getCounter(String key) {
+//		return Long.valueOf(String.valueOf(this.valueOps.get(key)));
+//	}
+//
+//	public Boolean exists(String key) {
+//		return this.redisTemplate.hasKey(key);
+//	}
+//
+//	public String randomKey() {
+//		return (String)this.redisTemplate.randomKey();
+//	}
+//
+//	public void rename(String oldkey, String newkey) {
+//		this.redisTemplate.rename(oldkey, newkey);
+//	}
+//
+//	public Boolean move(String key, int dbIndex) {
+//		return this.redisTemplate.move(key, dbIndex);
+//	}
+//
+//	public Boolean expire(String key, long seconds) {
+//		return this.redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
+//	}
+//
+//	public Boolean expire(String key, Duration timeout) {
+//		return this.expire(key, timeout.getSeconds());
+//	}
+//
+//	public Boolean expireAt(String key, Date date) {
+//		return this.redisTemplate.expireAt(key, date);
+//	}
+//
+//	public Boolean expireAt(String key, long unixTime) {
+//		return this.expireAt(key, new Date(unixTime));
+//	}
+//
+//	public Boolean pexpire(String key, long milliseconds) {
+//		return this.redisTemplate.expire(key, milliseconds, TimeUnit.MILLISECONDS);
+//	}
+//
+//	public Object getSet(String key, Object value) {
+//		return this.valueOps.getAndSet(key, value);
+//	}
+//
+//	public Boolean persist(String key) {
+//		return this.redisTemplate.persist(key);
+//	}
+//
+//	public String type(String key) {
+//		return this.redisTemplate.type(key).code();
+//	}
+//
+//	public Long ttl(String key) {
+//		return this.redisTemplate.getExpire(key);
+//	}
+//
+//	public Long pttl(String key) {
+//		return this.redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
+//	}
+//
+//	public void hSet(String key, Object field, Object value) {
+//		this.hashOps.put(key, field, value);
+//	}
+//
+//	public void hMset(String key, Map<Object, Object> hash) {
+//		this.hashOps.putAll(key, hash);
+//	}
+//
+//	public Object hGet(String key, Object field) {
+//		return this.hashOps.get(key, field);
+//	}
+//
+//	public List hmGet(String key, Object... fields) {
+//		return this.hmGet(key, (Collection)Arrays.asList(fields));
+//	}
+//
+//	public List hmGet(String key, Collection<Object> hashKeys) {
+//		return this.hashOps.multiGet(key, hashKeys);
+//	}
+//
+//	public Long hDel(String key, Object... fields) {
+//		return this.hashOps.delete(key, fields);
+//	}
+//
+//	public Boolean hExists(String key, Object field) {
+//		return this.hashOps.hasKey(key, field);
+//	}
+//
+//	public Map hGetAll(String key) {
+//		return this.hashOps.entries(key);
+//	}
+//
+//	public List hVals(String key) {
+//		return this.hashOps.values(key);
+//	}
+//
+//	public Set<Object> hKeys(String key) {
+//		return this.hashOps.keys(key);
+//	}
+//
+//	public Long hLen(String key) {
+//		return this.hashOps.size(key);
+//	}
+//
+//	public Long hIncrBy(String key, Object field, long value) {
+//		return this.hashOps.increment(key, field, value);
+//	}
+//
+//	public Double hIncrByFloat(String key, Object field, double value) {
+//		return this.hashOps.increment(key, field, value);
+//	}
+//
+//	public Object lIndex(String key, long index) {
+//		return this.listOps.index(key, index);
+//	}
+//
+//	public Long lLen(String key) {
+//		return this.listOps.size(key);
+//	}
+//
+//	public Object lPop(String key) {
+//		return this.listOps.leftPop(key);
+//	}
+//
+//	public Long lPush(String key, Object... values) {
+//		return this.listOps.leftPush(key, values);
+//	}
+//
+//	public void lSet(String key, long index, Object value) {
+//		this.listOps.set(key, index, value);
+//	}
+//
+//	public Long lRem(String key, long count, Object value) {
+//		return this.listOps.remove(key, count, value);
+//	}
+//
+//	public List lRange(String key, long start, long end) {
+//		return this.listOps.range(key, start, end);
+//	}
+//
+//	public void lTrim(String key, long start, long end) {
+//		this.listOps.trim(key, start, end);
+//	}
+//
+//	public Object rPop(String key) {
+//		return this.listOps.rightPop(key);
+//	}
+//
+//	public Long rPush(String key, Object... values) {
+//		return this.listOps.rightPush(key, values);
+//	}
+//
+//	public Object rPopLPush(String srcKey, String dstKey) {
+//		return this.listOps.rightPopAndLeftPush(srcKey, dstKey);
+//	}
+//
+//	public Long sAdd(String key, Object... members) {
+//		return this.setOps.add(key, members);
+//	}
+//
+//	public Object sPop(String key) {
+//		return this.setOps.pop(key);
+//	}
+//
+//	public Set sMembers(String key) {
+//		return this.setOps.members(key);
+//	}
+//
+//	public boolean sIsMember(String key, Object member) {
+//		return this.setOps.isMember(key, member);
+//	}
+//
+//	public Set sInter(String key, String otherKey) {
+//		return this.setOps.intersect(key, otherKey);
+//	}
+//
+//	public Set sInter(String key, Collection<String> otherKeys) {
+//		return this.setOps.intersect(key, otherKeys);
+//	}
+//
+//	public Object sRandMember(String key) {
+//		return this.setOps.randomMember(key);
+//	}
+//
+//	public List sRandMember(String key, int count) {
+//		return this.setOps.randomMembers(key, (long)count);
+//	}
+//
+//	public Long sRem(String key, Object... members) {
+//		return this.setOps.remove(key, members);
+//	}
+//
+//	public Set sUnion(String key, String otherKey) {
+//		return this.setOps.union(key, otherKey);
+//	}
+//
+//	public Set sUnion(String key, Collection<String> otherKeys) {
+//		return this.setOps.union(key, otherKeys);
+//	}
+//
+//	public Set sDiff(String key, String otherKey) {
+//		return this.setOps.difference(key, otherKey);
+//	}
+//
+//	public Set sDiff(String key, Collection<String> otherKeys) {
+//		return this.setOps.difference(key, otherKeys);
+//	}
+//
+//	public Boolean zAdd(String key, Object member, double score) {
+//		return this.zSetOps.add(key, member, score);
+//	}
+//
+//	public Long zAdd(String key, Map<Object, Double> scoreMembers) {
+//		Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet();
+//		scoreMembers.forEach((k, v) -> {
+//			tuples.add(new DefaultTypedTuple(k, v));
+//		});
+//		return this.zSetOps.add(key, tuples);
+//	}
+//
+//	public Long zCard(String key) {
+//		return this.zSetOps.zCard(key);
+//	}
+//
+//	public Long zCount(String key, double min, double max) {
+//		return this.zSetOps.count(key, min, max);
+//	}
+//
+//	public Double zIncrBy(String key, Object member, double score) {
+//		return this.zSetOps.incrementScore(key, member, score);
+//	}
+//
+//	public Set zRange(String key, long start, long end) {
+//		return this.zSetOps.range(key, start, end);
+//	}
+//
+//	public Set zRevrange(String key, long start, long end) {
+//		return this.zSetOps.reverseRange(key, start, end);
+//	}
+//
+//	public Set zRangeByScore(String key, double min, double max) {
+//		return this.zSetOps.rangeByScore(key, min, max);
+//	}
+//
+//	public Long zRank(String key, Object member) {
+//		return this.zSetOps.rank(key, member);
+//	}
+//
+//	public Long zRevrank(String key, Object member) {
+//		return this.zSetOps.reverseRank(key, member);
+//	}
+//
+//	public Long zRem(String key, Object... members) {
+//		return this.zSetOps.remove(key, members);
+//	}
+//
+//	public Double zScore(String key, Object member) {
+//		return this.zSetOps.score(key, member);
+//	}
+//
+//	public RedisTemplate<String, Object> getRedisTemplate() {
+//		return this.redisTemplate;
+//	}
+//
+//	public ValueOperations<String, Object> getValueOps() {
+//		return this.valueOps;
+//	}
+//
+//	public HashOperations<String, Object, Object> getHashOps() {
+//		return this.hashOps;
+//	}
+//
+//	public ListOperations<String, Object> getListOps() {
+//		return this.listOps;
+//	}
+//
+//	public SetOperations<String, Object> getSetOps() {
+//		return this.setOps;
+//	}
+//
+//	public ZSetOperations<String, Object> getZSetOps() {
+//		return this.zSetOps;
+//	}
+//}

+ 38 - 0
blade-core-redis/src/main/java/org/springblade/core/redis/cache/CacheKey.java

@@ -0,0 +1,38 @@
+package org.springblade.core.redis.cache;
+
+import org.springframework.lang.Nullable;
+
+import java.time.Duration;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/24
+ * Description
+ */
+public class CacheKey {
+	private final String key;
+	@Nullable
+	private Duration expire;
+
+	public CacheKey(String key) {
+		this.key = key;
+	}
+
+	public String getKey() {
+		return this.key;
+	}
+
+	@Nullable
+	public Duration getExpire() {
+		return this.expire;
+	}
+
+	public String toString() {
+		return "CacheKey(key=" + this.getKey() + ", expire=" + this.getExpire() + ")";
+	}
+
+	public CacheKey(final String key, @Nullable final Duration expire) {
+		this.key = key;
+		this.expire = expire;
+	}
+}

+ 34 - 0
blade-core-redis/src/main/java/org/springblade/core/redis/cache/ICacheKey.java

@@ -0,0 +1,34 @@
+package org.springblade.core.redis.cache;
+
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.lang.Nullable;
+
+import java.time.Duration;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/24
+ * Description
+ */
+public interface ICacheKey {
+	String getPrefix();
+
+	@Nullable
+	default Duration getExpire() {
+		return null;
+	}
+
+	default CacheKey getKey(Object... suffix) {
+		String prefix = this.getPrefix();
+		String key;
+		if (ObjectUtil.isEmpty(suffix)) {
+			key = prefix;
+		} else {
+			key = prefix.concat(StringUtil.join(suffix, ":"));
+		}
+
+		Duration expire = this.getExpire();
+		return expire == null ? new CacheKey(key) : new CacheKey(key, expire);
+	}
+}

+ 56 - 0
blade-core-sms/pom.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>blade-tool</artifactId>
+        <groupId>org.springblade</groupId>
+        <version>4.0.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>blade-core-sms</artifactId>
+    <name>${project.artifactId}</name>
+    <version>${blade.tool.version}</version>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <!--Blade-->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-core-tool</artifactId>
+            <version>${blade.tool.version}</version>
+        </dependency>
+        <dependency>
+            <artifactId>jackson-databind</artifactId>
+            <groupId>com.fasterxml.jackson.core</groupId>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.5.18</version>
+        </dependency>
+        <!--QiNiu-->
+        <dependency>
+            <groupId>com.qiniu</groupId>
+            <artifactId>qiniu-java-sdk</artifactId>
+            <version>7.9.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.qcloudsms</groupId>
+            <artifactId>qcloudsms</artifactId>
+            <version>1.0.6</version>
+        </dependency>
+        <dependency>
+            <groupId>com.yunpian.sdk</groupId>
+            <artifactId>yunpian-java-sdk</artifactId>
+            <version>1.2.7</version>
+        </dependency>
+        <dependency>
+            <groupId>com.huawei.apigateway</groupId>
+            <artifactId>java-sdk-core</artifactId>
+            <version>3.2.4</version>
+        </dependency>
+
+    </dependencies>
+</project>

+ 99 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/AliSmsTemplate.java

@@ -0,0 +1,99 @@
+package org.springblade.core.sms;
+
+import com.aliyuncs.CommonRequest;
+import com.aliyuncs.CommonResponse;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.http.MethodType;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.RedisUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.http.HttpStatus;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description
+ */
+public class AliSmsTemplate implements SmsTemplate {
+	private static final int SUCCESS = 200;
+	private static final String FAIL = "fail";
+	private static final String OK = "ok";
+	private static final String DOMAIN = "dysmsapi.aliyuncs.com";
+	private static final String VERSION = "2017-05-25";
+	private static final String ACTION = "SendSms";
+	private final SmsProperties smsProperties;
+	private final IAcsClient acsClient;
+	private final RedisUtil redisUtil;
+
+	@Override
+	public SmsResponse sendMessage(SmsData smsData, Collection<String> phones) {
+		CommonRequest request = new CommonRequest();
+		request.setSysMethod(MethodType.POST);
+		request.setSysDomain("dysmsapi.aliyuncs.com");
+		request.setSysVersion("2017-05-25");
+		request.setSysAction("SendSms");
+		request.putQueryParameter("PhoneNumbers", StringUtil.join(phones));
+		request.putQueryParameter("TemplateCode", this.smsProperties.getTemplateId());
+		request.putQueryParameter("TemplateParam", JsonUtil.toJson(smsData.getParams()));
+		request.putQueryParameter("SignName", this.smsProperties.getSignName());
+
+		try {
+			CommonResponse response = this.acsClient.getCommonResponse(request);
+			Map<String, Object> data = JsonUtil.toMap(response.getData());
+			String code = "fail";
+			if (data != null) {
+				code = String.valueOf(data.get("Code"));
+			}
+
+			return new SmsResponse(response.getHttpStatus() == 200 && code.equalsIgnoreCase("ok"), response.getHttpStatus(), response.getData());
+		} catch (ClientException var7) {
+			var7.printStackTrace();
+			return new SmsResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR.value(), var7.getMessage());
+		}
+	}
+
+	@Override
+	public SmsCode sendValidate(SmsData smsData, String phone) {
+		SmsCode smsCode = new SmsCode();
+		boolean temp = this.sendSingle(smsData, phone);
+		if (temp && StringUtil.isNotBlank(smsData.getKey())) {
+			String id = StringUtil.randomUUID();
+			String value = (String)smsData.getParams().get(smsData.getKey());
+			this.redisUtil.set(this.cacheKey(phone, id), value, 30L);
+			smsCode.setId(id).setValue(value);
+		} else {
+			smsCode.setSuccess(Boolean.FALSE);
+		}
+
+		return smsCode;
+	}
+
+	@Override
+	public boolean validateMessage(SmsCode smsCode) {
+		String id = smsCode.getId();
+		String value = smsCode.getValue();
+		String phone = smsCode.getPhone();
+		String cache = (String)this.redisUtil.get(this.cacheKey(phone, id));
+		if (StringUtil.isNotBlank(value) && StringUtil.equalsIgnoreCase(cache, value)) {
+			this.redisUtil.del(this.cacheKey(phone, id));
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	public AliSmsTemplate(final SmsProperties smsProperties, final IAcsClient acsClient, final RedisUtil redisUtil) {
+		this.smsProperties = smsProperties;
+		this.acsClient = acsClient;
+		this.redisUtil = redisUtil;
+	}
+}

+ 134 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/HuaweiSmsTemplate.java

@@ -0,0 +1,134 @@
+package org.springblade.core.sms;
+
+import cn.hutool.http.HttpRequest;
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.jackson.JsonUtil;
+import org.springblade.core.tool.utils.RedisUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.http.HttpStatus;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * Author pangqijun
+ * Date 2023/5/15
+ * Description
+ */
+public class HuaweiSmsTemplate implements SmsTemplate {
+
+    //无需修改,用于格式化鉴权头域,给"X-WSSE"参数赋值
+    private static final String WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
+    //无需修改,用于格式化鉴权头域,给"Authorization"参数赋值
+    private static final String AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"";
+
+    private final String apiUrl;
+    private final String sender;
+    private final SmsProperties smsProperties;
+    private final RedisUtil redisUtil;
+
+    public HuaweiSmsTemplate(String apiUrl, String sender, SmsProperties smsProperties, RedisUtil redisUtil) {
+        this.apiUrl = apiUrl;
+        this.sender = sender;
+        this.smsProperties = smsProperties;
+        this.redisUtil = redisUtil;
+    }
+
+    @Override
+    public SmsResponse sendMessage(SmsData smsData, Collection<String> phones) {
+        try {
+            Map<String, String> body = this.buildRequestBody(toTel(phones), smsData.getParams().get(smsData.getKey()));
+            String wsseHeader = this.buildWsseHeader(smsProperties.getAccessKey(), smsProperties.getSecretKey());
+            String result = HttpRequest.post(apiUrl + "/sms/batchSendSms/v1")
+                    .header("X-WSSE", wsseHeader)
+                    .header("Authorization", AUTH_HEADER_VALUE)
+                    .formStr(body)
+                    .execute().body();
+			HashMap<String, String> parse = JsonUtil.parse(result, new TypeReference<HashMap<String, String>>() {});
+            String code = parse.get("code");
+            if (!"000000".equals(code)) {
+                throw new RuntimeException("发送失败_" + code);
+            }
+            return new SmsResponse(true, 200, "success");
+        } catch (Exception e) {
+            e.printStackTrace();
+            return new SmsResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
+        }
+    }
+
+    @Override
+    public SmsCode sendValidate(SmsData smsData, String phone) {
+        SmsCode smsCode = new SmsCode();
+        boolean temp = this.sendSingle(smsData, phone);
+        if (temp && StringUtil.isNotBlank(smsData.getKey())) {
+            String id = StringUtil.randomUUID();
+            String value = (String)smsData.getParams().get(smsData.getKey());
+			this.redisUtil.set(this.cacheKey(phone, id), value, 30L);
+            smsCode.setId(id).setValue(value);
+        } else {
+            smsCode.setSuccess(Boolean.FALSE);
+        }
+
+        return smsCode;
+    }
+
+    @Override
+    public boolean validateMessage(SmsCode smsCode) {
+        String id = smsCode.getId();
+        String value = smsCode.getValue();
+        String phone = smsCode.getPhone();
+		String cache = (String)this.redisUtil.get(this.cacheKey(phone, id));
+        if (StringUtil.isNotBlank(value) && StringUtil.equalsIgnoreCase(cache, value)) {
+			this.redisUtil.del(this.cacheKey(phone, id));
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private Map<String, String> buildRequestBody(String receiver, String code) {
+        Map<String, String> map = new HashMap<>();
+        map.put("from", sender);
+        map.put("to", receiver);
+        map.put("templateId", smsProperties.getTemplateId());
+        map.put("signature", smsProperties.getSignName());
+        map.put("templateParas", "[\"" + code + "\"]");
+        return map;
+    }
+
+    private String buildWsseHeader(String appKey, String appSecret) {
+        if (null == appKey || null == appSecret || appKey.isEmpty() || appSecret.isEmpty()) {
+            throw new RuntimeException("buildWsseHeader(): appKey or appSecret is null.");
+        }
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+        String time = sdf.format(new Date()); //Created
+        String nonce = UUID.randomUUID().toString().replace("-", ""); //Nonce
+
+        MessageDigest md;
+        byte[] passwordDigest = null;
+
+        try {
+            md = MessageDigest.getInstance("SHA-256");
+            md.update((nonce + time + appSecret).getBytes());
+            passwordDigest = md.digest();
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+        String passwordDigestBase64Str = Base64.getEncoder().encodeToString(passwordDigest);
+        return String.format(WSSE_HEADER_FORMAT, appKey, passwordDigestBase64Str, nonce, time);
+    }
+
+    private String toTel(Collection<String> phones) {
+        List<String> temp = new ArrayList<>();
+        for (String phone : phones) {
+            temp.add("+86" + phone);
+        }
+        return StringUtil.join(temp);
+    }
+}

+ 73 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/QiniuSmsTemplate.java

@@ -0,0 +1,73 @@
+package org.springblade.core.sms;
+
+import com.qiniu.common.QiniuException;
+import com.qiniu.http.Response;
+import com.qiniu.sms.SmsManager;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.utils.RedisUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.http.HttpStatus;
+
+import java.time.Duration;
+import java.util.Collection;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/24
+ * Description
+ */
+public class QiniuSmsTemplate implements SmsTemplate {
+	private final SmsProperties smsProperties;
+	private final SmsManager smsManager;
+	private final RedisUtil redisUtil;
+
+	@Override
+	public SmsResponse sendMessage(SmsData smsData, Collection<String> phones) {
+		try {
+			Response response = this.smsManager.sendMessage(this.smsProperties.getTemplateId(), StringUtil.toStringArray(phones), smsData.getParams());
+			return new SmsResponse(response.isOK(), response.statusCode, response.toString());
+		} catch (QiniuException var4) {
+			var4.printStackTrace();
+			return new SmsResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR.value(), var4.getMessage());
+		}
+	}
+
+	@Override
+	public SmsCode sendValidate(SmsData smsData, String phone) {
+		SmsCode smsCode = new SmsCode();
+		boolean temp = this.sendSingle(smsData, phone);
+		if (temp && StringUtil.isNotBlank(smsData.getKey())) {
+			String id = StringUtil.randomUUID();
+			String value = (String)smsData.getParams().get(smsData.getKey());
+			this.redisUtil.set(this.cacheKey(phone, id), value, 30L);
+			smsCode.setId(id).setValue(value);
+		} else {
+			smsCode.setSuccess(Boolean.FALSE);
+		}
+
+		return smsCode;
+	}
+
+	@Override
+	public boolean validateMessage(SmsCode smsCode) {
+		String id = smsCode.getId();
+		String value = smsCode.getValue();
+		String phone = smsCode.getPhone();
+		String cache = (String)this.redisUtil.get(this.cacheKey(phone, id));
+		if (StringUtil.isNotBlank(value) && StringUtil.equalsIgnoreCase(cache, value)) {
+			this.redisUtil.del(this.cacheKey(phone, id));
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	public QiniuSmsTemplate(final SmsProperties smsProperties, final SmsManager smsManager, final RedisUtil redisUtil) {
+		this.smsProperties = smsProperties;
+		this.smsManager = smsManager;
+		this.redisUtil = redisUtil;
+	}
+}

+ 40 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/SmsTemplate.java

@@ -0,0 +1,40 @@
+package org.springblade.core.sms;
+
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsInfo;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description smsTemplate
+ */
+public interface SmsTemplate {
+	default String cacheKey(String phone, String id) {
+		return "blade:sms::captcha:" + phone + ":" + id;
+	}
+
+	default boolean send(SmsInfo smsInfo) {
+		return this.sendMulti(smsInfo.getSmsData(), smsInfo.getPhones());
+	}
+
+	default boolean sendSingle(SmsData smsData, String phone) {
+		return StringUtils.isEmpty(phone) ? Boolean.FALSE : this.sendMulti(smsData, Collections.singletonList(phone));
+	}
+
+	default boolean sendMulti(SmsData smsData, Collection<String> phones) {
+		SmsResponse response = this.sendMessage(smsData, phones);
+		return response.isSuccess();
+	}
+
+	SmsResponse sendMessage(SmsData smsData, Collection<String> phones);
+
+	SmsCode sendValidate(SmsData smsData, String phone);
+
+	boolean validateMessage(SmsCode smsCode);
+}

+ 78 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/TencentSmsTemplate.java

@@ -0,0 +1,78 @@
+package org.springblade.core.sms;
+
+import com.github.qcloudsms.SmsMultiSender;
+import com.github.qcloudsms.SmsMultiSenderResult;
+import com.github.qcloudsms.httpclient.HTTPException;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.RedisUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springframework.http.HttpStatus;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.Collection;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/24
+ * Description
+ */
+public class TencentSmsTemplate implements SmsTemplate {
+	private static final int SUCCESS = 0;
+	private static final String NATION_CODE = "86";
+	private final SmsProperties smsProperties;
+	private final SmsMultiSender smsSender;
+	private final RedisUtil redisUtil;
+
+
+	@Override
+	public SmsResponse sendMessage(SmsData smsData, Collection<String> phones) {
+		try {
+			Collection<String> values = smsData.getParams().values();
+			String[] params = StringUtil.toStringArray(values);
+			SmsMultiSenderResult senderResult = this.smsSender.sendWithParam("86", StringUtil.toStringArray(phones), Func.toInt(this.smsProperties.getTemplateId()), params, this.smsProperties.getSignName(), "", "");
+			return new SmsResponse(senderResult.result == 0, senderResult.result, senderResult.toString());
+		} catch (IOException | HTTPException var6) {
+			var6.printStackTrace();
+			return new SmsResponse(Boolean.FALSE, HttpStatus.INTERNAL_SERVER_ERROR.value(), var6.getMessage());
+		}
+	}
+	@Override
+	public SmsCode sendValidate(SmsData smsData, String phone) {
+		SmsCode smsCode = new SmsCode();
+		boolean temp = this.sendSingle(smsData, phone);
+		if (temp && StringUtil.isNotBlank(smsData.getKey())) {
+			String id = StringUtil.randomUUID();
+			String value = (String)smsData.getParams().get(smsData.getKey());
+			this.redisUtil.set(this.cacheKey(phone, id), value, 30L);
+			smsCode.setId(id).setValue(value);
+		} else {
+			smsCode.setSuccess(Boolean.FALSE);
+		}
+
+		return smsCode;
+	}
+	@Override
+	public boolean validateMessage(SmsCode smsCode) {
+		String id = smsCode.getId();
+		String value = smsCode.getValue();
+		String phone = smsCode.getPhone();
+		String cache = (String)this.redisUtil.get(this.cacheKey(phone, id));
+		if (StringUtil.isNotBlank(value) && StringUtil.equalsIgnoreCase(cache, value)) {
+			this.redisUtil.del(this.cacheKey(phone, id));
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	public TencentSmsTemplate(final SmsProperties smsProperties, final SmsMultiSender smsSender, final RedisUtil redisUtil) {
+		this.smsProperties = smsProperties;
+		this.smsSender = smsSender;
+		this.redisUtil = redisUtil;
+	}
+}

+ 74 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/YunpianSmsTemplate.java

@@ -0,0 +1,74 @@
+package org.springblade.core.sms;
+
+import com.yunpian.sdk.YunpianClient;
+import com.yunpian.sdk.model.Result;
+import com.yunpian.sdk.model.SmsBatchSend;
+import org.springblade.core.sms.model.SmsCode;
+import org.springblade.core.sms.model.SmsData;
+import org.springblade.core.sms.model.SmsResponse;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.support.Kv;
+import org.springblade.core.tool.utils.PlaceholderUtil;
+import org.springblade.core.tool.utils.RedisUtil;
+import org.springblade.core.tool.utils.StringUtil;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/25
+ * Description
+ */
+public class YunpianSmsTemplate implements SmsTemplate {
+	private final SmsProperties smsProperties;
+	private final YunpianClient client;
+	private final RedisUtil redisUtil;
+
+	@Override
+	public SmsResponse sendMessage(SmsData smsData, Collection<String> phones) {
+		String templateId = this.smsProperties.getTemplateId();
+		String templateText = PlaceholderUtil.getResolver("#", "#").resolveByMap(templateId, Kv.create().setAll(smsData.getParams()));
+		Map<String, String> param = this.client.newParam(2);
+		param.put("mobile", StringUtil.join(phones));
+		param.put("text", templateText);
+		Result<SmsBatchSend> result = this.client.sms().multi_send(param);
+		return new SmsResponse(result.getCode() == 0, result.getCode(), result.toString());
+	}
+
+	@Override
+	public SmsCode sendValidate(SmsData smsData, String phone) {
+		SmsCode smsCode = new SmsCode();
+		boolean temp = this.sendSingle(smsData, phone);
+		if (temp && StringUtil.isNotBlank(smsData.getKey())) {
+			String id = StringUtil.randomUUID();
+			String value = (String)smsData.getParams().get(smsData.getKey());
+			this.redisUtil.set(this.cacheKey(phone, id), value, 30L);
+			smsCode.setId(id).setValue(value);
+		} else {
+			smsCode.setSuccess(Boolean.FALSE);
+		}
+
+		return smsCode;
+	}
+
+	@Override
+	public boolean validateMessage(SmsCode smsCode) {
+		String id = smsCode.getId();
+		String value = smsCode.getValue();
+		String phone = smsCode.getPhone();
+		String cache = (String)this.redisUtil.get(this.cacheKey(phone, id));		if (StringUtil.isNotBlank(value) && StringUtil.equalsIgnoreCase(cache, value)) {
+			this.redisUtil.del(this.cacheKey(phone, id));
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	public YunpianSmsTemplate(final SmsProperties smsProperties, final YunpianClient client, final RedisUtil redisUtil) {
+		this.smsProperties = smsProperties;
+		this.client = client;
+		this.redisUtil = redisUtil;
+	}
+}

+ 38 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/config/AliSmsConfiguration.java

@@ -0,0 +1,38 @@
+package org.springblade.core.sms.config;
+
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.profile.DefaultProfile;
+import com.aliyuncs.profile.IClientProfile;
+import org.springblade.core.sms.AliSmsTemplate;
+import org.springblade.core.sms.props.SmsProperties;
+import org.springblade.core.tool.utils.RedisUtil;
+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;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description
+ */
+@Configuration(
+	proxyBeanMethods = false
+)
+@ConditionalOnClass({IAcsClient.class})
+@EnableConfigurationProperties({SmsProperties.class})
+@ConditionalOnProperty(
+	value = {"sms.name"},
+	havingValue = "aliyun"
+)
+public class AliSmsConfiguration {
+
+	@Bean
+	public AliSmsTemplate aliSmsTemplate(SmsProperties smsProperties, RedisUtil redisUtil) {
+		IClientProfile profile = DefaultProfile.getProfile(smsProperties.getRegionId(), smsProperties.getAccessKey(), smsProperties.getSecretKey());
+		IAcsClient acsClient = new DefaultAcsClient(profile);
+		return new AliSmsTemplate(smsProperties, acsClient, redisUtil);
+	}
+}

+ 10 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/constant/SmsConstant.java

@@ -0,0 +1,10 @@
+package org.springblade.core.sms.constant;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description
+ */
+public interface SmsConstant {
+	String CAPTCHA_KEY = "blade:sms::captcha:";
+}

+ 22 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/menus/SmsEnum.java

@@ -0,0 +1,22 @@
+package org.springblade.core.sms.menus;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description 短信枚举类
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsEnum {
+	YUNPIAN("yunpian", 1),
+	QINIU("qiniu", 2),
+	ALI("ali", 3),
+	TENCENT("tencent", 4),
+	HUAWEI("huawei", 5);
+
+	private final String name;
+	private final int category;
+}

+ 18 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/menus/SmsStatusEnum.java

@@ -0,0 +1,18 @@
+package org.springblade.core.sms.menus;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description 短信状态枚举类
+ */
+@Getter
+@AllArgsConstructor
+public enum SmsStatusEnum {
+	DISABLE(1),
+	ENABLE(2);
+
+	private final int num;
+}

+ 43 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/model/SmsCode.java

@@ -0,0 +1,43 @@
+package org.springblade.core.sms.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description
+ */
+@Data
+public class SmsCode implements Serializable {
+	private static final long serialVersionUID = 1L;
+	private boolean success;
+	private String phone;
+	private String id;
+
+	@JsonIgnore
+	private String value;
+
+	public SmsCode setSuccess(final boolean success) {
+		this.success = success;
+		return this;
+	}
+
+	public SmsCode setPhone(final String phone) {
+		this.phone = phone;
+		return this;
+	}
+
+	public SmsCode setId(final String id) {
+		this.id = id;
+		return this;
+	}
+
+	@JsonIgnore
+	public SmsCode setValue(final String value) {
+		this.value = value;
+		return this;
+	}
+}

+ 32 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/model/SmsData.java

@@ -0,0 +1,32 @@
+package org.springblade.core.sms.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description
+ */
+@Data
+public class SmsData implements Serializable {
+	private static final long serialVersionUID = 1L;
+	private String key;
+	private Map<String, String> params;
+
+	public SmsData(Map<String, String> params) {
+		this.params = params;
+	}
+
+	public SmsData setKey(String key) {
+		this.key = key;
+		return this;
+	}
+
+	public SmsData setParams(Map<String, String> params) {
+		this.params = params;
+		return this;
+	}
+}

+ 18 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/model/SmsInfo.java

@@ -0,0 +1,18 @@
+package org.springblade.core.sms.model;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description
+ */
+@Data
+public class SmsInfo implements Serializable {
+	private static final long serialVersionUID = 1L;
+	private SmsData smsData;
+	private Collection<String> phones;
+}

+ 22 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/model/SmsResponse.java

@@ -0,0 +1,22 @@
+package org.springblade.core.sms.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description
+ */
+@Data
+@AllArgsConstructor
+public class SmsResponse implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	private boolean success;
+	private Integer code;
+	private String msg;
+}

+ 21 - 0
blade-core-sms/src/main/java/org/springblade/core/sms/props/SmsProperties.java

@@ -0,0 +1,21 @@
+package org.springblade.core.sms.props;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/20
+ * Description
+ */
+@Data
+@ConfigurationProperties(prefix = "sms")
+public class SmsProperties {
+	private Boolean enabled;
+	private String name;
+	private String templateId;
+	private String regionId = "cn-hangzhou";
+	private String accessKey;
+	private String secretKey;
+	private String signName;
+}

+ 5 - 0
blade-core-tool/pom.xml

@@ -64,6 +64,11 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
         </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.7.20</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 22 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/constant/CacheConstant.java

@@ -0,0 +1,22 @@
+package org.springblade.core.tool.constant;
+
+/**
+ * Author pangqijun
+ * Date 2023/10/25
+ * Description
+ */
+public interface CacheConstant {
+	String BIZ_CACHE = "blade:biz";
+	String MENU_CACHE = "blade:menu";
+	String USER_CACHE = "blade:user";
+	String DICT_CACHE = "blade:dict";
+	String FLOW_CACHE = "blade:flow";
+	String SYS_CACHE = "blade:sys";
+	String RESOURCE_CACHE = "blade:resource";
+	String PARAM_CACHE = "blade:param";
+	String DEFAULT_CACHE = "default:cache";
+	String RETRY_LIMIT_CACHE = "retry:limit:cache";
+	String HALF_HOUR = "half:hour";
+	String HOUR = "hour";
+	String ONE_DAY = "one:day";
+}

+ 17 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/jackson/JsonUtil.java

@@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.type.MapType;
 import lombok.extern.slf4j.Slf4j;
 import org.springblade.core.tool.utils.*;
 import org.springframework.lang.Nullable;
@@ -401,6 +402,22 @@ public class JsonUtil {
 		}
 	}
 
+	public static <K, V> Map<K, V> readMap(@Nullable String content, Class<?> keyClass, Class<?> valueClass) {
+		if (ObjectUtil.isEmpty(content)) {
+			return Collections.emptyMap();
+		} else {
+			try {
+				return (Map)getInstance().readValue(content, getMapType(keyClass, valueClass));
+			} catch (IOException var4) {
+				throw Exceptions.unchecked(var4);
+			}
+		}
+	}
+
+	public static MapType getMapType(Class<?> keyClass, Class<?> valueClass) {
+		return getInstance().getTypeFactory().constructMapType(Map.class, keyClass, valueClass);
+	}
+
 	public static ObjectMapper getInstance() {
 		return JacksonHolder.INSTANCE;
 	}

+ 9 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/support/Kv.java

@@ -22,6 +22,7 @@ import java.sql.Time;
 import java.sql.Timestamp;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Map;
 
 /**
  * 链式map
@@ -68,6 +69,14 @@ public class Kv extends LinkedCaseInsensitiveMap<Object> {
 		return this;
 	}
 
+	public Kv setAll(Map<? extends String, ?> map) {
+		if (map != null) {
+			this.putAll(map);
+		}
+
+		return this;
+	}
+
 	/**
 	 * 设置列,当键或值为null时忽略
 	 *

+ 18 - 0
blade-core-tool/src/main/java/org/springblade/core/tool/utils/CollectionUtil.java

@@ -20,6 +20,7 @@ import org.springframework.util.CollectionUtils;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -79,4 +80,21 @@ public class CollectionUtil extends CollectionUtils {
 		return !CollectionUtils.isEmpty(map);
 	}
 
+	public static <K, V> Map<K, V> toMap(Object... keysValues) {
+		int kvLength = keysValues.length;
+		if (kvLength % 2 != 0) {
+			throw new IllegalArgumentException("wrong number of arguments for met, keysValues length can not be odd");
+		} else {
+			Map<K, V> keyValueMap = new HashMap(kvLength);
+
+			for(int i = kvLength - 2; i >= 0; i -= 2) {
+				K key = (K) keysValues[i];
+				V value = (V) keysValues[i + 1];
+				keyValueMap.put(key, value);
+			}
+
+			return keyValueMap;
+		}
+	}
+
 }

+ 5 - 1
blade-core-tool/src/main/java/org/springblade/core/tool/utils/WebUtil.java

@@ -36,6 +36,7 @@ import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.Enumeration;
+import java.util.Objects;
 
 
 /**
@@ -343,6 +344,9 @@ public class WebUtil extends org.springframework.web.util.WebUtils {
 		}
 	}
 
-
+	public static String getParameter(String name) {
+		HttpServletRequest request = getRequest();
+		return ((HttpServletRequest) Objects.requireNonNull(request)).getParameter(name);
+	}
 }
 

+ 9 - 0
pom.xml

@@ -54,6 +54,8 @@
         <module>blade-core-report</module>
         <module>blade-core-datascope</module>
         <module>blade-core-crypto</module>
+        <module>blade-core-sms</module>
+        <module>blade-core-redis</module>
     </modules>
 
     <dependencyManagement>
@@ -181,6 +183,13 @@
     </build>
 
     <repositories>
+        <repository>
+            <id>zzyd-repos</id>
+            <url>http://nexus.gzzzyd.com/repository/zzyd-blade/</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
         <repository>
             <id>aliyun-repos</id>
             <url>https://maven.aliyun.com/nexus/content/groups/public/</url>