Flask基础学习最终篇-与Python结合-上

1. 重置密码的一些知识

我们设计重置密码的时候,一般是发送一个重置密码的邮件到客户。邮件中一般包含用户信息来标识用户,标识用户一般有三种方法。

  1. 方法一:URL中附带用户ID

    直接在邮件中附带用户ID会暴露用户ID,容易被篡改成其他用户ID

  2. 方法二:服务端加密用户ID

    通过服务端加密然后附带到邮件,可以有效防止暴露ID

  3. 方法三:服务器缓存键值对

    通过在服务器端缓存一个键值对,key为一串随机字符串,Value为用户ID

方法二和三均要设置过期时间。

2. first_or_404

当我们进行查询的时候可以使用first或者first_or_404。两者的区别是first查询不到的时候返回的是Nonefirst_or_404是直接抛出异常(404 Not Found)不再执行下面的代码。

image-20180821111652276

first_or_404实现了对first的封装,当不存在的时候直接由Abort抛出异常,异常类属于HttpException

下面我们看下源码:

1
2
3
4
5
6
7
8
9
class BaseQuery(orm.Query):
def first_or_404(self):
"""Like :meth:`first` but aborts with 404 if not found instead of
returning `None`.
"""
rv = self.first()
if rv is None:
abort(404)
return rv

我们看到当查找为空的之后,会调用abort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def abort(status, *args, **kwargs):
'''
Raises an :py:exc:`HTTPException` for the given status code or WSGI
application::

abort(404) # 404 Not Found
abort(Response('Hello World'))

Can be passed a WSGI application or a status code. If a status code is
given it's looked up in the list of exceptions and will raise that
exception, if passed a WSGI application it will wrap it in a proxy WSGI
exception and raise that::

abort(404)
abort(Response('Hello World'))

'''
return _aborter(status, *args, **kwargs)

_aborter = Aborter()

我们看到abort方法内部只是调用了一个类。这个类当成了方法一样使用,这是因为类的内部实现了__call__方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Aborter(object):

"""
When passed a dict of code -> exception items it can be used as
callable that raises exceptions. If the first argument to the
callable is an integer it will be looked up in the mapping, if it's
a WSGI application it will be raised in a proxy exception.

The rest of the arguments are forwarded to the exception constructor.
"""

def __init__(self, mapping=None, extra=None):
if mapping is None:
mapping = default_exceptions
self.mapping = dict(mapping)
if extra is not None:
self.mapping.update(extra)

def __call__(self, code, *args, **kwargs):
if not args and not kwargs and not isinstance(code, integer_types):
raise HTTPException(response=code)
if code not in self.mapping:
raise LookupError('no exception for %r' % code)
raise self.mapping[code](*args, **kwargs)

一旦我们在类中实现了魔法方法__call__那么我们可以向方法一样使用类。这种可以直接当成函数一样调用的对象我们称之为可调用对象。

可调用对象

上面我们说了主要实现了魔法方法__call__的对象就是可调用对象。那可调用对象有哪些作用呢?

作用一:简化对象下方法的调用

假设我们一个对象下有一个函数,我们需要使用a.func()的方式调用,但是当我们写成可调用对象的时候可以直接使用a()的方式调用。

在哪些情况下可以将一个对象实现为可调用对象呢?

  1. 当对象下只有一个方法的时候(构造函数不算)
  2. 对象下某个方法使用很多的时候

作用二:模糊了函数和对象的区别

我们使用可调用对象就像使用函数一样a()func()。意义在于统一调用接口。

我们接着看源码

1
raise self.mapping[code](*args, **kwargs)

这里的mapping代表什么呢?

1
mapping = default_exceptions

我们看到初始化方法中将default_exceptions赋值给了mapping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
default_exceptions = {}
__all__ = ['HTTPException']


def _find_exceptions():
# 获得当前文件全部的变量
for name, obj in iteritems(globals()):
try:
# 判断类是否是HTTPException的子类
is_http_exception = issubclass(obj, HTTPException)
except TypeError:
is_http_exception = False
if not is_http_exception or obj.code is None:
continue
__all__.append(obj.__name__)
old_obj = default_exceptions.get(obj.code, None)
if old_obj is not None and issubclass(obj, old_obj):
continue
default_exceptions[obj.code] = obj
_find_exceptions()
del _find_exceptions

上面的操作就是讲同一个文件下所有的HTTPException子类加到default_exceptions

最终first_or_404返回的是HTTPException的子类。那具体是哪一个子类呢?

我们可以根据状态码来找到(default_exceptions存的就是根据状态码找到异常类)

1
2
3
4
5
6
7
8
9
10
11
12
class NotFound(HTTPException):

"""*404* `Not Found`

Raise if a resource does not exist and never existed.
"""
code = 404
description = (
'The requested URL was not found on the server. '
'If you entered the URL manually please check your spelling and '
'try again.'
)

其实HTTPException也是一个可调用对象

1
return _aborter(status, *args, **kwargs)

正是这段代码使在出现异常的之后直接返回前端错误页面,而不是继续执行代码。

装饰器app_errorhandler

first_or_404会抛出一个404的错误,我们如果在抛出的地方去捕捉,将会有很多冗余代码,我们可以写一个全局的异常处理,指定错误代码。

1
2
3
4
5
6
@web.app_errorhandler(404)
def page_not_found(e):
"""
AOP,处理所有的404请求
"""
return render_template('404.html'), 404

上面的代码会拦截所有的404异常代码,然后返回我们自定义的错误页面。

上面的代码我们可以编写更多的逻辑,如:记录错误日志,返回指定的字符串等。

3. 发送邮件

这里是使用flask-mail进行电子邮件的发送。

安装
1
pip install flask-mail
注册实例化
1
2
3
4
5
6
7
8
9
10
from flask_mail import Mail

mail = Mail()


def create_app(config=None):
app = Flask(__name__)

# 注册email模块
mail.init_app(app)
配置
1
2
3
4
5
6
7
# Email 配置
MAIL_SERVER = 'smtp.exmail.qq.com' # 邮件服务器
MAIL_PORT = 465 # 端口
MAIL_USE_SSL = True # 是否开启SSL
MAIL_USE_TSL = False
MAIL_USERNAME = '123@qq.com' # 用户名
MAIL_PASSWORD = 'Bmwzy1314520' # 密码 这个是授权码而不是邮件密码

Flask-mail文档:http://www.pythondoc.com/flask-mail/

4. 使用itsdangerous生成令牌

我们重置密码的策略是将用户ID加密后发送到用户邮件,这里使用itsdangerous进行加密处理

1
2
3
4
5
6
7
8
9
10
11
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer


# 我们在用户模型中书写生成token的代码
def generate_confirmation_token(self, expiration=604800):
# 实例化序列化器的第一个参数需要传入一个任意字符串
s = Serializer(current_app.config['SECRET_KEY'], expiration)
# 使用dumps将用户id保存到序列化对象中
temp = s.dumps({'confirm': self.id})
# 这里的temp 是byte字节类型的字符串 我们可以使用decode解码成普通字符串 temp.decode('utf-8')
return temp

这里我们可以放任何的信息在里面

接着我们要写一个校验token正确与否的函数

1
2
3
4
5
6
7
8
9
10
11
12
def confirm(self, token):
# 这里实例化使用的是同一个字符串,不然会报错
s = Serializer(current_app.config['SECRET_KEY'])
# 当不是我们自己生产的token的时候回报错,这里进行异常捕捉
try:
data = s.loads(token)
# 如果使用了decode 记得这里encode
except:
return False
if data.get('confirm') != self.id:
return False
return True
知识就是财富
如果您觉得文章对您有帮助, 欢迎请我喝杯水!