flask和vue搭建前后分离项目

项目目录搭建示意图

web是js前端代码项目      api是后台接口项目

后端项目的搭建可以用任何web框架 flask或者django都可以  和平常用flask和django开发不同的是 每个url对应的view处理函数不要返回模板内容

即不再调用render等其它渲染函数  全部使用 return json_response({'data': users, 'total': total})返回数据到前端即可

前端调用后端接口的方式如下:

    this.$http.get('/api/account/roles/').then(res => {
        this.tableData = res.result
    }, res => this.$layer_message(res.result)).finally(() => this.tableLoading = false)

通过这种方式实现前后端数据的交互和展示

前端实现权限管理控制

 1.在登录系统的时候把用户的权限集合获取到并且存储到localstorage中

 2.每个权限用一个唯一的字符串来标识即可

3.把用户权限判断函数挂载到全局Vue的原型上面

// 权限判断
    Vue.prototype.has_permission = function (str_code) {
        if (localStorage.getItem('is_supper') === 'true') {
            return true
        }
        let permissions = localStorage.getItem('permissions');
        if (!str_code || !permissions) return false;
        permissions = permissions.split(',');
        for (let or_item of str_code.split('|')) {
            if (isSubArray(permissions, or_item.split('&'))) {
                return true
            }
        }
        return false
    };


在main.js中导入
import GlobalTools from './plugins/globalTools'
Vue.use(GlobalTools, router);
View Code
            <el-table-column label="操作" width="240px" v-if="has_permission('assets_host_edit|assets_host_del|assets_host_valid')">
                <template slot-scope="scope">
                    <el-button v-if="has_permission('assets_host_edit')" size="small" @click="handleEdit(scope.row)">编辑</el-button>
                    <el-button v-if="has_permission('assets_host_valid')" size="small" type="primary" @click="valid(scope.row)"
                               :loading="btnValidLoading[scope.row.id]">验证
                    </el-button>
                    <el-button v-if="has_permission('assets_host_del')" size="small" type="danger" @click="deleteCommit(scope.row)"
                               :loading="btnDelLoading[scope.row.id]">删除
                    </el-button>
                </template>
</el-table-column>
vue组件中具体判断权限
router是全局的router对象 
// 路由导航钩子
    router.beforeEach((to, from, next) => {
        if (['/', '/login', '/deny','/account/person','/account/personset','/home','/welcome'].includes(to.path)) {
            next()
        } else if (to.meta.hasOwnProperty('permission') && Vue.prototype.has_permission(to.meta.permission)) {
            next()
        } else {
            next({path: '/deny'})
        }
    })
vue路由守卫

 Vue.use(GlobalTools, router);        //把router对象传递到GlobalTools模块中

后端实现权限管理

class User(db.Model, ModelMixin):
    __tablename__ = 'account_users'
   
   @property
    def permissions(self):
        if self.is_supper:
            return set()
        return Role.get_permissions(self.role_id)


class Role(db.Model, ModelMixin):
    __tablename__ = 'account_roles'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), unique=True, nullable=False)
    desc = db.Column(db.String(255))

    @staticmethod
    def get_permissions(role_id):
        sql = text('select p.name from account_role_permission_rel r, account_permissions p where r.role_id=%d and r.permission_id=p.id' % role_id)
        result = db.engine.execute(sql)
        return {x[0] for x in result}
模型定义

用户登录成功时候返回数据处理

 if user.is_active:
                if user.verify_password(form.password):
                    login_limit.pop(form.username, None)
                    token = uuid.uuid4().hex
                    user.access_token = token
                    user.token_expired = time.time() + 8 * 60 * 60
                    user.save()
                    return json_response({
                        'token': token,
                        'is_supper': user.is_supper,
                        'nickname': user.nickname,
                        'permissions': list(user.permissions)
         })
后台业务处理

 

后端接口调用鉴权设计和实现

1.通过中间件把登录用户信息存储到全局对象中

# coding=utf-8
from flask import request, make_response, g
from libs.tools import json_response
from apps.account.models import User
from public import app
import time
import flask_excel as excel


def init_app(app):
    excel.init_excel(app)
    app.before_request(cross_domain_access_before)
    app.before_request(auth_middleware)
    app.after_request(cross_domain_access_after)
    app.register_error_handler(Exception, exception_handler)
    app.register_error_handler(404, page_not_found)



def auth_middleware():
    if request.path == '/account/users/login/' or request.path.startswith('/apis/configs/') \
            or request.path.startswith('/apis/files/'):
        return None
    token = request.headers.get('X-TOKEN')
    if token and len(token) == 32:
        g.user = User.query.filter_by(access_token=token).first()
        if g.user and g.user.is_active and g.user.token_expired >= time.time():
            g.user.token_expired = time.time() + 8 * 60 * 60
            g.user.save()
            return None
    return json_response(message='Auth fail, please login'), 401
View Code

2.创建一个装饰器模块用来装饰所有需要鉴权的view处理函数

from flask import g
from public import app
from functools import wraps
from libs.tools import json_response

# Flask中的g对象是个很好的东西,主要用于在一个请求的过程中共享数据。
# 可以随意给g对象添加属性来保存数据,非常的方便
#
#

def require_permission(str_code):
    def decorate(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if not g.user.is_supper:
                or_list = [x.strip() for x in str_code.split('|')]
                for or_item in or_list:
                    and_set = {x.strip() for x in or_item.split('&')}
                    if and_set.issubset(g.user.permissions):
                        break
                else:
                    return json_response(message='Permission denied'), 403
            return func(*args, **kwargs)

        return wrapper

    return decorate
View Code

3.在view函数中调用装饰器

@blueprint.route('/', methods=['GET'])
@require_permission('assets_host_view | publish_app_publish_host_select | '
                    'job_task_add | job_task_edit | assets_host_exec')
def get():
    form, error = JsonParser(Argument('page', type=int, default=1, required=False),
                             Argument('pagesize', type=int, default=10, required=False),
                             Argument('host_query', type=dict, required=False), ).parse(request.args)
    if error is None:
        host_data = Host.query
        if form.page == -1:
            return json_response({'data': [x.to_json() for x in host_data.all()], 'total': -1})
        if form.host_query.get('name_field'):
            host_data = host_data.filter(Host.name.like('%{}%'.format(form.host_query['name_field'])))
        if form.host_query.get('zone_field'):
            host_data = host_data.filter_by(zone=form.host_query['zone_field'])

        result = host_data.limit(form.pagesize).offset((form.page - 1) * form.pagesize).all()
        return json_response({'data': [x.to_json() for x in result], 'total': host_data.count()})
    return json_response(message=error)
View Code

 

Token的实现和具体用途

       token是一个由服务端生成并返回给客户端的随机编码字符串  并且客户端每次向服务端发起请求的时候在请求头中必须包含token 具体用途如下:

          1.用来判断用户是否登录并且查看登录是否过期 类似于session

          2.用来对客户端请求服务端的第一道检查  如果没有token 所有的接口统一返回未登录

          3.token验证不能对登录后的用户实现具体的权限验证

 

token的存储设计 后台存储到用户信息表中

class User(db.Model, ModelMixin):
    __tablename__ = 'account_users'

    id = db.Column(db.Integer, primary_key=True)
    role_id = db.Column(db.Integer, db.ForeignKey('account_roles.id'))
    username = db.Column(db.String(50), unique=True, nullable=False)
    nickname = db.Column(db.String(50))
    password_hash = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(120))
    mobile = db.Column(db.String(30))
    is_supper = db.Column(db.Boolean, default=False)
    is_active = db.Column(db.Boolean, default=True)
    access_token = db.Column(db.String(32))
    token_expired = db.Column(db.Integer)

    role = db.relationship('Role')
View Code

 

token的后端生成     在用户登录成功的时候

@blueprint.route('/login/', methods=['POST'])
def login():
    form, error = JsonParser('username', 'password').parse()
    if error is None:
        user = User.query.filter_by(username=form.username).first()
        if user:
            if user.is_active:
                if user.verify_password(form.password):
                    login_limit.pop(form.username, None)
                    token = uuid.uuid4().hex
                    user.access_token = token
                    user.token_expired = time.time() + 8 * 60 * 60
                    user.save()
                    return json_response({
                        'token': token,
                        'is_supper': user.is_supper,
                        'nickname': user.nickname,
                        'permissions': list(user.permissions)
                    })
                else:
                    login_limit[form.username] += 1
                    if login_limit[form.username] >= 3:
                        user.update(is_active=False)
                    return json_response(message='用户名或密码错误,连续3次错误将会被禁用')
            else:
                return json_response(message='用户已被禁用,请联系管理员')
        elif login_limit[form.username] >= 3:
            return json_response(message='用户已被禁用,请联系管理员')
        else:
            login_limit[form.username] += 1
            return json_response(message='用户名或密码错误,连续3次错误将会被禁用')
    else:
        return json_response(message='请输入用户名和密码')
View Code

 

token截验证每个发送请求的客户端是否合法    通过中间件实现

def auth_middleware():
    if request.path == '/account/users/login/' or request.path.startswith('/apis/configs/') \
            or request.path.startswith('/apis/files/'):
        return None
    token = request.headers.get('X-TOKEN')
    if token and len(token) == 32:
        g.user = User.query.filter_by(access_token=token).first()
        if g.user and g.user.is_active and g.user.token_expired >= time.time():
            g.user.token_expired = time.time() + 8 * 60 * 60
            g.user.save()
            return None
    return json_response(message='Auth fail, please login'), 401
View Code

 

token的前端存储 存储到cookie或者localstorage中

            handleSubmit() {
                this.error = '';
                this.$refs['form'].validate(pass => {
                    if (!pass) {
                        return false
                    }
                    this.loading = true;
                    this.$http.post('/api/account/users/login/', this.form).then(res => {
                        localStorage.setItem('token', res.result['token']);
                        localStorage.setItem('is_supper', res.result['is_supper']);
                        localStorage.setItem('permissions', res.result['permissions']);
                        localStorage.setItem('nickname', res.result['nickname']);
                        this.$router.push('/welcome');
                    }, response => {
                        this.error = response.result
                    }).finally(() => this.loading = false)
                })
            }
        }
用户提交登录表单.js

 

token被集成到请求头中进行发送

    // 请求拦截器
    axios.interceptors.request.use(request => {
        if (request.url.startsWith('/api/')) {
            request.headers['X-TOKEN'] = localStorage.getItem('token');
            // request.url = config.apiServer + request.url.replace('/api', '')
            // request.url = config.apiServer + request.url
        }
        request.timeout = envs.request_timeout;
        return request;
    });
    // 返回拦截器
    axios.interceptors.response.use(response => {
        return handleResponse(response, router)
    }, error => {
        if (error.response) {
            return handleResponse(error.response, router)
        }
        return Promise.reject({result: '请求异常: ' + error.message})
    });
js请求拦截器

 

token无效示例

 

用户无权限示例

 

正常用户访问示例

 

posted @ 2019-05-10 16:01  不懂123  阅读(2215)  评论(0)    收藏  举报