容联云通讯是第三方平台,能够提供短信验证码和语音通信等功能,这里只测试使用短信验证码的功能,因此只需完成注册登录(无需实名认证等)即可使用其短信验证码免费测试服务,不过免费测试服务只能给控制台中指定的三个手机号发送短信,且只能有一个短信模板可以使用.
在该网址 https://doc.yuntongxun.com/p/5a533e0c3b8496dd00dce08c 可以查看python发送短信的demo实例和源码下载.下载后目录如下:
使用时,在项目目录中新建一个文件夹(如yuntongxun)将SDK中的两个文件 CCPRestSDK.py 和 xmltojson.py 放入该文件夹中.然后再使用DEMO中的 SendTemplateSMS.py 来调用SDK中的py文件即可.这里我们不使用官方提供的 SendTemplateSMS.py ,而是将其改造封装成一个单例模式的类的方式调用,文件名为sms.py
# coding=utf-8 from .CCPRestSDK import REST # 主帐号 accountSid = 控制台中的ACCOUNT SID # 主帐号Token accountToken = 控制台中的AUTH TOKEN # 应用Id appId = 控制台中的AppID # 请求地址,格式如下,不需要写http:// serverIP = 'app.cloopen.com' # 请求端口 serverPort = '8883' # REST版本号 softVersion = '2013-12-26' # 发送模板短信 # @param to 手机号码 # @param datas 内容数据 格式为列表 例如:['12','34'],如不需替换请填 '' # @param $tempId 模板Id class CCP(object): """自己封装的发送短信的辅助类""" # 用来保存对象的类属性 instance = None def __new__(cls): # 判断CCP类有没有已经创建好的对象,如果没有,创建一个对象,并且保存 # 如果有,则将保存的对象直接返回 if cls.instance is None: obj = super(CCP, cls).__new__(cls) # 初始化REST SDK obj.rest = REST(serverIP, serverPort, softVersion) obj.rest.setAccount(accountSid, accountToken) obj.rest.setAppId(appId) cls.instance = obj return cls.instance def send_template_sms(self, to, datas, temp_id): """""" result = self.rest.sendTemplateSMS(to, datas, temp_id) print('result:\n', result) # for k, v in result.iteritems(): # # if k == 'templateSMS': # for k, s in v.iteritems(): # print '%s:%s' % (k, s) # else: # print '%s:%s' % (k, v) # smsMessageSid:ff75e0f84f05445ba08efdd0787ad7d0 # dateCreated:20171125124726 # statusCode:000000 status_code = result.get("statusCode") if status_code == "000000": # 表示发送短信成功 return 0 else: # 发送失败 return -1 if __name__ == '__main__': ccp = CCP() ret = ccp.send_template_sms("17621081762", ["1234", "5"], 1) print(ret)
由于官方提供的代码CCPRestSDK.py是基于python2写的,本项目为python3,因此需要将其中一些python2的写法改成python3支持的语法,改写完成后为
#-*- coding: UTF-8 -*- # Copyright (c) 2014 The CCP project authors. All Rights Reserved. # # Use of this source code is governed by a Beijing Speedtong Information Technology Co.,Ltd license # that can be found in the LICENSE file in the root of the web site. # # http://www.yuntongxun.com # # An additional intellectual property rights grant can be found # in the file PATENTS. All contributing project authors may # be found in the AUTHORS file in the root of the source tree. # import md5 from hashlib import md5 # py3.x import base64 import datetime # import urllib2 import urllib.request as urllib2 # py3.x import json from .xmltojson import xmltojson from xml.dom import minidom class REST: AccountSid='' AccountToken='' AppId='' SubAccountSid='' SubAccountToken='' ServerIP='' ServerPort='' SoftVersion='' Iflog=True #是否打印日志 Batch='' #时间戳 BodyType = 'xml'#包体格式,可填值:json 、xml # 初始化 # @param serverIP 必选参数 服务器地址 # @param serverPort 必选参数 服务器端口 # @param softVersion 必选参数 REST版本号 def __init__(self,ServerIP,ServerPort,SoftVersion): self.ServerIP = ServerIP; self.ServerPort = ServerPort; self.SoftVersion = SoftVersion; # 设置主帐号 # @param AccountSid 必选参数 主帐号 # @param AccountToken 必选参数 主帐号Token def setAccount(self,AccountSid,AccountToken): self.AccountSid = AccountSid; self.AccountToken = AccountToken;
.......# 发送模板短信 # @param to 必选参数 短信接收彿手机号码集合,用英文逗号分开 # @param datas 可选参数 内容数据 # @param tempId 必选参数 模板Id def sendTemplateSMS(self, to,datas,tempId): self.accAuth() nowdate = datetime.datetime.now() self.Batch = nowdate.strftime("%Y%m%d%H%M%S") #生成sig signature = self.AccountSid + self.AccountToken + self.Batch; signature = signature.encode('utf-8') # py3 # sig = md5.new(signature).hexdigest().upper() sig = md5(signature).hexdigest().upper() # py3 #拼接URL url = "https://"+self.ServerIP + ":" + self.ServerPort + "/" + self.SoftVersion + "/Accounts/" + self.AccountSid + "/SMS/TemplateSMS?sig=" + sig #生成auth src = self.AccountSid + ":" + self.Batch; # auth = base64.encodestring(src).strip() auth = base64.encodestring(src.encode()).strip() req = urllib2.Request(url) self.setHttpHeader(req) req.add_header("Authorization", auth) #创建包体 b='' for a in datas: b+='%s'%(a) body =''+b+' %s %s %s \ \ '%(to, tempId,self.AppId) if self.BodyType == 'json': # if this model is Json ..then do next code b='[' for a in datas: b+='"%s",'%(a) b+=']' body = '''{"to": "%s", "datas": %s, "templateId": "%s", "appId": "%s"}'''%(to,b,tempId,self.AppId) # req.add_data(body) req.data = body.encode() # py3 data='' try: res = urllib2.urlopen(req); data = res.read() res.close() print(data) if self.BodyType=='json': #json格式 locations = json.loads(data) else: #xml格式 xtj=xmltojson() locations=xtj.main(data) if self.Iflog: self.log(url,body,data) return locations except Exception as error: print(error) if self.Iflog: self.log(url,body,data) return {'172001':'网络错误'} .......#设置包头 def setHttpHeader(self,req): if self.BodyType == 'json': req.add_header("Accept", "application/json") req.add_header("Content-Type", "application/json;charset=utf-8") else: req.add_header("Accept", "application/xml") req.add_header("Content-Type", "application/xml;charset=utf-8")
上述语法改写可以通过运行项目,看具体报错信息一步一步改成python3的写法,直到不报错为止,但是项目虽然不报错了,可还是不能成功发送短信,返回的报错信息是 {'172001':'网络错误'},经过百度和自己验证后,发现还有三处需要注意修改:
1. 运行报错:ModuleNotFoundError: No module named 'utils'
File "/home/python/study/ihome/ihome/libs/yuntongxun/sms.py", line 3, infrom .CCPRestSDK import REST File "/home/python/study/ihome/ihome/libs/yuntongxun/CCPRestSDK.py", line 18, in from xmltojson import xmltojson File "/home/python/.virtualenvs/flask/bin/xmltojson.py", line 23, in import utils ModuleNotFoundError: No module named 'utils'
我一开始以为是pip3中没有安装utils,后来再看上面的几行发现找的是虚拟环境中xmltojson.py而官方SDK源码中也提供了一个xmltojson.py文件,我们应该是使用这个文件,所以在开始import xmltojson时,应该写成
from .xmltojson import xmltojson
即从当前目录的xmltojson.py中导入
2.返回的报错信息是 {'172001':'网络错误'},原因是这里访问的url是https开头的,而python升级到 2.7.9 之后引入了一个新特性,当打开一个 https 链接时,会验证一次 SSL 证书。而当目标网站使用的是自签名的证书时就会抛出此异常。解决方案如下:在 sms.py或(SendTemplateSMS.py)文件顶部插入:
import ssl # 全局取消证书验证 ssl._create_default_https_context = ssl._create_unverified_context
3.操作完第二步之后,还是报错{'172001':'网络错误'},查看打印发现响应包体为空,说明发送数据是否有问题,后面发现给req添加请求体信息时,将原来的 py2写法:req.add_data(body),改成了py3写法:req.data = body,这时body是字符串类型,需要使用encode将其转码为byte类型,因此需要改成
req.data = body.encode()