目录
行云管家平台短信通知目前支持自定义配置短信网关,您可以使用行云管家平台自带的短信通知服务,同时也可以根据您的需要配置使用您自己的短信网关。
对于自定义短信网关,您需要按照以下描述进行开发及配置:
1、开发一个Web应用,该Web应用需要提供一个http接口,供行云管家平台调用。Web应用接收到调用请求后,再去调用您自有的短信通知业务代码,发送短信
2、到行云管家管理控制台中,配置启用短信网关,并使用自有短信网关,同时指定网关URL(即Web应用接口调用URL)
行云管家平台会以HTTP请求方式来对您的Web应用接口进行调用。
调用时的URL格式、请求头(header)、请求消息的消息体(body)、及接口内部处理过程描述如下。
行云管家平台所发出的每一次HTTP请求都会带上签名信息等相关参数,您的Web应用可以通过验证签名信息来确认请求是否由行云管家平台所发出。
行云管家平台在调用您的Web应用接口时,其调用请求为POST请求,签名信息等内容通过URL参数传递。格式如下:
其中:
1、{您的应用接口地址}: 为您的Web应用接口的调用地址,举例:假设您的Web应用访问IP为192.168.1.100,应用名称为sendMessage,则您的应用接口地址为:192.168.1.100/sendMessage
2、{行云管家平台OpenAPI AccessKey}: 行云管家管理控制台 OpenAPI中的AccessKey信息,可以通过登录行云管家管理控制台查询获得
3、{Web调用请求签名}: 签名信息,行云管家平台会通过一个固定的签名工具对调用请求进行签名,您的Web应用接口在接收到请求后,可以对该签名信息进行校验,以确保调用者是行云管家平台,保障调用安全
4、{调用请求唯一标识}: 行云管家平台每次调用您的Web应用接口时都会带上一个用于标识本次调用的请求标识,每次调用的请求标识都是不同的,您的Web应用可以根据此请求标识来识别请求
5、{时间戳}: 行云管家平台每次调用您的Web应用接口时都会带上一个时间戳,供您的Web应用做请求识别、日志记录等操作
行云管家平台在调用您的Web应用接口时,其请求头(header)消息如下:
该请求头(header)消息类似于普通的http请求的请求头消息,不做赘述。
行云管家平台在调用您的Web应用接口时,其请求消息体(body)如下:
该请求体(body)参数说明如下:
1、phones: 本次发送短信请求对应的短信接收者的手机号码列表,您的Web应用接收到请求后,需要调用您的短信通知业务代码,将短信发送给这些手机号
2、template: 本次发送短信请求所要使用的短信模版名称,您的Web应用接收到请求后,需要获取到该短信模版名称对应的模版内容(是一段文字),并将模版内容中的参数以实际值替换,然后将替换后的内容作为短信内容,发送给手机号码列表中的手机号
3、parameters: 本次发送短信请求对应短信模版中的参数值列表,您的Web应用接收到请求后,需要获取到模版内容(是一段文字),并将模版内容中的参数以本参数值列表中的对应值替换,然后将替换后的内容作为短信内容,发送给手机号码列表中的手机号
您的Web应用接口在接到调用请求时,其内部处理过程如下:
1、获取请求参数,包括:accessKeyId、请求版本version、请求签名signature、请求唯一标识nonce、请求时间戳timestamp
2、以相关参数配合accessKeySecret,计算得到请求对应的签名值
3、将计算得到的请求签名值与请求参数中所带签名值进行比较,判断请求合法性
4、如果请求合法
4.1、获取请求消息体(body)中各项参数值
4.2、根据请求参数值,结合消息模版,取得要发送的短信消息文本内容
4.3、调用您的短信发送业务代码,将短信消息文本内容发送予相应的手机号
4.4、根据您的实际需要,进行日志记录等其它操作
4.5、将操作结果返回予接口调用者(即行云管家)
5、如果请求不合法
5.1、根据您的实际需要,进行日志记录等操作
5.2、将操作结果返回予接口调用者(即行云管家)
前述Web应用调用请求URL中,有一个“Web调用请求签名”参数,该参数涉及的accessKeyId、accessKeySecret 是行云管家管理控制台OpenAPI中的AccessKey信息:

行云管家目前用到了六种短信模版,分别是:验证码模版、会话双因子认证模版、升级通知模版、监控预警模版、后付费资源消费预警模版、告警恢复模版。
短信模版内容分别如下:
【行云管家】您的验证码是:%code%,该验证码将会在%minute%分钟后过期。
【行云管家】您正在尝试进行的操作已开启双因子认证,请在%minute%分钟之内输入以下验证码:%code%,如非本人操作,请确认您的账户安全。
【行云管家】尊敬的用户,行云管家将于%startTime%进行版本升级,届时将暂停服务,请大家提前做好准备,升级工作预计于%endTime%完成,给您带来不便,敬请谅解!
【行云管家】您的%resourceType%“%resourceName%”由于%content%而产生预警,请确认预警原因。
【行云管家】您的云账户“%cloudAccount%”由于单日后付费资源消费大于%threshold%而产生预警,请确认预警原因。
【行云管家】您的%resourceType%“%resourceName%”%monitorItem%告警恢复,恢复时间:%time%。
提示:模版中两个%号隔开的是模版变动的内容,这些参数通过接口传递给第三方短信平台。如果您觉得行云管家短信网关配额不够,想用自有短信网关,可以参考我们的模版,关键参数通过接口传递您的自有短信网关上。
这里以Java Servlet开发Web应用为例,说明开发您的Web应用的过程。
1、首先,您需要创建一个Java Web工程,然后在工程中创建一个Java Servlet,Servlet的doPost()方法内容如下
response.setContentType("application/json; charset=UTF-8");
PrintWriter out = response.getWriter();
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 版本
String version = request.getParameter("version");
// 本次调用签名值
String signature = request.getParameter("signature");
// 行云管家管理控制台 OpenAPI中的AccessKey信息
String accessKeyId = request.getParameter("accessKeyId");
// 行云管家管理控制台 OpenAPI中的AccessKey信息
String accessKeySecret = "";
Map<String, Object> signMap = new HashMap<String, Object>();
signMap.put("timestamp", timestamp);
signMap.put("version", version);
signMap.put("nonce", nonce);
signMap.put("accessKeyId", accessKeyId);
// 通过工具类生成签名,与请求中携带的签名值进行比较,看是否相同
String signature0 = SignUtil.sign(null, signMap, "POST", accessKeySecret);
Map<String, Object> result = new HashMap<String, Object>();
if (signature.equals(signature0)) {
// 签名校验成功
// body为短信模版参数,拿到参数去自有短信平台发送短信
SmsRequest body = JSON.parseObject(request.getInputStream(), SmsRequest.class, null);
result.put("success", true);
result.put("msg", "签名成功");
//短信内容
String smsText = body.getSMSText();
//接收短信的手机号码列表
List<String> pns = body.getPhones();
//--------在这里调用您的业务代码,进行短信发送即可---------- start //
//--------在这里调用您的业务代码,进行短信发送即可---------- end //
} else {
// 签名校验失败,非行云管家平台发送请求
result.put("success", false);
result.put("msg", "签名失败");
System.out.println("签名失败");
}
out.println(JSON.toJSONString(result));
out.flush();
out.close();
}
2、用到的短信模版枚举类“SmsTemplate”代码内容如下:
/**
* 短信验证码
*/
VerifyCode(2),//验证码模版:验证码,超时(分)
Session2FA(2),//会话双因子认证:超时(分)、验证码
SystemUpgrade(2),//升级通知:开始时间,结束时间
MonitorAlert(3),//监控预警
CostAlert(2),//后付费资源消费预警
AlertRecover(4)//告警恢复
;
private int paramLength;//模版需要的参数长度
SmsTemplate(int paramLength) {
this.paramLength = paramLength;
}
public int getParamLength() {
return paramLength;
}
}
3、用到的请求体消息类“SmsRequest”代码内容如下:
//手机号列表
private List<String> phones = new ArrayList<String>();
//短信模版
private SmsTemplate template;
//模版参数
private List<String> parameters = new ArrayList<String>();
public List<String> getPhones() {
return phones;
}
public void setPhones(List<String> phones) {
this.phones = phones;
}
public SmsTemplate getTemplate() {
return template;
}
public void setTemplate(SmsTemplate template) {
this.template = template;
}
public List<String> getParameters() {
return parameters;
}
public void setParameters(List<String> parameters) {
this.parameters = parameters;
}
public String getSMSText() {
String sms = "";
switch (template) {
case VerifyCode :
sms = "【行云管家】您的验证码是:" + parameters.get(0) + ",该验证码将会在" +
parameters.get(1) + "分钟后过期。";
case Session2FA :
sms = "【行云管家】您正在尝试进行的操作已开启双因子认证,请在" + parameters.get(0) +
"分钟之内输入以下验证码:" + parameters.get(1) + ",如非本人操作,请确认您的账户安全。";
case SystemUpgrade :
sms = "【行云管家】尊敬的用户,行云管家将于" + parameters.get(0) +
"进行版本升级,届时将暂停服务,请大家提前做好准备,升级工作预计于" + parameters.get(1) +
"完成,给您带来不便,敬请谅解!";
case MonitorAlert :
sms = "【行云管家】您的" + parameters.get(0) + parameters.get(1) +
"由于" + parameters.get(2) + "而产生预警,请确认预警原因。";
case CostAlert :
sms = "【行云管家】您的云账户" + parameters.get(0) + "由于单日后付费资源消费大于" +
parameters.get(1) + "而产生预警,请确认预警原因。";
case AlertRecover :
sms = "【行云管家】您的" + parameters.get(0) + "“" + parameters.get(1) + "”" +
parameters.get(2) + "告警恢复,恢复时间:" + parameters.get(3) + "。";
default :
sms = "";
}
return sms;
}
}
4、用到的签名工具类“SignUtil”代码内容如下:
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class SignUtil {
private static final Object LOCK = new Object();
private static Mac macInstance;
private static final String ALGORITHM = "HmacSHA1";
public static String sign(String reqURl, Map<String, Object> parameterMap, String method,
String accessKeySecret) {
if (reqURl == null) {
reqURl = "/";
}
if (!reqURl.startsWith("/")) {
reqURl = "/" + reqURl;
}
String canonicalizedQueryString = canonicalize(parameterMap);
String StringToSign =
method + "&" +
percentEncode(reqURl) + "&" +
percentEncode(canonicalizedQueryString);
return computeSignature(accessKeySecret, StringToSign);
}
public static String sign(Map<String, Object> parameterMap, String method, String accessKeySecret) {
return sign("/", parameterMap, method, accessKeySecret);
}
private static String canonicalize(Map<String, Object> parameterMap) {
StringBuilder sb = new StringBuilder();
String[] parameterNames = parameterMap.keySet().toArray(new String[parameterMap.size()]);
Arrays.sort(parameterNames);
int count = 0;
for (String paramName : parameterNames) {
count++;
sb.append(percentEncode(paramName));
Object paramValue = parameterMap.get(paramName);
if (paramValue != null) {
sb.append("=").append(percentEncode(String.valueOf(paramValue)));
}
if (count < parameterNames.length) {
sb.append("&");
}
}
return sb.toString();
}
private static final String DEFAULT_ENCODING = "UTF-8";
private static String percentEncode(String value) {
try {
return value != null ? URLEncoder.encode(value, DEFAULT_ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String computeSignature(String key, String data) {
try {
byte[] signData = sign(key.getBytes(DEFAULT_ENCODING), data.getBytes(DEFAULT_ENCODING));
return "";
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("Unsupported algorithm: " + DEFAULT_ENCODING, ex);
}
}
private static byte[] sign(byte[] key, byte[] data) {
try {
if (macInstance == null) {
synchronized (LOCK) {
if (macInstance == null) {
macInstance = Mac.getInstance(ALGORITHM);
}
}
}
Mac mac = null;
try {
mac = (Mac) macInstance.clone();
} catch (CloneNotSupportedException e) {
mac = Mac.getInstance(ALGORITHM);
}
mac.init(new SecretKeySpec(key, ALGORITHM));
return mac.doFinal(data);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException("Unsupported algorithm: " + ALGORITHM, ex);
} catch (InvalidKeyException ex) {
throw new RuntimeException("Invalid key: " + key, ex);
}
}
private static String buildCanonicalizedResource(String resourcePath, Map<String, String> parameters) {
if (!resourcePath.startsWith("/")) {
throw new RuntimeException("Resource path should start with '/'");
}
StringBuilder builder = new StringBuilder();
builder.append(resourcePath);
if (parameters != null) {
String[] parameterNames = parameters.keySet().toArray(
new String[parameters.size()]);
Arrays.sort(parameterNames);
char separater = '?';
for (String paramName : parameterNames) {
builder.append(separater);
builder.append(paramName);
String paramValue = parameters.get(paramName);
if (paramValue != null) {
builder.append("=").append(paramValue);
}
separater = '&';
}
}
return builder.toString();
}
}
1、将上述Web工程打包为可发布到Web容器的应用,例如:打成war包
2、将打包输出物发布到Web容器中,例如:发布至Tomcat或Jetty中
3、测试并保证上述开发的Web应用接口可正常调用
1、登录至行云管家管理控制台
2、配置短信网关为您的自有短信网关


通过上述方法进行开发及配置,即可使用您的自有短信网关,在行云管家中进行验证码等内容的短信发送。