Flask-BBS论坛项目实战4-优化json数据的返回及提示框

接着上节,我们通过jsonify返回json数据非常方便

1
2
... 
return jsonify({"code": 400, "message": message})

返回什么数据则是公司接口编写的规范
返回值的规范(就算值为空,我们也必须返回以下字段)

1
2
3
4
5
{
"code": 200, #状态码
"message": "", #信息提示
"data": {}, #返回的数据,比如文章列表等等
}

状态码的规范

1
2
3
4
5
200:成功
401:没有授权
400:参数错误
405:方法错误
500:服务器错误

前面,我们每一次返回json数据需要写一串”jsonify({“code”: 400, “message”: message})”这样很麻烦,项目后期可能还有很多地方需要返回json。因此我们可以把它抽取出来进行封装成一个工具

  1. 先在项目下面建一个python package命名为utils(工具包,以后项目的工具都写在这里面)
  2. 新建一个utils包,在utils下新建xjson.py
utils/xjson.py
1
2
3
4
5
6
7
8
9
10
11
from flask import  jsonify

class StatusCode(object):
ok = 200
paramserror = 400
unauth = 401
methoderror = 405
servererror = 500

def json_result(code, message, data):
return jsonify({"code": code, "message":message, "data":data or {}})

这样,我们就可以在视图返回json的时候这样用:

1
2
3
from utils import xjson
...
return xjson.json_result(code=200, message='', data={})

但是这样依然不够方便,我们还是需要加上3个参数。因此我们继续改造xjson.py

utils/xjson.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from flask import  jsonify

class StatusCode(object):
ok = 200
paramserror = 400
unauth = 401
methoderror = 405
servererror = 500

def json_result(code, message, data):
return jsonify({"code": code, "message":message, "data":data or {}})


def json_success(message='', data=None):
return json_result(code=StatusCode.ok, message=message, data=data)

def json_params_error(message='', data=None):
"""
请求参数错误
"""
return json_result(code=StatusCode.paramserror, message=message, data=data)

def json_unauth_error(message='', data=None):
"""
没有权限访问
"""
return json_result(code=StatusCode.unauth, message=message, data=data)

def json_method_error(message='', data=None):
"""
请求方法错误
"""
return json_result(code=StatusCode.methoderror, message=message, data=data)

def json_server_error(message='', data=None):
"""
服务器内部错误
"""
return json_result(code=StatusCode.servererror, message=message, data=data)

这样,我们只要根据不同的情况使用定义好的函数就行了。
参数: code(已经不用自己传了),message(成功的状态下可以不传,默认为空),data(没有传此参数,也会自动为空字典))
把上节内容的代码,可以写成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ResetPwdView(views.MethodView):
decorators = [login_required]
def get(self):
return render_template('cms/cms_resetpwd.html')

def post(self):
resetpwd_form = ResetPwdForm(request.form)
if resetpwd_form.validate():
oldpwd = resetpwd_form.oldpwd.data
newpwd = resetpwd_form.newpwd.data
user = g.cms_user
if user.check_password(oldpwd):
user.password = newpwd
db.session.commit()
return xjson.json_success('修改成功')
else:
return xjson.json_params_error('原密码错误')
else:
message = resetpwd_form.get_error()
return xjson.json_params_error(message)

我们继续优化,接收到返回值,我们在前端做一些处理,如:密码修改成功,弹出一个成功的提示框。这个提示框我们采用sweetalert.
需要素材的同学请联系我wouldmissyou@163.com

在 static/common/下创建目录sweetalert,并把以上3个文件放进去,因为不仅仅修改密码会用到提示框,项目其他地方也会用到,所以把它放到common里面。
在父模板引入此3个文件

cms_base.html
1
2
3
4
5
6
<head>
...
<link href="{{ url_for('static', filename='common/sweetalert/sweetalert.css')}}" rel="stylesheet">
<script src="{{ url_for('static', filename='common/sweetalert/sweetalert.min.js') }}"></script>
<script src="{{ url_for('static', filename='common/sweetalert/xtalert.js') }}"></script>
</head>

现在就可以修改resetpwd.js,对返回值做处理了

resetpwd.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* Created by user on 2018/8/7.
*/

$(function () {
$('#submit').click(function (event) {
//阻止按钮默认的提交表单行为
event.preventDefault();
var oldpwdE = $('input[name=oldpwd]');
var newpwdE = $('input[name=newpwd]');
var newpwd2E = $('input[name=newpwd2]');

var oldpwd = oldpwdE.val();
var newpwd = newpwdE.val();
var newpwd2 = newpwd2E.val();

//这里使用我们自己封装好的bbsajax,它具有了csrf
bbsajax.post({
'url': '/cms/resetpwd/',
'data': {
'oldpwd': oldpwd,
'newpwd': newpwd,
'newpwd2': newpwd2
},
'success': function (data) {
//根据状态码判断
if (data['code'] === 200){
//弹出成功的提示框,提示语是从后台传过来的message
xtalert.alertSuccessToast(data['message']);
oldpwdE.val(''); //完成请求后把表单输入的值清空
newpwdE.val('');
newpwd2E.val('');
}else{
xtalert.alertError(data['message']);
oldpwdE.val('');
newpwdE.val('');
newpwd2E.val('');
}
},
'fail': function (error) {
xtalert.alertNetworkError('网络错误');
}
});
});
})

修改邮箱页面布局

新建cms/cms_resetemail.html

cms_resetemail.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{% extends 'cms/cms_base.html' %}

{% block title %}修改邮箱-CMS管理系统{% endblock %}

{% block page_title %}个人中心>>>修改邮箱{% endblock %}

{% block head %}
<style>
.form-container{
width: 300px;
}
</style>
{% endblock %}


{% block main_content %}
<form class="form-container">
<div class="form-group">
<div class="input-group">
<input type="email" class="form-control" placeholder="新的邮箱" name="email">
<div class="input-group-addon" style="cursor: pointer" id="get_captcha">获取验证码</div>
</div>
</div>

<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" placeholder="填写验证码" name="captcha">
</div>
</div>

<div class="form-group">
<div class="input-group">
<button type="submit" class="btn btn-primary" id="submit">提交修改</button>
</div>
</div>

</form>

{% endblock %}

创建一个视图,编辑cms.views.py

views.py
1
2
3
4
5
6
7
8
9
10
...
class ResetEmailView(views.MethodView):
decorators = [login_required]
def get(self):
return render_template('cms/cms_resetemail.html')

def post(self):
pass

bp.add_url_rule('/resetemail/', view_func=ResetEmailView.as_view('resetemail'))

修改cms/cms_base.html中的”修改密码” 链接

1
<li><a href="{{ url_for('cms.resetemail') }}">修改邮箱</a></li>

完成布局如下

获取验证码

流程:
  1. 用户输入 新的邮箱,点击 获取验证码,可以通过get方法使用AJAX发送到后台
  2. 后台接收到用户的新的邮箱,生成6位数的随机验证码,通过flask-mail插件发送验证码到该邮箱
  3. 把 邮箱作为key,验证码作为value存入memcached中,生存周期为300s(5分钟)
首先来配置flask-mail

安装flask-mail

1
pip install flask-mail

在exts.py中带falsk-mail

1
2
3
4
...
from flask_mail import Mail

mail = Mail()

在主程序bbs.py中,把mail和app绑定

1
2
3
4
...
from exts import mail

mail.init_app(app)

在config.py配置邮箱的连接和发送者信息,这里我是用的邮箱

1
2
3
4
5
6
7
#mail
MAIL_SERVER = 'smtp.163.com'
MAIL_PORT = '465'
MAIL_USE_SSL = True #使用SSL,端口号为465或587
MAIL_USERNAME = 'wouldmissyou@163.com'
MAIL_PASSWORD = 'xxxxxxxxx' #注意,这里的密码不是邮箱密码,而是授权码
MAIL_DEFAULT_SENDER = 'wouldmissyou@163.com' #默认发送者

现在来的 cms.views写个视图函数来测试发送邮件

1
2
3
4
5
6
7
8
9
10
11
12
...
from exts import mail
from flask_mail import Message

@bp.route('/test_email/')
def test_email():
msg = Message('Flask项目测试邮件', #这是邮件主题
sender='wouldmissyou@163.com',
recipients=['wouldmissyou@163.com'], #发送给谁,这是个列表,可以有多个接收者
body='Hello, 这是一封测试邮件,这是邮件的正文')
mail.send(msg) #发送
return 'success'

在浏览器中输入http://127.0.0.1:5000/cms/test_email/ 就会提示成功。

配置memecahced

因为我们要把验证码 存到 memcached中,所以这里先配置好它。
我在虚拟机192.168.0.103服务器中安装memcached并且启动它

1
2
3
python@python-virtual-machine:~$ ps aux|grep memcached
memcache 6929 0.3 0.1 63392 2724 ? Ssl 14:07 0:00 /usr/bin/memcached -m 64 -p 11211 -u memcache -l 0.0.0.0
python 6961 0.0 0.0 21312 944 pts/4 S+ 14:07 0:00 grep --color=auto memcached

安装python-memecahced插件

1
pip install python-memcached

我们工具包utils下新建个文件xcache.py对memcached 来封装下

xcache.py
1
2
3
4
5
6
7
8
9
10
11
12
import memcache

mc = memcache.Client(["192.168.0.103:11211"], debug=True)

def set(key, value, timeout=300):
return mc.set(key=key, val=value, time=timeout)

def get(key):
return mc.get(key)

def delete(key):
return mc.delete(key)

准备工作做好,我们可以写获取验证码的视图了,编辑cms.views.py

cms.views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from exts  mail
from flask_mail import Message
from utils xcache
import string, random

@bp.route('/email_captcha/')
@login_required
def email_captcha():
#/cms/emai_capthcha/?email=xxxx@qq.com
email = request.args.get('email')
if not email:
return xjson.json_params_error('请传递邮件参数!')

#生成6位数的随机验证码
source = list(string.ascii_letters)
source.extend(map(lambda x:str(x), range(0,10)))
captcha = ''.join(random.sample(source, 6))

#发送邮件
msg = Message('BBS论坛更换邮箱验证码',
recipients=[email],
body='您的验证码:{},5分钟内有效'.format(captcha))
try:
mail.send(msg)
except Exception as err:
print(err)
return xjson.json_server_error(message='邮件发送失败')

#验证码存入memcached
xcache.set(email, captcha)
return xjson.json_success(message='邮件发送成功')

AJAX请求,创建static/cms/js/resetemail.js

static/cms/js/resetemail.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$(function () {
$('#get_captcha').click(function (event) {
event.preventDefault();
var email = $('input[name=email]').val();
if(!email){
xtalert.alertInfoToast('请输入邮箱');
return;
}

bbsajax.get({
'url': '/cms/email_captcha/',
'data': {
'email': email
},
'success': function (data) {
if (data['code'] === 200){
xtalert.alertSuccessToast(data['message']);
}else{
xtalert.alertInfo(data['message']);
}
},
'fail': function (error) {
xtalert.alertNetworkError();
}
})
})
});

在cms_resetemail.html中引入resetemail.js

1
2
3
4
{% block head %}
...
<script src="{{ url_for('static', filename='cms/js/resetemail.js') }}"></script>
{% endblock %}

完成修改邮箱功能

现在就剩下把验证码填上,然后把邮箱和验证码提交到服务器进行验证并做相应的处理,然后返回处理结果到前端就OK了。

需要RestEmailView的post请求,对于用户提交上来的邮箱和验证码,我们需要对它们进行form验证。所以,编辑cms/forms.py,写一个RestEmailForm

cms/forms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from wtforms.validators import ValidationError
from .models import CMSUser
from utils import xcache

class RestEmailForm(BaseForm):
email = StringField(validators=[Email(message='邮箱格式错误'),InputRequired(message='请输入邮箱') ])
captcha = StringField(validators=[Length(min=6, max=6, message='验证码长度错误')])

def validate_email(self, field):
user = CMSUser.query.filter_by(email=field.data).first()
if user:
raise ValidationError('该邮箱已存在')

def validate_captcha(self, field):
email = self.email.data
captcha = field.data
captcha_cache = xcache.get(email)
#判断memcached中是否有对应的邮箱及验证码,小写进行比较,这样用户可以不区分大小写
if not captcha_cache or captcha.lower() != captcha_cache.lower():
raise ValidationError('邮箱验证码错误')

编辑RestEmailView的post请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ResetEmailView(views.MethodView):
decorators = [login_required]
def get(self):
return render_template('cms/cms_resetemail.html')

def post(self):
resetemail_form = RestEmailForm(request.form)
if resetemail_form.validate():
email = resetemail_form.email.data
g.cms_user.email = email
db.session.commit()
return xjson.json_success('邮箱修改成功')
else:
message = resetemail_form.get_error()
return xjson.json_params_error(message)

编辑resetemail.js,再写一个ajax的post请求,当点击”提交修改”,就会触发

resetemail.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//提交修改
$(function () {
$('#submit').click(function (event) {
event.preventDefault();
var emailE = $('input[name=email]');
var captchaE = $('input[name=captcha]');

var email = emailE.val();
var captcha = captchaE.val();

bbsajax.post({
'url': '/cms/resetemail/',
'data': {
'email': email,
'captcha': captcha
},
'success': function (data) {
if (data['code'] === 200){
xtalert.alertSuccessToast(data['message']);
emailE.val('');
captchaE.val('');
}else{
xtalert.alertInfo(data['message']);
}
},
'fail': function (error) {
xtalert.alertNetworkError();
}
})
})
});

到此,修改邮箱功能完成!

-------------本文结束感谢您的阅读-------------