[開源]OSharpNS 步步為營系列 - 4. 新增業務對外API
什麼是OSharp
OSharpNS全稱OSharp Framework with .NetStandard2.0,是一個基於.NetStandard2.0
開發的一個.NetCore
快速開發框架。這個框架使用最新穩定版的.NetCore SDK
(當前是.NET Core 2.2),對 AspNetCore 的配置、依賴注入、日誌、快取、實體框架、Mvc(WebApi)、身份認證、許可權授權等模組進行更高一級的自動化封裝,並規範了一套業務實現的程式碼結構與操作流程,使 .Net Core 框架更易於應用到實際專案開發中。
- 開源地址:https://github.com/i66soft/osharp
- 官方示例:https://www.osharp.org
- 文件中心:https://docs.osharp.org
- VS 外掛:https://marketplace.visualstudio.com/items?itemName=LiuliuSoft.osharp
概述
一個模組的 API層(Web層),主要負責如下幾個方面的工作:
- 接收
前端層
提交的資料查詢請求,使用服務層
提供的IQueryable<T>
查詢資料來源,查詢出需要的資料返回前端 - 接收
前端層
提交的業務處理請求,呼叫服務層
的服務,處理業務需求,並將操作結果返回前端 - 使用MVC的
Area-Controller-Action
的層次關係,聯合[ModuleInfo]
Module
的 樹形組織結構,API模組 的 依賴關係,構建出Module
的樹形資料 - 定義 API 的
可訪問方式
,API的訪問方式可分為匿名訪問
,登入訪問
和角色訪問
- 定義自動事務提交,涉及資料庫變更的業務,可在API定義自動事務提交,在業務層實現業務時即可不用考慮事務的問題
整個過程如下圖所示
API層 程式碼佈局
API層 程式碼佈局分析
API層 即是Web網站服務端的MVC控制器,控制器可按粒度需要不同,分為模組控制器和單實體控制器,這個由業務需求決定。
通常,後臺管理的控制器,是實體粒度的,即每個實體都有一個控制器,並且存在於 /Areas/Admin/Controlers
部落格模組的 API 層控制器,如下圖所示:
src 原始碼資料夾
└─Liuliu.Blogs.Web 專案Web工程
└─Areas 區域資料夾
└─Admin 管理區域資料夾
└─Controllers 管理控制器資料夾
└─Blogs 部落格模組資料夾
├─BlogController.cs 部落格管理控制器
└─PostController.cs 文章管理控制器
API定義及訪問控制的基礎建設
API定義
API定義即MVC或WebApi的 Area-Controller-Action
定義,為方便及規範此步驟的工作,OSharp定義了一些 API基礎控制器基類
,繼承這些基類,很容易實現API定義。
ApiController
ApiController
用於非Area的Api控制器,基類添加了 操作審計[AuditOperation]
,[ApiController]
特性,並定義了一個 [Route("api/[controller]/[action]")]
的路由特性
/// <summary>
/// WebApi控制器基類
/// </summary>
[AuditOperation]
[ApiController]
[Route("api/[controller]/[action]")]
public abstract class ApiController : Controller
{
/// <summary>
/// 獲取或設定 日誌物件
/// </summary>
protected ILogger Logger => HttpContext.RequestServices.GetLogger(GetType());
}
AreaApiController
與 無區域控制器基類ApiController
相對應,對於區域控制器,也定義了一個基類 AreaApiController
/// <summary>
/// WebApi的區域控制器基類
/// </summary>
[AuditOperation]
[ApiController]
[Route("api/[area]/[controller]/[action]")]
public abstract class AreaApiController : Controller
{ }
AdminApiController
對於相當常用的 管理Admin
區域,也同樣定義了一個控制器基類AdminApiController
,此基類繼承於AreaApiController
,並添加了區域特性[Area("Admin")]
和角色訪問限制特性[RoleLimit]
[Area("Admin")]
[RoleLimit]
public abstract class AdminApiController : AreaApiController
{ }
部落格模組API實現
[Description("管理-部落格資訊")]
public class BlogController : {==AdminApiController==}
{ }
[Description("管理-文章資訊")]
public class PostController : {==AdminApiController==}
{ }
Module樹形結構及依賴
ModuleInfoAttribute
為了描述 API的層級關係,OSharp定義了一個ModuleInfoAttribute
特性,把當前功能(Controller或者Action)封裝為一個模組(Module)節點,可以設定模組依賴的其他功能,模組的位置資訊等。此特性用於系統初始化時自動提取模組樹資訊Module。
/// <summary>
/// 描述把當前功能(Controller或者Action)封裝為一個模組(Module)節點,可以設定模組依賴的其他功能,模組的位置資訊等
/// 此特性用於系統初始化時自動提取模組樹資訊Module
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ModuleInfoAttribute : Attribute
{
/// <summary>
/// 獲取或設定 模組名稱,為空則取功能名稱
/// </summary>
public string Name { get; set; }
/// <summary>
/// 獲取或設定 模組程式碼,為空則取功能Action名
/// </summary>
public string Code { get; set; }
/// <summary>
/// 獲取或設定 層次序號
/// </summary>
public double Order { get; set; }
/// <summary>
/// 獲取或設定 模組位置,父級模組,模組在樹節點的位置,預設取所在類的位置,需要在名稱空間與當前類之間加模組,才設定此值
/// </summary>
public string Position { get; set; }
/// <summary>
/// 獲取或設定 父級位置模組名稱,需要在名稱空間與當前類之間加模組,才設定此值
/// </summary>
public string PositionName { get; set; }
}
[ModuleInfo]
特性主要有兩種用法:
- 在
Controller
上,主要控制模組的順序Order
,模組的位置Position
,模組名稱PositionName
,例如:
[ModuleInfo(Position = "Blogs", PositionName = "部落格模組")]
[Description("管理-部落格資訊")]
public class BlogController : AdminApiController
{ }
- 在
Action
上,主要用於標註哪些Action是作為可許可權分配的API,通常無需使用屬性,例如:
/// <summary>
/// 讀取部落格
/// </summary>
/// <returns>部落格頁列表</returns>
[HttpPost]
[ModuleInfo]
[Description("讀取")]
public PageData<BlogOutputDto> Read(PageRequest request)
{ }
DependOnFunctionAttribute
由於業務的關聯性和UI的合理佈局,API功能點並 不是單獨存在 的,要完成一個完整的操作,各個API功能點可能會 存在依賴性。例如:
- 在要進行管理列表中的
新增、更新、刪除
等操作,首先要能進入列表,即列表資料的讀取
操作,那麼新增、更新、刪除
等操作就對讀取
操作存在依賴需求。 - 對於
新增、更新
操作,通常需要對資料進行唯一性驗證
,那麼也會存在依賴關係
為了在程式碼中描述這些依賴關係,OSharp中定義了DependOnFunctionAttribute
特性,在Action上標註當前API對其他API(可跨Controller,跨Area)的依賴關係。
/// <summary>
/// 模組依賴的功能資訊,用於提取模組資訊Module時確定模組依賴的功能(模組依賴當前功能和此特性設定的其他功能)
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class DependOnFunctionAttribute : Attribute
{
/// <summary>
/// 初始化一個<see cref="DependOnFunctionAttribute"/>型別的新例項
/// </summary>
public DependOnFunctionAttribute(string action)
{
Action = action;
}
/// <summary>
/// 獲取或設定 區域名稱,為null(不設定)則使用當前功能所在區域,如要表示無區域的功能,需設定為空字串""
/// </summary>
public string Area { get; set; }
/// <summary>
/// 獲取或設定 控制器名稱,為null(不設定)則使用當前功能所在控制器
/// </summary>
public string Controller { get; set; }
/// <summary>
/// 獲取 功能名稱Action,不能為空
/// </summary>
public string Action { get; }
}
如下示例,表明管理列表中的新增文章
業務對文章讀取
有依賴關係
/// <summary>
/// 新增文章
/// </summary>
/// <param name="dtos">新增文章資訊</param>
/// <returns>JSON操作結果</returns>
[HttpPost]
[ModuleInfo]
[DependOnFunction("Read")]
[ServiceFilter(typeof(UnitOfWorkAttribute))]
[Description("新增")]
public async Task<AjaxResult> Create(PostInputDto[] dtos)
{ }
API訪問控制
API的訪問控制,分為三種:
- 匿名訪問
AllowAnonymousAttribute
:表示當前功能不需要登入即可訪問,無視登入狀態和角色要求 - 登入訪問
LoginedAttribute
:表示當前功能需要登入才能訪問,未登入拒絕訪問 - 角色訪問
RoleLimitAttribute
:表示當前功能需要登入並且使用者擁有指定角色,才能訪問,未登入或者登入但未擁有指定角色,拒絕訪問
API訪問控制的控制順序按照 就近原則,即離要執行的功能最近的那個限制生效。以Controller上的標註與Action上的標註為例:
- Controller無,Action無,不限制
- Controller有,Action無,以Controller為準
- Controller無,Action有,以Action為準
- Controller有,Action有,以Action為準
在AdminApiController
基類中,已經設定了[RoleLimit]
,表示Admin區域中的所有Controller和Action的預設訪問控制方式就是 角色訪問。
[Area("Admin")]
[RoleLimit]
public abstract class AdminApiController : AreaApiController
{ }
如想額外控制,則需要在實現Action的時候進行單獨配置
[HttpPost]
[ModuleInfo]
[Logined]
[Description("讀取")]
public PageData<BlogOutputDto> Read(PageRequest request)
{ }
自動事務提交
在傳統框架中,事務的提交是在業務層實現完業務操作之後即手動提交的,這種方式能更精準的控制事務的結束位置,但也有不能適用的情況,例如當一個業務涉及多個服務的時候,每個服務各自提交了事務,便無法保證所有操作在一個完整的事務上進行了。
為此,OSharp框架提出了一種新的事務提交方式:在Action中通過Mvc的Filter來自動提交事務。
自動提交事務是通過如下的UnitOfWorkAttribute
實現的:
/// <summary>
/// 自動事務提交過濾器,在<see cref="OnResultExecuted"/>方法中執行<see cref="IUnitOfWork.Commit()"/>進行事務提交
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
[Dependency(ServiceLifetime.Scoped, AddSelf = true)]
public class UnitOfWorkAttribute : ActionFilterAttribute
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
/// <summary>
/// 初始化一個<see cref="UnitOfWorkAttribute"/>型別的新例項
/// </summary>
public UnitOfWorkAttribute(IServiceProvider serviceProvider)
{
_unitOfWorkManager = serviceProvider.GetService<IUnitOfWorkManager>();
}
/// <summary>
/// 重寫方法,實現事務自動提交功能
/// </summary>
/// <param name="context"></param>
public override void OnResultExecuted(ResultExecutedContext context)
{
ScopedDictionary dict = context.HttpContext.RequestServices.GetService<ScopedDictionary>();
AjaxResultType type = AjaxResultType.Success;
string message = null;
if (context.Result is JsonResult result1)
{
if (result1.Value is AjaxResult ajax)
{
type = ajax.Type;
message = ajax.Content;
if (ajax.Successed())
{
_unitOfWorkManager?.Commit();
}
}
}
else if (context.Result is ObjectResult result2)
{
if (result2.Value is AjaxResult ajax)
{
type = ajax.Type;
message = ajax.Content;
if (ajax.Successed())
{
_unitOfWorkManager?.Commit();
}
}
else
{
_unitOfWorkManager?.Commit();
}
}
//普通請求
else if (context.HttpContext.Response.StatusCode >= 400)
{
switch (context.HttpContext.Response.StatusCode)
{
case 401:
type = AjaxResultType.UnAuth;
break;
case 403:
type = AjaxResultType.UnAuth;
break;
case 404:
type = AjaxResultType.UnAuth;
break;
case 423:
type = AjaxResultType.UnAuth;
break;
default:
type = AjaxResultType.Error;
break;
}
}
else
{
type = AjaxResultType.Success;
_unitOfWorkManager?.Commit();
}
if (dict.AuditOperation != null)
{
dict.AuditOperation.ResultType = type;
dict.AuditOperation.Message = message;
}
}
}
如一次請求中涉及資料的 新增、更新、刪除 操作時,在 Action 上新增 [ServiceFilter(typeof(UnitOfWorkAttribute))]
,即可實現事務自動提交。
/// <summary>
/// 新增文章
/// </summary>
/// <param name="dtos">新增文章資訊</param>
/// <returns>JSON操作結果</returns>
[HttpPost]
[ModuleInfo]
[ServiceFilter(typeof(UnitOfWorkAttribute))]
[Description("新增")]
public async Task<AjaxResult> Create(PostInputDto[] dtos)
{ }
AjaxReuslt
對於 前後端分離 的專案,前端向後端的請求都是通過 application/json
的方式來互動的,這就需要在後端對操作結果進行封裝。OSharp提供了AjaxResult
類來承載操作結果資料
/// <summary>
/// 表示Ajax操作結果
/// </summary>
public class AjaxResult
{
/// <summary>
/// 初始化一個<see cref="AjaxResult"/>型別的新例項
/// </summary>
public AjaxResult()
: this(null)
{ }
/// <summary>
/// 初始化一個<see cref="AjaxResult"/>型別的新例項
/// </summary>
public AjaxResult(string content, AjaxResultType type = AjaxResultType.Success, object data = null)
: this(content, data, type)
{ }
/// <summary>
/// 初始化一個<see cref="AjaxResult"/>型別的新例項
/// </summary>
public AjaxResult(string content, object data, AjaxResultType type = AjaxResultType.Success)
{
Type = type;
Content = content;
Data = data;
}
/// <summary>
/// 獲取或設定 Ajax操作結果型別
/// </summary>
public AjaxResultType Type { get; set; }
/// <summary>
/// 獲取或設定 訊息內容
/// </summary>
public string Content { get; set; }
/// <summary>
/// 獲取或設定 返回資料
/// </summary>
public object Data { get; set; }
/// <summary>
/// 是否成功
/// </summary>
public bool Successed()
{
return Type == AjaxResultType.Success;
}
/// <summary>
/// 是否錯誤
/// </summary>
public bool Error()
{
return Type == AjaxResultType.Error;
}
/// <summary>
/// 成功的AjaxResult
/// </summary>
public static AjaxResult Success(object data = null)
{
return new AjaxResult("操作執行成功", AjaxResultType.Success, data);
}
}
其中AjaxResultType
的可選項為:
/// <summary>
/// 表示 ajax 操作結果型別的列舉
/// </summary>
public enum AjaxResultType
{
/// <summary>
/// 訊息結果型別
/// </summary>
Info = 203,
/// <summary>
/// 成功結果型別
/// </summary>
Success = 200,
/// <summary>
/// 異常結果型別
/// </summary>
Error = 500,
/// <summary>
/// 使用者未登入
/// </summary>
UnAuth = 401,
/// <summary>
/// 已登入,但許可權不足
/// </summary>
Forbidden = 403,
/// <summary>
/// 資源未找到
/// </summary>
NoFound = 404,
/// <summary>
/// 資源被鎖定
/// </summary>
Locked = 423
}
業務服務層的操作結果OperationResult
,可以很輕鬆的轉換為AjaxResult
/// <summary>
/// 將業務操作結果轉ajax操作結果
/// </summary>
public static AjaxResult ToAjaxResult<T>(this OperationResult<T> result, Func<T, object> dataFunc = null)
{
string content = result.Message ?? result.ResultType.ToDescription();
AjaxResultType type = result.ResultType.ToAjaxResultType();
object data = dataFunc == null ? result.Data : dataFunc(result.Data);
return new AjaxResult(content, type, data);
}
/// <summary>
/// 將業務操作結果轉ajax操作結果
/// </summary>
public static AjaxResult ToAjaxResult(this OperationResult result)
{
string content = result.Message ?? result.ResultType.ToDescription();
AjaxResultType type = result.ResultType.ToAjaxResultType();
return new AjaxResult(content, type);
}
通過這些擴充套件方法,可以很簡潔的完成由OperationResult
到AjaxResult
的轉換
public async Task<AjaxResult> Creat(PostInputDto[] dtos)
{
OperationResult result = await _blogsContract.CreatePosts(dtos);
return result.ToAjaxResult();
}
部落格模組API實現
下面,我們來綜合運用上面定義的基礎建設,來實現 部落格模組 的API層。
API層的實現程式碼,將實現如下關鍵點:
- 定義各實體的 Controller 和 Action,使用
[Description]
特性來宣告各個功能點的顯示名稱 - 使用
[ModuleInfo]
特性來定義API模組的樹形結構 - 使用
[DependOnFunction]
來定義各API模組之間的依賴關係 - 在
AdminApiController
基類中,已經添加了[RoleLimit]
特性來配置所有Admin
區域的API都使用 角色限制 的訪問控制,如需特殊的訪問控制,可在 Action 上單獨配置 - 涉及實體
增加、更新、刪除
操作的業務,按需要新增[ServiceFilter(typeof(UnitOfWorkAttribute))]
特性來實現事務自動提交
!!! node
API模組對角色的許可權分配,將在後臺管理介面中進行許可權分配。
部落格 - BlogController
根據 <業務模組設計#WebAPI層> 中對部落格管理的定義,Blog實體的對外API定義如下表所示:
操作 | 訪問型別 | 操作角色 |
---|---|---|
讀取 | 角色訪問 | 部落格管理員、博主 |
申請開通 | 登入訪問 | 已登入未開通部落格的使用者 |
開通稽核 | 角色訪問 | 部落格管理員 |
更新 | 角色訪問 | 部落格管理員、博主 |
實現程式碼如下:
[ModuleInfo(Position = "Blogs", PositionName = "部落格模組")]
[Description("管理-部落格資訊")]
public class BlogController : AdminApiController
{
/// <summary>
/// 初始化一個<see cref="BlogController"/>型別的新例項
/// </summary>
public BlogController(IBlogsContract blogsContract,
IFilterService filterService)
{
BlogsContract = blogsContract;
FilterService = filterService;
}
/// <summary>
/// 獲取或設定 資料過濾服務物件
/// </summary>
protected IFilterService FilterService { get; }
/// <summary>
/// 獲取或設定 部落格模組業務契約物件
/// </summary>
protected IBlogsContract BlogsContract { get; }
/// <summary>
/// 讀取部落格列表資訊
/// </summary>
/// <param name="request">頁請求資訊</param>
/// <returns>部落格列表分頁資訊</returns>
[HttpPost]
[ModuleInfo]
[Description("讀取")]
public PageData<BlogOutputDto> Read(PageRequest request)
{
Check.NotNull(request, nameof(request));
Expression<Func<Blog, bool>> predicate = FilterService.GetExpression<Blog>(request.FilterGroup);
var page = BlogsContract.Blogs.ToPage<Blog, BlogOutputDto>(predicate, request.PageCondition);
return page.ToPageData();
}
/// <summary>
/// 申請開通部落格
/// </summary>
/// <param name="dto">部落格輸入DTO</param>
/// <returns>JSON操作結果</returns>
[HttpPost]
[ModuleInfo]
[DependOnFunction("Read")]
[ServiceFilter(typeof(UnitOfWorkAttribute))]
[Description("申請")]
public async Task<AjaxResult> Apply(BlogInputDto dto)
{
Check.NotNull(dto, nameof(dto));
OperationResult result = await BlogsContract.ApplyForBlog(dto);
return result.ToAjaxResult();
}
/// <summary>
/// 稽核部落格
/// </summary>
/// <param name="dto">部落格輸入DTO</param>
/// <returns>JSON操作結果</returns>
[HttpPost]
[ModuleInfo]
[DependOnFunction("Read")]
[ServiceFilter(typeof(UnitOfWorkAttribute))]
[Description("申請")]
public async Task<AjaxResult> Verify(BlogVerifyDto dto)
{
Check.NotNull(dto, nameof(dto));
OperationResult result = await BlogsContract.VerifyBlog(dto);
return result.ToAjaxResult();
}
/// <summary>
/// 更新部落格資訊
/// </summary>
/// <param name="dtos">部落格資訊輸入DTO</param>
/// <returns>JSON操作結果</returns>
[HttpPost]
[ModuleInfo]
[DependOnFunction("Read")]
[ServiceFilter(typeof(UnitOfWorkAttribute))]
[Description("更新")]
public async Task<AjaxResult> Update(BlogInputDto[] dtos)
{
Check.NotNull(dtos, nameof(dtos));
OperationResult result = await BlogsContract.UpdateBlogs(dtos);
return result.ToAjaxResult();
}
}
文章 - PostController
根據 <業務模組設計#WebAPI層> 中對文章管理的定義,Post實體的對外API定義如下表所示:
操作 | 訪問型別 | 操作角色 |
---|---|---|
讀取 | 角色訪問 | 部落格管理員、博主 |
新增 | 角色訪問 | 博主 |
更新 | 角色訪問 | 部落格管理員、博主 |
刪除 | 角色訪問 | 部落格管理員、博主 |
實現程式碼如下:
[ModuleInfo(Position = "Blogs", PositionName = "部落格模組")]
[Description("管理-文章資訊")]
public class PostController : AdminApiController
{
/// <summary>
/// 初始化一個<see cref="PostController"/>型別的新例項
/// </summary>
public PostController(IBlogsContract blogsContract,
IFilterService filterService)
{
BlogsContract = blogsContract;
FilterService = filterService;
}
/// <summary>
/// 獲取或設定 資料過濾服務物件
/// </summary>
protected IFilterService FilterService { get; }
/// <summary>
/// 獲取或設定 部落格模組業務契約物件
/// </summary>
protected IBlogsContract BlogsContract { get; }
/// <summary>
/// 讀取文章列表資訊
/// </summary>
/// <param name="request">頁請求資訊</param>
/// <returns>文章列表分頁資訊</returns>
[HttpPost]
[ModuleInfo]
[Description("讀取")]
public virtual PageData<PostOutputDto> Read(PageRequest request)
{
Check.NotNull(request, nameof(request));
Expression<Func<Post, bool>> predicate = FilterService.GetExpression<Post>(request.FilterGroup);
var page = BlogsContract.Posts.ToPage<Post, PostOutputDto>(predicate, request.PageCondition);
return page.ToPageData();
}
/// <summary>
/// 新增文章資訊
/// </summary>
/// <param name="dtos">文章資訊輸入DTO</param>
/// <returns>JSON操作結果</returns>
[HttpPost]
[ModuleInfo]
[DependOnFunction("Read")]
[ServiceFilter(typeof(UnitOfWorkAttribute))]
[Description("新增")]
public virtual async Task<AjaxResult> Create(PostInputDto[] dtos)
{
Check.NotNull(dtos, nameof(dtos));
OperationResult result = await BlogsContract.CreatePosts(dtos);
return result.ToAjaxResult();
}
/// <summary>
/// 更新文章資訊
/// </summary>
/// <param name="dtos">文章資訊輸入DTO</param>
/// <returns>JSON操作結果</returns>
[HttpPost]
[ModuleInfo]
[DependOnFunction("Read")]
[ServiceFilter(typeof(UnitOfWorkAttribute))]
[Description("更新")]
public virtual async Task<AjaxResult> Update(PostInputDto[] dtos)
{
Check.NotNull(dtos, nameof(dtos));
OperationResult result = await BlogsContract.UpdatePosts(dtos);
return result.ToAjaxResult();
}
/// <summary>
/// 刪除文章資訊
/// </summary>
/// <param name="ids">文章資訊編號</param>
/// <returns>JSON操作結果</returns>
[HttpPost]
[ModuleInfo]
[DependOnFunction("Read")]
[ServiceFilter(typeof(UnitOfWorkAttribute))]
[Description("刪除")]
public virtual async Task<AjaxResult> Delete(int[] ids)
{
Check.NotNull(ids, nameof(ids));
OperationResult result = await BlogsContract.DeletePosts(ids);
return result.ToAjaxResult();
}
}
至此,部落格模組的 API層程式碼 實現完畢。
API資料展示
執行Liuliu.Blogs
專案的後端工程Liuliu.Blogs.Web
,框架初始化時將通過 反射讀取API層程式碼結構,進行部落格模組的 API模組Module
- API功能點Function
的資料初始化,並分配好 依賴關係,功能點的 訪問控制 等約束。
Swagger檢視資料
在SwaggerUI中,我們可以看到生成的 API模組
部落格 - Blog
文章 - Post
後臺管理檢視資料
執行前端的 Angular 工程,我們可以在後臺管理的 許可權安全/模組管理 中,可看到 部落格模組
的模組資料以及模組分配的功能點資訊
{.img-fluid tag=2}
資料庫檢視資料
開啟資料庫管理工具,可以看到 Module 和 Function 兩個表的相關資料
資料庫中的 API模組Module
資料庫中的 API功能點Function
部落格模組授權
相關角色和使用者
部落格模組相關角色
根據 <業務模組設計#WebAPI層> 中對許可權控制的定義,我們需要建立兩個相關角色
- 博主:可申請部落格,更新自己的部落格,對自己的文章進行新增、更新、刪除操作
- 部落格管理員:可審批、更新、刪除所有部落格,對所有文章進行更新、刪除操作
新增的兩個角色如下:
名稱 | 備註 | 管理角色 | 預設 | 鎖定 |
---|---|---|---|---|
部落格管理員 | 部落格管理員角色 | 是 | 否 | 否 |
博主 | 部落格主人角色 | 否 | 否 | 否 |
註冊兩個測試使用者,並給使用者分配角色
新增測試使用者如下:
使用者名稱 | 使用者暱稱 | 分配角色 |
---|---|---|
[email protected] | 部落格管理員測試 | 部落格管理員 |
[email protected] | 博主測試01 | 博主 |
[email protected] | 博主測試02 | 博主 |
功能許可權
給角色分配API模組
API模組Module
對應的是後端的API模組,將Module分配給角色Role
,相應的Role即擁有Module的所有功能點Function
給
部落格管理員
角色分配功能許可權
給
博主
角色分配功能許可權
功能許可權預覽
分配好之後,擁有特定角色的使用者,便擁有模組所帶來的功能點許可權
部落格管理員使用者功能許可權
- 博主使用者功能許可權
測試博主01
測試博主02
資料許可權
OSharp框架內預設提供 角色 - 實體 配對的資料許可權指派。
部落格管理員
對於部落格管理員
角色,部落格管理員能管理 部落格Blog
和 文章Post
的所有資料,沒有資料許可權的約束要求。
博主
對於博主
角色,博主只能檢視並管理 自己的 部落格與文章,有資料許可權的約束要求。對博主的資料許可權約束如下:
對部落格的讀取、更新操作限制
UserId = @當前使用者
對文章的讀取、更新、刪除操作限制
UserId = @當前使用者
如此,對部落格模組的資料許可權約束分配完畢。
在下一節中,我們將完善前端專案,新增部落格模組的前端實現,你將看到一個完整的部落格模組實現。