[開源]OSharpNS 步步為營系列 - 4. 添加業務對外API

来源:https://www.cnblogs.com/guomingfeng/archive/2019/05/14/osharpns-steps-webapi.html
-Advertisement-
Play Games

一個模塊的 API層(Web層),主要負責如下幾個方面的工作:接收 前端層 提交的數據查詢請求,使用 服務層 提供的 IQueryable 查詢數據源,查詢出需要的數據返回前端。接收 前端層 提交的業務處理請求,調用 服務層 的服務,處理業務需求,並將操作結果返回前端。使用MVC的 Area-Co... ...


什麼是OSharp

OSharpNS全稱OSharp Framework with .NetStandard2.0,是一個基於.NetStandard2.0開發的一個.NetCore快速開發框架。這個框架使用最新穩定版的.NetCore SDK(當前是.NET Core 2.2),對 AspNetCore 的配置、依賴註入、日誌、緩存、實體框架、Mvc(WebApi)、身份認證、許可權授權等模塊進行更高一級的自動化封裝,並規範了一套業務實現的代碼結構與操作流程,使 .Net Core 框架更易於應用到實際項目開發中。

概述

一個模塊的 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 工程,我們可以在後臺管理的 許可權安全/模塊管理 中,可看到 博客模塊 的模塊數據以及模塊分配的功能點信息

資料庫查看數據

打開資料庫管理工具,可以看到 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 = @當前用戶

如此,對博客模塊的數據許可權約束分配完畢。

在下一節中,我們將完善前端項目,添加博客模塊的前端實現,你將看到一個完整的博客模塊實現。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 面向對象設計原則: 單一職責原則 SRP : 一個類或者行為只做一件事 。 降低代碼冗餘,提高可重用性,可維護性,可擴展性,可讀性 使用組合形式 單一職責原則 SRP : 一個類或者行為只做一件事 。 降低代碼冗餘,提高可重用性,可維護性,可擴展性,可讀性 使用組合形式 里氏替換原則 LSP : 所 ...
  • static:表示靜態的 static:可以用來修飾屬性、方法、代碼塊(或初始化塊)、內部類。 一、static修飾屬性(類變數): 1.由類創建的所有的對象,都共用這一個屬性。 2.當其中一個對象對此屬性進行修改,會導致其他對象對此屬性的一個調用。 VS 實例變數 (非 static 修飾的屬性, ...
  • 1. 內容大綱 1. 函數名的運用 2. 新特性:格式化輸出 3. 迭代器: 可迭代對象 獲取對象的方法 dir() 判斷一個對象是否是可迭代對象 小結 迭代器 迭代器的定義 判斷一個對象是否是迭代器 迭代器的取值 可迭代對象如何轉化成迭代器 while迴圈模擬for迴圈機制 小結 可迭代對象與迭代 ...
  • 最近由於項目需要,要用QT操作Word文檔。具體的工作需求:在指定的Word文檔(*.doc文件/*.docx文件)中查找關鍵字,找到後做高亮操作或者直接刪除操作,然後另存為到別的目錄(表示這個文件被操作過了)。 這個功能很簡單,確實挺簡單,但由於是第一次用QT操作Word文檔,所以仍需要經過一番查 ...
  • cv2.cvtColor(src, dst, code, dstCn)參數: src:輸入圖像 dst:輸出圖像,與輸入圖像具有相同大小和深度 code:色彩空間轉換代碼,例如cv2.COLOR_BGR2GRAY等 dstCn:目標圖像中的通道數;預設參數為0,從src和code自動導出通道介紹: ...
  • Python面向對象之文件操作 , 內容 文件的概念,文件的基本操作,文件/目錄的常用管理操作,文本文件的編碼格式。其中 文件的概念 包括 文件的概念和作用,文件的存儲方式;文件的基本操作 包括 文件操作步驟,操作文件的方法/函數,read方法,open函數,readline按行讀取文件內容;文件/... ...
  • 前面介紹了線程的基本用法,按理說足夠一般的場合使用了,只是每次開闢新線程,都得單獨定義專門的線程類,著實開銷不小。註意到新線程內部真正需要開發者重寫的僅有run方法,其實就是一段代碼塊,分線程啟動之後也單單執行該代碼段而已。因而完全可以把這段代碼抽出來,把它定義為類似方法的一串任務代碼,這樣能夠像調 ...
  • 終於A了這道題啊(坑啊) 教練說:這道題不能用map吧,複雜度不一個O(nlogn)嗎 於是我就一直想不出來,然後看題解代碼,一看就是map... 所以我就在想,那複雜度是不是也不是O(nlogn)呢 教練看了半天,說:好像確實不是誒 原來阻擋我的最大障礙是教練啊!!!(當時只給題面,也不知道時限) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...