素材巴巴 > 程序开发 >

[开源]OSharpNS 步步为营系列 - 4. 添加业务对外API

程序开发 2023-09-03 14:27:43

什么是OSharp

OSharpNS全称OSharp Framework with .NetStandard2.0,是一个基于.NetStandard2.0开发的一个.NetCore快速开发框架。这个框架使用最新稳定版的.NetCore SDK(当前是.NET Core 2.2),对 AspNetCore 的配置、依赖注入、日志、缓存、实体框架、Mvc(WebApi)、身份认证、权限授权等模块进行更高一级的自动化封装,并规范了一套业务实现的代码结构与操作流程,使 .Net Core 框架更易于应用到实际项目开发中。

概述

一个模块的 API层(Web层),主要负责如下几个方面的工作:

整个过程如下图所示
87201-20190513201121853-366499269.png

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]")] 的路由特性

/// 
 /// WebApi控制器基类
 /// 
 [AuditOperation]
 [ApiController]
 [Route("api/[controller]/[action]")]
 public abstract class ApiController : Controller
 {/// /// 获取或设置 日志对象/// protected ILogger Logger => HttpContext.RequestServices.GetLogger(GetType());
 }

AreaApiController

与 无区域控制器基类ApiController相对应,对于区域控制器,也定义了一个基类 AreaApiController

/// 
 /// WebApi的区域控制器基类
 /// 
 [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。

/// 
 /// 描述把当前功能(Controller或者Action)封装为一个模块(Module)节点,可以设置模块依赖的其他功能,模块的位置信息等
 /// 此特性用于系统初始化时自动提取模块树信息Module
 /// 
 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
 public class ModuleInfoAttribute : Attribute
 {/// /// 获取或设置 模块名称,为空则取功能名称/// public string Name { get; set; }/// /// 获取或设置 模块代码,为空则取功能Action名/// public string Code { get; set; }/// /// 获取或设置 层次序号/// public double Order { get; set; }/// /// 获取或设置 模块位置,父级模块,模块在树节点的位置,默认取所在类的位置,需要在命名空间与当前类之间加模块,才设置此值/// public string Position { get; set; }/// /// 获取或设置 父级位置模块名称,需要在命名空间与当前类之间加模块,才设置此值/// public string PositionName { get; set; }
 }

[ModuleInfo]特性主要有两种用法:

[ModuleInfo(Position = "Blogs", PositionName = "博客模块")]
 [Description("管理-博客信息")]
 public class BlogController : AdminApiController
 { }
/// 
 /// 读取博客
 /// 
 /// 博客页列表
 [HttpPost]
 [ModuleInfo]
 [Description("读取")]
 public PageData Read(PageRequest request)
 { }

DependOnFunctionAttribute

由于业务的关联性和UI的合理布局,API功能点并 不是单独存在 的,要完成一个完整的操作,各个API功能点可能会 存在依赖性。例如:

为了在代码中描述这些依赖关系,OSharp中定义了DependOnFunctionAttribute特性,在Action上标注当前API对其他API(可跨Controller,跨Area)的依赖关系。

/// 
 /// 模块依赖的功能信息,用于提取模块信息Module时确定模块依赖的功能(模块依赖当前功能和此特性设置的其他功能)
 /// 
 [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
 public class DependOnFunctionAttribute : Attribute
 {/// /// 初始化一个类型的新实例/// public DependOnFunctionAttribute(string action){Action = action;}/// /// 获取或设置 区域名称,为null(不设置)则使用当前功能所在区域,如要表示无区域的功能,需设置为空字符串""/// public string Area { get; set; }/// /// 获取或设置 控制器名称,为null(不设置)则使用当前功能所在控制器/// public string Controller { get; set; }/// /// 获取 功能名称Action,不能为空/// public string Action { get; }
 }

如下示例,表明管理列表中的新增文章业务对文章读取有依赖关系

/// 
 /// 新增文章
 /// 
 /// 新增文章信息
 /// JSON操作结果
 [HttpPost]
 [ModuleInfo]
 [DependOnFunction("Read")]
 [ServiceFilter(typeof(UnitOfWorkAttribute))]
 [Description("新增")]
 public async Task Create(PostInputDto[] dtos)
 { }

API访问控制

API的访问控制,分为三种:

API访问控制的控制顺序按照 就近原则,即离要执行的功能最近的那个限制生效。以Controller上的标注与Action上的标注为例:

AdminApiController基类中,已经设置了[RoleLimit],表示Admin区域中的所有Controller和Action的默认访问控制方式就是 角色访问。

[Area("Admin")]
 [RoleLimit]
 public abstract class AdminApiController : AreaApiController
 { }

如想额外控制,则需要在实现Action的时候进行单独配置

[HttpPost]
 [ModuleInfo]
 [Logined]
 [Description("读取")]
 public PageData Read(PageRequest request)
 { }

自动事务提交

在传统框架中,事务的提交是在业务层实现完业务操作之后即手动提交的,这种方式能更精准的控制事务的结束位置,但也有不能适用的情况,例如当一个业务涉及多个服务的时候,每个服务各自提交了事务,便无法保证所有操作在一个完整的事务上进行了。

为此,OSharp框架提出了一种新的事务提交方式:在Action中通过Mvc的Filter来自动提交事务。

自动提交事务是通过如下的UnitOfWorkAttribute实现的:

/// 
 /// 自动事务提交过滤器,在方法中执行进行事务提交
 /// 
 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
 [Dependency(ServiceLifetime.Scoped, AddSelf = true)]
 public class UnitOfWorkAttribute : ActionFilterAttribute
 {private readonly IUnitOfWorkManager _unitOfWorkManager;/// /// 初始化一个类型的新实例/// public UnitOfWorkAttribute(IServiceProvider serviceProvider){_unitOfWorkManager = serviceProvider.GetService();}/// /// 重写方法,实现事务自动提交功能/// /// public override void OnResultExecuted(ResultExecutedContext context){ScopedDictionary dict = context.HttpContext.RequestServices.GetService();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))],即可实现事务自动提交。

/// 
 /// 新增文章
 /// 
 /// 新增文章信息
 /// JSON操作结果
 [HttpPost]
 [ModuleInfo]
 [ServiceFilter(typeof(UnitOfWorkAttribute))]
 [Description("新增")]
 public async Task Create(PostInputDto[] dtos)
 { }

AjaxReuslt

对于 前后端分离 的项目,前端向后端的请求都是通过 application/json 的方式来交互的,这就需要在后端对操作结果进行封装。OSharp提供了AjaxResult类来承载操作结果数据

/// 
 /// 表示Ajax操作结果 
 /// 
 public class AjaxResult
 {/// /// 初始化一个类型的新实例/// public AjaxResult(): this(null){ }/// /// 初始化一个类型的新实例/// public AjaxResult(string content, AjaxResultType type = AjaxResultType.Success, object data = null): this(content, data, type){ }/// /// 初始化一个类型的新实例/// public AjaxResult(string content, object data, AjaxResultType type = AjaxResultType.Success){Type = type;Content = content;Data = data;}/// /// 获取或设置 Ajax操作结果类型/// public AjaxResultType Type { get; set; }/// /// 获取或设置 消息内容/// public string Content { get; set; }/// /// 获取或设置 返回数据/// public object Data { get; set; }/// /// 是否成功/// public bool Successed(){return Type == AjaxResultType.Success;}/// /// 是否错误/// public bool Error(){return Type == AjaxResultType.Error;}/// /// 成功的AjaxResult/// public static AjaxResult Success(object data = null){return new AjaxResult("操作执行成功", AjaxResultType.Success, data);}
 }

其中AjaxResultType的可选项为:

/// 
 /// 表示 ajax 操作结果类型的枚举
 /// 
 public enum AjaxResultType
 {/// /// 消息结果类型/// Info = 203,/// /// 成功结果类型/// Success = 200,/// /// 异常结果类型/// Error = 500,/// /// 用户未登录/// UnAuth = 401,/// /// 已登录,但权限不足/// Forbidden = 403,/// /// 资源未找到/// NoFound = 404,/// /// 资源被锁定/// Locked = 423
 }

业务服务层的操作结果OperationResult,可以很轻松的转换为AjaxResult

/// 
 /// 将业务操作结果转ajax操作结果
 /// 
 public static AjaxResult ToAjaxResult(this OperationResult result, Func 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);
 }/// 
 /// 将业务操作结果转ajax操作结果
 /// 
 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 Creat(PostInputDto[] dtos)
 {OperationResult result = await _blogsContract.CreatePosts(dtos);return result.ToAjaxResult();
 }

博客模块API实现

下面,我们来综合运用上面定义的基础建设,来实现 博客模块 的API层。

API层的实现代码,将实现如下关键点:

!!! node
API模块对角色的权限分配,将在后台管理界面中进行权限分配。

博客 - BlogController

根据 <业务模块设计#WebAPI层> 中对博客管理的定义,Blog实体的对外API定义如下表所示:

操作访问类型操作角色读取角色访问博客管理员、博主申请开通登录访问已登录未开通博客的用户开通审核角色访问博客管理员更新角色访问博客管理员、博主

实现代码如下:

[ModuleInfo(Position = "Blogs", PositionName = "博客模块")]
 [Description("管理-博客信息")]
 public class BlogController : AdminApiController
 {/// /// 初始化一个类型的新实例/// public BlogController(IBlogsContract blogsContract,IFilterService filterService){BlogsContract = blogsContract;FilterService = filterService;}/// /// 获取或设置 数据过滤服务对象/// protected IFilterService FilterService { get; }/// /// 获取或设置 博客模块业务契约对象/// protected IBlogsContract BlogsContract { get; }/// /// 读取博客列表信息/// /// 页请求信息/// 博客列表分页信息[HttpPost][ModuleInfo][Description("读取")]public PageData Read(PageRequest request){Check.NotNull(request, nameof(request));Expression> predicate = FilterService.GetExpression(request.FilterGroup);var page = BlogsContract.Blogs.ToPage(predicate, request.PageCondition);return page.ToPageData();}/// /// 申请开通博客/// /// 博客输入DTO/// JSON操作结果[HttpPost][ModuleInfo][DependOnFunction("Read")][ServiceFilter(typeof(UnitOfWorkAttribute))][Description("申请")]public async Task Apply(BlogInputDto dto){Check.NotNull(dto, nameof(dto));OperationResult result = await BlogsContract.ApplyForBlog(dto);return result.ToAjaxResult();}/// /// 审核博客/// /// 博客输入DTO/// JSON操作结果[HttpPost][ModuleInfo][DependOnFunction("Read")][ServiceFilter(typeof(UnitOfWorkAttribute))][Description("申请")]public async Task Verify(BlogVerifyDto dto){Check.NotNull(dto, nameof(dto));OperationResult result = await BlogsContract.VerifyBlog(dto);return result.ToAjaxResult();}/// /// 更新博客信息/// /// 博客信息输入DTO/// JSON操作结果[HttpPost][ModuleInfo][DependOnFunction("Read")][ServiceFilter(typeof(UnitOfWorkAttribute))][Description("更新")]public async Task 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
 {/// /// 初始化一个类型的新实例/// public PostController(IBlogsContract blogsContract,IFilterService filterService){BlogsContract = blogsContract;FilterService = filterService;}/// /// 获取或设置 数据过滤服务对象/// protected IFilterService FilterService { get; }/// /// 获取或设置 博客模块业务契约对象/// protected IBlogsContract BlogsContract { get; }/// /// 读取文章列表信息/// /// 页请求信息/// 文章列表分页信息[HttpPost][ModuleInfo][Description("读取")]public virtual PageData Read(PageRequest request){Check.NotNull(request, nameof(request));Expression> predicate = FilterService.GetExpression(request.FilterGroup);var page = BlogsContract.Posts.ToPage(predicate, request.PageCondition);return page.ToPageData();}/// /// 新增文章信息/// /// 文章信息输入DTO/// JSON操作结果[HttpPost][ModuleInfo][DependOnFunction("Read")][ServiceFilter(typeof(UnitOfWorkAttribute))][Description("新增")]public virtual async Task Create(PostInputDto[] dtos){Check.NotNull(dtos, nameof(dtos));OperationResult result = await BlogsContract.CreatePosts(dtos);return result.ToAjaxResult();}/// /// 更新文章信息/// /// 文章信息输入DTO/// JSON操作结果[HttpPost][ModuleInfo][DependOnFunction("Read")][ServiceFilter(typeof(UnitOfWorkAttribute))][Description("更新")]public virtual async Task Update(PostInputDto[] dtos){Check.NotNull(dtos, nameof(dtos));OperationResult result = await BlogsContract.UpdatePosts(dtos);return result.ToAjaxResult();}/// /// 删除文章信息/// /// 文章信息编号/// JSON操作结果[HttpPost][ModuleInfo][DependOnFunction("Read")][ServiceFilter(typeof(UnitOfWorkAttribute))][Description("删除")]public virtual async Task 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模块

后台管理查看数据

运行前端的 Angular 工程,我们可以在后台管理的 权限安全/模块管理 中,可看到 博客模块 的模块数据以及模块分配的功能点信息
87201-20190514132016311-83142455.png

数据库查看数据

打开数据库管理工具,可以看到 Module 和 Function 两个表的相关数据

博客模块授权

相关角色和用户

博客模块相关角色

根据 <业务模块设计#WebAPI层> 中对权限控制的定义,我们需要创建两个相关角色

新增的两个角色如下:

名称备注管理角色默认锁定博客管理员博客管理员角色是否否博主博客主人角色否否否

注册两个测试用户,并给用户分配角色

新增测试用户如下:

用户名用户昵称分配角色123202901@qq.com博客管理员测试博客管理员31529019@qq.com博主测试01博主2048949401@qq.com博主测试02博主

功能权限

给角色分配API模块

API模块Module对应的是后端的API模块,将Module分配给角色Role,相应的Role即拥有Module的所有功能点Function
功能权限授权示意图

功能权限预览

分配好之后,拥有特定角色的用户,便拥有模块所带来的功能点权限

数据权限

OSharp框架内默认提供 角色 - 实体 配对的数据权限指派。

博客管理员

对于博客管理员角色,博客管理员能管理 博客Blog 和 文章Post 的所有数据,没有数据权限的约束要求。

博主

对于博主角色,博主只能查看并管理 自己的 博客与文章,有数据权限的约束要求。对博主的数据权限约束如下:

如此,对博客模块的数据权限约束分配完毕。

在下一节中,我们将完善前端项目,添加博客模块的前端实现,你将看到一个完整的博客模块实现。

转载于:https://www.cnblogs.com/guomingfeng/p/osharpns-steps-webapi.html


标签:

素材巴巴 Copyright © 2013-2021 http://www.sucaibaba.com/. Some Rights Reserved. 备案号:备案中。