最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

手把手教你如何使用Vue+Django實(shí)現(xiàn)RBAC權(quán)限管理

 更新時(shí)間:2026年03月10日 08:43:10   作者:幸福清風(fēng)  
在開發(fā)一個(gè)復(fù)雜的Web應(yīng)用時(shí),權(quán)限管理是繞不開的核心環(huán)節(jié),本文將和大家一起探索并實(shí)現(xiàn)一個(gè)基于角色權(quán)限管理系統(tǒng),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

前言

在開發(fā)一個(gè)復(fù)雜的Web應(yīng)用時(shí),權(quán)限管理是繞不開的核心環(huán)節(jié)。它決定了誰能看到什么頁面、能對(duì)數(shù)據(jù)執(zhí)行哪些操作。一個(gè)設(shè)計(jì)不佳的權(quán)限系統(tǒng),輕則導(dǎo)致功能混亂,重則引發(fā)數(shù)據(jù)泄露等安全問題。

今天,我們將一起探索并實(shí)現(xiàn)一個(gè)基于角色(Role-Based Access Control, RBAC)的權(quán)限管理系統(tǒng)。我們將使用經(jīng)典的Vue.js前端Django后端技術(shù)棧,并提供一個(gè)完整、可運(yùn)行的示例代碼,讓你能快速上手并應(yīng)用到自己的項(xiàng)目中。

1. 權(quán)限模型設(shè)計(jì):我們管理的是什么

我們的權(quán)限系統(tǒng)遵循一個(gè)簡(jiǎn)化的RBAC模型,其核心思想是將權(quán)限與“用戶組”而非“用戶”直接關(guān)聯(lián)。

  • 用戶 (User):系統(tǒng)中的每一個(gè)使用者,擁有一個(gè)唯一的ID、用戶名和所屬的用戶組(如“管理員”、“普通用戶”、“學(xué)生”等)。
  • 用戶組 (User Group):一系列用戶的集合,代表了一種角色。例如,“管理員”組的成員擁有最高權(quán)限。
  • 權(quán)限 (Permission):定義了對(duì)某個(gè)特定資源(通常是后端的一個(gè)API接口或前端的一個(gè)頁面路徑)可以執(zhí)行的操作。我們將其結(jié)構(gòu)化為一個(gè)JSON對(duì)象,包含以下關(guān)鍵字段:
    • path: 資源路徑,例如 /api/user/table/user/list。
    • user_group: 該權(quán)限規(guī)則適用于哪個(gè)用戶組。
    • add, del, set, get: 四個(gè)布爾值(0或1),分別代表“增加”、“刪除”、“修改”、“查詢”的權(quán)限。
    • field_add, field_set, field_get: 字符串,定義了該用戶組在執(zhí)行增、改、查操作時(shí),可以訪問哪些數(shù)據(jù)字段。例如,"field_get": "name,age"表示只能查看nameage字段。
    • option: 一個(gè)JSON對(duì)象,用于存放更復(fù)雜的特殊權(quán)限,例如 "examine": true 可能代表擁有“審核”功能。

通過將這些權(quán)限規(guī)則存儲(chǔ)在一張auth表中,我們可以實(shí)現(xiàn)高度靈活的動(dòng)態(tài)權(quán)限配置。

2. 后端實(shí)現(xiàn):Django中的權(quán)限校驗(yàn)

后端是權(quán)限控制的第一道也是最后一道防線。我們的Django后端主要通過一個(gè)中間件(Middleware)來實(shí)現(xiàn)權(quán)限校驗(yàn)。

核心邏輯如下:

  • Token認(rèn)證:用戶登錄成功后,服務(wù)器會(huì)返回一個(gè)加密的token。后續(xù)的所有請(qǐng)求都需要在HTTP Header中攜帶這個(gè)x-auth-token。中間件首先解析這個(gè)token,從中獲取用戶ID,并查詢出完整的用戶信息(包括其用戶組)。
  • 權(quán)限緩存:為了提高性能,系統(tǒng)會(huì)將auth表中的所有權(quán)限規(guī)則加載到內(nèi)存中(一個(gè)全局字典dict_auth),避免每次請(qǐng)求都查詢數(shù)據(jù)庫。
  • 權(quán)限匹配與校驗(yàn):對(duì)于每一個(gè)請(qǐng)求,中間件都會(huì)提取其路徑(request.path)和請(qǐng)求方法(GET, POST等)。然后,它會(huì)根據(jù)當(dāng)前用戶的用戶組和請(qǐng)求的路徑,去緩存中查找對(duì)應(yīng)的權(quán)限規(guī)則。最后,根據(jù)請(qǐng)求方法(如GET對(duì)應(yīng)get權(quán)限)判斷用戶是否擁有執(zhí)行此操作的權(quán)限。
  • 默認(rèn)規(guī)則:系統(tǒng)內(nèi)置了一些默認(rèn)規(guī)則。例如,如果找不到特定路徑的權(quán)限配置,默認(rèn)游客只能“查”,不能“增刪改”。而“管理員”用戶組則被硬編碼為擁有所有路徑的全部權(quán)限。

這種設(shè)計(jì)確保了只有經(jīng)過身份驗(yàn)證且擁有足夠權(quán)限的請(qǐng)求才能到達(dá)真正的業(yè)務(wù)視圖函數(shù),從而保護(hù)了核心數(shù)據(jù)和功能。

3. 前端實(shí)現(xiàn):Vue中的動(dòng)態(tài)權(quán)限響應(yīng)

前端的權(quán)限控制主要是為了提供更好的用戶體驗(yàn)。它可以根據(jù)用戶的權(quán)限,動(dòng)態(tài)地顯示或隱藏頁面元素(如按鈕、菜單項(xiàng)),以及控制頁面跳轉(zhuǎn)。

我們通過一個(gè)Vue插件(permission.js)來封裝權(quán)限邏輯:

  • $check_action(path, action): 這是一個(gè)核心方法。它接受一個(gè)路徑和一個(gè)操作(如"get", "add"),返回truefalse。在組件中,我們可以這樣使用:v-if="$check_action('/user/table', 'add')",來決定是否顯示“添加用戶”按鈕。
  • $check_field(action, field): 用于字段級(jí)別的權(quán)限控制。例如,在用戶列表中,只有有權(quán)限的用戶才能看到“郵箱”列。
  • $get_auth(user_group): 在用戶登錄成功后,調(diào)用此方法向后端請(qǐng)求該用戶組的全部權(quán)限列表,并將其存儲(chǔ)在Vuex全局狀態(tài)中,供整個(gè)應(yīng)用隨時(shí)使用。

通過這種方式,前端可以做到“千人千面”,只向用戶展示他們有權(quán)操作的功能,避免了無效點(diǎn)擊和潛在的錯(cuò)誤。

4. 項(xiàng)目整體結(jié)構(gòu)

首先,讓我們來看一下整個(gè)項(xiàng)目的目錄結(jié)構(gòu),這有助于理解各個(gè)模塊的職責(zé)劃分。

permission_demo/
├── backend/ (Django后端)
│   ├── manage.py
│   ├── requirements.txt
│   └── app/
│       ├── __init__.py
│       ├── settings.py
│       ├── urls.py
│       ├── wsgi.py
│       ├── core/           # 核心工具類
│       │   └── base_service.py
│       ├── auth.py         # 權(quán)限中間件
│       ├── views/
│       │   ├── __init__.py
│       │   ├── user.py     # 用戶登錄、注冊(cè)視圖
│       │   └── auth.py     # 權(quán)限數(shù)據(jù)管理視圖
│       └── services/
│           ├── __init__.py
│           ├── user.py     # 用戶服務(wù)
│           ├── auth.py     # 權(quán)限服務(wù)
│           └── access_token.py # Token服務(wù)
└── frontend/ (Vue前端)
    ├── public/
    ├── src/
    │   ├── App.vue
    │   ├── main.js
    │   ├── router/
    │   │   └── index.js
    │   ├── store/
    │   │   └── index.js
    │   ├── plugins/
    │   │   └── permission.js  # 權(quán)限插件
    │   └── views/
    │       ├── Login.vue      # 登錄頁
    │       ├── UserList.vue   # 用戶列表頁 (含權(quán)限控制)
    │       └── AuthConfig.vue # 權(quán)限配置頁 (管理員配置權(quán)限)
    ├── package.json
    └── vue.config.js

關(guān)鍵文件解讀:

  • 后端 auth.py 中間件:是整個(gè)權(quán)限系統(tǒng)的基石,負(fù)責(zé)請(qǐng)求攔截和校驗(yàn)。
  • 前端 permission.js 插件:是前端權(quán)限控制的入口,提供了便捷的API。
  • UserList.vue:一個(gè)絕佳的實(shí)踐示例,展示了如何在列表頁中根據(jù)權(quán)限動(dòng)態(tài)顯示“新增”、“編輯”、“刪除”按鈕和表格列。
  • AuthConfig.vue:一個(gè)管理界面,允許管理員通過圖形化界面配置復(fù)雜的權(quán)限規(guī)則,真正實(shí)現(xiàn)了“配置即代碼”的靈活性。

5. 后端代碼

a. 權(quán)限中間件 (backend/app/auth.py)

from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse
from django.shortcuts import render
import json
import datetime

# 權(quán)限緩存
dict_auth = {}

class Auth:
    def Check(self, user, path, method="get"):
        """檢查用戶權(quán)限"""
        auth = self.Get_dict()
        
        # 默認(rèn)權(quán)限(游客)
        model_auth = {
            "user_group": "游客",
            "path": path,
            "add": 0,
            "del": 0,
            "set": 0,
            "get": 1,
            "field_add": "",
            "field_set": "",
            "field_get": "",
            "option": {}
        }
        
        user_group = user["user_group"] if user else "游客"
        
        # 從緩存獲取權(quán)限
        if (path in auth) and auth[path] and (user_group in auth[path]):
            model_auth = auth[path][user_group]
        else:
            # 管理員擁有所有權(quán)限
            if user_group == "管理員":
                model_auth = {
                    "user_group": "管理員",
                    "path": path,
                    "add": 1,
                    "del": 1,
                    "set": 1,
                    "get": 1,
                    "field_add": "*",
                    "field_set": "*",
                    "field_get": "*",
                    "option": {"examine": True}
                }
        
        # 檢查方法權(quán)限
        if model_auth.get(method, 0):
            return model_auth
        return None
    
    def Get_dict(self):
        """獲取所有權(quán)限配置"""
        if len(dict_auth.keys()) == 0:
            from app.services.auth import Auth as AuthService
            service = AuthService()
            lst = service.Get_list({}, {"page": 0})
            for o in lst:
                path = o["path"]
                if path not in dict_auth:
                    dict_auth[path] = {}
                dict_auth[path][o["user_group"]] = o
        return dict_auth

auth = Auth()

class AuthMiddleware(MiddlewareMixin):
    """權(quán)限驗(yàn)證中間件"""
    def process_request(self, request):
        # 跳過登錄和靜態(tài)資源
        if request.path in ['/api/user/login', '/api/user/register', '/static/']:
            return None
        
        user = None
        token = request.headers.get("x-auth-token")
        
        # 驗(yàn)證 Token
        if token:
            user_id = request.session.get(token)
            if not user_id:
                from app.services.access_token import Access_token
                obj = Access_token().Get_obj({"token": token})
                if obj:
                    user_id = obj["user_id"]
                    request.session[token] = user_id
            
            if user_id:
                from app.services.user import User
                user = User().Get_obj({"user_id": user_id})
                request.user = user
        
        # API 請(qǐng)求權(quán)限檢查
        if request.path.startswith('/api/'):
            path = request.path.replace('/api/', '/').split('?')[0]
            method = request.method.lower()
            
            # 提取基礎(chǔ)路徑(如 /user/list)
            model_auth = auth.Check(user, path, method)
            if not model_auth:
                 return HttpResponse(json.dumps({"error": {"code": 403, "message": "權(quán)限不足"}}, ensure_ascii=False), content_type='application/json')

        # 頁面請(qǐng)求權(quán)限檢查
        else:
            path = request.path
            model_auth = auth.Check(user, path, "get")
            if model_auth:
                request.auth = model_auth
            else:
                return render(request, "403.html", {}, status=403)
        
        return None

b. 核心工具類 (backend/app/core/base_service.py)

import pymysql
import json
import re

class Service:
    def __init__(self, config):
        self.table = config.get("table", "")
        self.size = config.get("size", 30)
        self.order = config.get("order", "desc")
        self.sort = config.get("sort", "create_time")
        self.where = config.get("where", {})
        self.like = config.get("like", True)
        self.fields = config.get("fields", "*")
        
        self.conn = None
        self.cursor = None
        self.obj = None
        self.error = None
        self.sql = ""
        self.count = 0

    def connect(self):
        try:
            self.conn = pymysql.connect(
                host='127.0.0.1',
                port=3306,
                user='root',
                password='root',
                database='permission_demo',
                charset='utf8'
            )
            self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)
        except Exception as e:
            self.error = {"code": 500, "message": f"數(shù)據(jù)庫連接失敗: {str(e)}"}

    def close(self):
        if self.cursor:
            self.cursor.close()
        if self.conn:
            self.conn.close()

    def Get_list(self, where=None, other=None):
        self.connect()
        if self.error:
            return []

        # 構(gòu)建 WHERE 條件
        conditions = []
        values = []
        if self.where:
            for key, value in self.where.items():
                if isinstance(value, list):
                    placeholders = ','.join(['%s'] * len(value))
                    conditions.append(f"{key} IN ({placeholders})")
                    values.extend(value)
                else:
                    conditions.append(f"{key} = %s")
                    values.append(value)
        
        if where:
            for key, value in where.items():
                if isinstance(value, list):
                    placeholders = ','.join(['%s'] * len(value))
                    conditions.append(f"{key} IN ({placeholders})")
                    values.extend(value)
                else:
                    conditions.append(f"{key} = %s")
                    values.append(value)

        where_clause = "WHERE " + " AND ".join(conditions) if conditions else ""

        # 分頁
        page = other.get("page", 0) if other else 0
        offset = page * self.size
        limit_clause = f"LIMIT {offset}, {self.size}" if page >= 0 else ""

        # 排序
        order_by = f"ORDER BY {self.sort} {self.order.upper()}"

        self.sql = f"SELECT {self.fields} FROM {self.table} {where_clause} {order_by} {limit_clause}"
        
        try:
            self.cursor.execute(self.sql, values)
            result = self.cursor.fetchall()
            
            # 獲取總數(shù)
            count_sql = f"SELECT COUNT(*) as total FROM {self.table} {where_clause}"
            self.cursor.execute(count_sql, values)
            self.count = self.cursor.fetchone()["total"]
            
            self.close()
            return result
        except Exception as e:
            self.error = {"code": 500, "message": f"SQL執(zhí)行錯(cuò)誤: {str(e)}"}
            self.close()
            return []

    def Get_obj(self, where, other=None):
        self.connect()
        if self.error:
            return None

        conditions = []
        values = []
        for key, value in where.items():
            op = "="
            if isinstance(value, str) and self.like:
                op = "LIKE"
                value = f"%{value}%"
            conditions.append(f"{key} {op} %s")
            values.append(value)

        where_clause = "WHERE " + " AND ".join(conditions)
        sql = f"SELECT {self.fields} FROM {self.table} {where_clause} LIMIT 1"
        
        try:
            self.cursor.execute(sql, values)
            result = self.cursor.fetchone()
            self.close()
            return result
        except Exception as e:
            self.error = {"code": 500, "message": f"SQL執(zhí)行錯(cuò)誤: {str(e)}"}
            self.close()
            return None

    def Add(self, data):
        self.connect()
        if self.error:
            return False

        columns = ', '.join(data.keys())
        placeholders = ', '.join(['%s'] * len(data))
        sql = f"INSERT INTO {self.table} ({columns}) VALUES ({placeholders})"
        
        try:
            self.cursor.execute(sql, list(data.values()))
            self.conn.commit()
            self.close()
            return True
        except Exception as e:
            self.error = {"code": 500, "message": f"SQL執(zhí)行錯(cuò)誤: {str(e)}"}
            self.close()
            return False

    def Set(self, data, where):
        self.connect()
        if self.error:
            return False

        set_clauses = ', '.join([f"{k} = %s" for k in data.keys()])
        where_conditions = ' AND '.join([f"{k} = %s" for k in where.keys()])
        sql = f"UPDATE {self.table} SET {set_clauses} WHERE {where_conditions}"
        
        try:
            self.cursor.execute(sql, list(data.values()) + list(where.values()))
            self.conn.commit()
            self.close()
            return True
        except Exception as e:
            self.error = {"code": 500, "message": f"SQL執(zhí)行錯(cuò)誤: {str(e)}"}
            self.close()
            return False

    def Del(self, where):
        self.connect()
        if self.error:
            return False

        where_conditions = ' AND '.join([f"{k} = %s" for k in where.keys()])
        sql = f"DELETE FROM {self.table} WHERE {where_conditions}"
        
        try:
            self.cursor.execute(sql, list(where.values()))
            self.conn.commit()
            self.close()
            return True
        except Exception as e:
            self.error = {"code": 500, "message": f"SQL執(zhí)行錯(cuò)誤: {str(e)}"}
            self.close()
            return False

c. 用戶服務(wù) (backend/app/services/user.py)

from app.core.base_service import Service
import hashlib

def md5hash(str):
    return hashlib.md5(str.encode()).hexdigest()

class User(Service):
    def __init__(self, *config):
        config_temp = config[0] if config else {
            "table": "user",
            "size": 30,
        }
        super(User, self).__init__(config_temp)
    
    def Login(self, body):
        """用戶登錄"""
        username = body.get("username")
        password = body.get("password")
        
        if not username or not password:
            return {"error": {"code": 70000, "message": "用戶名和密碼不能為空"}}
        
        # 查詢用戶
        obj = self.Get_obj({"username": username}, {"like": False})
        if not obj:
            return {"error": {"code": 70000, "message": "用戶不存在"}}
        
        # 驗(yàn)證密碼
        if obj["password"] != md5hash(password):
            return {"error": {"code": 70000, "message": "密碼錯(cuò)誤"}}
        
        # 檢查用戶狀態(tài)
        if obj["state"] != 1:
            return {"error": {"code": 70000, "message": "賬戶不可用"}}
        
        # 生成 Token
        import time
        timestamp = int(time.time()) * 1000
        token = md5hash(str(obj["user_id"]) + "_" + str(timestamp))
        
        # 存儲(chǔ) Token
        from app.services.access_token import Access_token
        Access_token().Add({
            "token": token,
            "user_id": obj["user_id"],
            "maxage": 120  # 2 小時(shí)有效期
        })
        
        obj["token"] = token
        return {"result": {"obj": obj}}
    
    def Register(self, body):
        """用戶注冊(cè)"""
        username = body.get("username")
        password = body.get("password")
        user_group = body.get("user_group", "普通用戶")
        
        if not username or not password:
            return {"error": {"code": 70000, "message": "用戶名和密碼不能為空"}}
        
        # 檢查用戶是否存在
        if self.Get_obj({"username": username}, {"like": False}):
            return {"error": {"code": 70000, "message": "用戶名已存在"}}
        
        # 添加用戶
        body["password"] = md5hash(password)
        body["state"] = 1
        result = self.Add(body)
        
        if self.error:
            return {"error": self.error}
        
        return {"result": {"bl": True, "message": "注冊(cè)成功"}}

d. 權(quán)限服務(wù) (backend/app/services/auth.py)

from app.core.base_service import Service

class Auth(Service):
    def __init__(self, *config):
        config_temp = config[0] if config else {
            "table": "auth",
            "size": 30,
        }
        super(Auth, self).__init__(config_temp)

e. Token服務(wù) (backend/app/services/access_token.py)

from app.core.base_service import Service
import datetime

class Access_token(Service):
    def __init__(self, *config):
        config_temp = config[0] if config else {
            "table": "access_token",
            "size": 30,
        }
        super(Access_token, self).__init__(config_temp)
    
    def Get_obj(self, where):
        """獲取有效Token"""
        # 先清理過期token
        expire_time = datetime.datetime.now() - datetime.timedelta(minutes=2) # 假設(shè)2小時(shí)過期
        self.Del({"create_time": ("<", expire_time.strftime('%Y-%m-%d %H:%M:%S'))})
        
        return super().Get_obj(where)

f. 用戶視圖 (backend/app/views/user.py)

from app.services.user import User
from app.services.auth import Auth

def State(ctx):
    """獲取用戶狀態(tài)"""
    user = ctx.request.user
    if user:
        auth_service = Auth()
        permissions = auth_service.Get_list({"user_group": user["user_group"]})
        user["permissions"] = permissions
        return {"result": {"obj": user}}
    return {"error": {"code": 401, "message": "未登錄"}}

def Quit(ctx):
    """退出登錄"""
    token = ctx.request.headers.get("x-auth-token")
    if token:
        from app.services.access_token import Access_token
        Access_token().Del({"token": token})
    return {"result": {"message": "退出成功"}}

g. 權(quán)限視圖 (backend/app/views/auth.py)

from app.services.auth import Auth

def Get_list(ctx):
    """獲取權(quán)限列表"""
    where = {}
    if "user_group" in ctx.request.GET:
        where["user_group"] = ctx.request.GET["user_group"]
    
    service = Auth()
    lst = service.Get_list(where, ctx.request.GET.dict())
    return {"result": {"list": lst, "count": service.count}}

h. Settings配置 (backend/app/settings.py)

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'app.auth.AuthMiddleware',  # 自定義權(quán)限中間件
]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'permission_demo',
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': '127.0.0.1',
        'PORT': '3306',
    }
}

6. 前端代碼

a. 權(quán)限插件 (frontend/src/plugins/permission.js)

import store from '@/store'

export default {
  install(Vue) {
    // 檢查路徑操作權(quán)限
    Vue.prototype.$check_action = function(path, action = "get") {
      const power = this.$get_power(path)
      if (power && power[action] !== 0 && power[action] !== false) {
        return true
      }
      return false
    }

    // 獲取權(quán)限對(duì)象
    Vue.prototype.$get_power = function(path) {
      const list = store.state.web.auth
      for (let i = 0; i < list.length; i++) {
        if (list[i].path === path) {
          return list[i]
        }
      }
      return null
    }

    // 檢查字段權(quán)限
    Vue.prototype.$check_field = function(action, field) {
      const power = this.$get_power(this.$route.path)
      if (power) {
        const auth = power[`field_${action}`]
        if (auth && auth !== '*') {
          return auth.split(',').includes(field)
        }
        return auth === '*'
      }
      return false
    }

    // 檢查特殊選項(xiàng)權(quán)限
    Vue.prototype.$check_option = function(path, option) {
      const power = this.$get_power(path)
      if (power && power.option) {
        return !!power.option[option]
      }
      return false
    }

    // 獲取用戶組權(quán)限
    Vue.prototype.$get_auth = function(user_group = "游客", callback) {
      if (!user_group) user_group = "游客"
      
      this.$get("~/api/auth/get_list?", { user_group }, (json) => {
        store.commit("set_auth", [])
        
        if (json.result && json.result.list) {
          store.commit("set_auth", json.result.list)
          if (callback) callback()
        } else if (json.error) {
          console.error(json.error)
        }
      })
    }
  }
}

b. 用戶狀態(tài)Store (frontend/src/store/index.js)

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    web: {
      user: {},
      auth: [],
    }
  },
  mutations: {
    set_user(state, user) {
      state.web.user = user
    },
    set_auth(state, auth) {
      state.web.auth = auth
    }
  },
  actions: {
  },
  modules: {
  }
})

c. 登錄頁面 (frontend/src/views/Login.vue)

<template>
  <div class="login-container">
    <el-form :model="form" :rules="rules" ref="loginForm" label-width="80px" class="login-form">
      <h2>用戶登錄</h2>
      <el-form-item label="用戶名" prop="username">
        <el-input v-model="form.username" placeholder="請(qǐng)輸入用戶名"></el-input>
      </el-form-item>
      <el-form-item label="密碼" prop="password">
        <el-input v-model="form.password" type="password" placeholder="請(qǐng)輸入密碼"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSubmit">登錄</el-button>
        <el-button @click="onRegister">注冊(cè)</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  name: 'Login',
  data() {
    return {
      form: {
        username: '',
        password: ''
      },
      rules: {
        username: [
          { required: true, message: '請(qǐng)輸入用戶名', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '請(qǐng)輸入密碼', trigger: 'blur' }
        ]
      }
    }
  },
  methods: {
    onSubmit() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.$post('~/api/user/login?', this.form, (res) => {
            if (res.result) {
              const user = res.result.obj
              this.$store.commit('set_user', user)
              localStorage.setItem('token', user.token)
              
              // 獲取權(quán)限
              this.$get_auth(user.user_group, () => {
                this.$router.push({ name: 'UserList' })
              })
            } else {
              this.$message.error(res.error.message)
            }
          })
        }
      })
    },
    onRegister() {
      this.$post('~/api/user/register?', this.form, (res) => {
        if (res.result) {
          this.$message.success(res.result.message)
        } else {
          this.$message.error(res.error.message)
        }
      })
    }
  }
}
</script>

<style scoped>
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-color: #f0f2f5;
}
.login-form {
  width: 400px;
  padding: 30px;
  background: white;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
</style>

d. 用戶列表頁面 (frontend/src/views/UserList.vue)

<template>
  <div class="user-list">
    <el-card>
      <div slot="header">
        <span>用戶管理</span>
        <el-button 
          v-if="$check_action('/user/table','add')"
          type="primary" 
          @click="handleAdd">
          添加用戶
        </el-button>
      </div>
      
      <el-table :data="list" v-loading="loading">
        <el-table-column prop="user_id" label="ID" width="80"></el-table-column>
        <el-table-column prop="username" label="用戶名"></el-table-column>
        <el-table-column prop="nickname" label="昵稱"></el-table-column>
        <!-- 根據(jù)字段權(quán)限動(dòng)態(tài)顯示列 -->
        <el-table-column prop="email" label="郵箱" v-if="$check_field('get', 'email')"></el-table-column>
        <el-table-column prop="user_group" label="用戶組"></el-table-column>
        <el-table-column prop="state" label="狀態(tài)" width="80">
          <template slot-scope="scope">
            <el-tag :type="scope.row.state === 1 ? 'success' : 'danger'">
              {{ scope.row.state === 1 ? '正常' : '禁用' }}
            </el-tag>
          </template>
        </el-table-column>
        
        <el-table-column label="操作" width="250" fixed="right">
          <template slot-scope="scope">
            <el-button 
              v-if="$check_action('/user/table','get')"
              type="text" 
              @click="handleView(scope.row)">
              查看
            </el-button>
            
            <el-button 
              v-if="$check_action('/user/table','set')"
              type="text" 
              @click="handleEdit(scope.row)">
              編輯
            </el-button>
            
            <el-button 
              v-if="$check_action('/user/table','del')"
              type="text" 
              style="color: red"
              @click="handleDelete(scope.row)">
              刪除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
  </div>
</template>

<script>
export default {
  name: 'UserList',
  data() {
    return {
      loading: false,
      list: []
    }
  },
  created() {
    this.loadData()
  },
  methods: {
    loadData() {
      this.loading = true
      this.$get("~/api/user/get_list?", {}, (res) => {
        this.loading = false
        if (res.result) {
          this.list = res.result.list
        }
      })
    },
    handleAdd() {
      this.$message.info('觸發(fā)添加操作')
      // 實(shí)現(xiàn)添加邏輯
    },
    handleEdit(row) {
      this.$message.info(`編輯用戶: ${row.username}`)
      // 實(shí)現(xiàn)編輯邏輯
    },
    handleDelete(row) {
      this.$confirm(`確定要?jiǎng)h除用戶 "${row.username}" 嗎?`)
        .then(() => {
          // 實(shí)現(xiàn)刪除邏輯
          this.$message.success('刪除成功')
          this.loadData()
        })
    },
    handleView(row) {
      this.$message.info(`查看用戶: ${row.username}`)
      // 實(shí)現(xiàn)查看邏輯
    }
  }
}
</script>

<style scoped>
.user-list {
  padding: 20px;
}
</style>

e. 權(quán)限配置頁面 (frontend/src/views/AuthConfig.vue)

<template>
  <div class="auth-config">
    <el-card>
      <div slot="header">
        <span>權(quán)限配置</span>
      </div>
      <el-form :model="form" inline>
        <el-form-item label="用戶組">
          <el-select v-model="form.user_group" placeholder="請(qǐng)選擇用戶組">
            <el-option label="管理員" value="管理員"></el-option>
            <el-option label="普通用戶" value="普通用戶"></el-option>
            <el-option label="游客" value="游客"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="路徑">
          <el-input v-model="form.path" placeholder="/user/list"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="loadPermissions">查詢</el-button>
        </el-form-item>
      </el-form>
      
      <el-table :data="permissions" v-loading="loading">
        <el-table-column prop="path" label="路徑"></el-table-column>
        <el-table-column prop="user_group" label="用戶組"></el-table-column>
        <el-table-column label="操作權(quán)限">
          <template slot-scope="scope">
            <el-checkbox v-model="scope.row.add" :true-label="1" :false-label="0">新增</el-checkbox>
            <el-checkbox v-model="scope.row.del" :true-label="1" :false-label="0">刪除</el-checkbox>
            <el-checkbox v-model="scope.row.set" :true-label="1" :false-label="0">修改</el-checkbox>
            <el-checkbox v-model="scope.row.get" :true-label="1" :false-label="0">查詢</el-checkbox>
          </template>
        </el-table-column>
        <el-table-column label="字段權(quán)限">
          <template slot-scope="scope">
            <el-input size="mini" v-model="scope.row.field_add" placeholder="add fields"></el-input>
            <el-input size="mini" v-model="scope.row.field_set" placeholder="set fields"></el-input>
            <el-input size="mini" v-model="scope.row.field_get" placeholder="get fields"></el-input>
          </template>
        </el-table-column>
        <el-table-column label="操作">
          <template slot-scope="scope">
            <el-button size="mini" @click="updatePermission(scope.row)">保存</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
  </div>
</template>

<script>
export default {
  name: 'AuthConfig',
  data() {
    return {
      loading: false,
      form: {
        user_group: '',
        path: ''
      },
      permissions: []
    }
  },
  methods: {
    loadPermissions() {
      if (!this.form.user_group || !this.form.path) {
        this.$message.warning('請(qǐng)先選擇用戶組和路徑')
        return
      }
      this.loading = true
      this.$get('~/api/auth/get_list?', this.form, (res) => {
        this.loading = false
        if (res.result) {
          this.permissions = res.result.list
        }
      })
    },
    updatePermission(row) {
      this.$post('~/api/auth/set?', row, (res) => {
        if (res.result) {
          this.$message.success('權(quán)限更新成功')
        } else {
          this.$message.error(res.error.message)
        }
      })
    }
  }
}
</script>

<style scoped>
.auth-config {
  padding: 20px;
}
</style>

f. Main.js入口文件 (frontend/src/main.js)

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import permission from './plugins/permission'
import axios from 'axios'

Vue.use(ElementUI)
Vue.use(permission)

// 配置 axios
Vue.prototype.$axios = axios.create({
  baseURL: 'http://127.0.0.1:8000',
  timeout: 10000
})

// 請(qǐng)求攔截器,自動(dòng)攜帶token
Vue.prototype.$axios.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers['x-auth-token'] = token
  }
  return config
})

// 簡(jiǎn)化 HTTP 方法
Vue.prototype.$get = function(url, params, callback) {
  this.$axios.get(url, { params }).then(res => {
    if (callback) callback(res.data)
  })
}

Vue.prototype.$post = function(url, data, callback) {
  this.$axios.post(url, data).then(res => {
    if (callback) callback(res.data)
  })
}

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

7. 數(shù)據(jù)庫設(shè)計(jì)

最后,是支撐整個(gè)權(quán)限系統(tǒng)的基礎(chǔ)——數(shù)據(jù)庫表結(jié)構(gòu)。

-- 創(chuàng)建數(shù)據(jù)庫
CREATE DATABASE IF NOT EXISTS permission_demo CHARACTER SET utf8 COLLATE utf8_general_ci;

USE permission_demo;

-- 用戶表
CREATE TABLE `user` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(32) NOT NULL,
  `nickname` varchar(50) DEFAULT NULL,
  `avatar` varchar(255) DEFAULT NULL,
  `email` varchar(100) DEFAULT NULL,
  `user_group` varchar(50) DEFAULT '普通用戶',
  `state` tinyint(1) DEFAULT 1,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 權(quán)限表
CREATE TABLE `auth` (
  `auth_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_group` varchar(50) NOT NULL,
  `mod_name` varchar(100) DEFAULT NULL,
  `table_name` varchar(100) DEFAULT NULL,
  `path` varchar(200) NOT NULL,
  `add` tinyint(1) DEFAULT 0,
  `del` tinyint(1) DEFAULT 0,
  `set` tinyint(1) DEFAULT 0,
  `get` tinyint(1) DEFAULT 1,
  `field_add` text,
  `field_set` text,
  `field_get` text,
  `option` text,
  PRIMARY KEY (`auth_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Token表
CREATE TABLE `access_token` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `token` varchar(32) NOT NULL,
  `user_id` int(11) NOT NULL,
  `maxage` int(11) DEFAULT 120,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- 插入初始權(quán)限數(shù)據(jù)示例
INSERT INTO `auth` (`user_group`, `path`, `add`, `del`, `set`, `get`, `field_add`, `field_set`, `field_get`, `option`) VALUES
('管理員', '/user/table', 1, 1, 1, 1, '*', '*', '*', '{}'),
('普通用戶', '/user/table', 0, 0, 0, 1, '', '', 'user_id,username,nickname', '{}');

8. 總結(jié)

本文從概念到實(shí)踐,帶你走了一遍完整的權(quán)限管理系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)過程。我們看到了一個(gè)成熟系統(tǒng)應(yīng)有的樣子:后端嚴(yán)格把關(guān),前端優(yōu)化體驗(yàn),兩者配合無間。

這套方案的核心價(jià)值在于其靈活性可維護(hù)性。通過將權(quán)限規(guī)則外化到數(shù)據(jù)庫,業(yè)務(wù)人員可以在不改動(dòng)代碼的情況下,輕松調(diào)整系統(tǒng)權(quán)限,這對(duì)于快速迭代的項(xiàng)目來說至關(guān)重要。

以上就是手把手教你如何使用Vue+Django實(shí)現(xiàn)RBAC權(quán)限管理的詳細(xì)內(nèi)容,更多關(guān)于Vue Django RBAC權(quán)限管理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解Vue之父子組件傳值

    詳解Vue之父子組件傳值

    這篇文章主要介紹了Vue之父子組件傳值,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 如何查看vue項(xiàng)目的node版本

    如何查看vue項(xiàng)目的node版本

    文章總結(jié):查看Vue項(xiàng)目中使用的Node版本,特別是當(dāng)項(xiàng)目使用Yarn和TypeScript時(shí),可以通過查看yarn.lock文件中的@types/node@version來確定版本
    2025-01-01
  • Vue實(shí)現(xiàn)可移動(dòng)水平時(shí)間軸

    Vue實(shí)現(xiàn)可移動(dòng)水平時(shí)間軸

    這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)可移動(dòng)水平時(shí)間軸,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-06-06
  • vue中循環(huán)多個(gè)li(表格)并獲取對(duì)應(yīng)的ref的操作代碼

    vue中循環(huán)多個(gè)li(表格)并獲取對(duì)應(yīng)的ref的操作代碼

    我想要獲取每一個(gè)循環(huán)并獲取每一個(gè)li(或者其它循環(huán)項(xiàng))的ref,以便于后續(xù)的操作,接下來通過本文給大家分享vue中循環(huán)多個(gè)li(表格)并獲取對(duì)應(yīng)的ref的操作代碼,感興趣的朋友跟隨小編一起看看吧
    2024-02-02
  • elementUI中input回車觸發(fā)頁面刷新問題與解決方法

    elementUI中input回車觸發(fā)頁面刷新問題與解決方法

    這篇文章主要給大家介紹了關(guān)于elementUI中input回車觸發(fā)頁面刷新問題與解決方法,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用elementUI具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2023-07-07
  • vue前端實(shí)現(xiàn)導(dǎo)出頁面為word的兩種方法代碼

    vue前端實(shí)現(xiàn)導(dǎo)出頁面為word的兩種方法代碼

    在前端開發(fā)中我們常常需要將頁面頁面為word文件,這篇文章主要給大家介紹了關(guān)于vue前端實(shí)現(xiàn)導(dǎo)出頁面為word的兩種方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-04-04
  • Vue中關(guān)于重新渲染組件的方法及總結(jié)

    Vue中關(guān)于重新渲染組件的方法及總結(jié)

    這篇文章主要介紹了Vue中關(guān)于重新渲染組件的方法及總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • 詳解Vue一個(gè)案例引發(fā)「內(nèi)容分發(fā)slot」的最全總結(jié)

    詳解Vue一個(gè)案例引發(fā)「內(nèi)容分發(fā)slot」的最全總結(jié)

    這篇文章主要介紹了詳解Vue一個(gè)案例引發(fā)「內(nèi)容分發(fā)slot」的最全總結(jié),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-12-12
  • Vue的輪播圖組件實(shí)現(xiàn)方法

    Vue的輪播圖組件實(shí)現(xiàn)方法

    下面小編就為大家分享一篇Vue的輪播圖組件實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • Vue3自定義打印實(shí)現(xiàn)原理詳解

    Vue3自定義打印實(shí)現(xiàn)原理詳解

    近接觸到了一個(gè) Vue3 的打印需求,我發(fā)現(xiàn)自己雖然從事前端開發(fā)已有多年,但對(duì)如何實(shí)現(xiàn)自定義打印還沒有深入研究,一般都是找現(xiàn)成的庫來解決問題,借這次的機(jī)會(huì)研究了一下如何實(shí)現(xiàn)自定義打印,需要的朋友可以參考下
    2024-07-07

最新評(píng)論

凌源市| 临泽县| 盘锦市| 黔江区| 称多县| 阿克陶县| 肇州县| 寻甸| 隆德县| 任丘市| 汨罗市| 长宁区| 融水| 始兴县| 汉寿县| 滦平县| 竹山县| 高雄县| 乐昌市| 广元市| 阜康市| 武义县| 呼和浩特市| 苗栗县| 远安县| 永靖县| 宁城县| 南汇区| 江都市| 平顶山市| 加查县| 思茅市| 叙永县| 瑞丽市| 永修县| 将乐县| 泸西县| 南昌县| 榆林市| 长乐市| 清涧县|