移动MAS短信平台就是一个发送短信的平台,可以发送普通短信、模板短信、一对一,一对多等短信
下面主要是基于Java(HTTP方式)实现短信的下行(发送)上行(接受)和接受短信状态
接口全是根据HTTP接口文档写的,有需要的可以关注下面的公众号回复:移动MAS接口文档
一、创建账号
1-1、登录后的界面如下图所示(http://mas.10086.cn)
1-2、配置接口
从上图左边的管理菜单进入
1-3、获取签名
二、代码
根据接口文档我写了下面的工具类,其实就是把接口文档翻译一下。因为我们是http请求,json数据格式。所以使用到了一个jsonUtils和一个httpUtils
2-1、HttpUtils
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;
public class HttpUtils {
private static Logger logger = LoggerFactory.getLogger(HttpUtils.class);
public static String sendPost(String url, String param, String orderType) throws Exception {
PrintWriter out = null;
String result = "";
DataInputStream dis=null;
// logger.debug("url=" + url + ";orderType=" + orderType + ";param=" + param);
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
conn.addRequestProperty("orderType", orderType);
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setRequestProperty("Content-Type", "application/Json; charset=UTF-8");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
//conn.setRequestProperty("contentType", "UTF-8");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
// out = new PrintWriter(conn.getOutputStream());
// // 发送请求参数
// out.print(param);
// // flush输出流的缓冲
// out.flush();
OutputStream os = conn.getOutputStream();
os.write(param.getBytes("UTF-8"));
os.flush();
// 定义BufferedReader输入流来读取URL的响应
byte[] bs = new byte[0];
byte[] btemp = new byte[1024];
int count =0;
dis = new DataInputStream(conn.getInputStream());
while((count = dis.read(btemp))>-1) {
byte[] temp = bs;
bs=new byte[temp.length+count];
System.arraycopy(temp, 0, bs, 0, temp.length);
System.arraycopy(btemp, 0, bs, temp.length, count);
}
result = new String(bs, "UTF-8");
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (dis != null) {
dis.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
public static String sendGet(String url, Map<String, Object> param) throws Exception {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key).toString());
}
}
URI uri = builder.build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
}
} catch (Exception e) {
e.printStackTrace();
throw e;
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
return resultString;
}
public static String sendGet(String url) throws Exception {
return sendGet(url, null);
}
public static String getEncoding(String str) {
String encode = "GB2312";
try {
if (str.equals(new String(str.getBytes(encode), encode))) { //判断是不是GB2312
String s = encode;
return s; //是的话,返回“GB2312“,以下代码同理
}
} catch (Exception exception) {
}
encode = "ISO-8859-1";
try {
if (str.equals(new String(str.getBytes(encode), encode))) { //判断是不是ISO-8859-1
String s1 = encode;
return s1;
}
} catch (Exception exception1) {
}
encode = "UTF-8";
try {
if (str.equals(new String(str.getBytes(encode), encode))) { //判断是不是UTF-8
String s2 = encode;
return s2;
}
} catch (Exception exception2) {
}
encode = "GBK";
try {
if (str.equals(new String(str.getBytes(encode), encode))) { //判断是不是GBK
String s3 = encode;
return s3;
}
} catch (Exception exception3) {
}
return "";
}
}
2-2、JsonUtils
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* json 相关格式化
*/
public class JsonUtils {
// 定义jackson对象
private static ObjectMapper MAPPER = new ObjectMapper();
{
MAPPER.setSerializationInclusion(Include.ALWAYS);
}
/**
* 将对象转换成json字符串。
* <p>Title: pojoToJson</p>
* <p>Description: </p>
* @param data
* @return
*/
public static String objectToJson(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
public static JsonNode stringToJsonNode(String data) {
try {
JsonNode jsonNode = MAPPER.readTree(data);
return jsonNode;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 将json结果集转化为对象
*
* @param jsonData json数据
* @param clazz 对象中的object类型
* @return
* @throws Exception
*/
public static <T> T objectToPojo(Object jsonData, Class<T> beanType) throws Exception {
T t = MAPPER.readValue(MAPPER.writeValueAsString(jsonData), beanType);
return t;
}
/**
* 将json结果集转化为对象
*
* @param jsonData json数据
* @param clazz 对象中的object类型
* @return
* @throws Exception
*/
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) throws Exception {
T t = MAPPER.readValue(jsonData, beanType);
return t;
}
/**
* 将json数据转换成pojo对象list
* <p>Title: jsonToList</p>
* <p>Description: </p>
* @param jsonData
* @param beanType
* @return
*/
public static <T>List<T> jsonToList(String jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = MAPPER.readValue(jsonData, javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将object数据转换成pojo对象list
* <p>Title: jsonToList</p>
* <p>Description: </p>
* @param jsonData
* @param beanType
* @return
*/
public static <T>List<T> objectToList(Object jsonData, Class<T> beanType) {
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
try {
List<T> list = MAPPER.readValue(MAPPER.writeValueAsString(jsonData), javaType);
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将JSON数据转换成Map
* @param jsonData
* @return
*/
public static Map<String,Object> jsonToMap(String jsonData) {
try {
Map map = MAPPER.readValue(jsonData, Map.class);
return map;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
2-3、封装的方法
你只需要去完善下面的beasParams方法的参数就好,参数在上面的截图里面都说了
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.util.DigestUtils;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 基于移动云MAS平台发送短信
*
* @author 小道仙
* @date 2020/10/11
*/
public class SmsServiceMas {
/**
* 一对一发送消息
*
* @param mobiles 手机号
* @param content 内容
* @return {"msgGroup":"0927152506001000833076","rspcod":"success","success":true}
* 如果发送失败,msgGroup为空
*/
public static JsonNode singleShot(String mobiles, String content){
Map<String, String> map = beasParams(mobiles, content, "",1);
Base64.Encoder encoder = Base64.getEncoder();
JsonNode jsonNode = null;
System.out.println(JsonUtils.objectToJson(map));
try {
String params = encoder.encodeToString(JsonUtils.objectToJson(map).getBytes("UTF-8"));
System.out.println(params);
String json = HttpUtils.sendPost("http://112.35.1.155:1992/sms/norsubmit", params, "");
jsonNode = JsonUtils.stringToJsonNode(json);
}catch (Exception e){
throw new RuntimeException("MAS消息发送失败");
}
return jsonNode;
}
/**
* 一对多发送消息
*
* @param mobiles 手机号
* @param content 内容
*/
public static JsonNode multiple(List<String> mobiles, String content){
if (mobiles == null || mobiles.isEmpty() || mobiles.size() > 5000){
throw new RuntimeException("电话号码异常");
}
String tmp = "";
for (String item : mobiles){
tmp += ","+item;
}
tmp = tmp.substring(1);
return singleShot(tmp, content);
}
/**
* 发送模板消息
*
* @param mobiles 手机号
* @param templateId 模板Id
* @param params 模板参数,格式["123","456"]
*/
public static JsonNode templateMsg(String mobiles, String templateId, String[] params){
JsonNode jsonNode = null;
Map<String, String> map = beasParams(mobiles, JsonUtils.objectToJson(params), templateId,2);
map.put("templateId",templateId);
Base64.Encoder encoder = Base64.getEncoder();
try {
System.out.println(JsonUtils.objectToJson(map));
String tmpParams = encoder.encodeToString(JsonUtils.objectToJson(map).getBytes("UTF-8"));
System.out.println(tmpParams);
String json = HttpUtils.sendPost("http://112.35.1.155:1992/sms/tmpsubmit", tmpParams, "");
jsonNode = JsonUtils.stringToJsonNode(json);
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("MAS模板消息发送失败");
}
return jsonNode;
}
/**
* 基本参数封装
*
* type == 1, 传递的是短信消息
* type == 2, 传递的模板参数
*/
private static Map<String,String> beasParams(String mobiles,String tmp,String templateId,int type){
// TODO 参数补全
String secretKey = "创建的账号";
String apId = "账号密码";
String sign = "签名编码";
String ecName = "公司名称";
// TODO 可以直接是空字符串
String addSerial = "";
String mac = "";
Map<String,String> map = new HashMap();
map.put("ecName",ecName);
map.put("apId",apId);
map.put("sign",sign);
map.put("mobiles",mobiles);
if (type == 1){
mac = ecName+apId+secretKey+mobiles+tmp+sign+addSerial;
map.put("content",tmp);
}else{
mac = ecName+apId+secretKey+templateId+mobiles+tmp+sign+addSerial;
map.put("params",tmp);
}
mac = DigestUtils.md5DigestAsHex(mac.getBytes()).toLowerCase();
map.put("addSerial",addSerial);
map.put("mac",mac);
return map;
}
}
2-4、MasStatusBean (短信状态接受bean)
/**
* MAS消息状态接受bean
*
* @author 小道仙
* @date 2020/10/11
*/
public class MasStatusBean {
/**
* 发送成功状态码:DELIVRD
*/
private String reportStatus;
/**
* 回执手机号,每批次返回一个号码
*/
private String mobile;
/**
* 提交时间,格式:yyyyMMddHHmmss
*/
private String submitDate;
/**
* 接收时间 格式:yyyyMMddHHmmss
*/
private String receiveDate;
/**
* 发送失败的状态码,如DB:0140
*/
private String errorCode;
/**
* 消息批次号,唯一编码,与发送响应中的msgGroup对应
*/
private String msgGroup;
public String getReportStatus() {
return reportStatus;
}
public void setReportStatus(String reportStatus) {
this.reportStatus = reportStatus;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getSubmitDate() {
return submitDate;
}
public void setSubmitDate(String submitDate) {
this.submitDate = submitDate;
}
public String getReceiveDate() {
return receiveDate;
}
public void setReceiveDate(String receiveDate) {
this.receiveDate = receiveDate;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getMsgGroup() {
return msgGroup;
}
public void setMsgGroup(String msgGroup) {
this.msgGroup = msgGroup;
}
@Override
public String toString() {
return "MasStatusBean{" +
"reportStatus='" + reportStatus + '\'' +
", mobile='" + mobile + '\'' +
", submitDate='" + submitDate + '\'' +
", receiveDate='" + receiveDate + '\'' +
", errorCode='" + errorCode + '\'' +
", msgGroup='" + msgGroup + '\'' +
'}';
}
}
2-5、短信回调bean
就是用户回复之后的回调函数
/**
* 短信回调Bean
*
* @author 陶梓洋
* @date 2020/9/11
*/
public class MsgCallbackBean {
/**
* 手机号码
*/
private String mobile;
/**
* 短信内容
*/
private String smsContent;
/**
* 短信发送时间,格式:yyyy-MM-dd HH:mm:ss
*/
private String sendTime;
/**
* 上行服务代码
*/
private String addSerial;
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getSmsContent() {
return smsContent;
}
public void setSmsContent(String smsContent) {
this.smsContent = smsContent;
}
public String getSendTime() {
return sendTime;
}
public void setSendTime(String sendTime) {
this.sendTime = sendTime;
}
public String getAddSerial() {
return addSerial;
}
public void setAddSerial(String addSerial) {
this.addSerial = addSerial;
}
@Override
public String toString() {
return "MsgCallbackBean{" +
"mobile='" + mobile + '\'' +
", smsContent='" + smsContent + '\'' +
", sendTime='" + sendTime + '\'' +
", addSerial='" + addSerial + '\'' +
'}';
}
}
三、测试
因为状态回调和上行短信回调都是移动要访问我们的服务器的,但是我开发又是本地,所以需要借助一个工具让我们本地地址给映射出去。
ngrok下载、安装、使用
我本地的项目端口是8123,所以我的映射代码是 ngrok http 8123
3-1、测试代码
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试发送短信
*/
@RestController
public class TestServlet {
@GetMapping("/testMas")
public Object fun(){
String[] params = new String[1];
params[0] = "3123da";
JsonNode jsonNode = SmsServiceMas.templateMsg("188276739493", "472fee4d8c4858bb3bacc067bc5e6a",params);
System.out.println(jsonNode);
return jsonNode;
}
/**
* 状态报告
*/
@RequestMapping("/mas/status")
public void statusReport(@RequestBody MasStatusBean masStatusBean){
System.out.println("接收到短信报告");
System.out.println(masStatusBean.toString());
}
/**
* 上行短信
*/
@RequestMapping("/visitCallback")
public void visitCallback(@RequestBody MsgCallbackBean msgCallbackBean){
System.out.println("接收到上行短信");
System.out.println(msgCallbackBean.toString());
}
}
3-2、配置回调地址
从上面的代码可以知道,我的回调地址是http://127.0.0.1:8123/mas/status 和 http://127.0.0.1:8123/visitCallback
3-3、配置短信模板
新建完成后提交审核
四、其它
4-1、查看短信发送状态
1、我们可以在短信状态回调函数里面查看
2、我们可以去移动MAS平台查看
错误CM:4004除了格式错误之外还可能是因为你本来是模板短信但是你调用了普通短信接口
错误IB:0011直接联系移动人员
addSerial参数不为空可能会出现有些手机号无法接收短信的问题
发布到Linux下的时候要注意生成的MAC可能不同,主要是字符集的问题。