前面的菜單、部門、職位與管理員管理功能完成後,接下來要處理的是將它們關聯起來,根據職位管理中選定的許可權控制菜單顯示以及頁面數據的訪問和操作。 那麼要怎麼改造呢?我們可以通過用戶的操作步驟來一步步進行處理,具體思路如下: 1.用戶在管理端登錄時,通過用戶記錄所綁定的職位信息,來確定用戶所擁有的許可權。我 ...
前面的菜單、部門、職位與管理員管理功能完成後,接下來要處理的是將它們關聯起來,根據職位管理中選定的許可權控制菜單顯示以及頁面數據的訪問和操作。
那麼要怎麼改造呢?我們可以通過用戶的操作步驟來一步步進行處理,具體思路如下:
1.用戶在管理端登錄時,通過用戶記錄所綁定的職位信息,來確定用戶所擁有的許可權。我們可以在登錄介面中,將該管理員的職位id存儲到session中,以方便後續的調用。
2.登錄成功後,跳轉進入管理界口,在獲取菜單列表時,需要對菜單列表進行處理,只列出當前用戶有許可權的菜單項。
3.在點擊菜單進入相關數據頁面或在數據頁面進行增刪改查等操作時,需要進行許可權判斷,判斷是否有許可權進行查看或操作。由於我們是前後端分離,所以許可權只需要在介面進行處理。
首先我們來簡單改造一下登錄介面login.py,只需要在將職位id存儲到session中就可以了
1 ############################################################## 2 ### 把用戶信息保存到session中 ### 3 ############################################################## 4 manager_id = manager_result.get('id', 0) 5 s['id'] = manager_id 6 s['login_name'] = username 7 s['positions_id'] = manager_result.get('positions_id', '') 8 s.save()
找到上面內容,在裡面插入 s['positions_id'] = manager_result.get('positions_id', '')
接下來改造菜單列表介面menu_info.py文件的@get('/api/main/menu_info/')介面,我們需要做以下操作:
1.首先從session中獲取當前用戶的職位id,然後根據職位id從職位表中讀取對應的許可權數據
2.其次在菜單的遍歷組裝過程中,添加判斷用戶的許可權,沒有許可權的菜單項直接過濾掉
1 @get('/api/main/menu_info/') 2 def callback(): 3 """ 4 主頁面獲取菜單列表數據 5 """ 6 # 獲取當前用戶許可權 7 session = web_helper.get_session() 8 if session: 9 _positions_logic = positions_logic.PositionsLogic() 10 page_power = _positions_logic.get_page_power(session.get('positions_id')) 11 else: 12 page_power = '' 13 if not page_power: 14 return web_helper.return_msg(-404, '您的登錄已超時,請重新登錄') 15 16 _menu_info_logic = menu_info_logic.MenuInfoLogic() 17 # 讀取記錄 18 result = _menu_info_logic.get_list('*', 'is_show and is_enabled', orderby='sort') 19 if result: 20 # 定義最終輸出的html存儲變數 21 html = '' 22 for model in result.get('rows'): 23 # 檢查是否有許可權 24 if ',' + str(model.get('id')) + ',' in page_power: 25 # 提取出第一級菜單 26 if model.get('parent_id') == 0: 27 # 添加一級菜單 28 temp = """ 29 <dl id="menu-%(id)s"> 30 <dt><i class="Hui-iconfont">%(icon)s</i> %(name)s<i class="Hui-iconfont menu_dropdown-arrow"></i></dt> 31 <dd> 32 <ul> 33 """ % {'id': model.get('id'), 'icon': model.get('icon'), 'name': model.get('name')} 34 html = html + temp 35 36 # 從所有菜單記錄中提取當前一級菜單下的子菜單 37 for sub_model in result.get('rows'): 38 # 檢查是否有許可權 39 if ',' + str(sub_model.get('id')) + ',' in page_power: 40 # 如果父id等於當前一級菜單id,則為當前菜單的子菜單 41 if sub_model.get('parent_id') == model.get('id'): 42 temp = """ 43 <li><a data-href="%(page_url)s" data-title="%(name)s" href="javascript:void(0)">%(name)s</a></li> 44 """ % {'page_url': sub_model.get('page_url'), 'name': sub_model.get('name')} 45 html = html + temp 46 47 # 閉合菜單html 48 temp = """ 49 </ul> 50 </dd> 51 </dl> 52 """ 53 html = html + temp 54 55 return web_helper.return_msg(0, '成功', {'menu_html': html}) 56 else: 57 return web_helper.return_msg(-1, "查詢失敗")
第9與第10行,就是從職位表中,讀取指定職位id的許可權page_power欄位值,第24行與第39行中,只需要判斷當前菜單id是否存在page_power欄位值中,就可以判斷是否擁有該菜單許可權了,因為在前面職位管理那裡,勾選了指定菜單id後,就會將菜單的id存儲到這個欄位中。
由於可能多處需要讀取許可權page_power欄位值,這裡我們需要在職位邏輯類positions_logic.py中添加get_page_power()方法,來獲取其值出來使用。
1 def get_page_power(self, positions_id): 2 """獲取當前用戶許可權""" 3 page_power = self.get_value_for_cache(positions_id, 'page_power') 4 if page_power: 5 return ',' + page_power + ',' 6 else: 7 return ','
我們調用ORM的get_value_for_cache()方法,直接通過主鍵id來讀取我們想要的欄位值,併在許可權字串兩端添加逗號,因為我們在比較菜單id是否存在於許可權字串時,不加上逗號可能會出錯,比如說許可權串有2,10,11,如果我們直接比較1是否存在於許可權串中,如果不轉為list,直接字元串比較,返回結果就會為True,因為10和11都存在1,而各增加逗號以後比較就不一樣了,,2,10,11,與,1,比較肯定返回的是False,也就是說當前管理員沒有擁有1這個菜單id的許可權。
PS:完成菜單列表功能的改造後,記得檢查菜單列表頁面(main.html)和改造的介面是否在上一章節結束後,添加到菜單管理項中,併在職位管理中將對應的許可權項打上勾,如果沒有的話,完成本文改造,登錄後臺將會提示你沒有訪問許可權。
最後要處理的是後臺管理各介面的許可權判斷,由於bottle勾子(@hook('before_request'))直接獲取當前訪問的路由(介面),所獲取到的都有具體值(比如:@get('/system/menu_info/<id:int>/') 這個路由,在勾子中取到的是/system/menu_info/1/, 由於id值是不固定的,我們要處理起來會很麻),所以我們只能在每個介面中直接處理,也就是說我們需要在每個介面中,添加固定的許可權判斷方法調用。
而許可權的處理需要對資料庫對資料庫進行讀取操作,所以我們可以在邏輯層文件夾中(logic)添加一個通用的邏輯層模塊_common_logic.py,將許可權判斷方法在這個文件中實現,方便調用。
這裡的許可權判斷實現原理是:通過獲取web來路html頁面名稱、當前介面訪問方式(method)、當前訪問的介面路由名稱,將它們組成一個key值,從菜單許可權初始化緩存中讀取出對應的菜單實體(後面會講到如何生成這個菜單許可權緩存),提取當前所訪問介面所對應的菜單id值,然後通過從session中獲取當前用戶的職位id,獲取當前用戶所擁有的職位許可權,將菜單id與職位許可權進行比較,判斷用戶是否擁有當前所訪問的介面許可權,從而達到對許可權的訪問控制。
具體實現這個許可權判斷方法,有以下步驟:
1.首先我們需要獲取web的來路地址HTTP_REFERER,由於我們在前面菜單管理中,錄入的html頁面地址不包括功能變數名稱和參數,所以來路地址需要去掉當前功能變數名稱和?號後面的附加參數,只保留html頁面名稱。
2.直接從從bottle的request中,讀取當前訪問介面的路由值(rule)
3.從bottle的request中獲取當前訪問介面的方式(get/post/put/delete)
4.將前面三步獲取的值組合成菜單對應的唯一key,然後在菜單許可權緩存中讀取對應的菜單實體
5.如果菜單記錄實體不存在,則表達當前介面未註冊或註冊時所提交的信息錯誤,當前用戶沒有該介面的訪問許可權
6.從session中獲取當前用戶登錄時所存儲的職位id,然後通過該id讀取對應的職位許可權
7.從菜單實體中提取菜單id,與職位許可權進行比較,判斷當前用戶是否擁有訪問該介面的許可權,如果有則跳過,沒有則拒絕訪問。
具體代碼如下:
1 #!/usr/bin/env python 2 # coding=utf-8 3 4 from bottle import request 5 from common import web_helper 6 from logic import menu_info_logic, positions_logic 7 8 def check_user_power(): 9 """檢查當前用戶是否有訪問當前介面的許可權""" 10 # 獲取當前頁面原始路由 11 rule = request.route.rule 12 # 獲取當前訪問介面方式(get/post/put/delete) 13 method = request.method.lower() 14 15 # 獲取來路url 16 http_referer = request.environ.get('HTTP_REFERER') 17 if http_referer: 18 # 提取頁面url地址 19 index = http_referer.find('?') 20 if index == -1: 21 url = http_referer[http_referer.find('/', 8) + 1:] 22 else: 23 url = http_referer[http_referer.find('/', 8) + 1: index] 24 else: 25 url = '' 26 27 # 組合當前介面訪問的緩存key值 28 key = url + method + '(' + rule + ')' 29 # 從菜單許可權緩存中讀取對應的菜單實體 30 menu_info = menu_info_logic.MenuInfoLogic() 31 model = menu_info.get_model_for_url(key) 32 if not model: 33 web_helper.return_raise(web_helper.return_msg(-1, "您沒有訪問許可權1" + key)) 34 35 # 讀取session 36 session = web_helper.get_session() 37 if session: 38 # 從session中獲取當前用戶登錄時所存儲的職位id 39 positions = positions_logic.PositionsLogic() 40 page_power = positions.get_page_power(session.get('positions_id')) 41 # 從菜單實體中提取菜單id,與職位許可權進行比較,判斷當前用戶是否擁有訪問該介面的許可權 42 if page_power.find(',' + str(model.get('id', -1)) + ',') == -1: 43 web_helper.return_raise(web_helper.return_msg(-1, "您沒有訪問許可權2")) 44 else: 45 web_helper.return_raise(web_helper.return_msg(-404, "您的登錄已失效,請重新登錄"))
對於前面所講的菜單許可權緩存,下麵詳細講解一下。
由於菜單跟介面都很多,我們在做許可權判斷時,就需要在訪問介面時,自動匹配找到該介面對應的菜單項,然後才可以根據菜單id和許可權字元進行比較,判斷是否擁有操作許可權,而自動匹配這裡如果直接通過資料庫查找的話,操作會比較複雜,也會影響使用性能,所以我們可以通過將在菜單管理中註冊的菜單項進行分解,按一定的規則組合生成對應的緩存key,存儲到nosql中,當訪問介面時,我們根據規則組合成對應的key直接在nosql中查找就可以實現我們想要的功能了。當然第一次訪問或我們清除緩存後,這些key值是不存在的,所以我們可以加個判斷,如果緩存不存在時,重新載入生成對應的key就可以了。
具體代碼如下:
1 def get_model_for_url(self, key): 2 """通過當前頁面路由url,獲取菜單對應的記錄""" 3 # 使用md5生成對應的緩存key值 4 key_md5 = encrypt_helper.md5(key) 5 # 從緩存中提取菜單記錄 6 model = cache_helper.get(key_md5) 7 # 記錄不存在時,運行記錄載入緩存程式 8 if not model: 9 self._load_cache() 10 model = cache_helper.get(key_md5) 11 return model 12 13 def _load_cache(self): 14 """全表記錄載入緩存""" 15 # 生成緩存載入狀態key,主要用於檢查是否已執行了菜單表載入緩存判斷 16 cache_key = self.__table_name + '_is_load' 17 # 將自定義的key存儲到全局緩存隊列中(關於全局緩存隊列請查看前面ORM對應章節說明) 18 self.add_relevance_cache_in_list(cache_key) 19 # 獲取緩存載入狀態,檢查記錄是否已載入緩存,是的話則不再執行 20 if cache_helper.get(cache_key): 21 return 22 # 從資料庫中讀取全部記錄 23 result = self.get_list() 24 # 標記記錄已載入緩存 25 cache_helper.set(cache_key, True) 26 # 如果菜單表沒有記錄,則直接退出 27 if not result: 28 return 29 # 迴圈遍歷所有記錄,組合處理後,存儲到nosql緩存中 30 for model in result.get('rows', {}): 31 # 提取菜單頁面對應的介面(後臺菜單管理中的介面值,同一個菜單操作時,經常需要訪問多個介面,所以這個值有中存儲多們介面值) 32 interface_url = model.get('interface_url', '') 33 if not interface_url: 34 continue 35 # 獲取前端html頁面地址 36 page_url = model.get('page_url', '') 37 38 # 同一頁面介面可能有多個,所以需要進行分割 39 interface_url_arr = interface_url.replace('\n', '').replace(' ', '').split(',') 40 # 逐個介面處理 41 for interface in interface_url_arr: 42 # html+介面組合生成key 43 url_md5 = encrypt_helper.md5(page_url + interface) 44 # 存儲到全局緩存隊列中,方便菜單記錄更改時,自動清除這些自定義緩存 45 self.add_relevance_cache_in_list(url_md5) 46 # 存儲到nosql緩存 47 cache_helper.set(url_md5, model)
這裡的許可權管理邏輯有點繞,需要認真思考與debug檢查,才能真正掌握。另外,也可以通過後臺菜單管理中,故意修改菜單項的某些值,來檢查這裡的代碼處理與變化。
完成以上代碼以後,許可權的處理就完成了,接下來只需要在每個後臺管理介面中添加下麵代碼就可以做到介面的訪問許可權控制了。
@get('/api/main/menu_info/') def callback(): """ 主頁面獲取菜單列表數據 """ # 檢查用戶許可權 _common_logic.check_user_power()
具體大家可以查看文章後面提供的源碼,看看後臺管理介面處理就清楚了。
版權聲明:本文原創發表於 博客園,作者為 AllEmpty 本文歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則視為侵權。
python開發QQ群:669058475 作者博客:http://www.cnblogs.com/EmptyFS/