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無效示例

 

用戶無權限示例

 

正常用戶訪問示例

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM