logo资料库

C#接口框架技术文档V1.2.pdf

第1页 / 共88页
第2页 / 共88页
第3页 / 共88页
第4页 / 共88页
第5页 / 共88页
第6页 / 共88页
第7页 / 共88页
第8页 / 共88页
资料共88页,剩余部分请下载后查看
FRXS接口框架技术文档 1.设计思想 芙蓉兴盛API接口框架(V20)采取插件式设计(实现扩展的项目不会对核心项目有任何影响),对http请求信息进行处理; 接口扩展只需继承框架核心基类(ActionBase)即可,新增,修改,删除,迭代接口对其他接口无任何影响,框架默认实现了 日志记录器,缓存器,接口访问记录器(方便管理统计),权限校验器,加解密器。框架采取插件开发模式,方便功能点的扩 展。 2.需要了解的一些概念以及组件使用 接口框架采取了插件式架构开发模式,因此代码里会引入一些设计模式,比如:依赖倒置,控制反转,依赖注入,IOC容器, AOP切面编程等。如果在此之前没有熟悉过,建议可以先了解下,便于理解代码架构设计。另外,框架引用了一些第三方组件, 如果之前没有熟悉,可以通过下面给出的网址来了解下其基本用法。 2.1 依赖注入:AutoFac,官网:http://docs.autofac.org/en/latest/ 2.2 序列化与反序列:Newtonsoft.Json,官网:http://www.newtonsoft.com/json 2.3 ORM(扩展项目会介绍到):EntityFramework 6.1.3 ,官网:https://msdn.microsoft.com/zh-cn/data/ef 3.框架主要流程以及核心接口 3.1 系统启动 系统启动的时候,会自动调用ApiApplication.Initialize方法,进行系统注册,内部实现的流程为:首先会注册此方 法传入的配置文件,如果未传入配置文件,那么系统会自动搜索接口服务根目录下面的~/ApiConfig.cs文件;如果存在此文 件系统将会进行配置注册。注册完配置文件后,进行系统所有服务接口注册,具体调用: ServicesContainer.Current.Init(),注册完所有服务后,进行注册所有实现IRouteProvider接口,此接口为路由配置接 口,注册路由后将自动启动实现IStartUp接口的所有实现类 3.2 接口搜索器(IActionSelector,DefaultActionSelector) 接口搜索器作为接口框架核心接口,其作用就是查找所有合法的Action接口,那么什么样的Action类才是合法的接口呢? 系统框架里定义,只有满足下列2个条件的类,才是合法的Action接口 1. 继承ActionBase抽象类,且抽象基类第一个参数RequestDto必须继承自RequestDotBase对象 2. Action类可访问类型必须为公开public 知道了合法的Action定义后,我再来开下 IActionSelector 接口,它定义了3个查找具体实现接口的方法,具体定义请看下 面接口定义: /// /// 用于搜索所有合法的 Action 接口信息 /// public interface IActionSelector { /// /// 获取全部实现的接口(实现里需要实现将有移除特性 ObsoleteAttribute 的 Action 过滤掉) /// /// 是否需要跳过系统接口 IEnumerable GetActionDescriptors(bool skipSystemActions = false); /// /// 根据接口名称获取接口信息 /// /// 接口名称,具体实现里请实现接口名称大小写不敏感 /// IEnumerable GetActionDescriptors(string actionName); /// /// 根据接口名称获取接口描述 /// /// 接口名称,具体实现里请实现接口名称大小写不敏感 /// 接口版本,如果不指定版本号,请实现类里实现返回版本号最大的接口描述对象 /// 返回接口描述,如果不存在则返回 null ActionDescriptor GetActionDescriptor(string actionName, string version);
} 系统框架里同时定义了一个默认的接口查找器实现(DefaultActionSelector),具体实现逻辑为,采取惰性扫描加载bin目 录下所有程序集,检测程序集里定义的类是否继承自 ActionBase 抽象类,如果是,则根据接口类型构造出接口描述对象 ActionDescriptor 添加到静态缓存字典;这样在第二次访问时候就无需再次扫描,提高程序加载速度。具体实现为: /// /// 系统默认的 Action 查找器 /// public class DefaultActionSelector : IActionSelector { /// /// 缓存所有的 action 接口,便于快速检索方法,提高接口检索速度;忽略大小写 /// private static readonly IList CachedActionDescriptors = new List(); /// /// 用于保存对应的接口名称和版本,方便快速的判断接口是否存在 /// private static readonly IList CachedActionNames = new List(); private static bool _initializationed = false; private static readonly object Locker = new object(); /// /// /// private readonly ITypeFinder _typeFinder; /// /// 系统默认的 Action 查找器 /// /// 类型查找接口 public DefaultActionSelector(ITypeFinder typeFinder) { typeFinder.CheckNullThrowArgumentNullException("typeFinder"); this._typeFinder = typeFinder; } /// /// 获取所有的合法接口 /// /// 返回所有合法的接口 ActionDescriptor 集合 /// 是否跳过系统框架接口 public virtual IEnumerable GetActionDescriptors(bool skipSystemActions = false) { //还未初始化 if (_initializationed) { return CachedActionDescriptors; } lock (Locker) { if (_initializationed) { return CachedActionDescriptors; } //已经初始化了标志 _initializationed = true; //筛选接口 this._typeFinder.FindClassesOfType(typeof(IAction)) //必须要继承自 ActionBase 抽象基类,将过期的接口直接忽略掉 .Where(type => type.IsAssignableToActionBase()).OrderBy(o => o.Name).ToList().ForEach(type => { //创建一个接口描述对象 var actionDescriptor = new ReflectedActionDescriptor(type).GetActionDescriptor(); //已经注销 if (actionDescriptor.IsObsolete) { return; } //接口名称 string actionName = actionDescriptor.ActionName; StringComparison.OrdinalIgnoreCase) && o.Version == actionDescriptor.Version); //查询缓存里是否存在了 var existsActionDescriptor = CachedActionDescriptors.FirstOrDefault(o => o.ActionName.Equals(actionName, //不存在就添加到缓存 if (existsActionDescriptor.IsNull()) { //添加到全局接口描述缓存 CachedActionDescriptors.Add(actionDescriptor); //添加到接口名称缓存,方便快速判断是否存在接口以及指定的版本 CachedActionNames.Add("{0}${1}".With(actionName, actionDescriptor.Version)); } else
//还原下操作 _initializationed = false; CachedActionDescriptors.Clear(); CachedActionNames.Clear(); //已经存在了键(实现类配置了相同的接口名称);直接抛出异常,便于在开发期就发现问题 throw new ApiException(Resource.CoreResource.DefaultActionSelector_FoundMoreThenOneActionName.With(actionName, type.FullName, existsActionDescriptor.ActionType.FullName, actionDescriptor.Version)); { } }); } //返回接口集合 return CachedActionDescriptors; } /// /// 根据接口名称获取接口信息 /// /// 接口名称,忽略大小写 /// 返回指定接口名称下面的所有接口版本 public IEnumerable GetActionDescriptors(string actionName) { return this.GetActionDescriptors().Where(o => o.ActionName.Equals(actionName, StringComparison.OrdinalIgnoreCase)).ToList(); } /// /// 根据接口名称获取接口描述对象 /// /// 接口名称 /// 接口版本,版本设置为空或者 null,框架将会使用同名接口版本号最大的接口 /// 如果未找到则返回 null public ActionDescriptor GetActionDescriptor(string actionName, string version) { //接口不存在直接返回 null if (actionName.IsNullOrEmpty()) { return null; //获取接口所有版本 var actionDescriptors = this.GetActionDescriptors(actionName); //为找到任何版本信息 if (actionDescriptors.IsNull() || actionDescriptors.IsEmpty()) { return null; //未指定版本号,直接获取指定接口最新的版本 if (version.IsNullOrEmpty()) { return actionDescriptors.OrderByDescending(o => o.Version).FirstOrDefault(); } } } //指定了版本号,就直接查找执行版本即可 return actionDescriptors.FirstOrDefault(o => o.Version == version); } } 知道了Action 接口是怎么被查找出来后,我们再来看一下作为Action接口基类的 ActionBase 抽象类,它定义为一个泛 型 抽 象 基 类 , 带 2 个 泛 型 参 数 , 第 一 个 参 数 是 客 户 端 上 送 业 务 参 数 对 应 的 RequestDto 接 受 对 象 , 此 对 象 必 须 继 承 RequestDtoBase。第二个参数是任意类型的参数,是接口处理后返回的真实业务数据对象,此对象对应于ActionResult.Data 属性。另外ActionBase也是所有Action接口的基类,在开发过程中,如果一个类,没有继承它,系统框架将不会认为类是一 个接口(即使实现了IAction接口),ActionBase里定义了一系列方便操作的属性,比如:Logger,CacheManager等等,来方 便日志记录和缓存管理。下面我们来看一下其定义: /// /// 核心类,接口 action 抽象类;所有外部实现的接口都需要继承此类 /// 注意:上送参数和下送数据不支持字典对象数据类型,能够用简易数据类型表达的,尽量用简易数据类型 /// /// /// 客户端上传 Data 参数 JSON 反序列化后对应的对象,此对象必须是继承于 RequestDtoBase 类的一个实体类 /// 命名约定规则为:接口类名+RequestDto。如果需要校验客户端 UserId 和 UserName 是 /// 否提交,实现类里请继承接口:IRequiredUserIdAndUserName,这样系统框架会在执行前先校验参数的 /// 准确性,数据约束规则完全兼容命名空间 System.ComponentModel.DataAnnotations 下是所有特性。可 /// 以方便的在上送参数对象属性上定义特性的方式来进行数据验证 /// /// /// 输出 ActionResult.Data 对象,就可以是任意的输出 DTO 类型,没有对此类型进行泛型约束 /// 如果此泛型类型指定为:NullResponseDto,系统自动生成 SDK 开发包的时候,将不会生成返回输出类 /// 注意:尽量不要将此类型定义成 object 类型,要不框架无法自动生成 SDK 开发包(客户端需要手工进行处理) /// 集合类型请尽量使用 List 数据类型方便系统框架自动完成 SDK 输出(注意:不支持字典,字典可以使用集合来替代) /// public abstract class ActionBase : IAction, IActionFilter, IDisposable
{ where TRequestDto : RequestDtoBase, new() /// /// 默认构造一个空的记录器,并且初始化一个空的日志记录器和一个空的缓存器 /// protected ActionBase() { this.Logger = NullLogger.Instance; this.CacheManager = NullCacheManager.Instance; } /// /// 用于记录日志,系统默认使用了空记录日志,如果需要使用其他日志记录器,比如将日志记录到数据库或者其他存储介质 /// 请在外部实现 ILogger 接口,然后注入覆盖系统默认的记录器 /// public ILogger Logger { get; set; } /// /// API 框架提供的缓存接口,具体实现请在外部实现具体的缓存实现(这里系统框架未实现任何缓存); /// 在进行具体的使用过程中,由于有可能会缓存键会引起冲突,建议缓存键使用 this.GetType().FullName 加具体缓存键来实现键的冲突或者在 外部定义好预定义的键,供具体实现类里调用 /// 由于接口层并不知道数据库的实体对象变化,因此建议在接口层使用缓存一般是在预知变化不会太频繁的情况下使用,其他情况下慎用;或者在 实体增加,修改,删除的时候,全部做缓存设置,删除操作 /// public ICacheManager CacheManager { get; set; } /// /// 当前 action 请求上下文信息(系统框架会自动赋值) /// public RequestContext RequestContext { get; set; } /// /// 当前 action 请求描述信息(系统框架会自动赋值) /// public ActionDescriptor ActionDescriptor { get; set; } /// /// 获取接口描述管理器 /// protected ActionDocResourceManager ActionDocResourceManager { return ActionDocResourceManager.Instance; get { } } /// /// 上送的参数(此参数系统在参加 action 实例的时候,会自动根据上送的参数进行绑定赋值) /// 此属性与强类型的 RequestDto 属性数据保持一致 /// object IAction.RequestDto { get; set; } /// /// 上送的参数(此参数系统在参加 action 实例的时候,会自动根据上送的参数进行绑定赋值) /// 注意:当上送的 JSON 格式不正确的时候,系统框架会自动抛出异常,无需处理;因此此属性一定有返回值 /// protected TRequestDto RequestDto { get { } } //获取接口请求参数对象 var requestDto = (this as IAction).RequestDto; //为空直接返回默认值 if (requestDto.IsNull()) { return default(TRequestDto); //转型成功,直接返回泛型模板对象 if (requestDto is TRequestDto) { return (TRequestDto)requestDto; //返回泛型模板类型默认值 return default(TRequestDto); } } /// /// 校验上送的参数是否正确(默认会校验是否为 null);失败会直接抛出异常,系统框架会自动捕捉到此异常 /// 此方法在框架执行 action.Executing()方法里会自动进行调用,如果需要改变上送参数对象数据校验,请在重写类里重写此方法即可,但是一 般情况下无需重写 /// protected virtual void ValidRequestDto() { //0.上送的参数对象不能为空 if (this.RequestDto.IsNull()) {
throw new ApiException(Resource.CoreResource.ActionBase_RequestDto_Null); } //1.如果 RequestDto 继承了 IRequestDtoValidatable 或者添加了特性校验 var requestDtoValidatorResult = new RequestDtoValidator(this.RequestDto, this.ActionDescriptor).Valid(); "{0}:{1}".With(item.MemberName, item.ErrorMessage)))); throw new ApiException(string.Join(" | ", requestDtoValidatorResult.Errors.Select(item => //校验失败,直接抛出异常 if (!requestDtoValidatorResult.IsValid) { } } /// /// 获取当前请求获取缓存键信息,方便重写实现类里直接使用 /// 只要接口名称和提交的数据包不变,生成的那么缓存键就不会变化,因此实现针对不同接口和不同请求数据包进行缓存 /// 具体计算方式为:ActionName + "." + subCacheKey + "." + Units.MD5(Data + Format).ToUpper(); /// /// 同一操作上下文,有可能需要不同的子缓存键;可以增加子缓存键,防止冲突 /// 返回本次请求缓存键 protected string GetRequestCacheKey(string subCacheKey = "") { return this.RequestContext.GetRequestCacheKey(subCacheKey); } /// /// 语言资源文件的读取;内部使用 XML 资源文件; /// 参数 1 为资源文件的键,自定义的语言资源包请使用接口的 ActionName 来作为节点名称,此委托会自动使用接口名称来构造键;KEY 只要输入 对应接口语言文件 key 节点名称即可 /// 参数 2 为在资源文件找不到的情况下,默认显示的信息; /// 返回值为读取到的键值信息; /// 资源文件的格式为:请参见:Host/Config/LanguageResource.xml /// 调用如:this.L("Exist_S","删除 {0} 出错,当前状态为:{1}" , "001", "已确定") /// protected Localizer L { get { return (resourceKey, defaultValue, args) => { //获取语言包 var languageResourceManager = LanguageResourceManager.Instance; //语言包不存在 if (languageResourceManager.IsNull()) { return defaultValue ?? string.Empty; //key 为空,直接返回默认的说明 if (resourceKey.IsNullOrEmpty()) { return defaultValue ?? string.Empty; } } //构造资源的 key 值;(忽略大小写) string key = "{0}.{1}".With(this.ActionDescriptor.ActionName, resourceKey); //获取资源 string value = languageResourceManager.GetLanguageResourceValue(key, defaultValue); //含有参数就进行格式化 return args.IsNull() || args.Length == 0 ? value : value.With(args); }; } } /// /// 框架异常错误的 ActionResult 对象 /// 对象默认的参数为: Flag = ActionResultFlag.EXCEPTION /// /// 异常消息 /// protected ActionResult ExceptionActionResult(string info) { return new ActionResult() { Flag = ActionResultFlag.EXCEPTION, Info = info }; } } /// /// 直接返回错误的 ActionResult 对象(此方法仅仅是为了实现类里方便调用返回) /// 对象默认参数为:Flag = ActionResultFlag.FAIL /// /// 输出的错误消息 /// 直接返回一个 Flag = ActionResultFlag.FAIL 的接口返回值对象 protected ActionResult ErrorActionResult(string info) { return new ActionResult() { Flag = ActionResultFlag.FAIL, Info = info };
/// /// 直接返回错误的 ActionResult 对象(此方法仅仅是为了实现类里方便调用返回) /// /// 输出的错误消息 /// 返回的一些数据(会被序列化成 JSON 或者 XML 格式输出给客户端) /// 直接返回一个 Flag = ActionResultFlag.FAIL 的接口返回值对象 protected ActionResult ErrorActionResult(string info, TResponseDto data) { return new ActionResult() { Flag = ActionResultFlag.FAIL, Info = info, Data = data }; /// /// 请求参数未提交错误 ActionResult 对象(此方法仅仅是为了方便实现类里方便调用返回) /// /// 参数名称 /// 返回一个现实错误的 ActionResult 对象 protected ActionResult ArgumentNullErrorActionResult(string argumentName) { return new ActionResult() { Flag = ActionResultFlag.FAIL, Info = Resource.CoreResource.ActionBase_ArgumentNullErrorActionResult_Info.With(argumentName) }; } /// /// 此方法仅仅用于返回一个不带任何返回值的 ActionResult 对象 /// 对象默认参数为:Flag = ActionResultFlag.SUCCESS, Info = "OK" /// /// protected ActionResult SuccessActionResult() { return new ActionResult() { Flag = ActionResultFlag.SUCCESS, Info = "OK" }; } } } } } /// /// 此方法用于返回一个成功的消息 ActionResult 对象 /// 对象默认参数为:Flag = ActionResultFlag.SUCCESS, Info = "OK" /// /// 需要返回给客户端的对象,会格式化成 XML 或者 JSON /// protected ActionResult SuccessActionResult(TResponseDto data) { return new ActionResult() { Flag = ActionResultFlag.SUCCESS, Info = "OK", Data = data }; /// /// 此方法用于返回一个成功的消息 ActionResult 对象 /// 对象默认参数为:Flag = ActionResultFlag.SUCCESS /// /// 成功消息说明 /// 需要返回给客户端的对象,会格式化成 XML 或者 JSON /// protected ActionResult SuccessActionResult(string info, TResponseDto data) { return new ActionResult() { Flag = ActionResultFlag.SUCCESS, Info = info, Data = data }; /// /// 显式实现下接口,防止子类里出现,调用的时候出现意外 /// 开始执行接口自定义的业务逻辑前,先执行下框架内部定义的一些判断 /// 此方法在内部调用过来受保护的 ValidRequestDto()方法,用于校验上送参数对象的正确性; /// void IAction.Executing() { this.ValidRequestDto(); /// /// 显式实现接口,此接口仅仅只调用泛型版本的方法 /// /// ActionResult IAction.Execute() { //执行外部定义的主方法 var actionResult = this.Execute(); //返回执行对象,转型成 object 类型 return new ActionResult() { Data = actionResult.Data, Flag = actionResult.Flag, Info = actionResult.Info }; } /// /// 泛型执行接口,系统框架执行的时候会自动调用此方法 /// 此方法必须在具体接口实现类里重写 /// ///
public abstract ActionResult Execute(); /// /// 执行:Execute() 方法前执行 /// /// /// 拦截器执行上下文,接口执行前拦截器,如果 ActionExecutingContext.Result 属性不为 null, /// 则代表拦截成功(如要需要进行拦截,请在方法体内将 ActionExecutingContext.Result 属性赋值即可),不会继续执行 Execute()方法 /// 保存默认值 null,则代表不拦截,继续后续的流程执行 /// protected virtual void OnActionExecuting(ActionExecutingContext actionExecutingContext) { } /// /// 显式实现下执行前触发的事件,方便以后扩展(系统框架里做一些事情) /// /// 拦截器执行上下文 void IActionFilter.OnActionExecuting(ActionExecutingContext actionExecutingContext) { this.OnActionExecuting(actionExecutingContext); } /// /// 执行:Execute() 方法后执行 /// /// 拦截器执行上下文 protected virtual void OnActionExecuted(ActionExecutedContext actionExecutedContext) { } /// /// 显式实现下执行后触发的事件,方便以后扩展(系统框架里做一些事情) /// /// 拦截器执行上下文 void IActionFilter.OnActionExecuted(ActionExecutedContext actionExecutedContext) { #region 先处理下需要清理的缓存键 if (!this.ActionDescriptor.UnloadCacheKeys.IsNull() && this.ActionDescriptor.UnloadCacheKeys.Length > 0) { //启动匹配缓存键的方式,删除相关缓存键 foreach (var unloadCacheKey in this.ActionDescriptor.UnloadCacheKeys) { this.CacheManager.RemoveByPattern(unloadCacheKey); this.Logger.Debug("清理缓存匹配键:{0} 成功".With(unloadCacheKey)); } catch (Exception ex) { this.Logger.Error(ex); try { } } } #endregion //执行完所有的事件之后 this.OnActionExecuted(actionExecutedContext); } /// /// 基类默认实现下 IDispose /// public void Dispose() { this.Dispose(true); //GC.SuppressFinalize(this); } /// /// 具体接口实现类可以重写此方法,系统框架在执行完接口后,会自动调用此方法 /// /// protected virtual void Dispose(bool disposing) { } } 3.3 接口创建器(IActionFactory,DefaultActionFactory ) 通过接口搜索器 IActionSelector 找出所有接口类型后,我们需要将接口类型进行激活, 负责创建接口实例的接口就 是 IActionFactory, 接口创建器定义了3个接口方法,2个Create()方法为创建出接口对象实例,ReleaseAction()方法为 释放接口资源(谁创建谁负责销毁)。具体定义如下: /// /// 接口创建器 /// public interface IActionFactory
{ } /// /// 根据指定接口名称创建一个调用接口 /// /// 当前请求上下文 /// 接口名称,实现类尽量实现大小写不敏感 /// 接口版本,实现类里需要实现,如果未指定接口版本,那么就获取接口最新版本号 /// IAction Create(RequestContext requestContext, string actionName, string version); /// /// 创建一个接口实例 /// /// 当前请求上下文 /// 接口描述对象 /// IAction Create(RequestContext requestContext, ActionDescriptor actionDescriptor); /// /// 释放 action 占用的资源,框架在执行完 IAction.Execute()方法后,执行此方法 /// /// action 实例 void ReleaseAction(IAction action); 同接口查找器一样,系统同样定义了默认实现(DefaultActionFactory),具体实现的代码如下: /// /// 默认的接口激活器实现类,内部使用了缓存提高执行效率 /// public class DefaultActionFactory : IActionFactory { /// /// /// private readonly IActionActivator _actionActivator; private readonly IActionSelector _actionSelector; /// /// 日志记录器 /// public ILogger Logger { get; set; } /// /// 方便注入新的激活器 /// /// 合法的 Action 接口查找器 /// 需要定义接口激活器 public DefaultActionFactory(IActionSelector actionSelector, IActionActivator actionActivator) { //判断 null actionActivator.CheckNullThrowArgumentNullException("actionActivator"); actionSelector.CheckNullThrowArgumentNullException("actionSelector"); this._actionActivator = actionActivator; this._actionSelector = actionSelector; this.Logger = NullLogger.Instance; } /// /// 根据接口名称,创建对应的接口实例 /// /// 当前请求上下文信息 /// 大小写不敏感 /// 接口版本 /// public virtual IAction Create(RequestContext requestContext, string actionName, string version) { //requestContext 参数不能为 null;系统框架级错误,直接抛出异常 requestContext.CheckNullThrowArgumentNullException("requestContext"); try { } //接口名称未提供 if (actionName.IsNullOrEmpty()) { requestContext); } //未找到接口(忽略大小写) if (actionDescriptor.IsNull()) { return new ErrorAction(new return new ErrorAction(new Exception(Resource.CoreResource.DefaultActionFactory_ActionNameIsNullOrString), //获取到接口描述 var actionDescriptor = this._actionSelector.GetActionDescriptor(actionName, version); Exception(Resource.CoreResource.DefaultActionFactory_ActionNameNotFound.With(actionName)), requestContext);
分享到:
收藏