符合一下關鍵詞,這篇博客有可能會對你有幫助css
這是一篇我的博客搭建的記錄博客,也是一篇關於Flask和Vue的簡單"工具書",最後的代碼會包含Web開發中經常使用的功能。(不全,只是使用頻率相對高的)前端
《Flask Web開發 基於Python的Web應用開發實戰》
Vue.jsvue
我的博客的解決方案那麼多,爲何我要本身再搭建一個呢?
其實搭建我的博客的目的並非爲了寫博客...不然直接使用WordPress了,我的博客只是我想要實踐本身學的技術,同時考慮到之後可能會加入負載均衡、集羣等技術,致使架構大改,或者嘗試實現語音控制等新玩法,一行一行碼出來的在操做的可行性上必然是更好的。ios
博客功能尚不健全,只實現瞭如下的基本功能
前端:註冊登錄,博客建立(markdown編輯器),首頁拉取全部文章,建立博客須要登錄狀態。
後端:以上服務須要的視圖函數,配置跨域,令牌管理與驗證,數據庫管理。git
出於記錄的分享的目的,將實現登陸狀態管理的代碼整理以下github
要實現基於令牌的登陸狀態管理,其思路大體以下vue-router
這是一個大體的思路,後續調用手保護的視圖函數部分,不管是讓先後端完成什麼操做,均可以執行根據須要實現。
如下部分將根據以上思路的順序,展現主要代碼,最後將貼出完成代碼。sql
先後端分離首選須要配置跨域,此處採用後端解決的方案,使用flask_cors庫,代碼以下:vuex
因爲會前端在獲取token後會在每次HTTP請求時將token設置在頭部,我給出的命名爲'token',若使用了其餘名稱,需在'Access-Control-Allow-Headers'中替換
from flask_cors import CORS CORS(app,supports_credentials=True) @app.after_request def after_request(resp): resp = make_response(resp) resp.headers['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8080' resp.headers['Access-Control-Allow-Methods'] = 'GET,POST' resp.headers['Access-Control-Allow-Headers'] = 'content-type,token' return resp
前端將獲取的賬號密碼傳遞給後臺,將請求獲取的token寫入Vuex中。(Vuex中會將token寫入localStorage)數據庫
let _this = this axios.post('http://127.0.0.1:5000/login',{ username:this.username, password:this.password, }) .then(function(response){ let token = response.data _this.changeLogin({Authorization: token}) }) .catch(function(error){ })
視圖函數將經過用戶名和密碼,驗證用戶信息,並生成token,返回token。
# Routes @app.route('/login',methods=['POST']) def login(): json = request.get_json() user = User.query.filter_by(username = json['username']).first() if user.verify_password(json['password']): g.currnet_user = user token = user.generate_auth_token(expiration=3600) return token return "wrong password"
配置Axios鉤子,在每次HTTP請求的頭部都添加token
axios.interceptors.request.use( config => { let token = localStorage.getItem('Authorization'); if(token){ config.headers.common['token'] = token } return config }, err => { return Promise.reject(err); });
flask_httpauth模塊實現的功能不多,其核心部分是咱們須要本身實現@auth.verify_password這個回調函數,當被@auth.login_required修飾的視圖函數被訪問時,會先執行回調函數,在回調函數中將獲取http頭部的token,並驗證該token是否合法,若合法則容許訪問。
from flask_httpauth import HTTPBasicAuth auth = HTTPBasicAuth() @auth.verify_password def verify_password(username_token): username_token = request.headers.get('Token') if username_token == '': return False else: g.currnet_user = User.verify_auth_token(username_token) g.token_used = True return g.currnet_user is not None @auth.login_required @app.route('/creatpost',methods=['POST']) def new_post(): json = request.get_json() newpost = Post(title=json['title'],content=json['content']) db.session.add(newpost) db.session.commit() return "200 OK"
以上部分便是實現基於令牌管理的代碼核心部分,閱讀以上代碼知曉思路便可,因爲其還調用了諸如ORM中的函數的緣由,因此只有以上部分代碼功能並不健全,請參考下面簡化後的完整代碼。
強調:如下代碼出於簡化的目的,皆爲實現功能的最基本碼,並無遵循各類規範。
import os from flask import Flask,make_response,render_template,redirect,url_for,jsonify,g,current_app,request,session from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy from flask_httpauth import HTTPBasicAuth from flask_login import login_user,UserMixin,LoginManager,login_required from werkzeug.security import generate_password_hash,check_password_hash from itsdangerous import TimedJSONWebSignatureSerializer as Serializer basedir = os.path.abspath(os.path.dirname(__file__)) # SQLite app = Flask(__name__) app.config['SECRET_KEY'] = 'secret-key' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir,'data.sqlite') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) # CORS CORS(app,supports_credentials=True) @app.after_request def after_request(resp): resp = make_response(resp) resp.headers['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8080' resp.headers['Access-Control-Allow-Methods'] = 'GET,POST' resp.headers['Access-Control-Allow-Headers'] = 'content-type,token' return resp # Http auth auth = HTTPBasicAuth() @auth.verify_password def verify_password(username_token): username_token = request.headers.get('Token') if username_token == '': return False else: g.currnet_user = User.verify_auth_token(username_token) g.token_used = True return g.currnet_user is not None @auth.error_handler def auth_error(): return unauthorized('Invalid credentials') # Routes @app.route('/login',methods=['POST']) def login(): json = request.get_json() user = User.query.filter_by(username = json['username']).first() if user.verify_password(json['password']): g.currnet_user = user token = user.generate_auth_token(expiration=3600) return token return "wrong password" @app.route('/register',methods=['POST']) def register(): json = request.get_json() email = json['username'] + '@email.com' user = User(email=email,username=json['username'],password=json['password']) db.session.add(user) db.session.commit() return "200 OK register" @app.route('/postlist') def article(): ptemp = Post.query.all() return jsonify({ 'posts': [post.to_json() for post in ptemp], }) @auth.login_required @app.route('/creatpost',methods=['POST']) def new_post(): json = request.get_json() newpost = Post(title=json['title'],content=json['content']) db.session.add(newpost) db.session.commit() return "200 OK" def unauthorized(message): response = jsonify({'error': 'unauthorized', 'message': message}) response.status_code = 401 return response # ORM class User(UserMixin,db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(64),unique=True,index=True) username = db.Column(db.String(64),unique=True,index=True) password_hash = db.Column(db.String(128)) @property def password(self): raise AttributeError('password is not a readable attribute') @password.setter def password(self,password): self.password_hash = generate_password_hash(password) def verify_password(self,password): return check_password_hash(self.password_hash,password) def generate_auth_token(self,expiration): s = Serializer(current_app.config['SECRET_KEY'],expires_in = expiration) return s.dumps({'id':self.id}).decode('utf-8') @staticmethod def verify_auth_token(token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return None return User.query.get(data['id']) class Post(db.Model): __tablename__ = 'posts' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(64),unique=True,index=True) content = db.Column(db.String(64)) def to_json(self): json_post = { 'title': self.title, 'content': self.content, } return json_post if __name__ == '__main__': db.drop_all() db.create_all() app.run()
import Vue from 'vue'; import App from './App.vue'; import VueRouter from 'vue-router'; import router from './router'; import iView from 'iview'; import 'iview/dist/styles/iview.css'; import axios from 'axios'; import vueAxios from 'vue-axios'; import store from './store'; import Vuex from 'vuex' Vue.config.productionTip = false Vue.use(VueRouter) Vue.use(iView) Vue.use(vueAxios,axios) Vue.use(Vuex) router.afterEach(route=>{ window.scroll(0,0); }) router.beforeEach((to,from,next)=>{ let token = localStorage.getItem('Authorization'); if(!to.meta.isLogin){ next() }else{ let token = localStorage.getItem('Authorization'); if(token == null || token == ''){ next('/') }else{ next() } } }) axios.interceptors.request.use( config => { let token = localStorage.getItem('Authorization'); if(token){ config.headers.common['token'] = token } return config }, err => { return Promise.reject(err); }); new Vue({ el:'#app', render: h => h(App), router, store, })
import Vue from 'vue'; import Vuex from 'vuex'; import store from './index'; Vue.use(Vuex); export default new Vuex.Store({ state:{ Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : '' }, mutations:{ changeLogin (state, user) { state.Authorization = user.Authorization; localStorage.setItem('Authorization', user.Authorization); } }, })
import Vue from 'vue' import Router from 'vue-router' import home from '../components/home.vue' import articleDetail from '../components/articleDetail' import createPost from '../components/createPost' Vue.use(Router) export default new Router({ mode:'history', routes:[ { path:'/', component:home, name:'home', meta:{ isLogin:false } }, { path:'/article', component:articleDetail, name:'article', meta:{ isLogin:false } }, { path:'/createpost', component:createPost, name:'createpost', meta:{ isLogin:true } }, ] })
<template> <div class="super"> <div class="header"> <div class="buttomDiv"> <Button type="success" class="loginButton" @click="showLoginModal">Login</Button> <Button type="primary" class="loginButton" @click="showRegisterModal">Register</Button> </div> </div> <div class = "content"> <div class="contentLeft"> <div v-for = "post in blogList" > <thumbnail v-bind:title=post.title v-bind:content=post.content ></thumbnail> </div> </div> <div class="contentRight"></div> </div> <Modal v-model="registerModalStatus" @on-ok="registerEvent"> <p>Register</p> <Input v-model="username" placeholder="Username" style="width: 300px" /> <Input v-model="password" placeholder="Password" style="width: 300px" /> </Modal> <Modal v-model="loginModalStatus" @on-ok="loginEvent"> <p>Login</p> <Input v-model="username" placeholder="Username" style="width: 300px" /> <Input v-model="password" placeholder="Password" style="width: 300px" /> </Modal> </div> </template> <script> import axios from 'axios' import {mapMutations} from 'vuex' import store from '../store' import thumbnail from './articleThumbnail.vue' export default{ name: 'home', data:function(){ return { loginModalStatus:false, registerModalStatus:false, username:'', password:'', blogList:'', } }, components:{ thumbnail:thumbnail, }, created(){ localStorage.removeItem("Authorization","") let _this = this axios.get('http://127.0.0.1:5000/postlist') .then(function(response){ _this.blogList = response.data.posts }) .catch(function(error){ }) }, methods:{ ...mapMutations([ 'changeLogin' ]), showRegisterModal:function(){ this.registerModalStatus = true; }, showLoginModal:function(){ this.loginModalStatus = true; }, registerEvent:function(){ let that = this axios.post('http://127.0.0.1:5000/register',{ username:this.username, password:this.password, }) .then(function(res){ }) .catch(function(error){ }) }, loginEvent:function(){ let _this = this axios.post('http://127.0.0.1:5000/login',{ username:this.username, password:this.password, }) .then(function(response){ let token = response.data _this.changeLogin({Authorization: token}) }) .catch(function(error){ }) }, navigator:function(){ this.$router.push("/article") }, }, } </script> <style scoped> </style>
完整代碼github地址
haythamBlog
haythamBlog_flask
要獲取更多Haytham原創文章,請關注公衆號"許聚龍":