前言

基于阿里云短信服务,支持单发、群发功能
注意:我申请的验证码签名,业务逻辑部分需要根据自己的需求再进行修改,

平台准备

这个准备工作需要在阿里云平台-短信服务操作,必须得有实名等资质,然后才能继续申请签名申请模板。这个相对繁琐一点,直接去看文档流程。
【准备工作,官方文档

公共代码

引入Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibabacloud-dysmsapi20170525</artifactId>
<version>2.0.24</version>
</dependency>

工具类

model类

用户实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import lombok.Data;
/**
* 用户实体
*/
@Data
public class SmsModel {
/**
* 姓名
*/
private String name;
/**
* 手机号
*/
private String phone;
/**
* 发送内容
*/
private String content;
/**
* 发送验证码
*/
private String verifyCode;
/**
* 签名
*/
private String signName;

public SmsModel() {}
public SmsModel(String phone) {this.phone = phone;}
public SmsModel(String phone, String smsCode) {
this.phone = phone;
this.content = content;
}
public SmsModel(String phone, String smsCode, String name) {
this.phone = phone;
this.content = content;
this.name = name;
}

}

请求阿里云API请求参数实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import lombok.Data;
/**
* 请求阿里云API请求参数实体
*/
@Data
public class SmsSendModel {
/**
* 手机号
*/
private String phone;
/**
* 发送内容
*/
private String templateParam;
/**
* 签名
*/
private String signName;

public SmsSendModel() {}
public SmsSendModel(String phone) {
this.phone = phone;
}
public SmsSendModel(String phone, String templateParam) {
this.phone = phone;
this.templateParam = templateParam;
}
}

Util工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
import com.example.demo.entity.Sms;
import com.example.demo.model.SmsModel;
import com.example.demo.model.SmsSendModel;

import com.alibaba.fastjson.JSONArray;
import com.google.gson.Gson;
import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import com.aliyun.core.http.HttpClient;
import com.aliyun.core.http.ProxyOptions;
import com.aliyun.httpcomponent.httpclient.ApacheAsyncHttpClientBuilder;
import com.aliyun.sdk.service.dysmsapi20170525.models.*;
import com.aliyun.sdk.service.dysmsapi20170525.*;
import darabonba.core.client.ClientOverrideConfiguration;
import javax.net.ssl.KeyManager;
import javax.net.ssl.X509TrustManager;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

public class AliCloudSms {
// 阿里云短信配置信息
private static final String ACCESS_KEY_ID = "xxxxxx";// 填写自己创建accessKeyId
private static final String ACCESS_KEY_SECRET = "xxxxx";// 填写自己创建accessKeySecret
private static final String SECURITY_TOKEN = ""; // STS令牌
private static final String REGION_ID = "cn-hangzhou";
private static final String PRODUCT = "Dysmsapi";
private static final String DOMAIN = "dysmsapi.aliyuncs.com";
private static final String SING_NAME = "内涵猫"; // 签名
private static final String TEMPLATE_CODE = "SMS_2000****";// 模板 SMS_154950909、SMS_205894328
// 代理
private static final String PROXY_HOSTNAME = "代理地址";
private static final Integer PROXY_PORT = 9001;
private static final String PROXY_USERNAME = "代理用户名";
private static final String PROXY_PASSWORD = "代理密码";

// HttpClient控制开关
private static final Boolean isHttpClient = false;

/**
* 单发短信
* @param sms
*/
public static void sendSmsOne(SmsSendModel sms){
// HttpClient配置
HttpClient httpClient = null;
if (isHttpClient == true){
httpClient = new ApacheAsyncHttpClientBuilder()
// 设置连接超时时间,默认为10秒
.connectionTimeout(Duration.ofSeconds(10))
// 设置响应超时时间,默认为20秒
.responseTimeout(Duration.ofSeconds(10))
// 设置连接池大小
.maxConnections(128)
// 设置连接池超时时间,默认为30秒
.maxIdleTimeOut(Duration.ofSeconds(50))
// 配置代理
.proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress(PROXY_HOSTNAME, PROXY_PORT)).setCredentials(PROXY_USERNAME, PROXY_PASSWORD))
// 如果是https连接,则需要配置证书,或者忽略证书(.ignoreSSL(true))
.x509TrustManagers(new X509TrustManager[]{})
.keyManagers(new KeyManager[]{})
.ignoreSSL(false)
.build();
}
// 配置凭据认证信息,包括ak、secret、token
StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
// 请确保环境变量ACCESS_KEY_ID和ACCESS_KEY_SECRET不为空
.accessKeyId(ACCESS_KEY_ID).accessKeySecret(ACCESS_KEY_SECRET)
// 使用STS令牌
//.securityToken(SECURITY_TOKEN)
.build());
// 配置客户端
AsyncClient client = AsyncClient.builder()
// 使用配置好的HttpClient,否则使用默认的HttpClient (Apache HttpClient)
//.httpClient(httpClient)
.credentialsProvider(provider)
// 服务级别配置
//.serviceConfiguration(Configuration.create())
// 重写客户端级配置,可以设置端点、Http请求参数等。
.overrideConfiguration(
ClientOverrideConfiguration.create()
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
.setEndpointOverride(DOMAIN)
.setConnectTimeout(Duration.ofSeconds(30))
)
.build();
// API请求的参数设置
SendSmsRequest sendSmsRequest = SendSmsRequest.builder()
.phoneNumbers(sms.getPhone())
.signName(sms.getSignName())
.templateCode(TEMPLATE_CODE)
.templateParam(sms.getTemplateParam())
// .templateParam(sms.getVerifyCode())
// 请求级配置重写,可以设置Http请求参数等。
// .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new HttpHeaders()))
.build();
// 异步获取API请求的返回值
CompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);
// 同步获取API请求的返回值
try{
SendSmsResponse resp = response.get();
System.out.println(new Gson().toJson(resp));
// 异步处理返回值
response.thenAccept(res -> {
System.out.println(new Gson().toJson(res));
}).exceptionally(throwable -> { // 处理异常
System.out.println(throwable.getMessage());
return null;
});
// 关闭客户端
client.close();
}catch (Exception e){
e.printStackTrace();
}
}

/**
* 群发短信
* @param params
*/
public static void sendSmsBatch(SmsSendModel params){
// HttpClient配置
HttpClient httpClient = null;
if (isHttpClient == true){
httpClient = new ApacheAsyncHttpClientBuilder()
// 设置连接超时时间,默认为10秒
.connectionTimeout(Duration.ofSeconds(10))
// 设置响应超时时间,默认为20秒
.responseTimeout(Duration.ofSeconds(10))
// 设置连接池大小
.maxConnections(128)
// 设置连接池超时时间,默认为30秒
.maxIdleTimeOut(Duration.ofSeconds(50))
// 配置代理
.proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress(PROXY_HOSTNAME, PROXY_PORT)).setCredentials(PROXY_USERNAME, PROXY_PASSWORD))
// 如果是https连接,则需要配置证书,或者忽略证书(.ignoreSSL(true))
.x509TrustManagers(new X509TrustManager[]{})
.keyManagers(new KeyManager[]{})
.ignoreSSL(false)
.build();
}
// 配置凭据认证信息,包括ak、secret、token
StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
// 请确保环境变量ACCESS_KEY_ID和ACCESS_KEY_SECRET不为空
.accessKeyId(ACCESS_KEY_ID)
.accessKeySecret(ACCESS_KEY_SECRET)
// 使用STS令牌
//.securityToken(System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN"))
.build());
// 配置客户端
AsyncClient client = AsyncClient.builder()
.region(REGION_ID)
// 使用配置好的HttpClient,否则使用默认的HttpClient (Apache HttpClient)
//.httpClient(httpClient)
.credentialsProvider(provider)
// 服务级别配置
//.serviceConfiguration(Configuration.create())
// 重写客户端级配置,可以设置端点、Http请求参数等。
.overrideConfiguration(
ClientOverrideConfiguration.create()
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
.setEndpointOverride(DOMAIN)
.setConnectTimeout(Duration.ofSeconds(30))
)
.build();
// API请求的参数设置
SendBatchSmsRequest sendBatchSmsRequest = SendBatchSmsRequest.builder()
.templateCode(TEMPLATE_CODE)
.signNameJson(params.getSignName())
.phoneNumberJson(params.getPhone())
.templateParamJson(params.getTemplateParam())
// 请求级配置重写,可以设置Http请求参数等。
// .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new HttpHeaders()))
.build();
// 异步获取API请求的返回值
CompletableFuture<SendBatchSmsResponse> response = client.sendBatchSms(sendBatchSmsRequest);
try{
// 异步获取API请求的返回值
SendBatchSmsResponse resp = response.get();
// System.out.println(new Gson().toJson(resp));
// 异步处理返回值
response.thenAccept(res -> {
System.out.println(new Gson().toJson(res));
}).exceptionally(throwable -> {
// 处理异常
System.out.println(throwable.getMessage());
return null;
});
// 关闭客户端
client.close();
}catch (Exception e){
e.printStackTrace();
}
}

/**
* 处理阿里云API请求参数
* @return
*/
public static SmsSendModel batchParam(SmsModel params) {
Map<String,Object> tpm = new HashMap<>();
if (params.getVerifyCode() != null && params.getContent() == null){
tpm.put("code", params.getVerifyCode());
}else {
tpm.put("content", params.getContent());
}
SmsSendModel result = new SmsSendModel();
// 1390000****
result.setPhone(params.getPhone());
// {"code":"1111"}
result.setTemplateParam(JSONArray.toJSONString(tpm));
// 阿里云
result.setSignName(params.getSignName());
return result;
}

/**
* 批量处理阿里云API请求参数
* @return
*/
public static SmsSendModel batchParams(List<SmsModel> list) {
List<String> phoneList = new LinkedList<>();
List<String> signList = new LinkedList<>();
List<Map<String,Object>> paramsList = new LinkedList<>();
for (int i = 0; i < list.size(); i++) {
phoneList.add(list.get(i).getPhone());
signList.add(SING_NAME);
// 添加内容
Map<String,Object> pm = new HashMap<>();
if (list.get(i).getVerifyCode() != null && list.get(i).getContent() == null){
pm.put("code", list.get(i).getVerifyCode());
}else {
pm.put("content", list.get(i).getContent());
}
paramsList.add(pm);
}
SmsSendModel result = new SmsSendModel();
// ["1590000****","1350000****"]
result.setPhone(JSONArray.toJSONString(phoneList));
// [{"name":"TemplateParamJson"},{"name":"TemplateParamJson"}]
result.setTemplateParam(JSONArray.toJSONString(paramsList));
// ["阿里云","阿里巴巴"]
result.setSignName(JSONArray.toJSONString(signList));
return result;
}

public static void main(String[] args) {
SmsSendModel sms = new SmsSendModel("15762178818");
sendSmsOne(sms);
}

}


请求层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import com.example.demo.common.Result;
import com.example.demo.entity.Sms;
import com.example.demo.model.SmsModel;
import com.example.demo.model.SmsSendModel;
import com.example.demo.utils.aliyun.AliCloudSms;
import org.springframework.web.bind.annotation.*;
import java.util.LinkedList;
import java.util.List;

/**
* 阿里云Controller
*/
@RestController
@RequestMapping("/sms/ali")
public class SmsAliController {
/**
* 发送短信(单条)
* @param phone
* @return
*/
@GetMapping("/sendSms")
public Result sendSms() {
String phone = "150*******"
SmsModel sm = new SmsModel();
sm.setPhone(phone);
sm.setVerifyCode(String.valueOf((int)(Math.random() * 900000 + 100000)));
sm.setSignName("内涵猫");
SmsSendModel m = AliCloudSms.batchParam(sm);
AliCloudSms.sendSmsOne(m);
return Result.success(m);
}

/**
* 群发短信
* @param params
* @return
*/
@PostMapping("/batchSendSms")
public Result batchSendSms() {
List<SmsModel> list = new LinkedList<>();
SmsModel ssm1 = new SmsModel();
ssm1.setSignName("内涵猫");
ssm1.setPhone("150*******");
ssm1.setName("张三");
ssm1.setVerifyCode(String.valueOf((int)(Math.random() * 900000 + 100000)));
// ssm1.setContent("KFC疯狂星期四,快来抢购吧!");
list.add(ssm1);

SmsModel ssm2 = new SmsModel();
ssm2.setSignName("内涵猫");
ssm2.setPhone("133*******");
ssm2.setName("张三");
ssm2.setVerifyCode(String.valueOf((int)(Math.random() * 900000 + 100000)));
// ssm2.setContent("KFC疯狂星期四,快来抢购吧!");
list.add(ssm2);

SmsModel ssm3 = new SmsModel();
ssm3.setSignName("内涵猫");
ssm3.setPhone("155*******");
ssm3.setName("王五");
ssm3.setVerifyCode(String.valueOf((int)(Math.random() * 900000 + 100000)));
// ssm3.setContent("KFC疯狂星期四,快来抢购吧!");
list.add(ssm3);

// 封装入参格式
SmsSendModel m = AliCloudSms.batchParams(list);
// 发送短信
AliCloudSms.sendSmsBatch(m);
return Result.success();
}
}

单条发送信息(SendSms)

阿里云官方文档:【单条发送短信

  1. PhoneNumbers
    • 国内短信,手机号码格式:+/+86/0086/86或无任何前缀的11位手机号码,例如:1390000**
    • 国际/港澳台消息,手机号码格式:国际区号+号码,例如:852000012**
    • 支持对多个手机号码发送短信,手机号码之间以半角逗号(,)分隔。上限为1000个手机号码。
    • 批量调用相对于单条调用及时性稍有延迟。
    • 验证码类型短信,建议使用单独发送的方式。
  2. SignName
    • 您可以登录短信服务控制台,选择国内消息或国际/港澳台消息,在签名管理页面获取。
    • 必须是已添加、并通过审核的短信签名。更多短信签名规范,请参见短信签名规范
  3. TemplateCode
  4. TemplateParam
    • 支持传入多个参数
    • 如果JSON中需要带换行符,请参照标准的JSON协议处理。模板变量规范,请参见短信模板规范
  5. SmsUpExtendCode
    • 上行短信指发送给通信服务提供商的短信,用于定制某种服务、完成查询,或是办理某种业务等,需要收费,按运营商普通短信资费进行扣费。
    • 扩展码是生成签名时系统自动默认生成的,不支持自行传入。
    • 无特殊需要可忽略此字段。
  6. OutId
    • 无特殊需要可忽略此字段。
字段名称 类型 字段详情 示例值
PhoneNumbers string 接收短信的手机号码。 1390000****
SignName string 短信签名名称 阿里云
TemplateCode string 短信模板CODE SMS_15255**** -
TemplateParam string 短信模板变量对应的实际值 {"name":"张三","number":"1390000****"}
SmsUpExtendCode string 上行短信扩展码,JSON数组格式 90999
OutId string 外部流水扩展字段 abcdefg
字段名称 类型 字段详情 示例值 补充
Code string 请求状态码 OK 其他错误码,请参见错误码列表
Message string 是否调用接口成功的描述 OK -
BizId string 发送回执ID 9006197469364984400 -
RequestId string 请求ID F655A8D5-B967-440B-8683-DAD6FF8D230E -

批量发送信息(SendBatchSms)

阿里云官方文档:【批量发送短信

  1. PhoneNumberJson
    • 国内短信,手机号码格式:+/+86/0086/86或无任何前缀的11位手机号码,例如:1390000**
    • 国际/港澳台消息,手机号码格式:国际区号+号码,例如:852000012**
    • 支持对多个手机号码发送短信,手机号码之间以半角逗号(,)分隔。上限为1000个手机号码。
    • 批量调用相对于单条调用及时性稍有延迟。
    • 验证码类型短信,建议使用单独发送的方式。
  2. SignNameJson
    • 您可以登录短信服务控制台,选择国内消息或国际/港澳台消息,在签名管理页面获取。
    • 必须是已添加、并通过审核的短信签名;且短信签名的个数必须与手机号码的个数相同、内容一一对应。
  3. TemplateCode
    • 必须是已添加、并通过审核的模板CODE;且发送国际/港澳台消息时,请使用国际/港澳台短信模版。
    • 参考取值来源:【增】AddSmsSign、【查】QuerySmsSignList、【改】ModifySmsSign
  4. TemplateParamJson
    • 支持传入多个参数
    • 如果JSON中需要带换行符,请参照标准的JSON协议处理。模板变量规范,请参见短信模板规范
  5. SmsUpExtendCodeJson
    • JSON数组格式。
    • 上行短信指发送给通信服务提供商的短信,用于定制某种服务、完成查询,或是办理某种业务等,需要收费,按运营商普通短信资费进行扣费。
    • 扩展码是生成签名时系统自动默认生成的,不支持自行传入。
    • 无特殊需要可忽略此字段。
  6. OutId
    • 长度小于256的字符串。
    • 无特殊需要可忽略此字段。
字段名称 类型 字段详情 示例值 正则
PhoneNumberJson string 接收短信的手机号码。 ["1590000****","1350000****"] ^\[\".*?\"(?:\,\".*?\")*\]$
SignNameJson string 短信签名名称 ["阿里云","阿里巴巴"] ^\[\".*?\"(?:\,\".*?\")*\]$
TemplateCode string 短信模板CODE SMS_15255**** -
TemplateParamJson string 短信模板变量对应的实际值 [{"name":"TemplateParamJson"},{"name":"TemplateParamJson"}] -
SmsUpExtendCodeJson string 上行短信扩展码,JSON数组格式 ["90999","90998"] -
OutId string 外部流水扩展字段,长度小于256的字符串 abcdefg -
字段名称 类型 字段详情 示例值 补充
Code string 请求状态码 OK 其他错误码,请参见错误码列表
Message string 是否调用接口成功的描述 OK -
BizId string 发送回执ID 9006197469364984400 -
RequestId string 请求ID F655A8D5-B967-440B-8683-DAD6FF8D230E -