很榮幸有時間能靜下心來寫在這篇文章拾氓,前段時間寫了一些沒有營養(yǎng)的文章對那些關(guān)注我的同學(xué)來說非常抱歉,接下來的一段日子里會圍繞近期所做的Flask項目寫一系列的博客商虐,以記錄自己的不足。
鑒于可能有些小白可能會看到這篇文章圈盔,于是我盡量寫的通俗易懂罢猪。
接下來進入正題觅廓,我這篇文章要寫的是一個系統(tǒng)的權(quán)限部分仙逻。權(quán)限的控制對于一個優(yōu)秀的系統(tǒng)來說至關(guān)重要茂翔,但是對于權(quán)限的設(shè)計和把空是比較麻煩的混蔼。
一般如果我們不考慮按鈕的話,邏輯大致如下:
把菜單和權(quán)限珊燎、權(quán)限用戶關(guān)聯(lián)起來惭嚣。
1、用戶頁面俐末,可以增刪改查料按,并且還要有一個分配權(quán)限的按鈕。
2卓箫、權(quán)限頁面载矿,可以增刪改查,并且有一個分配用戶的按鈕和一個分配菜單的按鈕。
3闷盔、建立兩個表弯洗,分別為用戶權(quán)限表(保存用戶ID和權(quán)限ID)、權(quán)限菜單表(保存權(quán)限ID和菜單ID)逢勾。
4牡整、當(dāng)在用戶頁面中選中一個用戶,點擊用戶的“分配權(quán)限”按鈕時溺拱,打開展示所有權(quán)限的頁面(并把用戶ID傳進去)逃贝,左邊展示所有還沒有分配的權(quán)限列表,右邊展現(xiàn)已經(jīng)分配的權(quán)限列表迫摔,然后選擇需要分配的左邊權(quán)限后沐扳,點擊分配,把數(shù)據(jù)分配到右邊已分配的列表中句占,然后點擊“確定”按鈕沪摄,把用戶ID和選擇的權(quán)限ID保存到用戶權(quán)限表。
5纱烘、當(dāng)在權(quán)限頁面選中一個權(quán)限杨拐,并點擊“分配用戶”時,處理方式和4相同擂啥,當(dāng)選擇需要分配權(quán)限的用戶后哄陶,同樣把用戶ID和權(quán)限ID保存到用戶權(quán)限表。
6啤它、當(dāng)在權(quán)限頁面選中一個權(quán)限奕筐,并點擊“分配菜單”時舱痘,打開一個樹展現(xiàn)所有菜單的頁面变骡,每個樹節(jié)點前面有一個復(fù)選框,并把這個權(quán)限已經(jīng)分配的樹默認(rèn)選中芭逝,然后在要分配的菜單節(jié)點樹前面的復(fù)選框上選中塌碌,最后保存數(shù)據(jù),把權(quán)限Id和所有選中的菜單ID保存到權(quán)限菜單表旬盯。
7台妆、當(dāng)用戶登陸系統(tǒng)的時候,首先檢查用戶輸入的口令信息胖翰,如果口令正確接剩,再根據(jù)用戶倒查用戶權(quán)限表,再通過用戶權(quán)限表查到的權(quán)限萨咳,到權(quán)限菜單表查詢相應(yīng)的菜單懊缺,再把相應(yīng)的菜單展示出來。
上面便是不考慮按鈕的情況下的業(yè)務(wù)邏輯培他,其實加上按鈕的話也是差不多的鹃两,因為按鈕隸屬于菜單遗座,只有給某個用戶分配了某個角色,這個用戶才能在登錄的時候看到他所擁有角色對應(yīng)下的菜單和按鈕俊扳,這樣即完成了角色的權(quán)限控制途蒋。
接下來開始我們的項目。
首先根據(jù)上面的業(yè)務(wù)描述馋记,我們大概可以用到的表和字段如下:
user表(id,name,tel,email,password) # 用戶表
role表(id,name,description) # 角色表
user_role表(id,user_id,role_id) # 用戶角色表
menu表(id,parent_id,lay,name,code,description) # 菜單表
action表(id,menu_id,name,code,description) # 按鈕表
role_menu(id,role_id,menu_id) # 角色菜單表
role_action(id,role_id,action_id) # 角色按鈕表
user_id
role_id
role_id
menu_id
role_id
action_id
menu_id
user
user_role
role
role_menu
menu
role_action
action
emmm号坡,這幾張表的關(guān)系大概如上吧。
大概邏輯有了梯醒,現(xiàn)在開始寫代碼:
----------------------------------start-----------------------------------#
'''
我們的框架使用Flask+sqlalchemy+flask_restplus
sqlalchemy為ORM數(shù)據(jù)庫映射 PS:sqlalchemy真的非常強大 使用起來非常方便
flask_restplus是swagger所呈現(xiàn)出來的一種網(wǎng)頁端接口測試工具 最大的有點是可以避免寫接口文檔
'''
根據(jù)user_id查詢 required=True為必填項
page_parser.add_argument('user_id', type=int, required=True, location='args')
用戶角色post新增/修改傳入?yún)?shù)
user_role_model = api.model('RoleUserRole', {
'role_id_list': fields.String('role id list 以逗號隔開","'),
'user_id': fields.Integer
})
flask_restplus頁面展示url /flask路由注冊/需注冊到藍(lán)圖上
@api.route('/role_by_user')
flask_restplus定義每一個類名展現(xiàn)在swagger的NameSpace上
class RoleByUser(Resource):
@api.expect(page_parser)
‘’‘
查詢已經(jīng)分配過角色的用戶 以用戶為主體
’‘’
def get(self):
# 自定義驗證傳入?yún)?shù)是否合法
form = RoleByUserForm().validate_for_api()
#實現(xiàn)代碼模塊化 將可復(fù)用查詢條件拆分出來 放在最后定義成了一個單獨的方法
task_filter = _form_and_task(form)
user_id = form.user_id.data
# 增加查詢條件
task_filter.append(UserRole.user_id == user_id)
page = Role.query.outerjoin(UserRole, Role.id == UserRole.role_id)
# task_filter為可變參數(shù)筋帖,可以傳入元組/列表
.filter(task_filter).order_by(
# sqlalchemy根據(jù)創(chuàng)建時間排序
text('role.create_time desc')
# paginate()分頁對象 傳入定義號的頁數(shù)
).paginate()
return Role().page(page)
@api.route('/role_by_not_user')
class RoleByNotUser(Resource):
# 和上面的類似 查詢未分配角色的用戶
@api.expect(page_parser)
def get(self):
form = RoleByUserForm().validate_for_api()
task_filter = _form_and_task(form)
user_id = form.user_id.data
# UserRole.user_id != user_id 查詢未分配角色的用戶
task_filter.append(UserRole.user_id != user_id)
page = Role.query.outerjoin(UserRole, Role.id == UserRole.role_id)
.filter(*task_filter).order_by(
text('role.create_time desc')
).paginate()
return Role().page(page)
# 新增 一個用戶可能對應(yīng)多個角色 傳入role_id_list
@api.expect(user_role_model)
def post(self):
form = RoleUserPostForm().validate_for_api()
user_id, role_id_list = form.user_id.data, form.role_id_list.data
# 傳入role_id_list使用“,”分開 使用split從每個“,”處分開
if role_id_list:
user_role_list = []
role_id_list = role_id_list.split(',')
# 遍歷role_id_list 將每個role_id存入上面定義的user_role_list列表中
# 調(diào)用我們自定義的save_all方法 將每個role_id存入UserRole表
for role_id in role_id_list:
user_role = UserRole()
user_role.role_id = role_id
user_role.user_id = int(user_id)
user_role_list.append(user_role)
UserRole().save_all(user_role_list)
’‘’
權(quán)限設(shè)置
’‘’
role_action_menu_parser = reqparse.RequestParser()
role_action_menu_parser.add_argument('role_id', type=int, required=True, location='args')
menu_action_lists = api.model('RoleActionMenuList', {
'mid': fields.Integer,
'type': fields.Integer
})
接收的參數(shù)為menu_action_list和role_id,menu_action_list中存的是mid和type
這里的type是為了區(qū)分菜單和按鈕 0-菜單 1-按鈕
role_action_menu_model = api.model('RoleActionMenu', {
'menu_action_list': fields.List(fields.Nested(menu_action_lists)),
'role_id': fields.Integer
})
‘’‘
namedtuple(命名元組)是繼承自tuple的子類 namedtuple創(chuàng)建一個和tuple類似的對象 而且對象擁有可訪問的屬性
普通tuple類型的成員 只能通過索引訪問 namedtuple在此基礎(chǔ)上還提供了通過名稱訪問的方式
’‘’
我們使用一個命名元組來定義按鈕和菜單的樹形集合
menu_action_tree = namedtuple('MenuActionTree', ['id', 'name', 'parent_id', 'lay', 'is_select', 'has_child', 'type'])
@api.route('/role_action_menu')
class RoleActionMenu(Resource):
@api.expect(role_action_menu_parser)
‘’‘
查詢該角色所能查到的所有的菜單和按鈕
’‘’
def get(self):
form = RoleIdForm().validate_for_api()
role_id = form.role_id.data
menus = Menu.query.filter().all() # 菜單
actions = Action.query.filter().all() # 按鈕
# 通過自定義樹形菜單和按鈕列表冤馏,通過role_id查詢拼接當(dāng)前角色所能看到的菜單和按鈕
# 分別構(gòu)造拼接菜單和按鈕樹形集合 并將菜單和按鈕的樹形合并
menu_action_trees = _menu_tree(role_id, menus)
menu_action_trees += _action_tree(role_id, actions, menus)
# 通過自定義get_tree方法將最后合并好的數(shù)據(jù)集合轉(zhuǎn)化為json傳給前臺
tree = get_tree(menu_action_trees)
return tree
‘’‘
新增角色菜單和按鈕
’‘’
@api.expect(role_action_menu_model)
def post(self):
form = RoleMenuActionForm().validate_for_api()
role_id, menu_action_list = form.role_id.data, form.menu_action_list.data
# 過濾 區(qū)分菜單和按鈕
if menu_action_list:
role_action = list(filter(lambda x: x['type'] == 1, menu_action_list))
role_menu = list(filter(lambda x: x['type'] == 0, menu_action_list))
# 使用自定義方法分別儲存菜單和按鈕到role_menu和role_action表
with db.auto_commit():
_save_menu(role_id, role_menu)
_save_action(role_id, role_action)
存儲菜單
def _save_menu(role_id, role_menu):
# 每次在存之前我們先刪除該角色之前存儲過的菜單
RoleMenu.query.filter_by(role_id=role_id).delete()
new_role_menu = []
# 遍歷role_menu表 通過role_id將給該角色添加菜單
for m in role_menu:
role_menu = RoleMenu()
role_menu.role_id = role_id
role_menu.menu_id = m['mid']
new_role_menu.append(role_menu)
RoleMenu().save_all(new_role_menu)
存儲按鈕
def _save_action(role_id, role_action):
# 和菜單一樣 在新增之前我們要刪除之前存儲過的按鈕
RoleAction.query.filter_by(role_id=role_id).delete()
new_role_action = []
# 遍歷role_acton表通過role_id將給該角色添加角色
for m in role_action:
role_action = RoleAction()
role_action.role_id = role_id
role_action.action_id = m['mid']
new_role_action.append(role_action)
RoleAction().save_all(new_role_action)
拼接action樹形
def _action_tree(role_id, actions, menus):
menu_action_trees = []
role_actions = RoleAction.query.filter(UserRole.role_id == role_id).all()
for action in actions:
action_parent = list(filter(lambda x: x.id == action.menu_id, menus))
# 判斷層級
if len(action_parent) > 0:
lay = action_parent[0].lay + 1
else:
lay = 0
# 是否選中 1-選中 0-未選中
is_select = [False for role_action in role_actions if role_action.action_id == action.id]
if is_select:
is_select = 1
else:
is_select = 0
# 按照前面定義命名元組參數(shù)順序按照順序傳入對應(yīng)參數(shù)
mct = menu_action_tree(
str(action.id) + 'action分割
action.name,
action.menu_id,
lay,
is_select,
0, # 按鈕是最后一級
1 # 0-菜單 1-按鈕
)
menu_action_trees.append(mct)
return menu_action_trees
拼接菜單樹形
def _menu_tree(role_id, menus):
menu_action_trees = []
role_menus = RoleMenu.query.filter(UserRole.role_id == role_id).all()
for menu in menus:
# 通過列表推導(dǎo)式判斷有無選中
is_select = [False for role_menu in role_menus if role_menu.menu_id == menu.id]
if is_select:
is_select = 1
else:
is_select = 0
mct = menu_action_tree(
menu.id,
menu.name,
menu.parent_id,
menu.lay,
is_select,
0, # 不好判斷暫定為0
0 # 0-菜單 1-按鈕
)
menu_action_trees.append(mct)
return menu_action_trees
通過姓名模糊查詢
def _form_and_task(form):
name = form.name.data
task_filter = [
Role.name.like('%' + name + '%') if name is not None else text(''),
]
return task_filter
----------------------------------end------------------------------------#
這樣我們就完成了按鈕日麸,角色,菜單逮光,用戶代箭,權(quán)限的校驗,文中少數(shù)自定義類或方法由于寫在了基類中涕刚,等到后面會慢慢列出嗡综。另外文章前面是以用戶為主體的角色綁定用戶,在用戶頁面還應(yīng)該有以角色為主體的用戶綁定角色杜漠,但是兩者都不盡相同极景,因此在本文中暫不列出,后續(xù)如果有需要的話再補上盼樟!
————————————————
版權(quán)聲明:本文為CSDN博主「DesolatePoison」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議晨缴,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_40695642/article/details/103414498