码迷,mamicode.com
首页 > Windows程序 > 详细

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

时间:2016-05-09 11:03:53      阅读:442      评论:0      收藏:0      [点我收藏+]

标签:

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

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

技术分享

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

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

 1 # coding:utf-8
 2 import datetime
 3 import hashlib
 4 import requests
 5 import json
 6 import base64
 7 
 8 
 9 def message_validate(phone_number, validate_number):
10     accountSid = "×××××××××"
11     accountToken = "×××××××××"
12     appid = "××××××××××"
13     templateId = 1
14     now = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
15     signature = accountSid + accountToken + now
16     m = hashlib.md5()
17     m.update(signature)
18     sigParameter = m.hexdigest().upper()
19     # sigParameter = hashlib.md5().update(signature).hexdigest().upper()
20     url = "https://sandboxapp.cloopen.com:8883/2013-12-26/Accounts/%s/SMS/TemplateSMS?sig=%s" % (accountSid, sigParameter)
21     authorization = accountSid + : + now
22     new_authorization = base64.encodestring(authorization).strip()
23     headers = {content-type: application/json;charset=utf-8, accept: application/json,
24                Authorization: new_authorization}
25     data = {to: phone_number, appId: appid, templateId: templateId, datas: [str(validate_number), 3]}
26     response = requests.post(url=url, data=json.dumps(data), headers=headers)
27     if response.json()[statusCode] == 000000:
28         return True, response.json().get(statusMsg)
29     else:
30         return False, response.json().get(statusMsg)
31 
32 if __name__ == __main__:
33     result, reason = message_validate(137×××××××, 123456)
34     if result:
35         print 发送成功
36     else:
37         print 发送失败
38         print 原因是: + reason.encode(utf-8)

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

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

技术分享

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

技术分享

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

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

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

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

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

2、验证短信

3、密码提交,

4、基本资料提交

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

  1 # coding:utf-8
  2 from flask import Flask, request, jsonify, g
  3 from model import User, db_session
  4 import hashlib
  5 import time
  6 import redis
  7 import uuid
  8 from qiniu import Auth, put_file, etag, urlsafe_base64_encode
  9 from util import message_validate
 10 import random
 11 from functools import wraps
 12 
 13 app = Flask(__name__)
 14 redis_store = redis.Redis(host=localhost, port=6380, db=4, password=dahai123)
 15 access_key = hP7WNicFRHPu2Bd24MaLj5VvmElXYJbRCoZfrVs6
 16 secret_key = bBZoaSLKD2lqCZUX6Zu-Fbby5UdTgNVoQT7SfVAV
 17 q = Auth(access_key=access_key, secret_key=secret_key)
 18 bucket_name = dameinv
 19 
 20 
 21 def login_check(f):
 22     @wraps(f)
 23     def decorator(*args, **kwargs):
 24         token = request.headers.get(token)
 25         if not token:
 26             return jsonify({code: 0, message: 需要验证})
 27         
 28         phone_number = redis_store.get(token:%s % token)
 29         if not phone_number or token != redis_store.hget(user:%s % phone_number, token):
 30             return jsonify({code: 2, message: 验证信息错误})
 31 
 32         return f(*args, **kwargs)
 33     return decorator
 34 
 35 
 36 @app.before_request
 37 def before_request():
 38     token = request.headers.get(token)
 39     phone_number = redis_store.get(token:%s % token)
 40     if phone_number:
 41         g.current_user = User.query.filter_by(phone_number=phone_number).first()
 42         g.token = token
 43     return
 44 
 45 
 46 @app.route(/login, methods=[POST])
 47 def login():
 48     phone_number = request.get_json().get(phone_number)
 49     password = request.get_json().get(password)
 50     user = User.query.filter_by(phone_number=phone_number).first()
 51     if not user:
 52         return jsonify({code: 0, message: 没有此用户})
 53 
 54     if user.password != password:
 55         return jsonify({code: 0, message: 密码错误})
 56 
 57     m = hashlib.md5()
 58     m.update(phone_number)
 59     m.update(password)
 60     m.update(str(int(time.time())))
 61     token = m.hexdigest()
 62 
 63     pipeline = redis_store.pipeline()
 64     pipeline.hmset(user:%s % user.phone_number, {token: token, nickname: user.nickname, app_online: 1})
 65     pipeline.set(token:%s % token, user.phone_number)
 66     pipeline.expire(token:%s % token, 3600*24*30)
 67     pipeline.execute()
 68 
 69     return jsonify({code: 1, message: 成功登录, nickname: user.nickname, token: token})
 70 
 71 
 72 @app.route(/user)
 73 @login_check
 74 def user():
 75     user = g.current_user
 76 
 77     nickname = redis_store.hget(user:%s % user.phone_number, nickname)
 78     return jsonify({code: 1, nickname: nickname, phone_number: user.phone_number})
 79 
 80 
 81 @app.route(/logout)
 82 @login_check
 83 def logout():
 84     user = g.current_user
 85 
 86     pipeline = redis_store.pipeline()
 87     pipeline.delete(token:%s % g.token)
 88     pipeline.hmset(user:%s % user.phone_number, {app_online: 0})
 89     pipeline.execute()
 90     return jsonify({code: 1, message: 成功注销})
 91 
 92 
 93 @app.route(/get-qiniu-token)
 94 def get_qiniu_token():
 95     key = uuid.uuid4()
 96     token = q.upload_token(bucket_name, key, 3600)
 97     return jsonify({code: 1, key: key, token: token})
 98 
 99 
100 @app.route(/set-head-picture, methods=[POST])
101 @login_check
102 def set_head_picture():
103     head_picture = request.get_json().get(head_picture)
104     user = g.current_user
105     user.head_picture = head_picture
106     try:
107         db_session.commit()
108     except Exception as e:
109         print e
110         db_session.rollback()
111         return jsonify({code: 0, message: 未能成功上传})
112     redis_store.hset(user:%s % user.phone_number, head_picture, head_picture)
113     return jsonify({code: 1, message: 成功上传})
114 
115 
116 @app.route(/register-step-1, methods=[POST])
117 def register_step_1():
118     """
119     接受phone_number,发送短信
120     """
121     phone_number = request.get_json().get(phone_number)
122     user = User.query.filter_by(phone_number=phone_number).first()
123 
124     if user:
125         return jsonify({code: 0, message: 该用户已经存在,注册失败})
126     validate_number = str(random.randint(100000, 1000000))
127     result, err_message = message_validate(phone_number, validate_number)
128 
129     if not result:
130         return jsonify({code: 0, message: err_message})
131 
132     pipeline = redis_store.pipeline()
133     pipeline.set(validate:%s % phone_number, validate_number)
134     pipeline.expire(validate:%s % phone_number, 60)
135     pipeline.execute()
136 
137     return jsonify({code: 1, message: 发送成功})
138 
139 
140 @app.route(/register-step-2, methods=[POST])
141 def register_step_2():
142     """
143     验证短信接口
144     """
145     phone_number = request.get_json().get(phone_number)
146     validate_number = request.get_json().get(validate_number)
147     validate_number_in_redis = redis_store.get(validate:%s % phone_number)
148 
149     if validate_number != validate_number_in_redis:
150         return jsonify({code: 0, message: 验证没有通过})
151 
152     pipe_line = redis_store.pipeline()
153     pipe_line.set(is_validate:%s % phone_number, 1)
154     pipe_line.expire(is_validate:%s % phone_number, 120)
155     pipe_line.execute()
156 
157     return jsonify({code: 1, message: 短信验证通过})
158 
159 
160 @app.route(/register-step-3, methods=[POST])
161 def register_step_3():
162     """
163     密码提交
164     """
165     phone_number = request.get_json().get(phone_number)
166     password = request.get_json().get(password)
167     password_confirm = request.get_json().get(password_confirm)
168 
169     if len(password) < 7 or len(password) > 30:
170         # 这边可以自己拓展条件
171         return jsonify({code: 0, message: 密码长度不符合要求})
172 
173     if password != password_confirm:
174         return jsonify({code: 0, message: 密码和密码确认不一致})
175 
176     is_validate = redis_store.get(is_validate:%s % phone_number)
177 
178     if is_validate != 1:
179         return jsonify({code: 0, message: 验证码没有通过})
180 
181     pipeline = redis_store.pipeline()
182     pipeline.hset(register:%s % phone_number, password, password)
183     pipeline.expire(register:%s % phone_number, 120)
184     pipeline.execute()
185 
186     return jsonify({code: 1, message: 提交密码成功})
187 
188 
189 @app.route(/register-step-4, methods=[POST])
190 def register_step_4():
191     """
192     基本资料提交
193     """
194     phone_number = request.get_json().get(phone_number)
195     nickname = request.get_json().get(nickname)
196 
197     is_validate = redis_store.get(is_validate:%s % phone_number)
198 
199     if is_validate != 1:
200         return jsonify({code: 0, message: 验证码没有通过})
201 
202     password = redis_store.hget(register:%s % phone_number, password)
203 
204     new_user = User(phone_number=phone_number, password=password, nickname=nickname)
205     db_session.add(new_user)
206 
207     try:
208         db_session.commit()
209     except Exception as e:
210         print e
211         db_session.rollback()
212         return jsonify({code: 0, message: 注册失败})
213     finally:
214         redis_store.delete(is_validate:%s % phone_number)
215         redis_store.delete(register:%s % phone_number)
216 
217     return jsonify({code: 1, message: 注册成功})
218 
219 
220 @app.teardown_request
221 def handle_teardown_request(exception):
222     db_session.remove()
223 
224 if __name__ == __main__:
225     app.run(debug=True, host=0.0.0.0, port=5001)

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

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

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

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

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

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

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

 1 # coding:utf-8
 2 import requests
 3 import json
 4 from qiniu import put_file
 5 
 6 
 7 class APITest(object):
 8     def __init__(self, base_url):
 9         self.base_url = base_url
10         self.headers = {}
11         self.token = None
12         self.qiniu_token = None
13         self.qiniu_key = None
14         self.qiniu_base_url = http://7xk6rc.com1.z0.glb.clouddn.com/
15 
16     def login(self, phone_number, password, path=/login):
17         payload = {phone_number: phone_number, password: password}
18         self.headers = {content-type: application/json}
19         response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
20         response_data = json.loads(response.content)
21         self.token = response_data.get(token)
22         return response_data
23 
24     def user(self, path=/user):
25         self.headers = {token: self.token}
26         response = requests.get(url=self.base_url + path, headers=self.headers)
27         response_data = json.loads(response.content)
28         return response_data
29 
30     def logout(self, path=/logout):
31         self.headers = {token: self.token}
32         response = requests.get(url=self.base_url + path, headers=self.headers)
33         response_data = json.loads(response.content)
34         return response_data
35 
36     def get_qiniu_token(self, path=/get-qiniu-token):
37         response = requests.get(url=self.base_url + path)
38         response_data = json.loads(response.content)
39         self.qiniu_token = response_data.get(token)
40         self.qiniu_key = response_data.get(key)
41         if self.qiniu_token and self.qiniu_key:
42             print 成功获取qiniu_token和qiniu_key,分别为%s和%s % (self.qiniu_token.encode(utf-8), self.qiniu_key.encode(utf-8))
43             localfile = /home/yudahai/PycharmProjects/blog01/app/my-test.png
44             ret, info = put_file(self.qiniu_token, self.qiniu_key, localfile)
45             print info.status_code
46             if info.status_code == 200:
47                 print 上传成功
48                 self.head_picture = self.qiniu_base_url + self.qiniu_key
49                 print 其url为: + self.head_picture.encode(utf-8)
50             else:
51                 print 上传失败
52         return response_data
53 
54     def set_head_picture(self, path=/set-head-picture):
55         payload = {head_picture: self.head_picture}
56         self.headers = {token: self.token, content-type: application/json}
57         response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
58         response_data = json.loads(response.content)
59         print response_data.get(message)
60         return response_data
61 
62     def register_step_1(self, phone_number, path=/register-step-1):
63         payload = {phone_number: phone_number}
64         self.headers = {content-type: application/json}
65         response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
66         response_data = json.loads(response.content)
67         print response_data.get(code)
68         return response_data
69 
70     def register_step_2(self, phone_number, validate_number, path=/register-step-2):
71         payload = {phone_number: phone_number, validate_number: validate_number}
72         self.headers = {content-type: application/json}
73         response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
74         response_data = json.loads(response.content)
75         print response_data.get(code)
76         return response_data
77 
78     def register_step_3(self, phone_number, password, password_confirm, path=/register-step-3):
79         payload = {phone_number: phone_number, password: password, password_confirm: password_confirm}
80         self.headers = {content-type: application/json}
81         response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
82         response_data = json.loads(response.content)
83         print response_data.get(code)
84         return response_data
85 
86     def register_step_4(self, phone_number, nickname, path=/register-step-4):
87         payload = {phone_number: phone_number, nickname: nickname}
88         self.headers = {content-type: application/json}
89         response = requests.post(url=self.base_url + path, data=json.dumps(payload), headers=self.headers)
90         response_data = json.loads(response.content)
91         print response_data.get(code)
92         return response_data
93 
94 if __name__ == __main__:
95     api = APITest(http://127.0.0.1:5001)
96     api.login(13565208554, 123456)
97     api.get_qiniu_token()
98     api.set_head_picture()
99     api.logout()

在命令行下验证一下吧。

技术分享

就是这么简单,整个过程是不是很简单?基于redis的验证机制就写到这。

 

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

标签:

原文地址:http://www.cnblogs.com/yueerwanwan0204/p/5460668.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!