【V4】短信服务API接入-帮助文档(公开版)

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参考

1、概述

1.1 目的

提供对接短信发送平台HTTP/HTTPS协议的统一规范,便于客户侧进行接口的对接开发

1.2 范围

平台对外提供的HTTP/HTTPS协议短信下发接口,以及状态、上行的推送和主动获取接口

1.3 术语

术语

英文

中文

HTTP

Hyper Text Transfer Protocol

超文本传输协议

HTTPS

Secure Hypertext Transfer Protocol

安全超文本传输协议

JSON

JavaScript Object Notation

一种轻量级的数据交换格式

spId

Service Provider Id

我方提供的发送账号的唯一标识

2、约束

3、鉴权认证

3.1 介绍

平台采用预共享密钥的方式来进行鉴权认证。即我方和客户侧之间共享spKey密钥,客户侧在发起请求时对请求信息进行签名,然后把构造好的签名放入请求头中,我方在接收到请求信息时采用同样的方式对请求信息进行签名,并根据签名是否一致进行认证

3.2 构造签名

3.2.1 构造signatue_origin字符串

构造公式如下:

signature_origin=${body_content} + ${timestamp}

构造参数说明如下:

3.2.2 加密

将signature_origin字符串使用HMAC-SHA256算法(使用我方提供的预共享密钥spKey作为算法的key)计算签名HMAC值,然后对生成的HMAC值进行base64编码即生成最终传递的签名signatue

3.2.3 构造请求头

将生成的签名signatue放入请求头中,格式如下:

Authorization: HMAC-SHA256 ${timestamp},${signature}

构造参数说明如下:

4、接口列表

4.1 短信发送接口(单内容多号码)

4.1.1 描述

支持客户侧单内容多号码的短信发送

4.1.2 请求基本信息

4.1.3 请求参数

参数名称

类型

是否必须

最大长度

说明

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分钟之内都视为即时下发短信,立马发出去

4.1.4 请求示例

{
    "content" : "【线上线下】您的验证码为123456,在10分钟内有效。",    "mobile" : "13800001111,8613955556666,+8613545556666",	"extCode" : "123456",	"msgid":"8629637681836384963",	"sId" : "123456789abcdefg"}

4.1.5 响应示例

{
    "status" : 0,    // 请求成功,详细代码参考4.8.1
    "msgid" : "8629637681836384963"}

参数说明

4.2 状态报告推送

4.2.1 描述

平台将短信下发的状态报告推送给客户

4.2.2 推送方式

4.2.3 推送参数

参数名称

类型

是否必须

说明

status

int

必须

提交响应状态

result

JSON Array

必须

状态反馈信息(打包数组)

result参数说明

参数名称

类型

是否必须

说明

phone

string

必须

手机号码

msgid

string

必须

与提交响应的msgid一致

status

string

必须

短信状态

fee

int

必须

短信计费条数

sId

string

可选(提交时传才会有)

批次号,可用于客户侧按照批次号对短信进行分组

donetime

string

必须

短信到达时间,格式:yyyyMMddHHmmss

4.2.4 推送示例

{
    "status" : 0,                                 // 固定值0
    "result" :
    [
        {
            "phone" : "13921350591",             // 手机号码
            "msgid" : "-8629637681836384963",    // 与提交中的msgid 一致 
            "status" : "DELIVRD" ,               // 短信状态,参考附表2
            "donetime" : "20170816153922",       // 短信到达时间   			"fee" : 2,                           // 短信计费条数			"sId" : "123456789abcdefg"           // 批次号        }
        //这里是数组会有多条,默认最大500条
,平台可配置    ]
}

4.2.5 响应示例

{  "status" : 0,  "msg" : "" }

参数说明

避免重复推送,客户侧正确接收到后请返回成功码0。

4.2.6 推送机制

4.3 状态报告主动获取

4.3.1 描述

平台支持客户主动来获取短信的状态报告,平台对状态报告最大缓存48小时,超过48小时未获取就会丢弃

4.3.2 请求基本信息

4.3.3 请求参数

参数名称

类型

是否必须

说明

maxSize

int

可选

默认500,支持范围[10, 1000],参数超出范围按照默认算

4.3.4 请求示例

{	"maxSize" : 100}

4.3.5 响应示例

见4.2.4

4.4 上行推送

4.4.1 描述

平台将短信回复的上行推送给客户

4.4.2 推送方式

4.4.3 推送参数

参数名称

类型

是否必须

说明

status

int

必须

上行响应状态

result

JSON Array

必须

上行回复内容(打包数组)

result参数说明

参数名称

类型

是否必须

说明

phone

string

必须

手机号码

extCode

string

必须

用户提交短信时候带的extCode

content

string

必须

上行内容

receivetime

string

必须

短信到达时间,格式:yyyyMMddHHmmss

sId

string

可选(提交时传才会有)

对应短信提交时的sId

sign

string

可选

对应短信提交时的签名(无下发上行没有此字段)

msgid

string

必须

对应短信提交时的msgid

4.4.4 推送示例

{ 
    "status" : 0,                             // 固定值0
    "result":
    [
         {
            "phone" : "13921350591",         // 手机号码
            "extCode" : "682",               // 用户提交短信时候带的extCode
            "content" : "上行回复内容" ,       // 上行内容
            "receivetime" : "20170816153922",// 短信到达时间			"sId" : "123456789abcdefg",      // 批次号			"sign" : "签名",			"msgid" : "-8629637681836384963" // 短信唯一编号        }
        // 这里是数组会有多条,默认最大500条
,平台可配置    ]
}

4.4.5 响应示例

{  "status" : 0,      //状态码  0表示成功,非0表示失败  "msg" : ""       // 错误信息}

参数说明

避免重复推送,客户侧正确接收到后请返回成功码0。

4.4.6 推送机制

4.5 上行主动获取

4.5.1 描述

平台支持客户主动来获取短信的上行报告,平台对上行报告最大缓存48小时,超过48小时未获取就会丢弃

4.5.2 请求基本信息

4.5.3 请求参数

参数名称

类型

是否必须

最大长度

说明

maxSize

int

可选

默认500,支持范围[10, 1000],参数超出范围按照默认算

4.5.4 请求示例

{	"maxSize" : 100}

4.5.5 响应示例

见4.4.4

4.6 预付费账号余额查询

4.6.1 描述

预付费账号剩余余额查询。

推荐使用登陆 【客户发送平台】 在[用户中心/个人资料]模块中扫码绑定微信公众号,后期余额不足时会自动提醒推送。

4.6.2 请求基本信息

4.6.3 请求参数

4.6.4 请求示例

4.6.5 响应示例

{	"status": 0       // 请求成功,详细代码参考4.9.1	"result": 10000    //当前余额条数}

4.7 获取发送账号日统计

4.7.1 描述

获取发送账号spId的每日短信发送情况统计

4.7.2 请求基本信息

4.7.3 请求参数

参数名称

类型

是否必须

最大长度

说明

date

String

必选

8

日期格式化:yyyyMMdd

示例: 20200101

4.7.4 请求示例

{	"date": "20200101"}

4.7.5 响应示例

{ 
    "status" : 0,                             // 请求成功,详细代码参考4.8.1
    "result":
	 {
            "spId" : "apiSendUser01",         // 发送账号            "date" : 20200101,               // 查询的日期            "sendCount" : 1000,               // 提交条数            "feeCount" : 1500 ,       // 发送条数            "successCount" : 1400,// 成功条数			"failCount" : 80,      // 失败条数			"exceptionCount" : 0,      // 异常条数			"noRespCount" : 20,      // 未反馈条数       }
}

4.8 返回错误码参考

4.8.1 提交或查询status代码表

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

不支持国际短信

4.8.2 状态报告反馈错误代码表

仅针对当前短信发送平台返回的内部反馈代码释义,不在此表中的代表运营商返回的状态码。

代码

含义

BLACK

黑名单

NOWAY

未匹配通道

CHECK

审核不通过

SIGN

签名错误,未报备

KEYWORD

内容含有非法关键词

LIMIT

超发限制

PHONERR

无归属地

SWITCH

通道切出

DELIVRD

反馈成功

TDBLACK

退订黑

TSBLACK

投诉黑

RDBLACK

红名单

AICHECK

智能拦截,一般是发送账号有误

INSUFFICIENT_BALANCE

余额不足

UNDELIV

反馈失败

TESTER_DELIVRD

压测专用码

ERRLMSG

CMPP长短信异常

4.9 短信加密发送接口(影响性能,非必要不推荐)

4.9.1 描述

支持客户侧单内容多号码的短信加密传输发送

使用AES对称加密,使用我方提供的预共享密钥spKey作为算法的key,发送时使用该秘钥对请求体进行加密

4.9.2 请求基本信息

4.9.3 请求参数

参数名称

类型

是否必须

最大长度

说明

content

string

必须

请求体加密后的Base64字符串

4.9.4 加密代码

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);
    }}

4.9.5 请求明文参数

{
    "content" : "【线上线下】您的验证码为123456,在10分钟内有效。",    "mobile" : "13800001111,8613955556666,+8613545556666",	"extCode" : "123456",	"sId" : "123456789abcdefg"}

4.9.6 请求参数

注意:构造请求头 signature_origin=${body_content} + ${timestamp} 中的 body_content使用加密后的json字符串!

//假设秘钥为 Z345_#${	"content":"eMHvnXPmLoJDRtAStEGHtRzNbsPzduN6XmM3F08b6EszuRlXdEw3FPSGpjVphixEeYh7LJkJpmP0OoaVI5kRXRTAT/Ny1a1Anzy2Ri6pgBpaYkzlHljPFBfgPNlGnDYSINebgUFr5wbgDvSGtcIoHdPoByb+srwaovZoose7q0Ms2pdn/kZOhym2hBNUta95EXfXDYKX4FjqFW2mENhFJEZUpsTSjfTpose6+ctt0NKocANGk14mk8qglHlyYebk"}

4.9.7 响应示例

{
    "status" : 0,    // 请求成功,详细代码参考4.8.1
    "msgid" : "8629637681836384963"}

4.10 API参考

JAVA SDK

https://github.com/wxxsxxGit/sms-sdk-java

4.10.1 描述

Java SDK 基于Java8 版本开发,主要依赖三方组件有OkHttp/Hutool等,有Okhttp3 几个版本可供依赖选择,请移步GitHub 专区参考阅读Readme

4.10.2 开始使用

添加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

4.10.3 调用示例

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
     */
    @Deprecated
    public static void getReport(V4Client v4Client) {
        ReportResp report = v4Client.getReport();
        System.out.println(JSONUtil.toJsonStr(report));
    }

    /**
     * 主动获取未读上行
     * 无特殊要求一次最多返回500条
     * 可提供回调URL自动推送(推荐)
     *
     * @param v4Client
     */
    @Deprecated
    public static void getDeliver(V4Client v4Client) {
        DeliverResp deliver = v4Client.getDeliver();
        System.out.println(JSONUtil.toJsonStr(deliver));
    }

    /**
     * 多内容打包发送接口(同步模式)最多100个内容,
     * msgId和extCode可以为空
     */
    @Deprecated
    public 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));
    }
}

GO SDK

https://github.com/wxxsxxGit/sms-sdk-go

4.10.1 描述

GO SDK 基于Go 1.16 版本开发,主要依赖三方组件有color v1.13.0 / viper v1.13.0等 请移步GitHub 专区参考阅读Readme

4.10.2 开始使用

进入下载的sms-sdk-go目录执行

go mod tidy

4.10.3 调用示例

package 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 name	viper.AddConfigPath("/etc") // call multiple times to add many search paths	viper.AddConfigPath(".")    // path to look for the config file in	viper.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 = templateId	log.Println("模板报备的code为", globalTemplateCode)	log.Println("等待模板审核...")	//判断是否审核成功	var tempValue uint8	for {		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 = value			break		}	}	//审核成功提交模板短信	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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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.DefaultClient	resp, 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联系管理员获取")}

Python3 SDK

https://github.com/wxxsxxGit/sms-sdk-python

4.10.1 描述

Python3 SDK 基于python3.10.7 版本开发,主要依赖三方组件, 具体请移步GitHub 专区参考阅读Readme

4.10.2 开始使用

详见https://github.com/wxxsxxGit/sms-sdk-python#操作步骤

4.10.3 调用示例

# -*- coding: utf-8 -*-
from wxxsxx.pkg.httputils import CurlStyleOutput
from wxxsxx.pkg.strutils import NormalizeKey,RandString
from wxxsxx.pkg.encrypt import AesECBEncrypt,HmacSha256AndBase64
from wxxsxx.smsutils.signer import SmsSigner
import yaml
import requests
import json
import time
import logging
import datetime
import os




with 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)
        return
    respDict = 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)
        return
    respDict = 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)
        return
    respDict = 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)
        return
    respDict = 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)
        return
    respDict = 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)
        return
    respDict = 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)
        return
    respDict = 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)
        return
    respDict = 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)
        return
    respDict = 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)
        return
    respDict = json.loads(resp._content)
    if respDict["status"] != 0:
        logging.info("status状态为"+str(respDict["status"]))
        return 0

    for v in respDict["templateList"]:
        if v["templateCode"] == templateCode and v["status"] == 0:
            return v["auditStatus"]
    return 0

def 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 False
    respDict = json.loads(resp._content)
    if respDict["status"] != 0:
        logging.info("status状态为"+str(respDict["status"]))
        return False
    else:
        return True


def 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 False
    respDict = json.loads(resp._content)
    if respDict["status"] != 0:
        logging.info("status状态为"+str(respDict["status"]))
        return False
    else:
        return True

def 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 False
    respDict = json.loads(resp._content)
    if respDict["status"] != 0:
        logging.info("status状态为"+str(respDict["status"]))
        return False
    else:
        return True

def 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 = 0
    while True:
        auditCode = demoTemplateStatus(globalTemplateCode)
        if auditCode != 0:
            finalAuditCode = auditCode
            break
        logging.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文件中")