1. 程式人生 > >[開源]OSharpNS 步步為營系列 - 4. 新增業務對外API

[開源]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]
    特性, 定義 Api模組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);
}

通過這些擴充套件方法,可以很簡潔的完成由OperationResultAjaxResult的轉換

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 = @當前使用者

如此,對部落格模組的資料許可權約束分配完畢。

在下一節中,我們將完善前端專案,新增部落格模組的前端實現,你將看到一個完整的部落格模組實現。