Flask前后端分离实践:Todo App(2)

  • 2019 年 11 月 30 日
  • 筆記

前序文章

本文项目地址: https://github.com/frostming/flask-vue-todo

在上一篇文章里我们已经用Flask+Vue搭建了一个可以把数据持久化到服务器的Todo App。那么,为了让多人一起使用这个App,我们需要对数据按用户做隔离,这样就自然需要一个注册/登录界面。在前后端分离的架构里,我们是怎么验证用户,保持会话的呢?

用户登录

先复习一下以往用Flask是怎么解决这问题的,没错,通过Flask-Login模块,从request中获取用户名和密码,验证通过后用login_user记录到会话中,之后的请求就会带有登录信息了。如果要退出登录,只需要调一下logout_user就可以了。

那么使用前后端分离以后,所有对后端的请求都是以Ajax的方式发送,上面的方法依然有效!区别仅仅在于,我们将请求改成JSON格式之后,后端是从request.get_json()中获取的。为此,我们专门建立一个名为auth的蓝图:

Python

@bp.route('/login', methods=['POST'])  def login():      user_data = request.get_json()      form = LoginForm(data=user_data)      if form.validate():          user = form.get_user()          login_user(user, remember=form.remember.data)          return jsonify({'status': 'success', 'user': user.to_json()})      return jsonify({'status': 'error', 'message': form.errors}), 403

后端只接收POST请求,因为GET都在前端那边,自然也就没有login_view的配置了。前端那边,axios发请求时自动会带上cookie,所以后端这边依然可以通过flask_login.current_user拿到当前用户。

表单与验证

现在我们需要一个包含表单的登录页面,而我们知道,所有的页面都是前端渲染。所以这里wtform或flask-boostrap就不太能派上用场了。好在表单也比较简单,不是很难写。

Html

<template>    <form action="/auth/login" method="post">      <h2>Login</h2>      <div class="form-group">        <label for="username">Username</label>        <input type="text" id="username" name="username" v-model="username" required>      </div>      <div class="form-group">        <label for="password">Password</label>        <input type="password" id="password" name="password" v-model="password" required>      </div>      <div class="form-group">        <label for="remember">          <input type="checkbox" id="remember" name="remember" v-model="remember">          Remember Me        </label>      </div>      <div class="form-footer">        <button type="submit" name="submit" class="btn">Submit</button>        <router-link to="/" class="btn">Return Home</router-link>      </div>    </form>  </template>

有一表单验证的工作,比如必填项,长度限制等,完全不需要后端的,可以在前端完成。我们需要写一个提交的函数,绑定到表单的submit动作上:

Javascript

methods: {  	checkForm (e) {  		e.preventDefault()  		const vm = this  		api.login({  			username: this.username,  			password: this.password,  			remember: this.remember  		}).then(data => {  			vm.$router.push({ path: '/' }, () => {  				vm.success('Logged in successfully!')  			})  		}).catch(e => {  			let errors = e.response.data.message  			for (let key in errors) {  				errors[key].forEach(e => {vm.error(`${key}: ${e}`)})  			}  		})  	}  }

但有些验证工作,比如密码校验,还是要麻烦后端的,所以这里我们获取后端返回的错误(储存在data.message中),然后依次渲染在页面中(这里我使用了一个Vue的插件Vue-flask-message来完成)。

后端验证这一块,由于没有渲染需求了,可以不用wtform这一套,改用marshmallow,但为了后面的方便,我还是使用了Flask-WTF,把验证放到表单类里。

Python

from flask_wtf import FlaskForm    class LoginForm(FlaskForm):      username = StringField('Username', validators=[Length(max=64)])      password = PasswordField('Password', validators=[Length(8, 16)])      remember = BooleanField('Remember Me')        def validate_username(self, field):          if not self.get_user():              raise ValidationError('Invalid username!')        def validate_password(self, field):          if not self.get_user():              return          if not self.get_user().check_password(field.data):              raise ValidationError('Incorrect password!')        def get_user(self):          return User.query.filter_by(username=self.username.data).first()

完成了登录部分,那么注册界面也大同小异,总结起来,大致思想是:

  • 对于无需后端的验证,由前端完成。
  • 后端的验证,通过响应内容传回错误。
  • 验证错误通过Vue-flash-message显示到页面上。
  • login和register的视图函数仅处理POST请求。