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
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();
///
/// 显式实现接口,此接口仅仅只调用泛型版本的方法
///
///
ActionResultpublic 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);