作者:Jade_K
鏈接:https://www.cnblogs.com/wms01/p/10903646.html
實際上權限系統(tǒng)老早之前我就在一直開發(fā),大概在剛畢業(yè)沒多久就想一個人寫一個系統(tǒng),斷斷續(xù)續(xù)一直堅持到現(xiàn)在,畢竟自己親動手自寫的系統(tǒng)才有收獲,本篇僅介紹權限。小小系統(tǒng)上不了臺面,望各位大神勿噴。
目前采用的是.NET Core微服務的方式實現(xiàn),本文不討論具體的中間件主要是(ocelot + consul等),一直參考微軟的 eShopOnContainers,進行簡單的實現(xiàn),但是ORM是用的Dapper,并簡單進行封裝 https://www.nuget.org/packages/JadeFramework.Dapper/
當然自己也封裝了一些簡單的插件進行復用https://github.com/wangmaosheng/JadeFramework
如下:
權限系統(tǒng)實現(xiàn)很簡單,權限的劃分我覺得可以分為三種:
1、菜單權限
2、按鈕權限
3、數(shù)據(jù)權限
簡單介紹下:
1、菜單權限。表示用戶是否能夠訪問該頁面(角色掛鉤)
2、按鈕權限。表示用戶是否能夠操作該頁面上的功能(角色掛鉤)
3、數(shù)據(jù)權限。表示用戶訪問頁面時進行數(shù)據(jù)篩選(該功能暫未實現(xiàn),這個要與具體的業(yè)務結合才能寫),與部門掛鉤,這個不太好理解,當然一般的權限系統(tǒng)這個功能也不會做,舉個簡單例子,OA系統(tǒng)里面我查看我的工資條,我應該只能看到我自己的數(shù)據(jù),但是我的部門經(jīng)理,他可以有權限看到該部門的全部數(shù)據(jù),這個就是數(shù)據(jù)權限。
為什么寫這個系統(tǒng)?
之前待過好幾家公司,發(fā)現(xiàn)他們的系統(tǒng)都是對菜單進行分配,當然了,業(yè)務需求只要這個就當我沒說,我只是覺得這樣做太不安全并且我覺得之前系統(tǒng)的實現(xiàn)方式可以進行一些優(yōu)化,所以就一直寫到現(xiàn)在,可能代碼質(zhì)量不如哪些大神的優(yōu)秀,系統(tǒng)在我看來太小,就簡單搭了個框架實現(xiàn)。你過條小水溝,沒必要造條橋。
要使用該系統(tǒng)前提條件:前端:Sea.js和Vue,對于sea.js,在前端這塊感覺已經(jīng)沒多少人用了,但是這中CMD思想是不會被淘汰的,你看最近比較火的layerui也是的,對于Vues只是簡單的應用,也就用到雙向綁定而已,開發(fā)復雜的頁面確實比較方便,但是簡單的頁面就得不償失了。
后端:consul、rabbitmq ,具體怎么安裝不在描述
大概的用戶訪問流程描述如下:
用戶登錄 ---->獲取該用戶角色 ---->通過角色獲取該角色對應的權限 并集 ---->返回相應數(shù)據(jù)
sys_user_role sys_role_resource
系統(tǒng)關系圖如下(MySQL):
具體功能實現(xiàn)請看代碼,這里不做闡述,菜單權限的分配通過角色表和菜單表的關聯(lián)表操作即可,但是按鈕的權限分配如何實現(xiàn)?我的實現(xiàn)方式是:把按鈕的操作也看成一種菜單的資源分配,只不過比較特殊,我這里不僅僅是對按鈕的顯示進行控制,我做的比較絕,也對后臺方法訪問權限也做了控制,這樣比較安全,對于按鈕權限的控制,實際上是明確的,比方說,一個刪除按鈕,它只能對應后臺的一個刪除方法,這個方法是明確的,對于頁面的按鈕的類型和個數(shù)是固定的,不然你沒辦法分配,基于這個前提,我對菜單的生成進行代碼控制從而達到控制目的,因此,菜單和按鈕和在一起稱之為資源表 sys_resource 。具體的實現(xiàn)代碼也不是很復雜,一層一層判斷即可,權限過濾器如下:
public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
public UrlAndButtonType UrlAndButtonType { get; }
public PermissionAuthorizationRequirement(string url, ButtonType buttonType, bool isPage)
{
UrlAndButtonType = new UrlAndButtonType()
{
Url = url,
ButtonType = (byte)buttonType,
IsPage = isPage
};
}
public PermissionAuthorizationRequirement(string url, byte buttonType, bool isPage)
{
UrlAndButtonType = new UrlAndButtonType()
{
Url = url,
ButtonType = buttonType,
IsPage = isPage
};
}
}
/// <summary>
/// 權限過濾器
/// </summary>
[Authorize]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class PermissionAttribute : TypeFilterAttribute
{
/// <summary>
/// 構造器
/// </summary>
/// <param name='url'>地址</param>
/// <param name='buttonType'>按鈕類型</param>
/// <param name='isPage'>是否是頁面</param>
public PermissionAttribute(string url = default(string), ButtonType buttonType = ButtonType.View, bool isPage = true) :
base(typeof(RequiresPermissionAttributeExecutor))
{
Arguments = new object[] { new PermissionAuthorizationRequirement(url, buttonType, isPage) };
}
/// <summary>
/// 構造器
/// </summary>
/// <param name='url'>地址</param>
/// <param name='buttonType'>按鈕類型</param>
/// <param name='isPage'>是否是頁面</param>
public PermissionAttribute(string url, byte buttonType, bool isPage = true) :
base(typeof(RequiresPermissionAttributeExecutor))
{
Arguments = new object[] { new PermissionAuthorizationRequirement(url, buttonType, isPage) };
}
private class RequiresPermissionAttributeExecutor : Attribute, IAsyncResourceFilter
{
private IPermissionStorageContainer _permissionStorage;
private PermissionAuthorizationRequirement _requiredPermissions;
public RequiresPermissionAttributeExecutor(
IPermissionStorageContainer permissionStorage, PermissionAuthorizationRequirement requiredPermissions)
{
_permissionStorage = permissionStorage;
_requiredPermissions = requiredPermissions;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
string menuUrl = _requiredPermissions.UrlAndButtonType.Url;
//判斷用戶權限
if (string.IsNullOrEmpty(menuUrl))
{
//區(qū)域判斷
string area = context.RouteData.Values['area'].ToString();
if (string.IsNullOrEmpty(area))
{
menuUrl = '/' + context.RouteData.Values['controller'] + '/' + context.RouteData.Values['action'];
}
else
{
menuUrl = '/' + area + '/' + context.RouteData.Values['controller'] + '/' + context.RouteData.Values['action'];
}
}
menuUrl = menuUrl.Trim().ToLower();
var dbpermission = await _permissionStorage.GetPermissionAsync();
var menu = dbpermission.Menus.FirstOrDefault(m => m.MenuUrl != null && m.MenuUrl.Trim().ToLower() == menuUrl);
if (menu != null)//地址存在
{
if (_requiredPermissions.UrlAndButtonType.ButtonType == default(byte))
{
await next();
}
else
{
byte buttonType = (byte)_requiredPermissions.UrlAndButtonType.ButtonType;
if (menu.MenuButton.Select(m => m.ButtonType).Contains(buttonType))//擁有操作權限
{
await next();
}
else
{
//沒有操作權限
if (_requiredPermissions.UrlAndButtonType.IsPage)
{
context.Result = new RedirectResult('/error/noauth');
}
else
{
context.Result = new ContentResult()
{
Content = PermissionStatusCodes.Status2Unauthorized.ToString()
};
}
await context.Result.ExecuteResultAsync(context);
}
}
}
else
{
//沒有操作權限
if (_requiredPermissions.UrlAndButtonType.IsPage)
{
context.Result = new RedirectResult('/error/noauth');
}
else
{
context.Result = new ContentResult()
{
Content = PermissionStatusCodes.Status2Unauthorized.ToString()
};
}
await context.Result.ExecuteResultAsync(context);
}
}
}
}
在對于的頁面添加過濾器即可,如下:
[HttpGet]
[Permission]
public async Task<IActionResult> Index(int pageIndex=1,int pageSize=10)
{
var res = await _messageService.GetPageAsync(pageIndex, pageSize);
return View(res);
}
[HttpGet]
[Permission('/Sys/Message/Index', ButtonType.View)]
public IActionResult Show()
{
return View();
}
系統(tǒng)界面展示圖:后臺模板是之前從網(wǎng)上找的并自己簡單改了一下,將就能看吧,實在不想花功夫在前端上面了@-^-@
運行步驟:
1、確保數(shù)據(jù)庫mssystem和mssystemlog存在 github文檔中
2、consul服務啟動,如下回車運行
3、VS項目啟動
管理員登錄賬號wms,密碼:所有賬號密碼都是123
代碼地址:
https://github.com/wangmaosheng/MsSystem-BPM-ServiceAndWebApps
如果覺得有點作用的話,可以 start 下,后續(xù)會持續(xù)更新
●編號313,輸入編號直達本文
●輸入m獲取文章目錄