flask开发restful api系列(5)-短信验证码

  我们现在开发app,注册用户的时候,不再像web一样,发送到个人邮箱了,毕竟个人邮箱在移动端填写验证都很麻烦,一般都采用短信验证码的方式。今天我们就讲讲这方面的内容。

  首先,先找一个平台吧。我们公司找的容联云通讯这个平台,至少目前为止,用的还可以。先在容联上注册一下,然后创建一个应用,如下图所示:

flask开发restful api系列(5)-短信验证码

  我只勾选了2个功能,他们这边还有很多其他功能,暂时用不到,就不选了。好了,点击"确认",一个应用就弄好了,下面就尝试着写代码发短信吧。

  容联为开发者提供了免费测试功能,但一个号码基本不会超过3次,所以用的时候要小心哦!容联的文档可能写的有点复杂了,反正我觉得稍微有点复杂,其实就是post一个request,我们把它提炼一下,得到下面这些代码。

 # coding:utf-8
import datetime
import hashlib
import requests
import json
import base64 def message_validate(phone_number, validate_number):
accountSid = "×××××××××"
accountToken = "×××××××××"
appid = "××××××××××"
templateId = ''
now = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
signature = accountSid + accountToken + now
m = hashlib.md5()
m.update(signature)
sigParameter = m.hexdigest().upper()
# sigParameter = hashlib.md5().update(signature).hexdigest().upper()
url = "https://sandboxapp.cloopen.com:8883/2013-12-26/Accounts/%s/SMS/TemplateSMS?sig=%s" % (accountSid, sigParameter)
authorization = accountSid + ':' + now
new_authorization = base64.encodestring(authorization).strip()
headers = {'content-type': 'application/json;charset=utf-8', 'accept': 'application/json',
'Authorization': new_authorization}
data = {'to': phone_number, 'appId': appid, 'templateId': templateId, 'datas': [str(validate_number), '']}
response = requests.post(url=url, data=json.dumps(data), headers=headers)
if response.json()['statusCode'] == '':
return True, response.json().get('statusMsg')
else:
return False, response.json().get('statusMsg') if __name__ == '__main__':
result, reason = message_validate('137×××××××', '')
if result:
print '发送成功'
else:
print '发送失败'
print '原因是:' + reason.encode('utf-8')

看,这就是重新编辑的函数,是不是很简单,逐行分析一下吧。

首先, accountSid和accountToken 其实就相当于账户的用户名和密码,主要在这个页面可以看到。

flask开发restful api系列(5)-短信验证码

其次,appid就是应用的appid,直接填进去就可以

flask开发restful api系列(5)-短信验证码

至于templateId,其实就是你添加新的模板的id号,我们这边用开发者账号,直接填写'1'就可以了。

好了, 下面的代码,只要熟悉http的人,都会非常熟悉,基本都是把账号的id和token加上时间戳,转换成md5值,然后再encode一下,变成http的基本验证。判别有没有成功,按官网返回的参数,直接解析一下,是'000000'就代表成功,否则失败。

是不是很简单?就是这么简单,好了,既然发送短信的函数写好了,下面就写注册api接口吧。

一般的移动注册api接口可以分为3步

1、提交电话号码,发送短信验证,

2、验证短信

3、密码提交,

4、基本资料提交

一共4个接口,就register 1 2 3 4 吧,具体代码如下:

 @app.route('/register-step-1', methods=['POST'])
def register_step_1():
"""
接受phone_number,发送短信
"""
phone_number = request.get_json().get('phone_number')
user = User.query.filter_by(phone_number=phone_number).first() if user:
return jsonify({'code': 0, 'message': '该用户已经存在,注册失败'})
validate_number = str(random.randint(100000, 1000000))
result, err_message = message_validate(phone_number, validate_number) if not result:
return jsonify({'code': 0, 'message': err_message}) pipeline = redis_store.pipeline()
pipeline.set('validate:%s' % phone_number, validate_number)
pipeline.expire('validate:%s' % phone_number, 60)
pipeline.execute() return jsonify({'code': 1, 'message': '发送成功'}) @app.route('/register-step-2', methods=['POST'])
def register_step_2():
"""
验证短信接口
"""
phone_number = request.get_json().get('phone_number')
validate_number = request.get_json().get('validate_number')
validate_number_in_redis = redis_store.get('validate:%s' % phone_number) if validate_number != validate_number_in_redis:
return jsonify({'code': 0, 'message': '验证没有通过'}) pipe_line = redis_store.pipeline()
pipe_line.set('is_validate:%s' % phone_number, '')
pipe_line.expire('is_validate:%s' % phone_number, 120)
pipe_line.execute() return jsonify({'code': 1, 'message': '短信验证通过'}) @app.route('/register-step-3', methods=['POST'])
def register_step_3():
"""
密码提交
"""
phone_number = request.get_json().get('phone_number')
password = request.get_json().get('password')
password_confirm = request.get_json().get('password_confirm') if len(password) < 7 or len(password) > 30:
# 这边可以自己拓展条件
return jsonify({'code': 0, 'message': '密码长度不符合要求'}) if password != password_confirm:
return jsonify({'code': 0, 'message': '密码和密码确认不一致'}) is_validate = redis_store.get('is_validate:%s' % phone_number) if is_validate != '':
return jsonify({'code': 0, 'message': '验证码没有通过'}) pipeline = redis_store.pipeline()
pipeline.hset('register:%s' % phone_number, 'password', password)
pipeline.expire('register:%s' % phone_number, 120)
pipeline.execute() return jsonify({'code': 1, 'message': '提交密码成功'}) @app.route('/register-step-4', methods=['POST'])
def register_step_4():
"""
基本资料提交
"""
phone_number = request.get_json().get('phone_number')
nickname = request.get_json().get('nickname') is_validate = redis_store.get('is_validate:%s' % phone_number) if is_validate != '':
return jsonify({'code': 0, 'message': '验证码没有通过'}) password = redis_store.hget('register:%s' % phone_number, 'password') new_user = User(phone_number=phone_number, password=password, nickname=nickname)
db_session.add(new_user) try:
db_session.commit()
except Exception as e:
print e
db_session.rollback()
return jsonify({'code': 0, 'message': '注册失败'})
finally:
redis_store.delete('is_validate:%s' % phone_number)
redis_store.delete('register:%s' % phone_number) return jsonify({'code': 1, 'message': '注册成功'})

看到上面register的4个步骤没有,这边要注意的是具体方法:

步骤1:提交手机号码,验证。这个很基础,就不用说了,重要的是,发送过短信之后,要把短信验证码存在redis里面,以便下一个接口调用;其次,这个存储过程,一定要用pipeline,还要设置一个超时删除。想一想,假设你的程序在注册的过程中,崩掉,或者你中断程序,最起码不要影响其他程序,如果没有超时值,会产生很多的垃圾值,并且你还很难注意到。

步骤2:从redis里找到之前存储的验证码,对比,成功就进入下一步。这边,我还设置了一个is_validate值,最主要是防止客户端同事在这步会出错,或者其他知道这个接口的人,直接用脚本访问后面的接口,这样会出现未知的错误。

步骤3:验证一下密码是否符合要求,然后看一下上一步设置的is_validate是否存在,上面说了,防止恶意用户直接访问下面的接口,然后保存password到一个redis的hash值。这边主要为了方便客户端同事,不然下一个接口还要重新上传password值,客户端同事一定会恼火的。

步骤4:提交基本资料,然后保存。这边最重要的是,不管注册成功失败,自己注意把redis里面的值清理干净。看看我上面的接口,所有这些临时注册值,都设置了一个超时值,超过时间,就清理掉。

整个过程就完成了,可以去验证一下结果了。(其实这里还有缺陷,假设在第二步,我知道你这个接口,写个小脚本,暴力破解你的验证码,很快就能拿到的。这边可以做个小改动,在redis里面加一个值,访问一次,则添加1,超过一定次数,就返回错误代码。很简单,这边就不深入了)

好了,客户端验证代码如下:

 # coding:utf-8
import requests
import json
from qiniu import put_file class APITest(object):
def __init__(self, base_url):
self.base_url = base_url
self.headers = {}
self.token = None
self.qiniu_token = None
self.qiniu_key = None
self.qiniu_base_url = 'http://7xk6rc.com1.z0.glb.clouddn.com/' def login(self, phone_number, password, path='/login'):
payload = {'phone_number': phone_number, 'password': password}
self.headers = {'content-type': 'application/json'}
response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
response_data = json.loads(response.content)
self.token = response_data.get('token')
return response_data def user(self, path='/user'):
self.headers = {'token': self.token}
response = requests.get(url=self.base_url + path, headers=self.headers)
response_data = json.loads(response.content)
return response_data def logout(self, path='/logout'):
self.headers = {'token': self.token}
response = requests.get(url=self.base_url + path, headers=self.headers)
response_data = json.loads(response.content)
return response_data def get_qiniu_token(self, path='/get-qiniu-token'):
response = requests.get(url=self.base_url + path)
response_data = json.loads(response.content)
self.qiniu_token = response_data.get('token')
self.qiniu_key = response_data.get('key')
if self.qiniu_token and self.qiniu_key:
print '成功获取qiniu_token和qiniu_key,分别为%s和%s' % (self.qiniu_token.encode('utf-8'), self.qiniu_key.encode('utf-8'))
localfile = '/home/yudahai/PycharmProjects/blog01/app/my-test.png'
ret, info = put_file(self.qiniu_token, self.qiniu_key, localfile)
print info.status_code
if info.status_code == 200:
print '上传成功'
self.head_picture = self.qiniu_base_url + self.qiniu_key
print '其url为:' + self.head_picture.encode('utf-8')
else:
print '上传失败'
return response_data def set_head_picture(self, path='/set-head-picture'):
payload = {'head_picture': self.head_picture}
self.headers = {'token': self.token, 'content-type': 'application/json'}
response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
response_data = json.loads(response.content)
print response_data.get('message')
return response_data def register_step_1(self, phone_number, path='/register-step-1'):
payload = {'phone_number': phone_number}
self.headers = {'content-type': 'application/json'}
response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
response_data = json.loads(response.content)
print response_data.get('code')
return response_data def register_step_2(self, phone_number, validate_number, path='/register-step-2'):
payload = {'phone_number': phone_number, 'validate_number': validate_number}
self.headers = {'content-type': 'application/json'}
response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
response_data = json.loads(response.content)
print response_data.get('code')
return response_data def register_step_3(self, phone_number, password, password_confirm, path='/register-step-3'):
payload = {'phone_number': phone_number, 'password': password, 'password_confirm': password_confirm}
self.headers = {'content-type': 'application/json'}
response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
response_data = json.loads(response.content)
print response_data.get('code')
return response_data def register_step_4(self, phone_number, nickname, path='/register-step-4'):
payload = {'phone_number': phone_number, 'nickname': nickname}
self.headers = {'content-type': 'application/json'}
response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
response_data = json.loads(response.content)
print response_data.get('code')
return response_data if __name__ == '__main__':
api = APITest('http://127.0.0.1:5001')
api.login('', '')
api.get_qiniu_token()
api.set_head_picture()
api.logout()

在命令行下验证一下吧。

flask开发restful api系列(5)-短信验证码

整个过程是不是很简单?基于redis的注册机制就写到这。

上一篇:java接入创蓝253短信验证码


下一篇:DSAPI 字符串和文件转Md5字符串