手把手教你如何使用Vue+Django實(shí)現(xiàn)RBAC權(quán)限管理
前言
在開發(fā)一個(gè)復(fù)雜的Web應(yīng)用時(shí),權(quán)限管理是繞不開的核心環(huán)節(jié)。它決定了誰能看到什么頁面、能對數(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è)簡化的RBAC模型,其核心思想是將權(quán)限與“用戶組”而非“用戶”直接關(guān)聯(lián)。
- 用戶 (User):系統(tǒng)中的每一個(gè)使用者,擁有一個(gè)唯一的ID、用戶名和所屬的用戶組(如“管理員”、“普通用戶”、“學(xué)生”等)。
- 用戶組 (User Group):一系列用戶的集合,代表了一種角色。例如,“管理員”組的成員擁有最高權(quán)限。
- 權(quán)限 (Permission):定義了對某個(gè)特定資源(通常是后端的一個(gè)API接口或前端的一個(gè)頁面路徑)可以執(zhí)行的操作。我們將其結(jié)構(gòu)化為一個(gè)JSON對象,包含以下關(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"表示只能查看name和age字段。option: 一個(gè)JSON對象,用于存放更復(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ù)的所有請求都需要在HTTP Header中攜帶這個(gè)x-auth-token。中間件首先解析這個(gè)token,從中獲取用戶ID,并查詢出完整的用戶信息(包括其用戶組)。 - 權(quán)限緩存:為了提高性能,系統(tǒng)會(huì)將
auth表中的所有權(quán)限規(guī)則加載到內(nèi)存中(一個(gè)全局字典dict_auth),避免每次請求都查詢數(shù)據(jù)庫。 - 權(quán)限匹配與校驗(yàn):對于每一個(gè)請求,中間件都會(huì)提取其路徑(
request.path)和請求方法(GET,POST等)。然后,它會(huì)根據(jù)當(dāng)前用戶的用戶組和請求的路徑,去緩存中查找對應(yīng)的權(quán)限規(guī)則。最后,根據(jù)請求方法(如GET對應(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)限的請求才能到達(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"),返回true或false。在組件中,我們可以這樣使用:v-if="$check_action('/user/table', 'add')",來決定是否顯示“添加用戶”按鈕。$check_field(action, field): 用于字段級別的權(quán)限控制。例如,在用戶列表中,只有有權(quán)限的用戶才能看到“郵箱”列。$get_auth(user_group): 在用戶登錄成功后,調(diào)用此方法向后端請求該用戶組的全部權(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 # 用戶登錄、注冊視圖
│ │ └── 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é)請求攔截和校驗(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 請求權(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')
# 頁面請求權(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):
"""用戶注冊"""
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": "注冊成功"}}
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)限對象
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="請輸入用戶名"></el-input>
</el-form-item>
<el-form-item label="密碼" prop="password">
<el-input v-model="form.password" type="password" placeholder="請輸入密碼"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">登錄</el-button>
<el-button @click="onRegister">注冊</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: 'Login',
data() {
return {
form: {
username: '',
password: ''
},
rules: {
username: [
{ required: true, message: '請輸入用戶名', trigger: 'blur' }
],
password: [
{ required: true, message: '請輸入密碼', 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="請選擇用戶組">
<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('請先選擇用戶組和路徑')
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
})
// 請求攔截器,自動(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
})
// 簡化 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)限,這對于快速迭代的項(xiàng)目來說至關(guān)重要。
以上就是手把手教你如何使用Vue+Django實(shí)現(xiàn)RBAC權(quán)限管理的詳細(xì)內(nèi)容,更多關(guān)于Vue Django RBAC權(quán)限管理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
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(表格)并獲取對應(yīng)的ref的操作代碼
我想要獲取每一個(gè)循環(huán)并獲取每一個(gè)li(或者其它循環(huán)項(xiàng))的ref,以便于后續(xù)的操作,接下來通過本文給大家分享vue中循環(huán)多個(gè)li(表格)并獲取對應(yīng)的ref的操作代碼,感興趣的朋友跟隨小編一起看看吧2024-02-02
elementUI中input回車觸發(fā)頁面刷新問題與解決方法
這篇文章主要給大家介紹了關(guān)于elementUI中input回車觸發(fā)頁面刷新問題與解決方法,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用elementUI具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-07-07
vue前端實(shí)現(xiàn)導(dǎo)出頁面為word的兩種方法代碼
在前端開發(fā)中我們常常需要將頁面頁面為word文件,這篇文章主要給大家介紹了關(guān)于vue前端實(shí)現(xiàn)導(dǎo)出頁面為word的兩種方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
詳解Vue一個(gè)案例引發(fā)「內(nèi)容分發(fā)slot」的最全總結(jié)
這篇文章主要介紹了詳解Vue一個(gè)案例引發(fā)「內(nèi)容分發(fā)slot」的最全總結(jié),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12

