基于项目搭建个人笔记本放在树莓派上吧!

项目代码已上传至GITEE

前后端分离

技术栈:

后端 :

1.flask

2.sqlalchemy

3.sqlite

前端 :

  1. VUE
  2. AXIOS
  3. ELEMENTUI

介绍

使用el-table模拟文件Tree系统,使用懒加载来增强体验,编辑器选用为mavon-editor,预览使用markdown-it,在后端使用正则表达式解析markdown toc目录增强浏览体验,编辑期间前端定时器会简单的定时缓存数据到后端

演示地址 因为css和js没有走cdn会很慢

flask vue axios 前后端分离 flask前后端分离项目_ios


flask vue axios 前后端分离 flask前后端分离项目_python_02


flask vue axios 前后端分离 flask前后端分离项目_json_03


flask vue axios 前后端分离 flask前后端分离项目_json_04

总体分为两部分: flask搭建的后端接口以及VUE的前端界面

前端代码后台基于GITHUB大佬的vue-element-admin,在这里使用了大佬的简单模板并进行修改,模板代码源于vue-admin-template

项目准备

基于vue-cli进行构建,电脑需安装npm环境

vue-admin-template(文中以VAT来简称)中clone代码, 根据markdown中文文档步骤构建环境并启动

准备一个最小的flask应用,若未使用过flask请于链接了解,文档中会有完整代码

1.开始吧!和Flask交互起来

npm run dev模式启动VAT框架,框架启动后可以发现输入任意密码既可以登录进后台中,是因为后台框架使用的是./mock文件中的数据进行后端模拟;

进入后发现框架已经做好了侧边栏,面包屑,以及基于AXIOS的token验证机制,只需要简单的修改既可以与后端进行交互起来

简单的调整
1.1 需要更改后端接口地址

更改文件 ./.env.development

# just a flag
ENV = 'development'

# base api
# VUE_APP_BASE_API = '/dev-api' # 更改为后端接口地址
VUE_APP_BASE_API = 'http://localhost:5000' # FLASK端口5000

删除模板中的mock数据的引用

更改文件./src/main.js 对如下部分进行注释 并自己选择是否使用中文版本的 element-ui

根据注释说明修改

/**
 * If you don't want to use mock-server
 * you want to use MockJs for mock api
 * you can execute: mockXHR()
 *
 * Currently MockJs will be used in the production environment,
 * please remove it before going online ! ! !
 */
if (process.env.NODE_ENV === 'production') {
  const { mockXHR } = require('../mock')
  mockXHR()
}

// set ElementUI lang to EN
Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui,按如下方式声明
// Vue.use(ElementUI)

重启前端服务,直到登录时显示network Error

1.2 阅览VAT框架中与后端交互的request模块

解析 @utils/requests.js

上半部分是对axios请求做了一个基本的配置,请求路径为 env.development中的 VUE_APP_BASE_API,超时时间为5s

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})

中间部分为配置axios的请求头,对请求(request)进行预处理,携带token进行验证

service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

后面部分为axios的返回数据(response)拦截器

如下代码可知 VAT框架模板预留了几个 response status code 英文注释比较清晰明了

response => {
    const res = response.data

    // if the custom code is not 20000, it is judged as an error.
    if (res.code !== 20000) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })

      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)
1.3 根据需求进行request请求代码的更改
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'

// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})

// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent
    if (store.getters.token) {
      // vuex 携带token验证
      Object.assign(config.headers, { Authorization: 'Bearer ' + getToken() })
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  response => {
    const res = response.data

    // flask all return 200
    if (res.code !== 200) {
      Message({
        message: 'requests error',
        type: 'error',
        duration: 5 * 1000
      })

      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
  error => {
    // flask因为使用了一些模块(flask_httpauth) token验证失败只返回401
    if (error.response) {
      switch (error.response.status) {
        case 401:
          MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
            confirmButtonText: 'Re-Login',
            cancelButtonText: 'Cancel',
            type: 'warning'
          }).then(() => {
            store.dispatch('user/resetToken').then(() => {
              location.reload()
            })
          })
          break
        case 400:
          Message({
            message: error.response.data.data.message,
            type: 'error',
            duration: 3 * 1000
          })
          break
        case 422:
          Message({
            message: error.response.data.data.message,
            type: 'error',
            duration: 3 * 1000
          })
          break
      }
    }
    return Promise.reject(error)
  }
)

export default service
1.4 搭建一个最小的Flask应用进行与VAT框架的交互

在python环境中pip install flask,编写如下脚本代码启动一个后端服务

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello Flask!'

if __name__ == '__main__':
    app.run()

运行 在浏览器中输入 127.0.0.1:5000 浏览器中打印出 ‘Hello Flask!’ 服务启动成功!

1.5 登录接口的解析

登录接口需要post数据 获取token存在浏览器本地中,与后端的每个接口都需要携带token进行验证

  1. @\src\utils\auth.js: 浏览器Cookie对token的处理
  2. @\src\store\getters.js: 本地储存用户信息的全局字典
  3. @\src\store\modules\user.js: 与用户登录的请求和处理基本都在这里

根据user.js的代码可在flask设计出登录接口返回的数据格式

import { login, logout, getInfo } from '@/api/user'

login: 无需携带token

login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { data } = response
        commit('SET_TOKEN', data.token)
        setToken(data.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

返回数据

{
    "token":"Bearer x",
}

info: 携带token

getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response
        if (!data) {
          return reject('Verification failed, please Login again.')
        }
        const { name, avatar } = data
        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

返回数据

{
    "name":"admin",
    "avatar":".png"
}

logout: 清除token并退出

无数据返回

1.6 登录接口的实现

接口位置 @\src\api\user.js, 修改为如下

import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/auth/login',
    method: 'post',
    data
  })
}

export function getInfo(token) {
  return request({
    url: '/auth/info',
    method: 'get'
    // params: { token }  # 获取到token后会自己携带token进行请求
  })
}

export function logout() {
  return request({
    url: '/auth/logout',
    method: 'post'
  })
}

登录组件位置 @\src\views\login\index.vue, 可以看到post form如下

loginForm: {
    username: 'admin',
    password: '111111'
},

修改flask的接口来兼容VAT吧! 复制如下代码到最小的flask应用中重新启动测试

def returnVueDataModel(message, code, **kwargs):
    kwargs['message'] = message
    kwargs['code'] = code
    return {"data": kwargs, 'code': 200}

@app.route('/auth/login', methods=['POST'])
def login():
    params = request.get_json()
    if 'username' not in params or 'password' not in params:
        return jsonify(returnVueDataModel('miss arguments', 400)), 400
    if params['username'] == 'admin' and params['password'] == '111111':
        return jsonify(returnVueDataModel('login success', 200, token='x'))
    return jsonify(returnVueDataModel('no existing user', 400)), 400

@app.route('/auth/info', methods=['GET'])
def info():
    token = request.headers.get('Authorization')
    if 'Bearer' not in token:
        return my_json(returnVueDataModel('please login!', 400)), 400
    return jsonify(returnVueDataModel('token verify pass', 200, name='Admin', avatar=""))
1.7 粗暴的解决跨域问题
Access to XMLHttpRequest at 'http://localhost:5000/auth/login' from origin 'http://localhost:9528' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
:5000/auth/login:1 Failed to load resource: net::ERR_FAILED
:9528/#/login?redirect=%2Fdashboard:1 Access to XMLHttpRequest at 'http://localhost:5000/auth/login' from origin 'http://localhost:9528' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

服务启动并登录后 会出现如上问题,需要配置flask的跨域请求 使用 flask_cors

Api文档地址

pip install flask_cors

from flask import Flask, request, jsonify

app = Flask(__name__)
from flask_cors import *  # 导入模块

CORS(app, supports_credentials=True, origins=['http://localhost:9528'])


def returnVueDataModel(message, code, **kwargs):
    kwargs['message'] = message
    kwargs['code'] = code
    return {"data": kwargs, 'code': 200}


@app.route('/', methods=['GET'])
def hello_world():
    return 'Hello Flask!'


@app.route('/auth/login', methods=['POST'])
def login():
    params = request.get_json()
    if 'username' not in params or 'password' not in params:
        return jsonify(returnVueDataModel('miss arguments', 400)), 400
    if params['username'] == 'admin' and params['password'] == '111111':
        return jsonify(returnVueDataModel('login success', 200, token='x'))
    return jsonify(returnVueDataModel('no existing user', 400)), 400


@app.route('/auth/info', methods=['GET'])
def info():
    token = request.headers.get('Authorization')
    if 'Bearer' not in token:
        return my_json(returnVueDataModel('please login!', 400)), 400
    return jsonify(returnVueDataModel('token verify pass', 200, name='Admin', avatar=""))


if __name__ == '__main__':
    app.run()
Success

短短十多分钟 完成了一个前后端分离的小项目,那么根据这个框架,可以搭建起来任意一个喜欢的后台管理系统,本次我们搭建一个小型的Blog,当然这个Blog因为也是闲着无聊写的,所以可能会有bug哟,比如markdown的Toc目录因为我使用的正则解析的,可能你们写法跟我不一样就无法解析出正常的目录啦

2. 后端的搭建

2.1 项目的启动

项目地址

clone到本地,在pycharm或喜欢的编辑器中打开,pycharm会自动创建虚拟环境以及pip 环境包

进入虚拟环境,输入如下命令,搭建sqlalchemy数据库环境

flask db init
flask db migrate
flask db upgrade

搭建完成后,在虚拟环境下输入>>flask shell 进入flask shell环境fake数据(noteapp\faker.py),如下为flask shell中包含的对象

def register_shell_context(app):
    """Register shell context objects."""

    def shell_context():
        """Shell context objects."""
        return {"db": db, "User": models.User, "Fold": models.Fold, "Blog": models.Blog,
                "fake": faker.faker, "wafer": models.Wafer}

    app.shell_context_processor(shell_context)

输入命令如下

>>> fake
<class 'noteapp.faker.faker'>
>>> fake().do()
--- 等待完成 若有报错缺少faker模块请pip安装
>>> User.query.all()
[<User('David Richardson')>, <User('Leonard Roberts')>, <User('Samuel Scott')>, <User('Eduardo Jones')>, <User('Shirley Johns')>]
--- 生成的用户列表 密码均为123456
>>> eixt()

回到虚拟环境 输入flask run启动服务

2.2 项目目录解析

目录结构

├── README.md            项目介绍
├── autoapp.py           程序入口
├── requirements.txt     项目依赖
├── migrations           flask db init 生成的数据库管理 若非必要请在git ignore
├── tests             	 一些单元测试及一些个人测试文件
├── noteapp              源码目录 
│  ├── models            持久化数据表模型
│  ├── upload            文件上传目录
│  ├── util              公共函数方法
│  ├── views             主要蓝图和业务代码
│  ├── app.py            程序初始化入口
│  ├── commands.py       创建虚拟环境下flask自定义命令
│  └── database.py       CURD
│  └── dev.db            持久化sqlite 
│  └── extensions.py     引入的库
│  └── faker.py          虚拟数据
│  └── settings.py       程序配置
└──

3. 前端的搭建

3.1 项目的启动

clone到本地,在vscode或喜欢的编辑器中打开,根据markdown文档搭建项目,npm run dev 运行

启动成功后浏览器会跳到文章开头的界面中,操作中会需要登录,请使用前面flask shell查到的用户密码登录
Tree中使用elementUi的table懒加载和根据懒加载更新某行数据的方式来模拟文件夹系统