# 联容云短信服务
> 如标题所示,本文只简要介绍联容云短信的使用,以及Java的实现。
## 简介
容联云通讯为企业及个人开发者提供各种云通信业务,包括网络通话、视频、会议、呼叫中心/IVR、IM等服务,开发者通过容联云通讯官网提供的API实现各种通讯功能。
首先,注册联容云的账号,注册过程简单。相比阿里云、腾讯云等短信服务申请难度,联容云比较简单,也适合初学者学习。注册就送8元的额度,测试使用的每条短信约1至2分钱,这个价钱与额度还是够个人学习使用。
![image-20220502133323048](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220502133323048.png)
如上图所示:我们需要获取`Account SID`、`Auth Token`、`Rest URL`、`AppID`共4项。
之后绑定测试号码,可绑定3个测试号码。
![image-20220502135339492](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220502135339492.png)
这是直接充值-标准短信。
![image-20220502132941241](C:\Users\Zhoujiankang\AppData\Roaming\Typora\typora-user-images\image-20220502132941241.png)
创建您的应用,填写应用名称。
![image-20220502143540192](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220502143540192.png)
OK!在搞定注册,绑定测试号码之后,就正式开始学习联容云的SDK。
![image-20220502141156279](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220502141156279.png)
## Java SDK
#### 前提条件
在安装和使用Java SDK前,确保您已经:
- 安装Java环境。云通讯 Java SMS SDK要求使用JDK1.7或更高版本;
- 已经注册云通讯账号且已获取auth token,并已开通应用。
#### Java SDK安装方式
- 添加maven依赖
- 在集成开发环境中导入JAR文件
##### 添加maven依赖
如果您使用Maven管理Java项目,可以通过在pom.xml文件中添加Maven依赖安装云通讯 [Java SMS SDK](https://mvnrepository.com/artifact/com.cloopen/java-sms-sdk)。您可以在Maven库中查看Maven依赖信息。
```xml
<dependency>
<groupId>com.cloopen</groupId>
<artifactId>java-sms-sdk</artifactId>
<version>1.0.3</version>
</dependency>
```
##### 在集成开发环境中导入JAR文件
无论您使用Eclipse还是IntelliJ IDEA作为集成开发环境,都可以通过导入 [java-sms-sdk](https://repo1.maven.org/maven2/com/cloopen/java-sms-sdk/1.0.1/java-sms-sdk-1.0.1.jar) JAR文件的方式安装云通讯 Java SMS SDK。
Java SDK GitHub地址
[https://github.com/cloopen/java-sms-sdk](https://github.com/cloopen/java-sms-sdk内容)[内容](https://github.com/cloopen/java-sms-sdk内容)
##### 调用示例
```java
//生产环境请求地址:app.cloopen.com
String serverIp = "app.cloopen.com";
//请求端口
String serverPort = "8883";
//主账号,登陆云通讯网站后,可在控制台首页看到开发者主账号ACCOUNT SID和主账号令牌AUTH TOKEN
String accountSId = "accountSId";
String accountToken = "auth token";
//请使用管理控制台中已创建应用的APPID
String appId = "appId";
CCPRestSmsSDK sdk = new CCPRestSmsSDK();
sdk.init(serverIp, serverPort);
sdk.setAccount(accountSId, accountToken);
sdk.setAppId(appId);
sdk.setBodyType(BodyType.Type_JSON);
String to = "1352*******";
String templateId= "templateId";
String[] datas = {"变量1","变量2","变量3"};
String subAppend="1234"; //可选 扩展码,四位数字 0~9999
String reqId="fadfafas"; //可选 第三方自定义消息id,最大支持32位英文数字,同账号下同一自然天内不允许重复
//HashMap<String, Object> result = sdk.sendTemplateSMS(to,templateId,datas);
HashMap<String, Object> result = sdk.sendTemplateSMS(to,templateId,datas,subAppend,reqId);
if("000000".equals(result.get("statusCode"))){
//正常返回输出data包体信息(map)
HashMap<String,Object> data = (HashMap<String, Object>) result.get("data");
Set<String> keySet = data.keySet();
for(String key:keySet){
Object object = data.get(key);
System.out.println(key +" = "+object);
}
}else{
//异常返回输出错误码和错误信息
System.out.println("错误码=" + result.get("statusCode") +" 错误信息= "+result.get("statusMsg"));
}
```
通过刚才的示例代码,要获取到开发者主账号`ACCOUNT SID`、主账号令牌`AUTH TOKEN`、应用ID`AppID`,生产环境请求地址和端口都是固定的。
在这创建`CCPRestSmsSDK `对象,设立属性值。
```java
CCPRestSmsSDK sdk = new CCPRestSmsSDK();
sdk.init(serverIp, serverPort);
sdk.setAccount(accountSId, accountToken);
sdk.setAppId(appId);
// json请求包体
sdk.setBodyType(BodyType.Type_JSON);
```
然后建立`sendTemplateSMS`发送模板。(先学习一下源码中`sendTemplateSMS`方法是如何实现的)
```java
/**
* 发送短信模板请求
*
* @param to
* 必选参数 短信接收端手机号码集合,用英文逗号分开,每批发送的手机号数量不得超过100个
* @param templateId
* 必选参数 模板Id
* @param datas
* 可选参数 内容数据,用于替换模板中{序号}
* @param subAppend 可选参数 扩展码,四位数字 0~9999
* @param reqId 可选参数 自定义消息id,最大支持32位,同账号下同一自然天内不允许重复
*
* @return
*/
public HashMap<String, Object> sendTemplateSMS(String to, String templateId, String[] datas,String subAppend,String reqId) {
return send(to,templateId,datas,subAppend,reqId);
}
private HashMap<String, Object> send(String to, String templateId, String[] datas,String subAppend,String reqId){
HashMap<String, Object> validate = accountValidate();
if(validate!=null)
return validate;
if ((StringUtils.isEmpty(to)) || (StringUtils.isEmpty(App_ID)) || (StringUtils.isEmpty(templateId)))
throw new IllegalArgumentException("必填参数:" + (StringUtils.isEmpty(to) ? " 手机号码 " : "") + (StringUtils.isEmpty(templateId) ? " 模板Id " : "") + "为空");
String timestamp = DateUtil.dateToStr(new Date(), DateUtil.DATE_TIME_NO_SLASH);
String sig = ParmUtils.generateSig(ACCOUNT_SID, ACCOUNT_TOKEN, timestamp);
String authorization="";
try {
authorization = ParmUtils.generateAuthorization(ACCOUNT_SID,timestamp);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new RuntimeException("生成authorization异常" + e.getMessage());
}
String url = getBaseUrl().append("/" + AcountType + "/").append(ACCOUNT_SID).append("/" + TemplateSMS + "?sig=").append(sig).toString();
String requsetbody = "";
if (BODY_TYPE == BodyType.Type_JSON) {
requsetbody = generateJson(to,templateId,datas,subAppend,reqId);
}else {
requsetbody = generateXml(to,templateId,datas,subAppend,reqId);
}
log.info("sendTemplateSMS Request url:" + url);
log.info("sendTemplateSMS Request body:" + requsetbody);
String result = HttpClientUtil.post(url,authorization,requsetbody,BODY_TYPE, Constant.UTF8);
if (result == null || result == "")
{
return getMyError("172001", "网络错误");
}
try {
if (BODY_TYPE == BodyType.Type_JSON) {
return jsonToMap(result);
} else {
return xmlToMap(result);
}
} catch (Exception e) {
return getMyError("172003", "返回包体错误");
}
}
```
来看一下,下面5个参数的含义吧。其中`to`、`templateId`是必选参数,`datas`、`subAppend`、` reqId`是可选参数。
```java
* @param to
* 必选参数 短信接收端手机号码集合,用英文逗号分开,每批发送的手机号数量不得超过100个
* @param templateId
* 必选参数 模板Id
* @param datas
* 可选参数 内容数据,用于替换模板中{序号}
* @param subAppend 可选参数 扩展码,四位数字 0~9999
* @param reqId 可选参数 自定义消息id,最大支持32位,同账号下同一自然天内不允许重复
```
值得一说的是,个人开发者,只有3个测试号码。因此,这里的`to`只能填写绑定测试号码。`templateId`模板Id即为你创建的模板ID。由于不能上线,也不能创建自定义模板。只能使用默认模板。
![image-20220502150506789](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220502150506789.png)
其中{序号}(例如{1})就是借口替换的变量,也就是参数`datas`。
例如: code为生成的验证码,"2"表示在2分钟内。
```java
String[] codes = {code, "2"};
```
这是手机端接收到验证码。
`【云通讯】您使用的是云通讯短信模板,您的验证码是869056,请于2分钟内正确输入`
![image-20220502153615536](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220502153615536.png)
由于俺只是个人开发者,应用上不了线,更多功能也是用不了。其他参数`subAppend`和`reqId`的含义就不深度介绍了(没钱用不了),这两个参数也是可选参数,可以设置,也可以不设置。看使用需求,一般都是
参数讲完了,再来看看这部分代码。
```java
// 首先判断状态码 00000表示短信发送成功
if("000000".equals(result.get("statusCode"))){
//正常返回输出data包体信息(map)
HashMap<String,Object> data = (HashMap<String, Object>) result.get("data");
//keySet() -返回map所有键的集合
Set<String> keySet = data.keySet();
for(String key:keySet){
Object object = data.get(key);
System.out.println(key +" = "+object);
}
}else{
//异常返回输出错误码和错误信息
System.out.println("错误码=" + result.get("statusCode") +" 错误信息= "+result.get("statusMsg"));
}
```
![image-20220502162709083](https://mybolg-typora.oss-cn-beijing.aliyuncs.com/image-20220502162709083.png)
这样短信服务就调用成功啦,短信内容发送到手机。
# SpringBoot项目中短信服务的实现
#### 添加依赖
```xml
<!--联容云SMS依赖-->
<dependency>
<groupId>com.cloopen</groupId>
<artifactId>java-sms-sdk</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
```
#### 配置文件
```yaml
server:
port: 8204
spring:
application:
name: service-msm
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 1800000
lettuce:
pool:
max-active: 20
max-wait: -1
max-idle: 5
min-idle: 0
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# rabbitmq:
# host: 127.0.0.1
# port: 5672
# username: gust
# password: guest
# 联容云配置
lianrongyun:
serverIp: app.cloopen.sendTemplate
serverPort: 8883
accountSId: XXXXXXXXXXXXXXXXXXXXXXXXXXX
accountToken: XXXXXXXXXXXXXXXXXXXXXXXXXXX
appId: XXXXXXXXXXXXXXXXXXXXXXXXXXX
templateId: 1
```
```java
@Configuration
@Data
@ConfigurationProperties(prefix = "lianrongyun")
public class LRYConfig
{
private String serverIp;
private String serverPort;
private String accountSId;
private String accountToken;
private String appId;
private String templateId;
}
```
```java
package com.zhoujk.yygh.msm.service.impl;
import com.cloopen.rest.sdk.BodyType;
import com.cloopen.rest.sdk.CCPRestSmsSDK;
import com.zhoujk.yygh.hosp.vo.msm.MsmVo;
import com.zhoujk.yygh.msm.common.LRYConfig;
import com.zhoujk.yygh.msm.common.Sms;
import com.zhoujk.yygh.msm.service.MsmService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Set;
/**
* @author : zhoujiankang
* @Desc: 联容云短信实现类
* @since : 2022/4/30 11:06
*/
@Slf4j
@Service
public class MsmServiceImpl implements MsmService
{
@Autowired private LRYConfig lryConfig;
private CCPRestSmsSDK getSdk()
{
CCPRestSmsSDK sdk = new CCPRestSmsSDK();
sdk.init(lryConfig.getServerIp(), lryConfig.getServerPort());
sdk.setAccount(lryConfig.getAccountSId(), lryConfig.getAccountToken());
sdk.setAppId(lryConfig.getAppId());
sdk.setBodyType(BodyType.Type_JSON);
return sdk;
}
@Override
public boolean send(String phone)
{
//判断手机号是否为空
return !StringUtils.isEmpty(phone);
}
/**
*
* @param phone 手机号
* @param codes 内容数据,用于替换模板中{序号}
* @return
*/
public boolean sendTemplate(String phone, String[] codes)
{
HashMap<String, Object> result = this.getSdk().sendTemplateSMS(phone, lryConfig.getTemplateId(), codes);
log.info("SDKTestGetSubAccounts result=" + result);
if ("000000".equals(result.get("statusCode"))) {
//正常返回输出data包体信息(map) 打印出来看看
HashMap<String, Object> data = (HashMap<String, Object>) result.get("data");
Set<String> keySet = data.keySet();
for (String key : keySet) {
Object object = data.get(key);
System.out.println(key + " = " + object);
}
} else {
//异常返回输出错误码和错误信息
System.out.println("错误码=" + result.get("statusCode") + " 错误信息= " + result.get("statusMsg"));
return false;
}
return true;
}
@Override
public boolean send(String phone, String code)
{
String[] codes = {code, "2"};
return sendTemplate(phone, codes);
}
@Override
public boolean send(MsmVo msmVo)
{
String name = (String) msmVo.getParam().get("name");
String date = (String) msmVo.getParam().get("reserveDate");
//手机号不为空且就医提醒不为空,走提醒就医的短信模板!
if (!StringUtils.isEmpty(msmVo.getPhone()) && !StringUtils.isEmpty((CharSequence) msmVo.getParam().get("jiuyitixing"))) {
String code1 = Sms.SUCCESS + name;
String code2 = Sms.DATE + date;
String[] codes = {code1, code2};
return this.sendTemplate(msmVo.getPhone(), codes);
}
//手机号不为空,进行发送,这个走的是预约订单成功的短信模板
if (!StringUtils.isEmpty(msmVo.getPhone())) {
String code1 = Sms.INFO + name;
String code2 = Sms.DATE + date;
String[] codes = {code1, code2};
return this.sendTemplate(msmVo.getPhone(), codes);
}
return false;
}
}
```
## 参考
https://github.com/cloopen/java-sms-sdk
# 附录
这是我同学写的腾讯云短信实现。
##### 依赖
```xml
<!--腾讯云短信SDK-->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>3.1.405</version>
</dependency>
```
##### 实现类
```java
import com.bunny.edu.user.exception.UserErrorEnum;
import com.bunny.framework.exception.BusinessException;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.sms.v20190711.SmsClient;
import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest;
import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse;
import com.tencentcloudapi.sms.v20190711.models.SendStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@RefreshScope
public class TencentyunSmsUtil {
private static String secretId;
private static String secretKey;
public static String sendSms(String mobile, String code) {
try {
// 实例化一个认证对象
Credential cred = new Credential(secretId, secretKey);
// 配置超时,请求方法等,可跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("sms.tencentcloudapi.com");
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化 SMS 的 client 对象
SmsClient client = new SmsClient(cred, "ap-guangzhou", clientProfile);
// 在抽离SMS时以下内容全部通过数据库实体传进来
SendSmsRequest req = new SendSmsRequest();
// 短信应用ID
String sdkAppId = "1400501695";
req.setSmsSdkAppid(sdkAppId);
// 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名
String signName = "趣玩无忧";
req.setSign(signName);
// 在抽离SMS时以下内容通过MQ获得
// 模板 ID: 必须填写已审核通过的模板 ID
String templateId = "906467";
req.setTemplateID(templateId);
// 手机号
String[] phoneNumberSet = {"+86" + mobile};
req.setPhoneNumberSet(phoneNumberSet);
// 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空
// 目前就一个,填写验证码,自己程序生成
String[] templateParamSet = {code};
req.setTemplateParamSet(templateParamSet);
// 通过 client 对象调用 SendSms 方法发起请求
SendSmsResponse resp = client.SendSms(req);
SendStatus[] sendStatusSet = resp.getSendStatusSet();
if ("Ok".equals(sendStatusSet[0].getCode())) {
return sendStatusSet[0].getCode();
} else {
throw new Exception(sendStatusSet[0].getMessage());
}
} catch (Exception e) {
log.error("Tencentyun 短信发送失败:{} ,{}", mobile, e);
throw new BusinessException(UserErrorEnum.MESSAGE_FAILED_TO_SEND);
}
}
@Value("${sms.secretId}")
public void setSecretId(String secretId) {
TencentyunSmsUtil.secretId = secretId;
}
@Value("${sms.secretKey}")
public void setSecretKey(String secretKey) {
TencentyunSmsUtil.secretKey = secretKey;
}
}
```
`yaml`配置
```yaml
sms:
secretId: XXXXXXXXXXXXXXXXXX
secretKey: XXXXXXXXXXXXxXXXX
```
联容云短信SMS服务