Java SDK
https://github.com/wxxsxxGit/sms-sdk-java
自助API报备模板及模板发送
https://www.yuque.com/docs/share/8446f03b-5132-4e87-b8d6-48b9cee0846a?
发布时间 | 版本 | 修订人 | 变更摘要 |
2020-05-28 | 1.0 | 郑维君 | |
2020-07-28 | 1.1 | 关宏新 | 1、增加实际提交接口地址域名 2、新增4.7、4.8、4.9章节内容 |
2020-08-25 | 1.2 | 郑维君 | 修复导出word格式时部分内容由于格式不支持导致丢失的问题 |
2020-08-28 | 1.3 | 郑维君 | 1、统一修正msgId为msgid 2、增加spId术语 |
2021-04-12 | 1.4 | 夏帅 | 增加定时短信字段sendTime |
2021-06-15 | 1.5 | 夏帅 | 上行增加签名字段sign |
2022-09-07 | 1.6 | 钟祥 | 增加4.9加密方式发送 |
2022-09-30 | 1.7 | 关宏新 | 增加API参考 |
2025-04-24 | 1.8 | 关宏新 | 增加4.10、4.11加密号码推送回执 |
提供对接短信发送平台HTTP/HTTPS协议的统一规范,便于客户侧进行接口的对接开发
平台对外提供的HTTP/HTTPS协议短信下发接口,以及状态、上行的推送和主动获取接口
术语 | 英文 | 中文 |
HTTP | Hyper Text Transfer Protocol | 超文本传输协议 |
HTTPS | Secure Hypertext Transfer Protocol | 安全超文本传输协议 |
JSON | JavaScript Object Notation | 一种轻量级的数据交换格式 |
spId | Service Provider Id | 我方提供的发送账号的唯一标识 |
平台采用预共享密钥的方式来进行鉴权认证。即我方和客户侧之间共享spKey密钥,客户侧在发起请求时对请求信息进行签名,然后把构造好的签名放入请求头中,我方在接收到请求信息时采用同样的方式对请求信息进行签名,并根据签名是否一致进行认证
构造公式如下:
signature_origin=${body_content} + ${timestamp}
构造参数说明如下:
将signature_origin字符串使用HMAC-SHA256算法(使用我方提供的预共享密钥spKey作为算法的key)计算签名HMAC值,然后对生成的HMAC值进行base64编码即生成最终传递的签名signatue
将生成的签名signatue放入请求头中,格式如下:
Authorization: HMAC-SHA256 ${timestamp},${signature}
构造参数说明如下:
支持客户侧单内容多号码的短信发送
参数名称 | 类型 | 是否必须 | 最大长度 | 说明 |
content | string | 必须 | 1005 | 短信内容,例如: 【线上线下】您的验证码为123456,在10分钟内有效。 |
mobile | string | 必须 | 由数量决定 | 短信号码,多个用“,”分割,号码数量<=10000 |
extCode | string | 可选 | 12 | 扩展码,必须可解析为数字 |
msgid | string | 可选 | 64 | 自定义msgid,若不传,由我们平台生成一个msgid返回,若设置此值,平台将使用此msgid作为此次提交的唯一编号并返回此msgid |
sId | string | 可选 | 64 | 批次号,可用于客户侧按照批次号对短信进行分组 |
sendTime | string | 可选 | 时间戳(long) | 定时时间的毫秒数,最长不超过30天,不传或者时间在5分钟之内都视为即时下发短信,立马发出去 |
{"content" : "【线上线下】您的验证码为123456,在10分钟内有效。","mobile" : "13800001111,8613955556666,+8613545556666","extCode" : "123456","msgid":"8629637681836384963","sId" : "123456789abcdefg"}
{"status" : 0, // 请求成功,详细代码参考4.8.1"msgid" : "8629637681836384963"}
参数说明
平台将短信下发的状态报告推送给客户
参数名称 | 类型 | 是否必须 | 说明 |
status | int | 必须 | 提交响应状态 |
result | JSON Array | 必须 | 状态反馈信息(打包数组) |
result参数说明
参数名称 | 类型 | 是否必须 | 说明 |
phone | string | 必须 | 手机号码 |
msgid | string | 必须 | 与提交响应的msgid一致 |
status | string | 必须 | 短信状态 |
fee | int | 必须 | 短信计费条数 |
sId | string | 可选(提交时传才会有) | 批次号,可用于客户侧按照批次号对短信进行分组 |
donetime | string | 必须 | 短信到达时间,格式:yyyyMMddHHmmss |
{"status" : 0, // 固定值0"result" :[{"phone" : "13921350591", // 手机号码"msgid" : "-8629637681836384963", // 与提交中的msgid 一致"status" : "DELIVRD" , // 短信状态,参考附表2"donetime" : "20170816153922", // 短信到达时间"fee" : 2, // 短信计费条数"sId" : "123456789abcdefg" // 批次号}//这里是数组会有多条,默认最大500条 ,平台可配置]}
{"status" : 0,"msg" : ""}
参数说明
避免重复推送,客户侧正确接收到后请返回成功码0。
平台支持客户主动来获取短信的状态报告,平台对状态报告最大缓存48小时,超过48小时未获取就会丢弃
参数名称 | 类型 | 是否必须 | 说明 |
maxSize | int | 可选 | 默认500,支持范围[10, 1000],参数超出范围按照默认算 |
{"maxSize" : 100}
见4.2.4
平台将短信回复的上行推送给客户
参数名称 | 类型 | 是否必须 | 说明 |
status | int | 必须 | 上行响应状态 |
result | JSON Array | 必须 | 上行回复内容(打包数组) |
result参数说明
参数名称 | 类型 | 是否必须 | 说明 |
phone | string | 必须 | 手机号码 |
extCode | string | 必须 | 用户提交短信时候带的extCode |
content | string | 必须 | 上行内容 |
receivetime | string | 必须 | 短信到达时间,格式:yyyyMMddHHmmss |
sId | string | 可选(提交时传才会有) | 对应短信提交时的sId |
sign | string | 可选 | 对应短信提交时的签名(无下发上行没有此字段) |
msgid | string | 必须 | 对应短信提交时的msgid |
{"status" : 0, // 固定值0"result":[{"phone" : "13921350591", // 手机号码"extCode" : "682", // 用户提交短信时候带的extCode"content" : "上行回复内容" , // 上行内容"receivetime" : "20170816153922",// 短信到达时间"sId" : "123456789abcdefg", // 批次号"sign" : "签名","msgid" : "-8629637681836384963" // 短信唯一编号}// 这里是数组会有多条,默认最大500条 ,平台可配置]}
{"status" : 0, //状态码 0表示成功,非0表示失败"msg" : "" // 错误信息}
参数说明
避免重复推送,客户侧正确接收到后请返回成功码0。
平台支持客户主动来获取短信的上行报告,平台对上行报告最大缓存48小时,超过48小时未获取就会丢弃
参数名称 | 类型 | 是否必须 | 最大长度 | 说明 |
maxSize | int | 可选 | 默认500,支持范围[10, 1000],参数超出范围按照默认算 |
{"maxSize" : 100}
见4.4.4
预付费账号剩余余额查询。
推荐使用登陆 【客户发送平台】 在[用户中心/个人资料]模块中扫码绑定微信公众号,后期余额不足时会自动提醒推送。
无
无
{"status": 0 // 请求成功,详细代码参考4.9.1"result": 10000 //当前余额条数}
获取发送账号spId的每日短信发送情况统计
参数名称 | 类型 | 是否必须 | 最大长度 | 说明 |
date | String | 必选 | 8 | 日期格式化:yyyyMMdd 示例: 20200101 |
{"date": "20200101"}
{"status" : 0, // 请求成功,详细代码参考4.8.1"result":{"spId" : "apiSendUser01", // 发送账号"date" : 20200101, // 查询的日期"sendCount" : 1000, // 提交条数"feeCount" : 1500 , // 发送条数"successCount" : 1400,// 成功条数"failCount" : 80, // 失败条数"exceptionCount" : 0, // 异常条数"noRespCount" : 20, // 未反馈条数}}
status 代码 | 代码含义 |
0 | 成功 |
100001 | 鉴权失败 |
100002 | 必选参数为空 |
100003 | 参数格式错误 |
100004 | 系统错误 |
100005 | timestamp过期 |
1001 | 账号不能为空 |
1002 | 账号不合法 |
1003 | ip不能为空 |
1004 | ip校验失败 |
1005 | 密码不能为空 |
1006 | 密码错误 |
1007 | 手机号码不能为空或者手机号码位数不正确或者数量过多 |
1008 | 缺少手机参数 |
1009 | 扩展必须为数字 |
10010 | msgid长度限制 |
10011 | 内容为空 |
10012 | 内容含关键字 |
10013 | 内容超过系统支持最大长度 |
10014 | 签名超长或为空 |
10016 | 余额不足 |
10017 | 账号已禁用 |
10018 | 该用户此时间段禁止提交 |
10019 | 不允许主动获取和主动推送同时调用 |
10020 | 打包短信错误 |
10021 | 批量短信数量超限 |
10022 | 定时短信添加错误 |
10023 | sId过长 |
10024 | 定时短信时间异常 |
10025 | 国际短信接口不支持国内号码 |
10026 | 不支持国际短信 |
仅针对当前短信发送平台返回的内部反馈代码释义,不在此表中的代表运营商返回的状态码。
代码 | 含义 |
BLACK | 黑名单 |
NOWAY | 未匹配通道 |
CHECK | 审核不通过 |
SIGN | 签名错误,未报备 |
KEYWORD | 内容含有非法关键词 |
LIMIT | 超发限制 |
PHONERR | 无归属地 |
SWITCH | 通道切出 |
DELIVRD | 反馈成功 |
TDBLACK | 退订黑 |
TSBLACK | 投诉黑 |
RDBLACK | 红名单 |
AICHECK | 智能拦截,一般是发送账号有误 |
INSUFFICIENT_BALANCE | 余额不足 |
UNDELIV | 反馈失败 |
TESTER_DELIVRD | 压测专用码 |
ERRLMSG | CMPP长短信异常 |
支持客户侧单内容多号码的短信加密传输发送
使用AES对称加密,使用我方提供的预共享密钥spKey作为算法的key,发送时使用该秘钥对请求体进行加密
接口为HTTPS加密传输,该接口多一次加解密过程,特别报文字节过多时,会影响响应性能,所以非必要不推荐
参数名称 | 类型 | 是否必须 | 最大长度 | 说明 |
content | string | 必须 | 请求体加密后的Base64字符串 |
public class CryptoUtil {private static Logger logger = LoggerFactory.getLogger(CryptoUtil.class);private final static int AES_KEY_LENGTH = 16;private final static String ALGORITHM = "AES/ECB/PKCS5Padding";/*** AES 密钥长度规定为16位,这里判断密钥长度* 不满足16位的统一前置填充字符'a',大于长度的统一截取前16位*/private static byte[] getBytesBySkey(String key) {if (key.length() >= AES_KEY_LENGTH) {return StrUtil.sub(key, 0, 16).getBytes(CharsetUtil.CHARSET_UTF_8);}return StrUtil.fill(key, 'a', AES_KEY_LENGTH, true).getBytes(CharsetUtil.CHARSET_UTF_8);}/*** Aes加密* @param encryptString 明文字符串* @param key 秘钥* @return 加密数据base64之后的密文字符串* @throws Exception*/public static String encrypt(String encryptString, String key) throws Exception{byte[] raw = getBytesBySkey(key);SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, skeySpec);byte[] encrypted = cipher.doFinal(encryptString.getBytes("utf-8"));return Base64.encode(encrypted);}}
{"content" : "【线上线下】您的验证码为123456,在10分钟内有效。","mobile" : "13800001111,8613955556666,+8613545556666","extCode" : "123456","sId" : "123456789abcdefg"}
注意:构造请求头 signature_origin=${body_content} + ${timestamp} 中的 body_content使用加密后的json字符串!
//假设秘钥为 Z345_#${"content":"eMHvnXPmLoJDRtAStEGHtRzNbsPzduN6XmM3F08b6EszuRlXdEw3FPSGpjVphixEeYh7LJkJpmP0OoaVI5kRXRTAT/Ny1a1Anzy2Ri6pgBpaYkzlHljPFBfgPNlGnDYSINebgUFr5wbgDvSGtcIoHdPoByb+srwaovZoose7q0Ms2pdn/kZOhym2hBNUta95EXfXDYKX4FjqFW2mENhFJEZUpsTSjfTpose6+ctt0NKocANGk14mk8qglHlyYebk"}
{"status" : 0, // 请求成功,详细代码参考4.8.1"msgid" : "8629637681836384963"}
平台将短信下发的状态报告推送给客户,支持回执手机号码的加密传输发送
使用AES对称加密,使用我方提供的预共享密钥spKey作为算法的key,回执推送时使用该秘钥对手机号码进行加密
同 上4.9.4 加密代码
参数名称 | 类型 | 是否必须 | 说明 |
status | int | 必须 | 提交响应状态 |
result | JSON Array | 必须 | 状态反馈信息(打包数组) |
result参数说明
参数名称 | 类型 | 是否必须 | 说明 |
phone | string | 必须 | 手机号码,AES加密 |
msgid | string | 必须 | 与提交响应的msgid一致 |
status | string | 必须 | 短信状态 |
fee | int | 必须 | 短信计费条数 |
sId | string | 可选(提交时传才会有) | 批次号,可用于客户侧按照批次号对短信进行分组 |
donetime | string | 必须 | 短信到达时间,格式:yyyyMMddHHmmss |
encrypted | Integer | 必须 | 1 表示手机号码是AES加密 |
{"status" : 0, // 固定值0"result" :[{"phone" : "f8/LCvVIYGXCWXyZT3NCAQ==",//AES密文手机号码,秘钥为:Z345_#$// 明文手机号码:13921350591"msgid" : "-8629637681836384963", // 与提交中的msgid 一致"status" : "DELIVRD" , // 短信状态,参考附表2"donetime" : "20170816153922", // 短信到达时间"fee" : 2, // 短信计费条数"sId" : "123456789abcdefg", // 批次号"encrypted": 1 // 1表示phone是aes加密,非1或者无该字段表示phone为明文号码}//这里是数组会有多条,默认最大500条 ,平台可配置]}
{"status" : 0,"msg" : ""}
参数说明
避免重复推送,客户侧正确接收到后请返回成功码0。
平台将短信回复的上行推送给客户,支持上行回复手机号码的加密传输发送
使用AES对称加密,使用我方提供的预共享密钥spKey作为算法的key,回执推送时使用该秘钥对手机号码进行加密
同 上4.9.4 加密代码
参数名称 | 类型 | 是否必须 | 说明 |
status | int | 必须 | 上行响应状态 |
result | JSON Array | 必须 | 上行回复内容(打包数组) |
result参数说明
参数名称 | 类型 | 是否必须 | 说明 |
phone | string | 必须 | 手机号码,AES加密 |
extCode | string | 必须 | 用户提交短信时候带的extCode |
content | string | 必须 | 上行内容 |
receivetime | string | 必须 | 短信到达时间,格式:yyyyMMddHHmmss |
sId | string | 可选(提交时传才会有) | 对应短信提交时的sId |
sign | string | 可选 | 对应短信提交时的签名(无下发上行没有此字段) |
msgid | string | 必须 | 对应短信提交时的msgid |
encrypted | Integer | 必须 | 1 表示phone字段是AES加密 非1或者无该字段表示phone为明文号码 |
{"status" : 0, // 固定值0"result":[{"phone" : "f8/LCvVIYGXCWXyZT3NCAQ==",//AES密文手机号码,秘钥为:Z345_#$// 明文手机号码:13921350591 ,"extCode" : "682", // 用户提交短信时候带的extCode"content" : "上行回复内容" , // 上行内容"receivetime" : "20170816153922",// 短信到达时间"sId" : "123456789abcdefg", // 批次号"sign" : "签名","msgid" : "-8629637681836384963" ,// 短信唯一编号"encrypted": 1 // 1表示phone是aes加密,非1或者无该字段表示phone为明文号码}// 这里是数组会有多条,默认最大500条 ,平台可配置]}
{"status" : 0, //状态码 0表示成功,非0表示失败"msg" : "" // 错误信息}
参数说明
避免重复推送,客户侧正确接收到后请返回成功码0。
https://github.com/wxxsxxGit/sms-sdk-java
Java SDK 基于Java8 版本开发,主要依赖三方组件有OkHttp/Hutool等,有Okhttp3 几个版本可供依赖选择,请移步GitHub 专区参考阅读Readme
添加SDK依赖
<dependency><groupId>cn.wxxsxx</groupId><artifactId>sms-sdk-java</artifactId><version>选择依赖的版本</version></dependency>//参考Maven中央库 https://search.maven.org/artifact/cn.wxxsxx/sms-sdk-java
import cn.hutool.core.collection.CollUtil;import cn.hutool.core.date.DateTime;import cn.hutool.core.lang.UUID;import cn.hutool.core.map.MapUtil;import cn.hutool.core.util.RandomUtil;import cn.hutool.core.util.StrUtil;import cn.hutool.json.JSONUtil;import com.xsxx.sms.V4Client;import com.xsxx.sms.model.BatchSubmitResp;import com.xsxx.sms.model.DeliverResp;import com.xsxx.sms.model.ReportResp;import com.xsxx.sms.model.Sms;import com.xsxx.sms.model.template.SmsSubimtByTemplateReqVo;import com.xsxx.sms.model.template.SmsTemplate;import com.xsxx.sms.util.AES;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/*** 四代API提交接口* https://thoughts.teambition.com/share/5f1f8ebc865e26001a7536b7* 四代接口增加:* 1. 加密验证* 2. 防重放* 3. 推送验证** @author momo* @date 2020-01-03 15:14:54*/public class DemoV4Client {/*** 并发线程数,取值范围(1-60)* 理论速度 = 1000/ping * threadCount* 默认10线程发送:理论速度500;* 线程数选择需要考虑客户机性能,不是越大越好* <p>* 更高的性能可以 new 多个ApiClient 协同处理短消息*/private static final int httpWinSize = 20;public static void main(String[] args) {try {// 具体【url/spId/spKey/fetchURL/templateURL】参数请找商务或者我司技术支持String spId, url, spKey, fetchURL, templateURL;V4Client v4Client = new V4Client(url, spId, spKey, httpWinSize, fetchURL, templateURL);// 添加模板// addTemplate(v4Client);// 修改模板// modifyTemplate(v4Client);// 删除模板// deleteTemplate(v4Client);// 查询模板状态// queryTemplateStatus(v4Client);// 单模板发送// submitByTemplateCode(v4Client);// 批量模板发送// batchSubmitByTemplateCode(v4Client);// 单内容发送// submit(v4Client);// 单内容AES// submitAES(v4Client);// 多内容多号码发送 不推荐// batchSms(v4Client);//日统计查询// v4Client.getDailyStats("20220811");//余额查询// v4Client.getBalance();// 测速// testSpeed(v4Client, 1_0000);} catch (Exception e) {e.printStackTrace();}}/*** 单内容多号码发送* 最多100个号码,每个手机号保证是11位数字有效手机号* Sms sms = new Sms("11000000000","【签名】不带扩展码号");* Sms sms = new Sms("11000000000,11000000000","【签名】带扩展码号","666");* Sms sms = new Sms("11000000000,11000000000","【签名】带扩展码号,自定义字段","666","customId-whatever-except-emoji");* apiClient.submit(sms);** @param v4Client*/public static void submit(V4Client v4Client) {Sms sms = new Sms("11000000000", "【线上线下submit SDK DEMO】验证码 " + System.currentTimeMillis() + ",5分钟内有效。如非本人操作,请忽略。", "666");boolean isSync = v4Client.submit(sms, resp -> {System.out.println(JSONUtil.toJsonStr(resp));});}/*** 报备模板** @param v4Client* @throws Exception*/public static void addTemplate(V4Client v4Client) throws Exception {SmsTemplate smsTemplate = new SmsTemplate();smsTemplate.setTemplateName("线上线下addTemplate SDK DEMO " + RandomUtil.randomString(10));smsTemplate.setTemplateContent("线上线下addTemplate SDK DEMO template content ${" + RandomUtil.randomString(4) + "} template " + UUID.fastUUID().toString());smsTemplate.setTemplateType(RandomUtil.randomInt(0, 3));smsTemplate.setRemark("线上线下addTemplate SDK DEMO template " + DateTime.now().toString());System.out.println("add params:\n" + JSONUtil.toJsonStr(smsTemplate));boolean isSync = v4Client.addOrModifyTemplate(true, smsTemplate, resp -> {System.out.println(JSONUtil.toJsonStr(resp));});}/*** 修改模板** @param v4Client* @throws Exception*/public static void modifyTemplate(V4Client v4Client) throws Exception {SmsTemplate smsTemplate = new SmsTemplate();smsTemplate.setTemplateName("线上线下modifyTemplate SDK DEMO " + RandomUtil.randomString(10));smsTemplate.setTemplateContent("线上线下modifyTemplate SDK DEMO template content ${" + RandomUtil.randomString(4) + "} template ");smsTemplate.setTemplateType(RandomUtil.randomInt(0, 3));smsTemplate.setRemark("线上线下modifyTemplate SDK DEMO template " + DateTime.now().toString());smsTemplate.setTemplateCode(-7476205602580885282L);System.out.println("modify params:\n" + JSONUtil.toJsonStr(smsTemplate));boolean isSync = v4Client.addOrModifyTemplate(false, smsTemplate, resp -> {System.out.println(JSONUtil.toJsonStr(resp));});}/*** 删除模板** @param v4Client* @throws Exception*/public static void deleteTemplate(V4Client v4Client) throws Exception {boolean isSync = v4Client.deleteTemplate(-7476205602580885282L, resp -> {System.out.println(JSONUtil.toJsonStr(resp));});}/*** 查询模板状态** @param v4Client* @throws Exception*/public static void queryTemplateStatus(V4Client v4Client) throws Exception {boolean isSync = v4Client.queryTemplateStatus(CollUtil.newArrayList(-7481228446574445708L, -7479127829609579659L), resp -> {System.out.println(JSONUtil.toJsonStr(resp));});}/*** 短信模板单条发送** @param v4Client* @throws Exception*/public static void submitByTemplateCode(V4Client v4Client) throws Exception {SmsSubimtByTemplateReqVo smsSubimtByTemplateReqVo = new SmsSubimtByTemplateReqVo();smsSubimtByTemplateReqVo.setSignName("线上线下submit by template SDK DEMO");List<String> mobiles = new ArrayList<String>();int count = RandomUtil.randomInt(4, 5);for (int i = 0; i < count; i++) {mobiles.add(RandomUtil.randomEle(CollUtil.newArrayList("", "86", "0086", "+86")) + RandomUtil.randomString("12", 1) + RandomUtil.randomString("0123456789", 10));}smsSubimtByTemplateReqVo.setMobile(CollUtil.join(mobiles, StrUtil.COMMA));smsSubimtByTemplateReqVo.setExtCode(RandomUtil.randomNumbers(RandomUtil.randomInt(1, 12)));smsSubimtByTemplateReqVo.setMsgid(UUID.randomUUID().toString(true));smsSubimtByTemplateReqVo.setTemplateCode(-7479043716970054794L);smsSubimtByTemplateReqVo.setParams(JSONUtil.toJsonStr(MapUtil.of("9ju9", RandomUtil.randomString(RandomUtil.randomInt(1, 12)))));smsSubimtByTemplateReqVo.setsId(RandomUtil.randomString("0123456789", 5));System.out.println("submit params: \n " + JSONUtil.toJsonStr(smsSubimtByTemplateReqVo));boolean isSync = v4Client.submitByTemplateCode(smsSubimtByTemplateReqVo, resp -> {System.out.println(JSONUtil.toJsonStr(resp));});}/*** 短信模板单条发送** @param v4Client* @throws Exception*/public static void batchSubmitByTemplateCode(V4Client v4Client) throws Exception {List<SmsSubimtByTemplateReqVo> reqVoList = new ArrayList<>();int count = RandomUtil.randomInt(4, 5);for (int i = 0; i < count; i++) {SmsSubimtByTemplateReqVo smsSubimtByTemplateReqVo = new SmsSubimtByTemplateReqVo();smsSubimtByTemplateReqVo.setSignName("线上线下 batch submit by templateCode SDK DEMO");smsSubimtByTemplateReqVo.setMobile(RandomUtil.randomEle(CollUtil.newArrayList("", "86", "0086", "+86")) + RandomUtil.randomString("12", 1) + RandomUtil.randomString("0123456789", 10));smsSubimtByTemplateReqVo.setExtCode(RandomUtil.randomNumbers(RandomUtil.randomInt(1, 12)));smsSubimtByTemplateReqVo.setMsgid(UUID.randomUUID().toString(true));smsSubimtByTemplateReqVo.setTemplateCode(RandomUtil.randomEle(CollUtil.newArrayList(-7479043716970054794L, -7479127829609579659L)));Map<String, Object> params = new HashMap<>();params.put("9ju9", RandomUtil.randomString(RandomUtil.randomInt(1, 12)));params.put("f8xk", RandomUtil.randomString(RandomUtil.randomInt(1, 12)));smsSubimtByTemplateReqVo.setParams(JSONUtil.toJsonStr(params));smsSubimtByTemplateReqVo.setsId(RandomUtil.randomString("0123456789", 5));reqVoList.add(smsSubimtByTemplateReqVo);}System.out.println("batch submit params: \n" + JSONUtil.toJsonStr(reqVoList));boolean isSync = v4Client.batchSubmitByTemplateCode(reqVoList, resp -> {System.out.println(JSONUtil.toJsonStr(resp));});}/*** 短信AES加密发送** @param v4Client* @throws Exception*/public static void submitAES(V4Client v4Client) throws Exception {Sms sms = new Sms("13800000001", "【线上线下submit SDK DEMO】验证码 " + System.currentTimeMillis() + ",5分钟内有效。如非本人操作,请忽略。", "666");boolean isSync = v4Client.submitByAes(sms, resp -> {System.out.println(JSONUtil.toJsonStr(resp));});}/*** 速度测试,注意修改内容,否则会驳回** @param v4Client* @param amount 测试数量*/public static void testSpeed(V4Client v4Client, int amount) {Long phone = 11000000000l;long diff = 0;// 同步计数int syncCount = 0;Long start = System.currentTimeMillis();for (int i = 1; i <= amount; i++) {if (i % 1000 == 0) {diff = System.currentTimeMillis() - start;System.out.println(i * 100 / amount + "%\t" + (i * 1000 / diff) + "条/秒");}phone++;Sms sms = new Sms(phone.toString(), "【线上线下testSpeed SDK DEMO】带扩展码号" + i, String.valueOf(i), String.valueOf(i));boolean isSync = v4Client.submit(sms, resp -> {if (resp.getStatus() != 0) {System.out.println(sms.getMobile() + "\t" + resp.getStatus() + "\t" + resp.getMsg());}});if (isSync) {syncCount++;}}diff = System.currentTimeMillis() - start;System.out.println("共" + diff + "秒\t " + (amount * 1000 / diff) + "条/秒");System.out.println("同步占比:" + syncCount * 100 / amount + "%");}/*** 主动获取未读短信状态* 成功为DELIVRD,无特殊要求一次最多返回500条,可以用msgId来匹配返回的状态* 可提供回调URL自动推送(推荐)** @param v4Client*/@Deprecatedpublic static void getReport(V4Client v4Client) {ReportResp report = v4Client.getReport();System.out.println(JSONUtil.toJsonStr(report));}/*** 主动获取未读上行* 无特殊要求一次最多返回500条* 可提供回调URL自动推送(推荐)** @param v4Client*/@Deprecatedpublic static void getDeliver(V4Client v4Client) {DeliverResp deliver = v4Client.getDeliver();System.out.println(JSONUtil.toJsonStr(deliver));}/*** 多内容打包发送接口(同步模式)最多100个内容,* msgId和extCode可以为空*/@Deprecatedpublic static void batchSms(V4Client v4Client) {//多内容打包数组,!!每个内容只支持1个手机号码List<Sms> smsContents = new ArrayList<>();Long phone = 18888888888L;for (int i = 0; i < 100; i++) {smsContents.add(new Sms(phone.toString(), "【线上线下batchSms SDK DEMO】内容不确定-V4 batchSms-" + i, "666", "sid-it's me"));phone++;}BatchSubmitResp batchSubmitResp = v4Client.submit(smsContents);System.out.println(JSONUtil.toJsonStr(batchSubmitResp));}}
https://github.com/wxxsxxGit/sms-sdk-go
GO SDK 基于Go 1.16 版本开发,主要依赖三方组件有color v1.13.0 / viper v1.13.0等 请移步GitHub 专区参考阅读Readme
进入下载的sms-sdk-go目录执行
go mod tidypackage mainimport ("bytes""encoding/base64""encoding/json""errors""fmt""io/ioutil""log""net/http""os""strconv""strings""time""v4sms/pkg/httputils""v4sms/pkg/strutils""v4sms/smsutils""github.com/fatih/color""github.com/spf13/viper")var globalTemplateCode int64var smsSigner *smsutils.SmsSignervar logFile *os.File// var smsSigner *smsutils.SmsSigner = smsutils.NewSmsSigner(spId, spKey, smsSendUrl, reportUrl, templateUrl)func init() {logFile, _ = os.Create("http_details.txt")viper.SetConfigName("sms") // name of config file (without extension)viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the nameviper.AddConfigPath("/etc") // call multiple times to add many search pathsviper.AddConfigPath(".") // path to look for the config file inviper.AddConfigPath("./config/")viper.AddConfigPath("../config/")viper.AddConfigPath("../../config/")viper.AddConfigPath("../../../config/")if err := viper.ReadInConfig(); err != nil {if _, ok := err.(viper.ConfigFileNotFoundError); ok {fmt.Println(color.RedString("config file not found:" + err.Error()))configPrompt()os.Exit(111)} else {fmt.Println(color.RedString("config file found,can not use:" + err.Error()))configPrompt()os.Exit(112)}}spId := viper.GetString("spId")spKey := viper.GetString("spKey")smsSendUrl := viper.GetString("smsSendUrl")reportUrl := viper.GetString("reportUrl")templateUrl := viper.GetString("templateUrl")if len(spId) == 0 ||len(spKey) == 0 ||len(smsSendUrl) == 0 ||len(reportUrl) == 0 ||len(templateUrl) == 0 {configPrompt()os.Exit(113)}smsSigner = smsutils.NewSmsSigner(spId, spKey, smsSendUrl, reportUrl, templateUrl)}func main() {//单条内容发送log.Println("短信发送接口(单内容多号码)")demoSingleSend()sperator(1)//单条内容加密发送log.Println("短信加密发送接口")demoSingleSecureSend()sperator(1)//多内容批量发送log.Println("短信多发接口")demoMultiSend()sperator(1)//主动获取状态报告log.Println("状态报告主动获取")demoStatusFetch()sperator(1)//主动获取上行log.Println("上行主动获取")demoUpstreamFetch()sperator(1)//查询余额log.Println("预付费账号余额查询")demoBalanceFetch()sperator(1)//查询每日发送统计log.Println("获取发送账号spId的每日短信发送情况统计")demoDailyStatsFetch()sperator(1)//模板报备log.Println("模板报备")templateId, err := demoTemplateAdd()if err != nil {fmt.Println(err.Error())return}sperator(1)//把服务端生成的templateCode设置为公共的值globalTemplateCode = templateIdlog.Println("模板报备的code为", globalTemplateCode)log.Println("等待模板审核...")//判断是否审核成功var tempValue uint8for {m, err := demoTemplateStatus(globalTemplateCode)if err != nil {time.Sleep(5 * time.Second)}value, ok := m[globalTemplateCode]if !ok {fmt.Println("出现错误", globalTemplateCode, "不存在")return}if value == 0 {log.Println(globalTemplateCode, "联系管理员审核")time.Sleep(10 * time.Second)continue} else {tempValue = valuebreak}}//审核成功提交模板短信if tempValue == 1 {log.Println(globalTemplateCode, "审核通过")sperator(1)} else if tempValue == 2 {//审核失败修改模板,只有在模板审核失败时才可以修改模板log.Println("模板审核失败,模板修改后提交")err = demoTemplateModify(globalTemplateCode)if err != nil {fmt.Println(err.Error())return}//模板被禁用} else {log.Println(globalTemplateCode, "审核状态为", tempValue, "退出")}//模板发送单条短信log.Println("模板发送单条短信")err = demoTemplateSendSms(globalTemplateCode)if err != nil {fmt.Println("demoTemplateSendSms", err.Error())return}sperator(1)//模板批量发送短信log.Println("模板发送批量短信")err = demoTemplateSendBatchSms(globalTemplateCode)if err != nil {fmt.Println("demoTemplateSendBatchSms", err.Error())return}sperator(1)//删除模板log.Println("10秒后将删除模板", globalTemplateCode)time.Sleep(10 * time.Second)err = demoTemplateDelete(globalTemplateCode)if err != nil {fmt.Println("demoTemplateDelete", err.Error())return}log.Println("删除模板", globalTemplateCode, "成功")logFile.Close()}func demoSingleSend() {requestBody := &smsutils.SingleSendRequestBody{Content: "【线上线下】您的验证码为123456,在10分钟内有效。",Mobile: "13800001111,13955556666,13545556666",ExtCode: "123456",SId: "123456789abcdefg"}finalReqBody, err := json.Marshal(requestBody)if err != nil {fmt.Println("json.Marshal error", err.Error())return}r, _ := http.NewRequest("POST", smsSigner.SingleSendUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {fmt.Println(err)}defer resp.Body.Close()finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))}func demoSingleSecureSend() {requestBody := &smsutils.SingleSendRequestBody{Content: "【线上线下】您的验证码为123456,在10分钟内有效。",Mobile: "13800001111,13955556666,13545556666",ExtCode: "123456",SId: "123456789abcdefg"}jsonByte, err := json.Marshal(requestBody)if err != nil {fmt.Println("requestBody json.Marshal error", err.Error())return}bAfterEncrypt, err := smsutils.AesECBEncrypt([]byte(jsonByte), []byte(smsutils.NormalizeKey(smsSigner.SpKey)))if err != nil {fmt.Println(err.Error())return}contentString := base64.StdEncoding.EncodeToString(bAfterEncrypt)ssrb := &smsutils.SecureSendRequestBody{Content: contentString}finalReqBody, err := json.Marshal(ssrb)if err != nil {fmt.Println("ssrb json.Marshal error", err.Error())return}r, _ := http.NewRequest("POST", smsSigner.SingleSecureSendUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {fmt.Println(err)}defer resp.Body.Close()finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))}func demoMultiSend() {smsBody1 := &smsutils.BatchSendItemReuqestBody{Content: "【线上线下】线上线下欢迎你参观1",Mobile: "13800001111,8613955556666,+8613545556666",ExtCode: "123456",MsgId: "123456787"}smsBody2 := &smsutils.BatchSendItemReuqestBody{Content: "【线上线下】线上线下欢迎你参观2",Mobile: "13800001111,8613955556666,+8613545556666",ExtCode: "123456",MsgId: "123456788"}smsBody3 := &smsutils.BatchSendItemReuqestBody{Content: "【线上线下】线上线下欢迎你参观3",Mobile: "13800001111,8613955556666,+8613545556666",ExtCode: "123456",MsgId: "123456789"}requestBody := []*smsutils.BatchSendItemReuqestBody{}requestBody = append(requestBody, smsBody1)requestBody = append(requestBody, smsBody2)requestBody = append(requestBody, smsBody3)finalReqBody, err := json.Marshal(requestBody)if err != nil {fmt.Println("json.Marshal error", err.Error())return}//fmt.Println(string(jsonByte))r, _ := http.NewRequest("POST", smsSigner.MultiSendUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {fmt.Println(err)}defer resp.Body.Close()finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))}func demoStatusFetch() {requestBody := &smsutils.ActiveFetchRequestBody{MaxSize: 500,}finalReqBody, err := json.Marshal(requestBody)if err != nil {fmt.Println("json.Marshal error", err.Error())return}r, _ := http.NewRequest("POST", smsSigner.StatusFetchUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {fmt.Println(err)}defer resp.Body.Close()finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))}func demoUpstreamFetch() {requestBody := &smsutils.ActiveFetchRequestBody{MaxSize: 500,}finalReqBody, err := json.Marshal(requestBody)if err != nil {fmt.Println("json.Marshal error", err.Error())return}r, _ := http.NewRequest("POST", smsSigner.UpstreamFetchUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {fmt.Println(err)}defer resp.Body.Close()finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))}func demoBalanceFetch() {r, _ := http.NewRequest("POST", smsSigner.BalanceFetchUrl(), nil)smsSigner.Sign(r, nil)client := http.DefaultClientresp, err := client.Do(r)if err != nil {fmt.Println(err)}defer resp.Body.Close()finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)}logFile.WriteString(httputils.CurlStyleOutput(r, resp, nil, finalRespBody))}func demoDailyStatsFetch() {requestBody := &smsutils.DailyStatsRequestBody{Date: time.Now().Format("20060102"),}finalReqBody, err := json.Marshal(requestBody)if err != nil {fmt.Println("json.Marshal error", err.Error())return}r, _ := http.NewRequest("POST", smsSigner.DailyStatsUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {fmt.Println(err)}defer resp.Body.Close()finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))}//模板报备http请求demofunc demoTemplateAdd() (int64, error) {requestBody := &smsutils.TemplateAddRequestBody{TemplateName: "线上线下addTemplate SDK DEMO " + strutils.RandString(10),TemplateType: 2,TemplateContent: "线上线下addTemplate SDK DEMO template content ${code} template " + strutils.RandString(20),Remark: "线上线下addTemplate SDK DEMO template " + time.Now().Format("2006-01-02 15:04:05"),}finalReqBody, err := json.Marshal(requestBody)if err != nil {return 0, err}r, _ := http.NewRequest("POST", smsSigner.TemplateAddUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {return 0, err}defer resp.Body.Close()finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {return 0, err}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))tarb := &smsutils.TemplateAddRespBody{}err = json.Unmarshal(finalRespBody, &tarb)if err != nil {return 0, err}return tarb.TemplateCode, nil}//模板修改 http请求demofunc demoTemplateModify(templateCode int64) error {requestBody := &smsutils.TemplateModifyRequestBody{TemplateCode: templateCode,TemplateType: 2,TemplateName: "线上线下addTemplate SDK DEMO " + strutils.RandString(10),TemplateContent: "线上线下addTemplate SDK DEMO template content ${code} modify template " + strutils.RandString(20),Remark: "线上线下addTemplate SDK DEMO template " + time.Now().Format("2006-01-02 15:04:05"),}finalReqBody, err := json.Marshal(requestBody)if err != nil {return err}r, _ := http.NewRequest("POST", smsSigner.TemplateModifyUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {return err}defer resp.Body.Close()finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {return err}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))tmrb := &smsutils.TemplateModifyRespBody{}err = json.Unmarshal(finalRespBody, &tmrb)if err != nil {return err}if tmrb.Status != 0 {return errors.New("status为" + strconv.FormatInt(int64(tmrb.Status), 10) + ",msg为" + tmrb.Msg)}return nil}//模板查询 http请求demofunc demoTemplateStatus(templateCodes ...int64) (map[int64]uint8, error) {tSlice := []string{}for _, v := range templateCodes {tSlice = append(tSlice, strconv.FormatInt(v, 10))}requestBody := &smsutils.TemplateStatusRequestBody{TemplateCodes: strings.Join(tSlice, ","),}finalReqBody, err := json.Marshal(requestBody)if err != nil {return nil, err}r, _ := http.NewRequest("POST", smsSigner.TemplateStatusUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {return nil, err}defer resp.Body.Close()if resp.StatusCode != http.StatusOK {return nil, errors.New("http状态码为" + strconv.FormatInt(int64(resp.StatusCode), 10))}finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {return nil, err}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))tsrb := &smsutils.TemplateStatusRespBody{}err = json.Unmarshal(finalRespBody, &tsrb)if err != nil {return nil, err}if tsrb.Status != 0 {return nil, errors.New("status为" + strconv.FormatInt(int64(tsrb.Status), 10) + ",msg为" + tsrb.Msg)}m := make(map[int64]uint8)for _, v := range tsrb.TemplateList {m[v.TemplateCode] = v.AuditStatus}return m, nil}//模板删除 请求demofunc demoTemplateDelete(templateCode int64) error {requestBody := &smsutils.TemplateDeleteRequestBody{TemplateCode: templateCode,}finalReqBody, err := json.Marshal(requestBody)if err != nil {return err}r, _ := http.NewRequest("POST", smsSigner.TemplateDeleteUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {return err}defer resp.Body.Close()if resp.StatusCode != http.StatusOK {return errors.New("http状态码为" + strconv.FormatInt(int64(resp.StatusCode), 10))}finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {return err}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))tdrb := &smsutils.TemplateDeleteRespBody{}err = json.Unmarshal(finalRespBody, &tdrb)if err != nil {return err}if tdrb.Status != 0 {return errors.New("status为" + strconv.FormatInt(int64(tdrb.Status), 10) + ",msg为" + tdrb.Msg)}return nil}//模板单条发送 请求demofunc demoTemplateSendSms(templateCode int64) error {params := make(map[string]string)params["code"] = "普通的一个"paramsByte, err := json.Marshal(params)if err != nil {return err}requestBody := &smsutils.TemplateSendSmsRequestItem{SignName: "模板测试",TemplateCode: templateCode,Params: string(paramsByte),Mobile: "18799991367,12899190876,13914117531",}finalReqBody, err := json.Marshal(requestBody)if err != nil {return err}r, _ := http.NewRequest("POST", smsSigner.TemplateSendSmsUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {return err}defer resp.Body.Close()if resp.StatusCode != http.StatusOK {return errors.New("http状态码为" + strconv.FormatInt(int64(resp.StatusCode), 10))}finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {return err}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))tdrb := &smsutils.TemplateSendSmsRespBody{}err = json.Unmarshal(finalRespBody, &tdrb)if err != nil {return err}if tdrb.Status != 0 {return errors.New("status为" + strconv.FormatInt(int64(tdrb.Status), 10) + ",msg为" + tdrb.Msg)}fmt.Println("成功号码为", tdrb.SuccessList)fmt.Println("失败号码为", tdrb.FailList)fmt.Println("短信分片为", tdrb.SplitCount)fmt.Println("msgid为", tdrb.Msgid)return nil}//模板批量发送 请求demofunc demoTemplateSendBatchSms(templateCode int64) error {params := make(map[string]string)params["code"] = "普通的一个"paramsByte, err := json.Marshal(params)if err != nil {return err}item1 := &smsutils.TemplateSendSmsRequestItem{SignName: "模板测试1",TemplateCode: templateCode,Params: string(paramsByte),Mobile: "18505101387",}item2 := &smsutils.TemplateSendSmsRequestItem{SignName: "模板测试2",TemplateCode: templateCode,Params: string(paramsByte),Mobile: "12899190872",}item3 := &smsutils.TemplateSendSmsRequestItem{SignName: "模板测试3",TemplateCode: templateCode,Params: string(paramsByte),Mobile: "18799991362",}item4 := &smsutils.TemplateSendSmsRequestItem{SignName: "模板测试4",TemplateCode: templateCode,Params: string(paramsByte),Mobile: "13914117532",}item5 := &smsutils.TemplateSendSmsRequestItem{SignName: "模板测试5",TemplateCode: templateCode,Params: string(paramsByte),Mobile: "1895606996",}requestBody := &smsutils.TemplateSendBatchSmsRequestBody{item1,item2,item3,item4,item5,}finalReqBody, err := json.Marshal(requestBody)if err != nil {return err}r, _ := http.NewRequest("POST", smsSigner.TemplateSendBatchSmsUrl(), bytes.NewReader(finalReqBody))smsSigner.Sign(r, finalReqBody)client := http.DefaultClientresp, err := client.Do(r)if err != nil {return err}defer resp.Body.Close()if resp.StatusCode != http.StatusOK {return errors.New("http状态码为" + strconv.FormatInt(int64(resp.StatusCode), 10))}finalRespBody, err := ioutil.ReadAll(resp.Body)if err != nil {return err}logFile.WriteString(httputils.CurlStyleOutput(r, resp, finalReqBody, finalRespBody))tdrb := &smsutils.TemplateSendBatchSmsRespBody{}err = json.Unmarshal(finalRespBody, &tdrb)if err != nil {return err}if tdrb.Status != 0 {return errors.New("status为" + strconv.FormatInt(int64(tdrb.Status), 10) + ",msg为" + tdrb.Msg)}for _, v := range tdrb.Result {fmt.Println(v)}return nil}func sperator(sec int) {// fmt.Println(strings.Repeat("*", 30) + "\n")fmt.Printf("\n")time.Sleep(time.Duration(sec) * time.Second)}func configPrompt() {log.Println("配置文件默认为/etc/sms.yaml\n" +"需要5个配置项spId,spKey,smsSendUrl,reportUrl,templateUrl联系管理员获取")}
https://github.com/wxxsxxGit/sms-sdk-python
Python3 SDK 基于python3.10.7 版本开发,主要依赖三方组件, 具体请移步GitHub 专区参考阅读Readme
详见https://github.com/wxxsxxGit/sms-sdk-python#操作步骤
# -*- coding: utf-8 -*-from wxxsxx.pkg.httputils import CurlStyleOutputfrom wxxsxx.pkg.strutils import NormalizeKey,RandStringfrom wxxsxx.pkg.encrypt import AesECBEncrypt,HmacSha256AndBase64from wxxsxx.smsutils.signer import SmsSignerimport yamlimport requestsimport jsonimport timeimport loggingimport datetimeimport oswith open("config/sms.yaml", encoding='utf-8') as fr:config = yaml.safe_load(fr)smsSigner = SmsSigner(config["spId"],config["spKey"],config["smsSendUrl"],config["reportUrl"],config["templateUrl"])fileToWrite = open("http_details.txt",'w',encoding=("utf-8"))def demoSingleSend():requestBody = {"content": "【线上线下】您的验证码为123456,在10分钟内有效。","mobile": "13800001111,13955556666,13545556666","extCode": "123456","sId": "123456789abcdefg"}finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.SingleSendUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoSingleSend()状态码为"+resp.status_code)returnrespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))def demoSingleSecureSend():requestBody = {"content": "【线上线下】您的验证码为123456,在10分钟内有效。我当然没问题","mobile": "13800001111","extCode": "123456","sId": "123456789abcdefg"}tempReqBody = json.dumps(requestBody,ensure_ascii=False)# tempReqBody = json.dumps(requestBody,ensure_ascii=False)tempReqBody = '''{"content":"【线上线下】您的验证码为123456,在10分钟内有效。","mobile":"13800001111,13955556666,13545556666","extCode":"123456","sId":"123456789abcdefg"}'''contentString = AesECBEncrypt(tempReqBody, NormalizeKey(smsSigner.SpKey))secondReqBody = {"content":contentString}finalReqBody = json.dumps(secondReqBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.SingleSecureSendUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoSingleSecureSend()状态码为"+resp.status_code)returnrespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))def demoMultiSend():smsBody1 = {"content": "【线上线下】线上线下欢迎你参观1","mobile": "13800001111,8613955556666,+8613545556666","extCode": "123456","msgId": "123456787"}smsBody2 = {"content": "【线上线下】线上线下欢迎你参观2","mobile": "13800001111,8613955556666,+8613545556666","extCode": "123456","msgId": "123456787"}smsBody3 = {"content": "【线上线下】线上线下欢迎你参观3","mobile": "13800001111,8613955556666,+8613545556666","extCode": "123456","msgId": "123456787"}requestBody = [smsBody1, smsBody2,smsBody3]finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.MultiSendUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoMultiSend()状态码为"+resp.status_code)returnrespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))def demoStatusFetch():requestBody = {"maxSize": 500,}finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.StatusFetchUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoStatusFetch()状态码为"+resp.status_code)returnrespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))def demoUpstreamFetch():requestBody = {"maxSize": 500,}finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.UpstreamFetchUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoUpstreamFetch()状态码为"+resp.status_code)returnrespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))def demoBalanceFetch():headers = smsSigner.Sign("")resp = requests.post(smsSigner.StatusFetchUrl(),headers=headers,timeout=3)output = CurlStyleOutput(headers,"",resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoBalanceFetch()状态码为"+resp.status_code)returnrespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))def demoDailyStatsFetch():requestBody = {"date": datetime.datetime.now().strftime("%Y%m%d"),}finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.DailyStatsUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoDailyStatsFetch状态码为"+resp.status_code)returnrespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))def demoTemplateAdd():requestBody = {"templateName": "线上线下addTemplate SDK DEMO " + RandString(10),"templateType": 2,"templateContent": "线上线下addTemplate SDK DEMO template content ${code} template " + RandString(20),"remark": "线上线下addTemplate SDK DEMO template " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),}finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.TemplateAddUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoTemplateAdd状态码为"+resp.status_code)returnrespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))else:return respDict["templateCode"]def demoTemplateModify(templateCode):requestBody = {"templateCode": templateCode,"templateType": 2,"templateName": "线上线下addTemplate SDK DEMO " + RandString(10),"templateContent": "线上线下addTemplate SDK DEMO template content ${code} modify template " + RandString(20),"remark": "线上线下addTemplate SDK DEMO template " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),}finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.TemplateModifyUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoTemplateModify状态码为"+resp.status_code)returnrespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))return respDict["status"]logging.info("模板修改成功")#返回状态0,1,2,3 0是没审核 1审核通过 2审核失败 3禁用def demoTemplateStatus(templateCode):tList = [str(templateCode)]requestBody = {"templateCodes": ",".join(tList),}finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.TemplateStatusUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoTemplateStatus状态码为"+resp.status_code)returnrespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))return 0for v in respDict["templateList"]:if v["templateCode"] == templateCode and v["status"] == 0:return v["auditStatus"]return 0def demoTemplateDelete(templateCode):requestBody = {"templateCode": templateCode,}finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.TemplateDeleteUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoTemplateDelete状态码为"+resp.status_code)return FalserespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))return Falseelse:return Truedef demoTemplateSendSms(templateCode):params = {"code": "本届预选赛"}paramsString = json.dumps(params)requestBody = {"signName": "模板测试","templateCode": templateCode,"params": paramsString,"mobile": "18799991367,12899190876,13914117531",}finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.TemplateSendSmsUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoTemplateSendSms()状态码为"+resp.status_code)return FalserespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))return Falseelse:return Truedef demoTemplateSendBatchSms(templateCode):params = {"code": "本届预选赛"}paramsString = json.dumps(params)item1 = {"signName": "模板测试1","templateCode": templateCode,"params": paramsString,"mobile": "18505101387",}item2 = {"signName": "模板测试2","templateCode": templateCode,"params": paramsString,"mobile": "12899190872",}item3 = {"signName": "模板测试3","templateCode": templateCode,"params": paramsString,"mobile": "18799991362",}item4 = {"signName": "模板测试4","templateCode": templateCode,"params": paramsString,"mobile": "13914117532",}item5 = {"signName": "模板测试5","templateCode": templateCode,"params": paramsString,"mobile": "1895606996",}requestBody = [item1,item2,item3,item4,item5]finalReqBody = json.dumps(requestBody,ensure_ascii=False)headers = smsSigner.Sign(finalReqBody)resp = requests.post(smsSigner.TemplateSendBatchSmsUrl(),headers=headers,data=finalReqBody.encode("utf-8"),timeout=3)output = CurlStyleOutput(headers,finalReqBody,resp)fileToWrite.write(output)if resp.status_code != 200:logging.info("demoTemplateSendSms()状态码为"+resp.status_code)return FalserespDict = json.loads(resp._content)if respDict["status"] != 0:logging.info("status状态为"+str(respDict["status"]))return Falseelse:return Truedef sperator(sec):print("\n")time.sleep(sec)def configPrompt():logging.info("配置文件默认为/etc/sms.yaml\n" +"需要5个配置项spId,spKey,smsSendUrl,reportUrl,templateUrl联系管理员获取")if __name__ == "__main__":globalTemplateCode = ""console_fmt = "%(asctime)s--->%(message)s"logging.basicConfig(level="INFO",format=console_fmt)logging.info("短信发送接口(单内容多号码)")demoSingleSend()sperator(1)#单条内容加密发送logging.info("短信加密发送接口")demoSingleSecureSend()sperator(1)# #多内容批量发送logging.info("短信多发接口")demoMultiSend()sperator(1)# #主动获取状态报告logging.info("状态报告主动获取")demoStatusFetch()sperator(1)# #主动获取上行logging.info("上行主动获取")demoUpstreamFetch()sperator(1)# #查询余额logging.info("预付费账号余额查询")demoBalanceFetch()sperator(1)# #查询每日发送统计logging.info("获取发送账号spId的每日短信发送情况统计")demoDailyStatsFetch()sperator(1)# #模板报备logging.info("模板报备")globalTemplateCode = demoTemplateAdd()print("模板报备的code为{0}".format(globalTemplateCode))sperator(1)logging.info("等待模板审核...")finalAuditCode = 0while True:auditCode = demoTemplateStatus(globalTemplateCode)if auditCode != 0:finalAuditCode = auditCodebreaklogging.info("模板还未审核,请联系管理员审核...")time.sleep(10)if finalAuditCode == 2:logging.info("模板审核未通过,尝试重新提交模板")demoTemplateModify(globalTemplateCode)os._exit(11)elif finalAuditCode == 3:logging.info("模板被禁用")os._exit(12)else:logging.info("模板审核通过")sperator(1)#模板发送单条短信logging.info("模板发送单条短信")demoTemplateSendSms(globalTemplateCode)sperator(1)#模板批量发送短信logging.info("模板发送批量短信")demoTemplateSendBatchSms(globalTemplateCode)sperator(1)logging.info("10秒后将删除模板{0:d}".format(globalTemplateCode))time.sleep(10)deleteResult = demoTemplateDelete(globalTemplateCode)if deleteResult:logging.info("删除模板{0:d}成功".format(globalTemplateCode))fileToWrite.close()sperator(1)logging.info("http请求内容保存在http_details.txt文件中")