net下新开发框架never
先上github地址:https://github.com/shelldudu/never ? 引用其图片说明该构架所涉及到的工具 ? ? ? ? ? ? 其中使用包含了一些开发设计模式,比如message的订阅与发布,熔断机制等。 ? 【一】系统的整体设计以ApplicationStartup开始,加入启动服务,期间可根据注册不同组件。直接上代码 netcore的部分代码 1 /// <summary> 2 /// 该方法被ConfigureServices里面的base.ConfigureServicese调用,由于ConfigureServices方法会使用netcore系统的不同组件,所以在其后面启动,是将这些组件方案所注册的ioc规则加入到自己的ioc规则里面去 3 /// 同时替换了系统IServiceCollection自己生成的IServiceProvider对象 4 /// </summary> 5 /// <param name="sender"></param> 6 /// <param name="e"></param> 7 private void Startup_OnStarting(object sender,Never.StartupEventArgs e) 8 { 9 //ddd的command里面使用了恢复(即一些命令出错后被保存后过段时间再执行),当前使用sqlite本地数据库方式 10 var commandfile = new FileInfo(AppContext.BaseDirectory + "App_Datacommand_demo.db"); 11 //ddd的event跟上面的一样 12 var eventfile = new FileInfo(AppContext.BaseDirectory + "App_Dataevent_demo.db"); 13 //使用nlog组件 14 var logfile = new FileInfo(AppContext.BaseDirectory + "App_Confignlog.config"); 15 //配置文件的读取 16 var configReader = new AppConfigReader(this.Configuration); 17 18 /*json序列化配置*/ 19 DefaultSetting.DefaultDeserializeSetting = new JsonDeserializeSetting() { DateTimeFormat = DateTimeFormat.ChineseStyle }; 20 DefaultSetting.DefaultSerializeSetting = new JsonSerializeSetting() { DateTimeFormat = DateTimeFormat.ChineseStyle }; 21 22 //注册程序集过滤,因为整个启动过程会分析程序集里面的Type对象,很多dll我们不用分析,只焦点到我们现在注入的2个规则就行,"Never" + "B2C",正则只要匹配到该字符就加加载到待分析的dll集合中 23 e.Startup.RegisterAssemblyFilter("B2C".CreateAssemblyFilter()).RegisterAssemblyFilter("Never".CreateAssemblyFilter()); 24 25 //ioc分开2种启动方法:第一与最后,主要原因如下:(1)服务启动有先后顺序,不同的系统组件所注册的顺序不同的,但有些组件要求在所有环境下都只有第一或最后启动(2)由于使用环境自动注册这种设计下,一些组件要手动注册会带自己的规则就会被自动注册覆盖 26 e.Startup.UseEasyIoC( 27 (x,y,z) => 28 { 29 //先启动该服务注册组件, 30 },31 (x,z) => 32 { 33 //再按自己的个性化注册组件,比如Controller在下面UseApiDependency后会自动注入,但是我想HomeController注入的时候使用memecahed,这种情况就要手动注入了,说明注入了ICaching接口有多个实现 34 //x.RegisterType<Controllers.HomeController,Controllers.HomeController>().WithParameter<Never.Caching.ICaching>("memcached"); 35 36 //注入query与repository实例,为什么不用自动注入?哈哈,因为在framework或netcore等各种不同的环境下大家读取配置文件是不同的,一旦写死在B2C.Message.SqlData.Query里面读取配置文件,则使用不同的host技术就出现极大问题, 37 //比如netcore没有connectionString这种配置(或者有人说可以手动引用System.Configuration,这不是嫌麻烦吗),并且sqlclient核心的dbproviderfactory有时候要手动构造 38 x.RegisterInstance(new B2C.Message.SqlData.Query.QueryDaoBuilder(Infrastructure.SqldbType.sqlserver,() => configReader["message_conn"])); 39 x.RegisterInstance(new B2C.Message.SqlData.Repository.RepositoryDaoBuilder(Infrastructure.SqldbType.sqlserver,() => configReader["message_conn"])); 40 }); 41 42 //使用环境下自动注册组件, 43 e.Startup.UseAutoInjectingAttributeUsingIoC(new IAutoInjectingEnvironmentProvider[] 44 { 45 //在message该环境下,所有单例注册组件只有匹配message的才注册,(1)有些组件是线程的,那么不会被描述和注入中,除非再加个线程provider;(2)即使是单例provider,但所运行不是message环境,所以也不会注入 46 SingletonAutoInjectingEnvironmentProvider.UsingRuleContainerAutoInjectingEnvironmentProvider("message"),47 }) 48 //使用统一配置中心读取配置文件,实用性在后面有讲到 49 .UseConfigClient(new IPEndPoint(IPAddress.Parse(configReader["config_host"]),configReader.IntInAppConfig("config_port")),out var configFileClient); 50 configFileClient.Startup(TimeSpan.FromMinutes(10),new[] { new ConfigFileClientRequest { FileName = "message_api" } },(c,t) => 51 { 52 var content = t; 53 if (c != null && c.FileName == "message_api") 54 { 55 System.IO.File.WriteAllText(System.IO.Path.Combine(this.Environment.ContentRootPath,"appsettings.app.json"),content); 56 } 57 }).Push("message_api").GetAwaiter().GetResult(); 58 59 60 e.Startup 61 .UseCounterCache() //使用countcache 62 .UseConcurrentCache() //使用安全countcache 63 .UseDataContractJson() //使用datacontract技术的序列化,实现了IJsonSerialize接口 64 .UseEasyJson(string.Empty) //使用easyjson技术的序列化,实现了IJsonSerialize接口 65 .UseNLog(logfile) //使用nlog 66 .UseAppConfig(configReader) //将IConfigReader注入 67 .UseForceCheckAggregateRootImplIHandle() //这几个Force都是为了检查ddd开发一些要求,比如是否继承某个类,某些接口 68 .UseForceCheckCommandAppDomainAttribute() //检查所有的command是否带了特定attribute 69 .UseForceCheckCommandEvenWithNoParamaterCtor() //检查所有的commandhandler所要的构造参数是否被注入中 70 .UseForceCheckCommandHandlerCtor() //检查所有的eventhandler所要的构造参数是否被注入中 71 .UseForceCheckEventAppDomainAttribute()//检查所有的event是否带了特定attribute 72 .UseForceCheckEventHandlerCtor() //检查所有的eventhandler所要的构造参数是否被注入中 73 .UseForceCheckMessageSubscriberCtor() //使用消息的订单与发布 74 .UseInjectingCommandHandlerEventHandler(Never.IoC.ComponentLifeStyle.Singleton) //注入所有的commandhandler,在commandbus执行其对象行为 75 .UseSqliteEventProviderCommandBus<DefaultCommandContext>(new SqliteFailRecoveryStorager(commandfile,eventfile)) //使用cqrs组件,指定sqlite作为恢复组件, 76 .UseApiModelStateValidation() //mvc,webapi的模型参数验证,测试的时候在netcore 2.2下会返回404,2.1正常 77 .UseApiActionCustomRoute(e.Collector as IServiceCollection) //自定义路由,相同于在controller可以使用httpget等route技术 78 .UseApiDependency(e.Collector as IServiceCollection);//注入所有的controller 79 80 //配置中心更新配置文件后,系统不一定马上能重新加载,所以我们睡眠1秒 81 e.Startup.Startup(TimeSpan.FromSeconds(1),(x) => 82 { 83 //我们在此启动看看所使用组件是否正常启动 84 using (var sc = x.ServiceLocator.BeginLifetimeScope()) 85 { 86 sc.Resolve<ICommandBus>(); 87 sc.Resolve<ILoggerBuilder>(); 88 sc.Resolve<IJsonSerializer>(); 89 var home = sc.Resolve<Controllers.MessageController>(); 90 91 var logger = sc.Resolve<ILoggerBuilder>().Build(typeof(Startup)); 92 logger.Info("startup at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); 93 } 94 }); 95 } ? ?Controller使用直接注入了 ? [Route("api/")] [ApiController] public class VCodeController : BasicController,IVCodeService { #region field and ctor private readonly IEmailCodeQuery emailCodeQuery = null; private readonly IMobileCodeQuery mobileCodeQuery = null; private readonly ICommandBus commandBus = null; private readonly ILoggerBuilder loggerBuilder = null; private readonly IJsonSerializer jsonSerializer = null; public VCodeController(ICommandBus commandBus,ILoggerBuilder loggerBuilder,IJsonSerializer jsonSerializer,IEmailCodeQuery emailCodeQuery,IMobileCodeQuery mobileCodeQuery) { this.commandBus = commandBus; this.loggerBuilder = loggerBuilder; this.jsonSerializer = jsonSerializer; this.emailCodeQuery = emailCodeQuery; this.mobileCodeQuery = mobileCodeQuery; } #endregion field and ctor /// <summary> /// 校验邮箱验证码,组成了 api/a9a900aee8c6 这一个路由 /// </summary> /// <param name="reqs"></param> /// <returns></returns> [ApiActionRemark("a9a900aee8c6","HttpPost"),HttpPost] public ApiResult<string> CheckEmailValidateCode(CheckEmailValidateCodeReqs reqs) { if (!this.TryValidateModel(reqs)) { return Anonymous.NewApiResult(ApiStatus.Fail,string.Empty,this.ModelErrorMessage); } try { var handler = this.commandBus.Send(new DestroyEmailCodeCommand(NewId.GenerateGuid()) { Email = reqs.Email,UsageType = reqs.UsageType,VCode = reqs.VCode,}); if (handler == null) { return Anonymous.NewApiResult(ApiStatus.Fail,"验证失败"); } if (handler.Status != CommandHandlerStatus.Success) { return Anonymous.NewApiResult(ApiStatus.Error,this.HandlerMerssage(handler)); } return Anonymous.NewApiResult(ApiStatus.Success,string.Empty); } catch (Exception ex) { this.loggerBuilder.Build(typeof(VCodeController)).Error("check email code error",ex); return Anonymous.NewApiResult(ApiStatus.Error,string.Empty,ex.GetMessage()); } } } ? ? ? 【二】构架的组成部分
? ....etc ? 【三】该构架优势? 通过上述代码可以知道,整个构架的初始化都在global或startup里面实现的,所有业务的开发者可以直接在业务上开发而不用注意组件如何注入。并且搭建环境也是非常简单。 对远程方法的调用,直接变成了本地调用方式,而一些参数等很多细节的功能做得也很极致, ? /// <summary> /// 修改密码 /// </summary> [HttpPost,ApiActionRemark("ForgetPwd","HttpPost")] public IActionResult ForgetPwd(RegisterViewModel model) { //这个验证规则实现的方式很灵活,可以直接查看model的实现 /* /// <summary> /// 创建用户命令验证 /// </summary> private class RequestValidator : Validator<RegisterViewModel> { public override IEnumerable<KeyValuePair<Expression<Func<RegisterViewModel,object>>,string>> RuleFor(RegisterViewModel target) { if (target.Password.IsNullOrWhiteSpace()) yield return new KeyValuePair<Expression<Func<RegisterViewModel,string>(model => model.Password,"密码为空"); if (target.UserName.IsNullOrWhiteSpace()) yield return new KeyValuePair<Expression<Func<RegisterViewModel,string>(model => model.UserName,"手机号码为空"); if (target.VCode.IsNullOrWhiteSpace()) yield return new KeyValuePair<Expression<Func<RegisterViewModel,string>(model => model.VCode,"短信验证码为空"); } } */ if (!this.TryValidateModel(model)) { return this.Json("0002",this.ModelErrorMessage); } //实际上这里是webapi方法,使用代理生成类,带熔断, var api = this.validateCodeService.CheckMobileValidateCode(new Message.Contract.Request.CheckMobileValidateCodeReqs { UsageType = UsageType.找回登录密码,Mobile = model.UserName,Platform = this.GetAppPlatform(),VCode = model.VCode,}); if (api.Status != ApiStatus.Success) { return this.Json("0002",api.Status == ApiStatus.Fail ? api.Message : "验证码不正确"); } api = this.userService.ChangePassword(new ChangePwdReqs() { Mobile = model.UserName,Password = model.Password,api.Status == ApiStatus.Fail ? api.Message : "修改密码错误"); } return this.Json("0000","修改成功"); } ? 在性能方面也不必担心,比如拿json的序列化与反序列化,在反序列化timespan下,2700x + 32g内存1000万次测试,jsonnet 使用12.6秒(GC=3.7万),easyser使用2.6秒(GC=3.7K),jil使用0.8秒(GC=1.2k) sqlient 与dapper这一类相似,但是所衍生出的eqsysql,对比ibatis后会发现极少的配置与灵活的使用(如没有了resultmap,alias),你可以将同一个sqlTab语句可使用不同的参数组合生成不同的sql语句,也不用指定查询的返回类型,如QueryForObject<T1>, QueryForObject<T2>都可以使用相同的sqlTab,其中T1与T2可以是结构体或对象 ? 文章导航:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |