C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载

本文首发于码友网–一个专注.NET/.NET Core开发的编程爱好者社区。文章目录C#/.NET基于Topshelf创建Windows服务的系列文章目录:C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载 (1)在C#/.NET应用程序开发中创建一个基于Topshelf的应用程序守护进程(服务) (2)C#/.NET基于Topshelf创建Windows服务的守护程序作为服务启动的客户端桌面程序不显示UI界面的问题分析和解决方案 (3)前言对于使用Windows操作系统的人来说,Windows Service(Windows服务)应该不会陌生。在Windows操作系统中,我们可以在"运行"窗口中运行service.msc:即可打开一个查看Windows服务的窗口,如图:Windows服务基本都是一些后台运行的服务进程,没有UI界面,每个服务处理着各自独立的任务并且有专门的启动或者停止策略。所以,Windows服务在很多情况下会被用来者处理一些定时任务或者调度。那么,对于.NET的开发者来说,可不可以自己创建Windows服务呢,如何使用C#创建Windows服务呢?本文就为大家分享一种基于Topshelf创建的Windows服务的方法。创建Topshelf服务项目首先打开Visual Studio(本文使用的是Visual Studio 2019),打开新建项目的对话框,选择.NET Framework的控制台应用程序(Console App(.NET Framework)),如图:注:只可选择控制台应用程序点击"下一步",在项目名称中输入TopshelfDemoService,.NET Framework 选择4.6.2,其中选项根据自己情况填写即可,最后点击"创建"按钮。安装Topshelf组件在TopshelfDemoService项目中,打开Nuget包管理工具,搜索Topshelf,在搜索结果中选中Topshelf,点击"安装",如图:编写Topshelf服务的示例程序代码Topshelf组件安装完成后,我们就可以开始编写服务的示例代码了。首先,创建一个名为HealthMonitorService.cs的类(其作用假设为定时监控某个系统的运行健康状况),在其中分别创建方法:Start()和Stop()以及一个定时器,让定时器定时执行检查系统健康状况的任务(这里模拟的每秒向控制台输出一条文本信息),完整的代码如下:using System;using System.Timers;namespace TopshelfDemoService{ internal class HealthMonitorService { private readonly Timer _timer; public HealthMonitorService() { _timer = new Timer(1000) { AutoReset = true }; _timer.Elapsed += (sender, eventArgs) => Console.WriteLine(“执行系统健康检查任务,所有指标均正常。执行时间:{0}”, DateTime.Now); } public void Start() { _timer.Start(); } public void Stop() { _timer.Stop(); } }}再创建一个名为MyServiceConfigure.cs的服务配置类,这个类主要用来配置Topshelf服务的各种运行参数,代码如下:using System;using Topshelf;namespace TopshelfDemoService{ internal class MyServiceConfigure { internal static void Configure() { var rc = HostFactory.Run(host => // 1 { host.Service<HealthMonitorService>(service => // 2 { service.ConstructUsing(() => new HealthMonitorService()); // 3 service.WhenStarted(s => s.Start()); // 4 service.WhenStopped(s => s.Stop()); // 5 }); host.RunAsLocalSystem(); // 6 host.EnableServiceRecovery(service => // 7 { service.RestartService(3); // 8 }); host.SetDescription(“Windows service based on topshelf”); // 9 host.SetDisplayName(“Topshelf demo service”); // 10 host.SetServiceName(“TopshelfDemoService”); // 11 host.StartAutomaticallyDelayed(); // 12 }); var exitCode = (int)Convert.ChangeType(rc, rc.GetTypeCode()); // 13 Environment.ExitCode = exitCode; } }}注:其中数字的含义请见本文末尾的解释。最后,打开Program.cs文件,开启Topshelf服务,如下:namespace TopshelfDemoService{ class Program { static void Main(string[] args) { MyServiceConfigure.Configure(); } }}好了,完成到这里,整个示例程序就写好了,按F5运行示例程序,你将看到如下类似的控制台信息:可以看到,我们创建的TopshelfDemoService服务每秒向控制台打印了一条文本信息,这和我们的预期是吻合的。这样,我们就成功创建了一个基于Topshelf的Windows服务,当然,这也只是一个简单和示例服务程序,其中没有复杂的业务逻辑和配置等等。这些都等待你去发掘。作为Windows服务安装和卸载我们刚才运行的只是一个控制台应用程序,如果将这个控制台应用程序关掉,定时任务也会被停止了。如果我们希望定时任务可以一直运行,那需要将这个控制台应用程序作为服务安装到Windows服务进程中,如何操作呢?非常简单的安装和卸载命令。首先,以管理员身份打开一个命令行工具,进入到控制台应用程序所在目录。安装安装服务运行如下命令:TopshelfDemoService.exe install打开Windows服务查看窗口(刷新),可以看到Topshelf demo service已经在服务列表中了,如图:这时,我们只需要按照Windows服务来操作这个服务即可。卸载如果需要卸载服务,则运行如下命令:TopshelfDemoService.exe uninstallTopshelf配置参数说明1.设置服务主机使用HostFactory.Run()来创建并运行一个Topshelft服务。2.设置Topshelf使用类型HealthMonitorService作为服务类。3.配置如何创建一个服务的实例,这里采用的是使用关键字new来实例化一个HealthMonitorService对象,你也可以使用IoCp容器来实例化服务对象。4.设置当服务启动时执行的操作。5.设置当服务停止时执行的操作。6.设置将服务以本地系统身份运行。7.启动恢复服务模式(当服务意外停止后自动恢复)。8.设置第一次自动恢复服务的延迟时间为3分钟。9.设置Topshelf服务在Windows服务中的描述信息。10.设置Topshelf服务在Windows服务中的显示名称。11.设置Topshelf服务在Windows服务中的服务名称。12.设置Topshelf服务随Windows启动时自动运行(延迟)。13.设置服务的退出代码。示例代码托管和下载本示例代码托管地址可以在原出处找到:示例代码下载地址 ...

April 12, 2019 · 1 min · jiezi

区块链开发者教程大汇总,Php/Python/C#/Node/Go

Mixin Network是一个免费,1秒确认的高速转账网络。我们撰写了基于多种语言的系列教程,开发者可以15分钟搭建一个比特币收款应用。教程PHPGoNode.jsC#Python创建机器人接收发送消息php 1Go 1Node.js 1C# 1python 1机器人接收发送比特币php 2Go 2Node.js 2C# 2python 2创建比特币钱包,转账php 3Go 3Node.js 3C# 3python 3买卖比特币php 4Go 4Node.js 4C# 4Mixin Network开发者资料汇总

April 12, 2019 · 1 min · jiezi

程序猿修仙之路--算法之直接插入排序

<img src=“https://timgsa.baidu.com/timg...;quality=80&size=b9999_10000&sec=1541245646279&di=70b2cfd6752b26aa4e9cb04579c8dd6a&imgtype=0&src=http%3A%2F%2Fwww.psahz.com%2Fuploads%2Fallimg%2F181006%2F094Q62X3-2.jpg" width=“100%” hegiht=“20%” align=center />==算法主要衡量标准==时间复杂度(运行时间)在算法时间复杂度维度,我们主要对比较和交换的次数做对比,其他不交换元素的算法,主要会以访问数组的次数的维度做对比。其实有很多同学对于算法的时间复杂度有点模糊,分不清什么所谓的 O(n),O(nlogn),O(logn)…等,也许下图对一些人有一些更直观的认识。空间复杂度(额外的内存使用)排序算法的额外内存开销和运行时间同等重要。 就算一个算法时间复杂度比较优秀,空间复杂度非常差,使用的额外内存非常大,菜菜认为它也算不上一个优秀的算法。结果的正确性这个指标是菜菜自己加上的,我始终认为一个优秀的算法最终得到的结果必须是正确的。就算一个算法拥有非常优秀的时间和空间复杂度,但是结果不正确,又有什么意义呢?==原理==每次在无序的列表中取一个元素插入到一个有序列表的适当位置,成为一个元素加1的新的有序列表。。插入排序根据原理又分为 直接插入排序、二分插入排序、希尔排序等,今天主要讲一下直接插入排序。直接插入排序是一种稳定的排序算法假设排序顺序从左至右,具体步骤如下:列表第一个元素和前面元素比较,如果小于前面元素(其实不存在),则交换位置。(这步其实可以没有)列表第二个元素和前面元素(第一个元素)比较,如果小于前面元素,则交换位置。列表第三个元素和前面元素(第二个元素)比较,如果小于前面元素,则交换位置。如果和前面元素交换了位置,现在在第二个位置上,则接着继续和前面元素比较(第一个元素),如果小于前面元素,接着再次交换位置,然后再次重复比较过程…….继续重复以上过程,直到最后一个元素完成比较比较移动过程中,如果元素不需要移动意味着该元素排序完毕。++网络上的插入排序大多都是新建一个有序列表用来存放最终结果,其实在无序列表上进行排序操作空间复杂度才更优++也许一张更直观的图比上千句话效果都好:==复杂度==时间复杂度比较次数对于长度为N的主键不重复的列表,插入排序 平均情况下需要n²/4次比较,最坏情况下需要n²/2次比较,最好的情况下需要n-1 次比较。交换次数对于长度为N的主键不重复的列表,插入排序平均情况下需要n²/4次交换,最坏情况下需要n²/2次交换,最好情况下需要0次交换。==性能和特点==总体来说,直接插入排序是一种比较简单的排序算法,很容易理解也很好用代码实现,当然他的特点也很明显:运行时间和数据初始状态有关插入排序的思想是把一个元素插入一个有序的列表中,假如这个元素的位置正好是有序部分的末尾呢?也就是说当前元素不用移动位置。再一次假如整个列表都是有序的会发生什么情况呢?根本就不需要移动任何元素。这也就是为什么在最好的情况下交换次数为0,比较次数为n-1的原因。假如列表的很大一部分元素是有序的,插入排序可能比大多数排序算法都要快。==适用场景==直接插入排序对于小型列表或者非随机元素列表很有效。例如:部分元素有序。大体可归纳为:每个元素距离自己的最终位置都不远。一个有序的大列表连接一个小列表。列表中只有少数元素不正确。==其他==为什么插入排序是稳定呢?插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。==实现案例==c static void Main(string[] args) { List<int> data = new List<int>() ; for (int i = 0; i < 10; i++) { data.Add(new Random(Guid.NewGuid().GetHashCode()).Next(1, 100)); } //打印原始数组值 Console.WriteLine($“原始数据: {string.Join(”,”, data)}"); int n = data.Count; //此处可以直接从第二个元素开始 for (int i = 1; i < n; i++) { //查找最小的元素的索引 for (int j = i; j>0 ; j–) { if (data[j] < data[j - 1]) { //异或法 交换两个变量,不用临时变量 data[j] = data[j] ^ data[j-1]; data[j-1] = data[j] ^ data[j - 1]; data[j ] = data[j] ^ data[j - 1]; } } } //打印排序后的数组 Console.WriteLine($“排序数据: {string.Join(”,", data)}"); Console.Read(); }运行结果:原始数据: 72,78,42,60,84,74,60,79,72,52排序数据: 42,52,60,60,72,72,74,78,79,84Go家里没环境,还得翻墙,以后再补上吧,望见谅。独乐不如众乐我表弟在追学校的一个女生,每天短信无数,可那妞从来都不回他。我对他说:骚年!女人的天性只是八卦和好奇心!就你这样还想泡妞呢!看你表哥的!我用他手机给那妞发:你是我们学校三大美女之一,但我只喜欢你。半分钟之后,那妞就回了:另外两个是谁,你为什么只喜欢我啊?<img src=“https://timgsa.baidu.com/timg...;quality=80&size=b9999_10000&sec=1541252359891&di=deecf761d736d3a2fab178e7e1a4d519&imgtype=0&src=http%3A%2F%2Fimg2.dzwww.com%3A8888%2Ftupian%2F20170927%2F201709270938c7cfcb55355b15.jpg" align=center />添加关注,查看更精美版本,收获更多精彩 ...

April 12, 2019 · 1 min · jiezi

color

命名空间System.Drawing.ColorColor是结构体,值类型.字段Empty,表示无效颜色,不是任何颜色,可以用来初始化一个color变量.Color c = Color.Empty;属性public byte A { get; }public byte R { get; }public byte G { get; }public byte B { get; }返回color的alpha,red,green,blue分量.其他属性都是系统颜色,可以为color变量赋值Color c = Color.OrangeRed;方法public static Color FromArgb(int alpha, int red, int green, int blue);Color c = Color.FromArgb(1,2,3,4);虽然参数是int类型,但是只能传入0-255,即一个bytealpha = 255表示完全不透明.

April 10, 2019 · 1 min · jiezi

安倍慌了,中国码农都能2秒买到比特币?

ExinCore 提供了一个实时买卖API。买卖比特币和其他资产只需2两秒钟。1秒付款,1秒收款。资产始终在自己手里。支持exincore api的开源钱包:Python wallet多语言教程PHP 买卖比特币Go 买卖比特币Javascript 买卖比特币C#买卖比特币ExinCore是一个基于Mixin Network 公链的服务。Mixin Network为开发者提供了丰富的开发资料,开发者可以30分钟搭建自己的数字货币支付服务。

April 10, 2019 · 1 min · jiezi

is和as运算符

is引用 is 类判断引用指向的堆中实例是否是类或类的基类创建的实例。不会抛出异常,是返回true,否返回false。as引用 as 类返回引用所指向的堆中实例的类类型或基类类型的引用,该引用指向堆中实例。不会抛出异常,失败返回null。

April 8, 2019 · 1 min · jiezi

破解生意参谋data加密数据以及transit-id

最近有朋友说,生意参谋又丧心病狂的加密了,给数据爬取的同学造成很大的压力,然后我也研究了下数据加密,最终破解了出来生意参谋加密1. 请求header transit-id加密用途:请求过滤在前端进行数据请求的时候,会在request header里面添加transit-id,来过滤非法请求,也就是说不符合transit-id规则的请求一律拒绝加密方法transit-id如下图所示,transit-id是用rsa加密的,因此每次传入的transit-id都不一样,并且生意参谋后台会对transit-id进行验证2. 返回数据data加密返回数据data是用aes加密的,实际请求数据如下图所示

April 4, 2019 · 1 min · jiezi

“宇宙最强” IDE,Visual Studio 2019 正式发布

转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。本文由葡萄城翻译并发布今天凌晨Visual Studio 2019已经正式发布,现在已经可以下载了。使用Visual Studio 2019,您和您的团队将在构建当前和未来项目时将变得更有效率,因为您可以从IDE中的新加入的创新功能中获益。正如之前我们分享的那样,Visual Studio 2019在某些领域将会比Visual Studio 2017更加先进。它可以通过简化克隆Git仓库或打开现有项目或文件夹使您开发节奏更有效率。同时改进了项目启动页,使你启动新项目更容易。当在编写代码时,你会注意到Visual Studio 2019改进了代码导航并添加了许多重构选项,包括了文档运行状况指示器和一键式代码清理以应用多个重构规则。Live Share功能也已包含在新版本中,那么Live Share是什么?它是一个团队协作工具,可以通过这个工具与您的团队成员进行实时协作开发,通过一张图可以很好说明这个功能是什么。现在Visual Studio 2019已经默认包含了此功能。Mac系统也迎来了Visual Studio 2019该版本引入了全新的C#编辑器,该编辑器基于Windows平台上的Visual Studio内核构建,提供了更平滑的编辑体验和更强大的代码自动完成和快速修复建议。更多功能,请点击此处了解。全新更好用的启动窗口允许同时启动多个实例移动App和游戏开发者也在Mac平台迎来了全新的Xamarin工具和Unity工具。新发布的IDE能够使你打开及创建不同类型的常见项目:从跨平台C ++应用程序,到使用Xamarin编写的Android和iOS的.NET移动应用程序,再到使用Azure服务的云原生应用程序。Visual Studio 2019的目标是从开发,测试,调试甚至部署支持这些项目,同时最大限度地减少您在不同应用程序,门户和网站之间切换付出的成本。如果您在下载及使用Visual Studio过程中有任何心得和分享,欢迎在下方评论框中分享。Windows下载:https://visualstudio.microsof…Mac下载:https://visualstudio.microsof…注:ComponentOne Enterprise 在即将发布的全新版本中,已经全面支持了Visual Studio 2019,请大家下载试用本文是由葡萄城技术开发团队发布,转载请注明出处:葡萄城官网了解可嵌入您系统的在线 Excel,请前往SpreadJS纯前端表格控件

April 3, 2019 · 1 min · jiezi

.NETCore 新型 ORM 功能介绍

简介FreeSql 是一个功能强大的 .NETStandard 库,用于对象关系映射程序(O/RM),支持 .NETCore 2.1+ 或 .NETFramework 4.6.1+。定义IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, @“Data Source=|DataDirectory|/test.db;Pooling=true;Max Pool Size=10”) .UseAutoSyncStructure(true) //自动同步实体结构到数据库 .Build();入门篇查询1、查询一条fsql.Select<Xxx>.Where(a => a.Id == 1).First();2、分页:第1页,每页20条fsql.Select<Xxx>.Page(1, 20).ToList();细节说明:SqlServer 2012 以前的版本,使用 row_number 分页;SqlServer 2012+ 版本,使用最新的 fetch next rows 分页;3、INfsql.Select<Xxx>.Where(a => new { 1,2,3 }.Contains(a.Id)).ToList();4、联表fsql.Select<Xxx>.LeftJoin<Yyy>((a, b) => a.YyyId == b.Id).ToList();5、Exists子表fsql.Select<Xxx>.Where(a => fsql.Select<Yyy>(b => b.Id == a.YyyId).Any()).ToList();6、GroupBy & Havingfsql.Select<Xxx>.GroupBy(a => new { a.CategoryId }).Having(a => a.Count > 2).ToList(a => new { a.Key, a.Count() });7、指定字段查询fsql.Select<Xxx>.Limit(10).ToList(a => a.Id);fsql.Select<Xxx>.Limit(10).ToList(a => new { a.Id, a.Name });fsql.Select<Xxx>.Limit(10).ToList(a => new Dto());8、执行SQL返回实体fsql.Ado.Query<Xxx>(“select * from xxx”);fsql.Ado.Query<(int, string, string)>(“select * from xxx”);fsql.Ado.Query<dynamic>(“select * from xxx”);插入1、单条fsql.Insert<Xxx>().AppendData(new Xxx()).ExecuteAffrows();2、单条,返回自增值fsql.Insert<Xxx>().AppendData(new Xxx()).ExecuteIdentity();3、单条,返回插入的行(SqlServer 的 output 特性)fsql.Insert<Xxx>().AppendData(new Xxx()).ExecuteInserted();4、批量fsql.Insert<Xxx>().AppendData(数组).ExecuteAffrows();5、批量,返回插入的行(SqlServer 的 output 特性)fsql.Insert<Xxx>().AppendData(数组).ExecuteInserted();6、指定列fsql.Insert<Xxx>().AppendData(new Xxx()).InsertColumns(a => a.Title).ExecuteAffrows();fsql.Insert<Xxx>().AppendData(new Xxx()).InsertColumns(a => new { a.Id, a.Title}).ExecuteAffrows();7、忽略列fsql.Insert<Xxx>().AppendData(new Xxx()).IgnoreColumns(a => a.Title).ExecuteAffrows();fsql.Insert<Xxx>().AppendData(new Xxx()).IgnoreColumns(a => new { a.Id, a.Title}).ExecuteAffrows();8、事务fsql.Insert<Xxx>().AppendData(new Xxx()).WithTransaction(事务对象).ExecuteAffrows();更新1、指定列fsql.Update<Xxx>(1).Set(a => a.CreateTime, DateTime.Now).ExecuteAffrows();2、累加,set clicks = clicks + 1fsql.Update<Xxx>(1).Set(a => a.Clicks + 1).ExecuteAffrows();3、保存fsql.Update<Xxx>().SetSource(单个实体).ExecuteAffrows();4、批量保存fsql.Update<Xxx>().SetSource(数组).ExecuteAffrows();5、忽略列fsql.Update<Xxx>().SetSource(数组).IgnoreColumns(a => new { a.Clicks, a.CreateTime }).ExecuteAffrows();6、更新条件fsql.Update<Xxx>().SetSource(数组).Where(a => a.Clicks > 100).ExecuteAffrows();7、事务fsql.Update<Xxx>(1).Set(a => a.Clicks + 1).WithTransaction(事务对象).ExecuteAffrows();删除1、dywhere主键值new[] { 主键值1, 主键值2 }Xxx对象new[] { Xxx对象1, Xxx对象2 }new { id = 1 }fsql.Delete<Xxx>(new[] { 1, 2 }).ExecuteAffrows();//DELETE FROM xxx WHERE (Id = 1 OR Id = 2)fsql.Delete<Xxx>(new Xxx { Id = 1, Title = “test” }).ExecuteAffrows();//DELETE FROM xxx WHERE (Id = 1)fsql.Delete<Xxx>(new[] { new Xxx { Id = 1, Title = “test” }, new Xxx { Id = 2, Title = “test” } }).ExecuteAffrows();//DELETE FROM xxx WHERE (Id = 1 OR Id = 2)fsql.Delete<Xxx>(new { id = 1 }).ExecuteAffrows();//DELETE FROM xxx WHERE (Id = 1)2、条件fsql.Delete<Xxx>().Where(a => a.Id == 1).ExecuteAffrows();//DELETE FROM xxx WHERE (Id = 1)fsql.Delete<Xxx>().Where(“id = ?id”, new { id = 1 }).ExecuteAffrows();//DELETE FROM xxx WHERE (id = ?id)var item = new Xxx { Id = 1, Title = “newtitle” };var t7 = fsql.Delete<Xxx>().Where(item).ExecuteAffrows();//DELETE FROM xxx WHERE (Id = 1)var items = new List<Xxx>();for (var a = 0; a < 10; a++) items.Add(new Xxx { Id = a + 1, Title = $“newtitle{a}”, Clicks = a * 100 });fsql.Delete<Xxx>().Where(items).ExecuteAffrows();//DELETE FROM xxx WHERE (Id IN (1,2,3,4,5,6,7,8,9,10))3、事务fsql.Delete<Xxx>().Where(a => a.Id == 1).WithTransaction(事务对象).ExecuteAffrows();初级篇表达式支持功能丰富的表达式函数解析,方便程序员在不了解数据库函数的情况下编写代码。这是 FreeSql 非常特色的功能之一,深入细化函数解析尽量做到满意,所支持的类型基本都可以使用对应的表达式函数,例如 日期、字符串、IN查询、数组(PostgreSQL的数组)、字典(PostgreSQL HStore)等等。1、查找今天创建的数据fsql.Delete<Xxx>().Where(a => a.CreateTime.Date == DateTime.Now.Date).ToList();2、SqlServer 下随机获取记录fsql.Delete<Xxx>().OrderBy(a => Guid.NewGuid()).Limit(1).ToSql();4、表达式函数全览表达式MySqlSqlServerPostgreSQLOracle功能说明a ? b : ccase when a then b else c endcase when a then b else c endcase when a then b else c endcase when a then b else c enda成立时取b值,否则取c值a ?? bifnull(a, b)isnull(a, b)coalesce(a, b)nvl(a, b)当a为null时,取b值数字 + 数字a + ba + ba + ba + b数字相加数字 + 字符串concat(a, b)cast(a as varchar) + cast(b as varchar)case(a as varchar) + ba+b字符串相加,a或b任意一个为字符串时a - ba - ba - ba - ba - b减a * ba * ba * ba * ba * b乘a / ba / ba / ba / ba / b除a % ba % ba % ba % bmod(a,b)模等等…5、数组表达式MySqlSqlServerPostgreSQLOracle功能说明a.Length–case when a is null then 0 else array_length(a,1) end-数组长度常量数组.Length–array_length(array[常量数组元素逗号分割],1)-数组长度a.Any()–case when a is null then 0 else array_length(a,1) end > 0-数组是否为空常量数组.Contains(b)b in (常量数组元素逗号分割)b in (常量数组元素逗号分割)b in (常量数组元素逗号分割)b in (常量数组元素逗号分割)IN查询a.Contains(b)–a @> array[b]-a数组是否包含b元素a.Concat(b)–a + b-数组相连a.Count()–同 Length-数组长度一个细节证明 FreeSql 匠心制作通用的 in 查询 select.Where(a => new []{ 1,2,3 }.Contains(a.xxx))假设 xxxs 是 pgsql 的数组字段类型,其实会与上面的 in 查询起冲突,FreeSql 解决了这个矛盾 select.Where(a => a.xxxs.Contains(1))6、字典 Dictionary<string, string>表达式MySqlSqlServerPostgreSQLOracle功能说明a.Count–case when a is null then 0 else array_length(akeys(a),1) end-字典长度a.Keys–akeys(a)-返回字典所有key数组a.Values–avals(a)-返回字典所有value数组a.Contains(b)–a @> b-字典是否包含ba.ContainsKey(b)–a? b-字典是否包含keya.Concat(b)–a + b-字典相连a.Count()–同 Count-字典长度7、JSON JToken/JObject/JArray表达式MySqlSqlServerPostgreSQLOracle功能说明a.Count–jsonb_array_length(coalesce(a, ‘[]))-json数组类型的长度a.Any()–jsonb_array_length(coalesce(a, ‘[])) > 0-json数组类型,是否为空a.Contains(b)–coalesce(a, ‘{}’) @> b::jsonb-json中是否包含ba.ContainsKey(b)–coalesce(a, ‘{}’) ? b-json中是否包含键ba.Concat(b)–coalesce(a, ‘{}’) + b::jsonb-连接两个jsonParse(a)–a::jsonb-转化字符串为json类型8、字符串表达式MySqlSqlServerPostgreSQLOracleSqlitestring.Empty’‘‘‘‘‘‘‘string.IsNullOrEmpty(a)(a is null or a = ‘’)(a is null or a = ‘’)(a is null or a = ‘’)(a is null or a = ‘’)(a is null or a = ‘’)a.CompareTo(b)strcmp(a, b)-case when a = b then 0 when a > b then 1 else -1 endcase when a = b then 0 when a > b then 1 else -1 endcase when a = b then 0 when a > b then 1 else -1 enda.Contains(‘b’)a like ‘%b%‘a like ‘%b%‘a ilike’%b%‘a like ‘%b%‘a like ‘%b%‘a.EndsWith(‘b’)a like ‘%b’a like ‘%b’a ilike’%b’a like ‘%b’a like ‘%b’a.IndexOf(b)locate(a, b) - 1locate(a, b) - 1strpos(a, b) - 1instr(a, b, 1, 1) - 1instr(a, b) - 1a.Lengthchar_length(a)len(a)char_length(a)length(a)length(a)a.PadLeft(b, c)lpad(a, b, c)-lpad(a, b, c)lpad(a, b, c)lpad(a, b, c)a.PadRight(b, c)rpad(a, b, c)-rpad(a, b, c)rpad(a, b, c)rpad(a, b, c)a.Replace(b, c)replace(a, b, c)replace(a, b, c)replace(a, b, c)replace(a, b, c)replace(a, b, c)a.StartsWith(‘b’)a like ‘b%‘a like ‘b%‘a ilike’b%‘a like ‘b%‘a like ‘b%‘a.Substring(b, c)substr(a, b, c + 1)substring(a, b, c + 1)substr(a, b, c + 1)substr(a, b, c + 1)substr(a, b, c + 1)a.ToLowerlower(a)lower(a)lower(a)lower(a)lower(a)a.ToUpperupper(a)upper(a)upper(a)upper(a)upper(a)a.Trimtrim(a)trim(a)trim(a)trim(a)trim(a)a.TrimEndrtrim(a)rtrim(a)rtrim(a)rtrim(a)rtrim(a)a.TrimStartltrim(a)ltrim(a)ltrim(a)ltrim(a)ltrim(a)使用字符串函数可能会出现性能瓶颈,虽然不推荐使用,但是作为功能库这也是不可缺少的功能之一。9、日期表达式MySqlSqlServerPostgreSQLOracleDateTime.Nownow()getdate()current_timestampsystimestampDateTime.UtcNowutc_timestamp()getutcdate()(current_timestamp at time zone ‘UTC’)sys_extract_utc(systimestamp)DateTime.Todaycurdateconvert(char(10),getdate(),120)current_datetrunc(systimestamp)DateTime.MaxValuecast(‘9999/12/31 23:59:59’ as datetime)‘9999/12/31 23:59:59’‘9999/12/31 23:59:59’::timestampto_timestamp(‘9999-12-31 23:59:59’,‘YYYY-MM-DD HH24:MI:SS.FF6’)DateTime.MinValuecast(‘0001/1/1 0:00:00’ as datetime)‘1753/1/1 0:00:00’‘0001/1/1 0:00:00’::timestampto_timestamp(‘0001-01-01 00:00:00’,‘YYYY-MM-DD HH24:MI:SS.FF6’)DateTime.Compare(a, b)a - ba - bextract(epoch from a::timestamp-b::timestamp)extract(day from (a-b))DateTime.DaysInMonth(a, b)dayofmonth(last_day(concat(a, ‘-’, b, ‘-1’)))datepart(day, dateadd(day, -1, dateadd(month, 1, cast(a as varchar) + ‘-’ + cast(b as varchar) + ‘-1’)))extract(day from (a ‘-’ b ‘-01’)::timestamp+‘1 month’::interval-‘1 day’::interval)cast(to_char(last_day(a ‘-’ b ‘-01’),‘DD’) as number)DateTime.Equals(a, b)a = ba = ba = ba = bDateTime.IsLeapYear(a)a%4=0 and a%100<>0 or a%400=0a%4=0 and a%100<>0 or a%400=0a%4=0 and a%100<>0 or a%400=0mod(a,4)=0 AND mod(a,100)<>0 OR mod(a,400)=0DateTime.Parse(a)cast(a as datetime)cast(a as datetime)a::timestampto_timestamp(a,‘YYYY-MM-DD HH24:MI:SS.FF6’)a.Add(b)date_add(a, interval b microsecond)dateadd(millisecond, b / 1000, a)a::timestamp+(b ’ microseconds’)::interval增加TimeSpan值a + ba.AddDays(b)date_add(a, interval b day)dateadd(day, b, a)a::timestamp+(b ’ day’)::intervala + ba.AddHours(b)date_add(a, interval b hour)dateadd(hour, b, a)a::timestamp+(b ’ hour’)::intervala + b/24a.AddMilliseconds(b)date_add(a, interval b1000 microsecond)dateadd(millisecond, b, a)a::timestamp+(b ’ milliseconds’)::intervala + b/86400000a.AddMinutes(b)date_add(a, interval b minute)dateadd(minute, b, a)a::timestamp+(b ’ minute’)::intervala + b/1440a.AddMonths(b)date_add(a, interval b month)dateadd(month, b, a)a::timestamp+(b ’ month’)::intervaladd_months(a,b)a.AddSeconds(b)date_add(a, interval b second)dateadd(second, b, a)a::timestamp+(b ’ second’)::intervala + b/86400a.AddTicks(b)date_add(a, interval b/10 microsecond)dateadd(millisecond, b / 10000, a)a::timestamp+(b ’ microseconds’)::intervala + b/86400000000a.AddYears(b)date_add(a, interval b year)dateadd(year, b, a)a::timestamp+(b ’ year’)::intervaladd_months(a,b12)a.Datecast(date_format(a, ‘%Y-%m-%d’) as datetime)convert(char(10),a,120)a::datetrunc(a)a.Daydayofmonth(a)datepart(day, a)extract(day from a::timestamp)cast(to_char(a,‘DD’) as number)a.DayOfWeekdayofweek(a)datepart(weekday, a) - 1extract(dow from a::timestamp)case when to_char(a)=‘7’ then 0 else cast(to_char(a) as number) enda.DayOfYeardayofyear(a)datepart(dayofyear, a)extract(doy from a::timestamp)cast(to_char(a,‘DDD’) as number)a.Hourhour(a)datepart(hour, a)extract(hour from a::timestamp)cast(to_char(a,‘HH24’) as number)a.Millisecondfloor(microsecond(a) / 1000)datepart(millisecond, a)extract(milliseconds from a::timestamp)-extract(second from a::timestamp)*1000cast(to_char(a,‘FF3’) as number)a.Minuteminute(a)datepart(minute, a)extract(minute from a::timestamp)cast(to_char(a,‘MI’) as number)a.Monthmonth(a)datepart(month, a)extract(month from a::timestamp)cast(to_char(a,‘FF3’) as number)a.Secondsecond(a)datepart(second, a)extract(second from a::timestamp)cast(to_char(a,‘SS’) as number)a.Subtract(b)timestampdiff(microsecond, b, a)datediff(millisecond, b, a) * 1000(extract(epoch from a::timestamp-b::timestamp)1000000)a - ba.Tickstimestampdiff(microsecond, ‘0001-1-1’, a) * 10datediff(millisecond, ‘1970-1-1’, a) * 10000 + 621355968000000000extract(epoch from a::timestamp)10000000+621355968000000000cast(to_char(a,‘FF7’) as number)a.TimeOfDaytimestampdiff(microsecond, date_format(a, ‘%Y-%m-%d’), a)‘1970-1-1 ’ + convert(varchar, a, 14)extract(epoch from a::time)1000000a - trunc(a)a.Yearyear(a)datepart(year, a)extract(year from a::timestamp)年cast(to_char(a,‘YYYY’) as number)a.Equals(b)a = ba = ba = ba = ba.CompareTo(b)a - ba - ba - ba - ba.ToString()date_format(a, ‘%Y-%m-%d %H:%i:%s.%f’)convert(varchar, a, 121)to_char(a, ‘YYYY-MM-DD HH24:MI:SS.US’)to_char(a,‘YYYY-MM-DD HH24:MI:SS.FF6’)10、时间表达式MySql(微秒)SqlServer(秒)PostgreSQL(微秒)Oracle(Interval day(9) to second(7))TimeSpan.Zero00-0微秒numtodsinterval(0,‘second’)TimeSpan.MaxValue922337203685477580922337203685477580-numtodsinterval(233720368.5477580,‘second’)TimeSpan.MinValue-922337203685477580-922337203685477580-numtodsinterval(-233720368.5477580,‘second’)TimeSpan.Compare(a, b)a - ba - b-extract(day from (a-b))TimeSpan.Equals(a, b)a = ba = b-a = bTimeSpan.FromDays(a)a 1000000 60 60 24a 1000000 60 60 24-numtodsinterval(a86400,‘second’)TimeSpan.FromHours(a)a 1000000 60 * 60a 1000000 60 * 60-numtodsinterval(a3600,‘second’)TimeSpan.FromMilliseconds(a)a * 1000a * 1000-numtodsinterval(a/1000,‘second’)TimeSpan.FromMinutes(a)a 1000000 60a 1000000 60-numtodsinterval(a60,‘second’)TimeSpan.FromSeconds(a)a * 1000000a * 1000000-numtodsinterval(a,‘second’)TimeSpan.FromTicks(a)a / 10a / 10-numtodsinterval(a/10000000,‘second’)a.Add(b)a + ba + b-a + ba.Subtract(b)a - ba - b-a - ba.CompareTo(b)a - ba - b-extract(day from (a-b))a.Daysa div (1000000 60 60 * 24)a div (1000000 60 60 * 24)-extract(day from a)a.Hoursa div (1000000 60 60) mod 24a div (1000000 60 60) mod 24-extract(hour from a)a.Millisecondsa div 1000 mod 1000a div 1000 mod 1000-cast(substr(extract(second from a)-floor(extract(second from a)),2,3) as number)a.Secondsa div 1000000 mod 60a div 1000000 mod 60-extract(second from a)a.Ticksa * 10a * 10-(extract(day from a)86400+extract(hour from a)3600+extract(minute from a)60+extract(second from a))10000000a.TotalDaysa / (1000000 60 60 * 24)a / (1000000 60 60 * 24)-extract(day from a)a.TotalHoursa / (1000000 60 60)a / (1000000 60 60)-(extract(day from a)*24+extract(hour from a))a.TotalMillisecondsa / 1000a / 1000-(extract(day from a)86400+extract(hour from a)3600+extract(minute from a)60+extract(second from a))1000a.TotalMinutesa / (1000000 * 60)a / (1000000 * 60)- (extract(day from a)1440+extract(hour from a)60+extract(minute from a))a.TotalSecondsa / 1000000a / 1000000-(extract(day from a)86400+extract(hour from a)3600+extract(minute from a)*60+extract(second from a))a.Equals(b)a = ba = b-a = ba.ToString()cast(a as varchar)cast(a as varchar)-to_char(a)11、数学函数表达式MySqlSqlServerPostgreSQLOracleMath.Abs(a)abs(a)abs(a)abs(a)Math.Acos(a)acos(a)acos(a)acos(a)acos(a)Math.Asin(a)asin(a)asin(a)asin(a)asin(a)Math.Atan(a)atan(a)atan(a)atan(a)atan(a)Math.Atan2(a, b)atan2(a, b)atan2(a, b)atan2(a, b)-Math.Ceiling(a)ceiling(a)ceiling(a)ceiling(a)ceil(a)Math.Cos(a)cos(a)cos(a)cos(a)cos(a)Math.Exp(a)exp(a)exp(a)exp(a)exp(a)Math.Floor(a)floor(a)floor(a)floor(a)floor(a)Math.Log(a)log(a)log(a)log(a)log(e,a)Math.Log10(a)log10(a)log10(a)log10(a)log(10,a)Math.PI(a)3.14159265358979313.14159265358979313.14159265358979313.1415926535897931Math.Pow(a, b)pow(a, b)power(a, b)pow(a, b)power(a, b)Math.Round(a, b)round(a, b)round(a, b)round(a, b)round(a, b)Math.Sign(a)sign(a)sign(a)sign(a)sign(a)Math.Sin(a)sin(a)sin(a)sin(a)sin(a)Math.Sqrt(a)sqrt(a)sqrt(a)sqrt(a)sqrt(a)Math.Tan(a)tan(a)tan(a)tan(a)tan(a)Math.Truncate(a)truncate(a, 0)floor(a)trunc(a, 0)trunc(a, 0)12、类型转换表达式MySqlSqlServerPostgreSQLOracleSqliteConvert.ToBoolean(a), bool.Parse(a)a not in (‘0’,‘false’)a not in (‘0’,‘false’)a::varchar not in (‘0’,‘false’,‘f’,’no’)-a not in (‘0’,‘false’)Convert.ToByte(a), byte.Parse(a)cast(a as unsigned)cast(a as tinyint)a::int2cast(a as number)cast(a as int2)Convert.ToChar(a)substr(cast(a as char),1,1)substring(cast(a as nvarchar),1,1)substr(a::char,1,1)substr(to_char(a),1,1)substr(cast(a as character),1,1)Convert.ToDateTime(a), DateTime.Parse(a)cast(a as datetime)cast(a as datetime)a::timestampto_timestamp(a,‘YYYY-MM-DD HH24:MI:SS.FF6’)datetime(a)Convert.ToDecimal(a), decimal.Parse(a)cast(a as decimal(36,18))cast(a as decimal(36,19))a::numericcast(a as number)cast(a as decimal(36,18))Convert.ToDouble(a), double.Parse(a)cast(a as decimal(32,16))cast(a as decimal(32,16))a::float8cast(a as number)cast(a as double)Convert.ToInt16(a), short.Parse(a)cast(a as signed)cast(a as smallint)a::int2cast(a as number)cast(a as smallint)Convert.ToInt32(a), int.Parse(a)cast(a as signed)cast(a as int)a::int4cast(a as number)cast(a as smallint)Convert.ToInt64(a), long.Parse(a)cast(a as signed)cast(a as bigint)a::int8cast(a as number)cast(a as smallint)Convert.ToSByte(a), sbyte.Parse(a)cast(a as signed)cast(a as tinyint)a::int2cast(a as number)cast(a as smallint)Convert.ToString(a)cast(a as decimal(14,7))cast(a as decimal(14,7))a::float4to_char(a)cast(a as character)Convert.ToSingle(a), float.Parse(a)cast(a as char)cast(a as nvarchar)a::varcharcast(a as number)cast(a as smallint)Convert.ToUInt16(a), ushort.Parse(a)cast(a as unsigned)cast(a as smallint)a::int2cast(a as number)cast(a as unsigned)Convert.ToUInt32(a), uint.Parse(a)cast(a as unsigned)cast(a as int)a::int4cast(a as number)cast(a as decimal(10,0))Convert.ToUInt64(a), ulong.Parse(a)cast(a as unsigned)cast(a as bigint)a::int8cast(a as number)cast(a as decimal(21,0))Guid.Parse(a)substr(cast(a as char),1,36)cast(a as uniqueidentifier)a::uuidsubstr(to_char(a),1,36)substr(cast(a as character),1,36)Guid.NewGuid()-newid()—new Random().NextDouble()rand()rand()random()dbms_random.valuerandom()CodeFirst参数选项说明IsAutoSyncStructure【开发环境必备】自动同步实体结构到数据库,程序运行中检查实体表是否存在,然后创建或修改IsSyncStructureToLower转小写同步结构IsSyncStructureToUpper转大写同步结构,适用 OracleIsConfigEntityFromDbFirst使用数据库的主键和自增,适用 DbFirst 模式,无须在实体类型上设置 [Column(IsPrimary)] 或者 ConfigEntity。此功能目前可用于 mysql/sqlserver/postgresql。IsNoneCommandParameter不使用命令参数化执行,针对 Insert/Update,调试神器IsLazyLoading延时加载导航属性对象,导航属性需要声明 virtual1、配置实体(特性)public class Song { [Column(IsIdentity = true)] public int Id { get; set; } public string Title { get; set; } public string Url { get; set; } public virtual ICollection<Tag> Tags { get; set; } [Column(IsVersion = true)] public long versionRow { get; set; }}2、在外部配置实体fsql.CodeFirst .ConfigEntity<Song>(a => { a.Property(b => b.Id).IsIdentity(true); a.Property(b => b.versionRow).IsVersion(true); });DbFirst1、获取所有数据库fsql.DbFirst.GetDatabases();//返回字符串数组, [“cccddd”, “test”]2、获取指定数据库的表信息fsql.DbFirst.GetTablesByDatabase(fsql.DbFirst.GetDatabases()[0]);//返回包括表、列详情、主键、唯一键、索引、外键、备注等信息3、生成实体new FreeSql.Generator.TemplateGenerator().Build(fsql.DbFirst, @“C:\Users\28810\Desktop\github\FreeSql\Templates\MySql\simple-entity”, //模板目录(事先下载) @“C:\Users\28810\Desktop\你的目录”, //生成后保存的目录 “cccddd” //数据库);高级篇Repository 仓储实现1、单个仓储var curd = fsql.GetRepository<Xxx, int>();//curd.Find(1);var item = curd.Get(1);curd.Update(item);curd.Insert(item);curd.Delete(1);curd.Select.Limit(10).ToList();2、工作单元using (var uow = fsql.CreateUnitOfWork()) { var songRepos = uow.GetRepository<Song>(); var userRepos = uow.GetRepository<User>(); //上面两个仓储,由同一UnitOfWork uow 创建 //在此执行仓储操作 //这里不受异步方便影响 uow.Commit();}3、局部过滤器 + 数据验证var topicRepository = fsql.GetGuidRepository<Topic>(a => a.UserId == 1);之后在使用 topicRepository 操作方法时:查询/修改/删除时附过滤条件,从而达到不会修改其他用户的数据;添加时,使用过滤条件验证合法性,若不合法则抛出异常;如以下方法就会报错:topicRepository.Insert(new Topic { UserId = 2 })4、乐观锁更新实体数据,在并发情况下极容易造成旧数据将新的记录更新。FreeSql 核心部分已经支持乐观锁。乐观锁的原理,是利用实体某字段,如:long version,更新前先查询数据,此时 version 为 1,更新时产生的 SQL 会附加 where version = 1,当修改失败时(即 Affrows == 0)抛出异常。每个实体只支持一个乐观锁,在属性前标记特性:[Column(IsVersion = true)] 即可。无论是使用 FreeSql/FreeSql.Repository/FreeSql.DbContext,每次更新 version 的值都会增加 15、DbContextdotnet add package FreeSql.DbContext实现类似 EFCore 使用方法,跟踪对象状态,最终通过 SaveChanges 方法以事务的方式提交整段操作。using (var ctx = new SongContext()) { var song = new Song { BigNumber = “1000000000000000000” }; ctx.Songs.Add(song); song.BigNumber = (BigInteger.Parse(song.BigNumber) + 1).ToString(); ctx.Songs.Update(song); var tag = new Tag { Name = “testaddsublist”, Tags = new[] { new Tag { Name = “sub1” }, new Tag { Name = “sub2” }, new Tag { Name = “sub3”, Tags = new[] { new Tag { Name = “sub3_01” } } } } }; ctx.Tags.Add(tag); ctx.SaveChanges();}public class Song { [Column(IsIdentity = true)] public int Id { get; set; } public string BigNumber { get; set; } [Column(IsVersion = true)] //乐观锁 public long versionRow { get; set; }}public class Tag { [Column(IsIdentity = true)] public int Id { get; set; } public int? Parent_id { get; set; } public virtual Tag Parent { get; set; } public string Name { get; set; } public virtual ICollection<Tag> Tags { get; set; }}public class SongContext : DbContext { public DbSet<Song> Songs { get; set; } public DbSet<Tag> Tags { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder builder) { builder.UseFreeSql(fsql); }}导航属性支持 1对1、1对多、多对1、多对多 的约定导航属性配置,主要用于表达式内部查询;//OneToOne、ManyToOnevar t0 = fsql.Select<Tag>().Where(a => a.Parent.Parent.Name == “粤语”).ToList();//OneToManyvar t1 = fsql.Select<Tag>().Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10)).ToList();//ManyToManyvar t2 = fsql.Select<Song>().Where(s => s.Tags.AsSelect().Any(t => t.Name == “国语”)).ToList();不朽篇读写分离数据库读写分离,本功能是客户端的读写分离行为,数据库服务器该怎么配置仍然那样配置,不受本功能影响,为了方便描术后面讲到的【读写分离】都是指客户端的功能支持。各种数据库的读写方案不一,数据库端开启读写分离功能后,读写分离的实现大致分为以下几种:1、nginx代理,配置繁琐且容易出错;2、中件间,如MyCat,MySql可以其他数据库怎么办?3、在client端支持;FreeSql 实现了第3种方案,支持一个【主库】多个【从库】,【从库】的查询策略为随机方式。若某【从库】发生故障,将切换到其他可用【从库】,若已全部不可用则使用【主库】查询。出现故障【从库】被隔离起来间隔性的检查可用状态,以待恢复。IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.MySql, connstr) .UseSlave(“connectionString1”, “connectionString2”) //使用从数据库,支持多个 .Build();select.Where(a => a.Id == 1).ToOne();//读【从库】(默认)select.Master().WhereId(a => a.Id == 1).ToOne();//强制读【主库】下面是以前某项目的测试图片,以供参考,整个过程无感切换和恢复:分区分表FreeSql 提供 AsTable 分表的基础方法,GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装。var logRepository = fsql.GetGuidRepository<Log>(null, oldname => $"{oldname}_{DateTime.Now.ToString(“YYYYMM”)}");上面我们得到一个日志仓储按年月分表,使用它 CURD 最终会操作 Log_201903 表。合并两个仓储,实现分表下的联表查询:fsql.GetGuidRepository<User>().Select.FromRepository(logRepository) .LeftJoin<Log>(b => b.UserId == a.Id) .ToList();租户1、按租户字段区分FreeSql.Repository 现实了 filter(过滤与验证)功能,如:var topicRepos = fsql.GetGuidRepository<Topic>(t => t.TerantId == 1);使用 topicRepos 对象进行 CURD 方法:在查询/修改/删除时附加此条件,从而达到不会修改 TerantId != 1 的数据;在添加时,使用表达式验证数据的合法性,若不合法则抛出异常;利用这个功能,我们可以很方便的实现数据分区,达到租户的目的。2、按租户分表FreeSql.Repository 现实了 分表功能,如:var tenantId = 1;var reposTopic = orm.GetGuidRepository<Topic>(null, oldname => $"{oldname}{tenantId}");上面我们得到一个仓储按租户分表,使用它 CURD 最终会操作 Topic_1 表。3、按租户分库与方案二相同,只是表存储的位置不同。4、全局设置通过注入的方式设置仓储类的全局过滤器。public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<IFreeSql>(Fsql); services.AddFreeRepository(filter => { var tenantId = 求出当前租户id; filter .Apply<ISoftDelete>(“softdelete”, a => a.IsDeleted == false) .Apply<ITenant>(“tenant”, a => a.TenantId == tenantId) }, this.GetType().Assembly );}结束语这次全方位介绍 FreeSql 的功能,只抽取了重要内容发布,由于功能实在太多不方便在一篇文章介绍祥尽。我个人是非常想展开编写,将每个功能的设计和实现放大来介绍,但还是先希望得到更多人的关注,不然就是一台独角戏了。gayhub: https://github.com/2881099/FreeSql,肯请献上宝贵的一星,谢谢! ...

April 2, 2019 · 9 min · jiezi

一个导出小工具

说明一个导出QQ群成员信息的工具使用双击QunExporter.exe,会打开https://qun.qq.com/页面(利用CEF项目),登录QQ账号,软件获取登录信息点击获取群列表按钮,之后即可导出群成员信息下载源码

April 1, 2019 · 1 min · jiezi

安卓手机可以CAD看图吗?怎么查看接收的CAD图纸文件?

安卓手机可以CAD看图吗?怎么查看接收的CAD图纸文件?问道这个问题,当然也是可以的啊,随着现在各种手机应用app的不断改善,移动端也可以做什么相关的操作应用。那么在手机上怎么快速的进行查看CAD图纸文件?那么还是要有一款简单实用的转换app操作软件了,迅捷CAD手机看图app。可以快速的打开各种CAD格式的图纸文件,还可以简单的进行图纸的编辑操作,那么具体的要怎么进行?下面就带大家一起操作一下流程,大家也可以准备好相关的操作应用软件app和小编一起进行操作演示哦。1.第一步,但在你的手机界面中找到自带的应用市场软件,安卓手机直接在手机自带的应用商店即可搜索关键词进行安装下载,或者是在浏览器中也可以完成操作安装,待搜索完毕后,在对应的软件中点击下载安装至我们手机桌面即可。2.等到将迅捷CAD看图软件下载完毕后,在我们的安卓手机桌面找到这款迅捷CAD看图软件,点开进入操作界面即可。进入软件操作界面之后,往右边滑动,会显示一个登陆界面,我们可以直接点击登陆即可。3.然后我们在“最近打开”或“所有文件”标签栏下,选择需要查看的CAD图纸所存储的文件夹,找到该CAD图纸,点击“打开”查看,要是在QQ或者是微信中接收到人家发送过来的CAD微信图纸文件,我们直接点击下载即可在Tencent—micromsg—download进行找到图纸文件。4.然后我们就可以滑动界面,将图纸进行自由缩放,将图纸调整到合适的大小位置进查看了。那么以上就是关于安卓手机看图的演示教程了,希望可以对大家有所帮助哦!

April 1, 2019 · 1 min · jiezi

CLR Via第一 章 知识点整理(4) FCL、CTS、CLI和CLS

FCL(Framework Class Library) Framework 类库:FCL是 .net Framework 包含的一组DLL程序集的统称,FCL包含了提供了很多功能,关于这一部分没有什么好说的,只需要了解大致你需要的功能由什么类提供以及类型在那个命名空间,接着在查询接口文档即可。以下是FCL常用的命名空间CTS(Common Type System)公共类型系统CTS 是是微软制定的一套类的定义规则,如果存在与CTS不符合的定义或者功能则会出错,但是不使用则会通过CTS规范规定:字段、方法、属性、时间CTS访问规定:private、family、family and assembly、assembly、family or assembly、public当然还有继承、虚方法等等的其他规则,但是并没有必要去专门学习CTS,因为在学习一门新的语言时就会接触到到这些还有一点就是,没有必要实现全部的功能(IL实现的全部),例如C#的访问规定就只有一部分,而且语言也可以定不符合CTS的功能,应为只要不使用就不会出错,例如C语言的继承规则CLI(Common Language Infrastructure) 公共语言基础结构ECMA将CTS和.net Framework其他组件的标准化CLS(Common Language Specification) 公共语言规范微软定义的一套规范,它时CTS的子集,只有符合了这一套规则才可以说是符合是面向CLR的语言,以及使用其他面向CLR语言提供的组件如果编写其他语言使用的组件,功能不在CLS规范类可能出现错误,可以使用[assembly:CLSCompliant(true)]特性进行检查至此第一章的内容全部结束了,在这一章我们大致了解了1、源码编译成模块,并合并成程序集2、CLR如何与程序集工作,以及CLR的初始化3、FCL、CTS、CLI和CLS 规范第二章我们会更加详细的了解程序集,包括元数据、清单等等,以及程序集的部署

April 1, 2019 · 1 min · jiezi

通过 C# 买卖Bitcoin

方案一: 通过ExinCore API进行币币交易Exincore 提供了基于Mixin Network的币币交易API.你可以支付USDT给ExinCore, ExinCore会以最低的价格,最优惠的交易费将你购买的比特币转给你, 每一币交易都是匿名的,并且可以在区块链上进行验证,交易的细节只有你与ExinCore知道!ExinCore 也不知道你是谁,它只知道你的UUID.预备知识:你先需要创建一个机器人, 方法在 教程一.安装依赖包正如教程一里我们介绍过的, 我们需要依赖 mixin-csharp-sdk, 你应该先安装过它了, 这儿我们再安装 MsgPack.Cli 软件包. dotnet add package MixinCSharpSdk dotnet add package MsgPack.Cli –version 1.0.1充币到 Mixin Network, 并读出它的余额.通过ExinCore API, 可以进行BTC, USDT, EOS, ETH 等等交易, 此处演示用 USDT购买BTC 或者 用BTC购买USDT。交易前,先检查一下钱包地址。完整的步骤如下:检查比特币或USDT的余额,钱包地址。并记下钱包地址。从第三方交易所或者你的冷钱包中,将币充到上述钱包地址。再检查一下币的余额,看到帐与否。(比特币的到帐时间是5个区块的高度,约100分钟)。比特币与USDT的充值地址是一样的。 MixinApi mixinApiNewUser = new MixinApi(); mixinApiNewUser.Init(UserIDNewUser, “”, SessionIDNewUser, PinTokenNewUser, PrivateKeyNewUser); Asset AssetBTC = mixinApiNewUser.ReadAsset(USRCONFIG.ASSET_ID_BTC); Console.WriteLine(“New User " + UserIDNewUser + " ’s BTC balance is " + AssetBTC.balance); Console.WriteLine(“New User " + UserIDNewUser + " ’s BTC address is " + AssetBTC.public_key);查询ExinCore市场的价格信息如何来查询ExinCore市场的价格信息呢?你要先了解你交易的基础币是什么,如果你想买比特币,卖出USDT,那么基础货币就是USDT;如果你想买USDT,卖出比特币,那么基础货币就是比特币.string jsonData = FetchMarketPrice(“815b0b1a-2764-3736-8faa-42d694fa620a”);var marketObj = JsonConvert.DeserializeObject<MarketInfo>(jsonData);foreach (AssetInfo value in marketObj.data){ Console.WriteLine(value);}public class MarketInfo{ public string code { get; set; } public string message { get; set; } public List<AssetInfo> data { get; set; } public override string ToString() { return JsonConvert.SerializeObject(this); }}public class AssetInfo{ public string base_asset { get; set; } public string base_asset_symbol { get; set; } public string exchange_asset_symbol { get; set; } public string price { get; set; } public string minimum_amount { get; set; } public string maximum_amount { get; set; } public List<string> exchanges { get; set; } public override string ToString() { return JsonConvert.SerializeObject(this); }}public static string FetchMarketPrice(string asset_id){ return FetchMarketPriceAsync(asset_id).Result;}public static async Task<string> FetchMarketPriceAsync(string asset_id){ HttpClient client = new HttpClient(); // Call asynchronous network methods in a try/catch block to handle exceptions try { HttpResponseMessage response = await client.GetAsync(“https://exinone.com/exincore/markets?base_asset=" + asset_id); response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); // Above three lines can be replaced with new helper method below // string responseBody = await client.GetStringAsync(uri); Console.WriteLine(responseBody); return responseBody; } catch(HttpRequestException e) { Console.WriteLine("\nException Caught!”); Console.WriteLine(“Message :{0} “,e.Message); } return null;}交易前,创建一个Memo!在第二章里,基于Mixin Network的 C# 比特币开发教程: 机器人接受比特币并立即退还用户, 我们学习过退还用户比特币,在这里,我们除了给ExinCore支付币外,还要告诉他我们想购买的币是什么,即将想购买的币存到memo里。private static string TargetAssetID(string asset_id) { Guid guid = new Guid(asset_id); var gbytes = guid.ToByteArray(); Array.Reverse(gbytes,0,4); Array.Reverse(gbytes,4,2); Array.Reverse(gbytes,6,2); var serializer = MessagePackSerializer.Get(gbytes.GetType()); var stream = new MemoryStream(); serializer.Pack(stream, gbytes); return Convert.ToBase64String(stream.ToArray());}币币交易的完整流程转币给ExinCore时,将memo写入你希望购买的币,否则,ExinCore会直接退币给你!如果你想卖出比特币买入USDT,调用方式如下://config.cspublic static string EXIN_BOT = “61103d28-3ac2-44a2-ae34-bd956070dab1”;// public static string EXIN_BOT = “0b1a2027-4fd6-3aa0-b3a3-814778bb7a2e”;public static string MASTER_UUID = “0b4f49dc-8fb4-4539-9a89-fb3afc613747”;public static string ASSET_ID_BTC = “c6d0c728-2624-429b-8e0d-d9d19b6592fa”;public static string ASSET_ID_EOS = “6cfe566e-4aad-470b-8c9a-2fd35b49c68d”;public static string ASSET_ID_USDT= “815b0b1a-2764-3736-8faa-42d694fa620a”;//Program.csif (cmd == “5” ) { var memo = TargetAssetID(USRCONFIG.ASSET_ID_USDT); Console.WriteLine(memo); using (TextReader fileReader = File.OpenText(@“mybitcoin_wallet.csv”)) { var csv = new CsvReader(fileReader); csv.Configuration.HasHeaderRecord = false; while (csv.Read()) { string PrivateKeyNewUser; csv.TryGetField<string>(0, out PrivateKeyNewUser); string PinTokenNewUser; csv.TryGetField<string>(1, out PinTokenNewUser); string SessionIDNewUser; csv.TryGetField<string>(2, out SessionIDNewUser); string UserIDNewUser; csv.TryGetField<string>(3, out UserIDNewUser); string PinNewUser; csv.TryGetField<string>(4, out PinNewUser); MixinApi mixinApiNewUser = new MixinApi(); mixinApiNewUser.Init(UserIDNewUser, “”, SessionIDNewUser, PinTokenNewUser, PrivateKeyNewUser); // Console.WriteLine(mixinApiNewUser.CreatePIN(””, “123456”).ToString()); Transfer reqInfo = mixinApiNewUser.Transfer(USRCONFIG.ASSET_ID_BTC, USRCONFIG.EXIN_BOT, “0.0001”, PinNewUser.ToString(), System.Guid.NewGuid().ToString(), memo); Console.WriteLine(reqInfo); } }}如果你想卖出USDT买入比特币,调用方式如下:if (cmd == “6” ) { var memo = TargetAssetID(USRCONFIG.ASSET_ID_BTC); Console.WriteLine(memo); using (TextReader fileReader = File.OpenText(@“mybitcoin_wallet.csv”)) { var csv = new CsvReader(fileReader); csv.Configuration.HasHeaderRecord = false; while (csv.Read()) { string PrivateKeyNewUser; csv.TryGetField<string>(0, out PrivateKeyNewUser); string PinTokenNewUser; csv.TryGetField<string>(1, out PinTokenNewUser); string SessionIDNewUser; csv.TryGetField<string>(2, out SessionIDNewUser); string UserIDNewUser; csv.TryGetField<string>(3, out UserIDNewUser); string PinNewUser; csv.TryGetField<string>(4, out PinNewUser); MixinApi mixinApiNewUser = new MixinApi(); mixinApiNewUser.Init(UserIDNewUser, “”, SessionIDNewUser, PinTokenNewUser, PrivateKeyNewUser); // Console.WriteLine(mixinApiNewUser.CreatePIN(””, “123456”).ToString()); Transfer reqInfo = mixinApiNewUser.Transfer(USRCONFIG.ASSET_ID_USDT, USRCONFIG.EXIN_BOT, “1”, PinNewUser.ToString(), System.Guid.NewGuid().ToString(), memo); Console.WriteLine(reqInfo); } }}交易完成后,Exincore会将你需要的币转到你的帐上,同样,会在memo里,记录成交价格,交易费用等信息!你只需要按下面的方式解开即可!NetworkSnapshots 读取钱包的交易记录。using (TextReader fileReader = File.OpenText(@“mybitcoin_wallet.csv”)){ var csv = new CsvReader(fileReader); csv.Configuration.HasHeaderRecord = false; while (csv.Read()) { string PrivateKeyNewUser; csv.TryGetField<string>(0, out PrivateKeyNewUser); string PinTokenNewUser; csv.TryGetField<string>(1, out PinTokenNewUser); string SessionIDNewUser; csv.TryGetField<string>(2, out SessionIDNewUser); string UserIDNewUser; csv.TryGetField<string>(3, out UserIDNewUser); string PinNewUser; csv.TryGetField<string>(4, out PinNewUser); MixinApi mixinApiNewUser = new MixinApi(); mixinApiNewUser.Init(UserIDNewUser, “”, SessionIDNewUser, PinTokenNewUser, PrivateKeyNewUser); // Console.WriteLine(mixinApiNewUser.CreatePIN("", “123456”).ToString()); var snaps = mixinApiNewUser.NetworkSnapshots(10,“2019-03-26T01:49:52.462741863Z”, “815b0b1a-2764-3736-8faa-42d694fa620a”, “ASC”,true); // Console.WriteLine(snaps); foreach (var sn in snaps) { if ( Convert.ToDouble(sn.amount) > 0 ) { if ( sn.data != null ) { var memoBytes = Convert.FromBase64String(sn.data); var memoObj = MessagePackSerializer.UnpackMessagePackObject(memoBytes); Console.WriteLine(memoObj.ToString()); var xR = JsonConvert.DeserializeObject<ExchangeResult>(memoObj.ToString()); Console.WriteLine(xR.C); if (xR.C == “1000”) { Console.WriteLine("———–Successfully–Exchange————-"); Console.WriteLine(“You got " + sn.amount.ToString() + " back!”); Console.WriteLine(“Price is " + xR.P + " Fee is " + xR.F + " Percent of fee: " + Convert.ToDouble(xR.F)/Convert.ToDouble(sn.amount)*100 + " %”); Console.WriteLine(“Fee Asset uuid: " + HexStringToUUID(xR.FA)); Console.WriteLine(“trace uuid: " + HexStringToUUID(xR.O)); Console.WriteLine(”———-end of snapshots query————–”); } } } } }}一次成功的交易如下:———–Successfully–Exchange————-You got 0.3923244 back!Price is 3938.62 Fee is 0.0007878 Percent of fee: 0.200803212851406 %Fee Asset uuid: 815b0b1a-2764-3736-8faa-42d694fa620atrace uuid: 1a3d8561-26e7-49bb-8ae3-ed85ce2bb957———-end of snapshots query————–读取币的余额通过读取币的余额,来确认交易情况! MixinApi mixinApiNewUser = new MixinApi(); mixinApiNewUser.Init(UserIDNewUser, “”, SessionIDNewUser, PinTokenNewUser, PrivateKeyNewUser); Asset AssetBTC = mixinApiNewUser.ReadAsset(USRCONFIG.ASSET_ID_BTC); Console.WriteLine(“New User " + UserIDNewUser + " ’s BTC balance is " + AssetBTC.balance); Console.WriteLine(“New User " + UserIDNewUser + " ’s BTC address is " + AssetBTC.public_key);源代码执行编译执行,即可开始交易了.[x] dotnet build 编译项目.[x] dotnet bin/Debug/netcoreapp2.2/bitcoin_wallet.dll 运行它.本代码执行时的命令列表:1: Create Bitcoin Wallet and update PIN2: Read Bitcoin balance & address3: Read USDT balance & address4: Read EOS balance & address5: pay 0.0001 BTC buy USDT6: pay $1 USDT buy BTC7: Read Snapshots8: Fetch market price(USDT)9: Fetch market price(BTC)v: Verify Wallet Pinq: ExitMake your choose:完整代码Solution Two: List your order on Ocean.One exchange ...

March 31, 2019 · 4 min · jiezi

已实现乐观锁功能,FreeSql.DbContext 准备起航

上回说到 FreeSql.DbContext 的规则,以及演示它的执行过程,可惜当时还不支持“乐观锁”,对于更新数据来讲并不安全。FreeSql 核心库 v0.3.27 已提供乐观锁支持。实现原理乐观锁的原理,是利用实体某字段,如:long version,更新前先查询数据,此时 version 为 1,更新时产生的 SQL 会附加 where version = 1,当修改失败时(即 Affrows == 0)抛出异常。每个实体只支持一个乐观锁,在属性前标记特性:[Column(IsVersion = true)] 即可。无论是使用 FreeSql/FreeSql.Repository/FreeSql.DbContext,每次更新 version 的值都会增加 1至此,FreeSql.DbContext 的更新操作就安全了。安装dotnet add package FreeSql.DbContext测试功能下面演示更新 BigNumber 属性,为什么定义他为 string 呢,对于数字的更新 set clicks = clicks + 1,是安全的操作。BigInteger 了解吗,我们就当 BigNumber 是一个超大的数字吧,普通数字无法表示的。var fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, @“Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10”) .UseAutoSyncStructure(true) .UseLazyLoading(true) .UseNoneCommandParameter(true) .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) .Build();public class Song { [Column(IsIdentity = true)] public int Id { get; set; } public string BigNumber { get; set; } [Column(IsVersion = true)]//使用简单 public long versionRow { get; set; }}public class SongContext : DbContext { public DbSet<Song> Songs { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder builder) { builder.UseFreeSql(fsql); }}当更新时,版本不正确提示以下错误,DbContext 将回滚操作:总结FreeSql.DbContext 实现类似 EFCore 使用方法,跟踪对象状态,最终通过 SaveChanges 方法提交事务。目前是第二个初版,已实现状态跟踪保存(导航属性的跟踪暂时不支持)。配合乐观锁这个杀手锏,FreeSql 越来越有 ORM 的影子了。github: https://github.com/2881099/Fr… (求星星,谢谢) ...

March 29, 2019 · 1 min · jiezi

aelf开发进展更新:经济系统适配升级改进已完成,侧链多节点调试正按计划推进

截至目前,经济系统适配改进之后的共识模块开发已完成,浏览器插件第二版已按计划完成更新,经济系统回归已完成80%。以下是详细开发进展。上周开发进展❏ Release-v0.7.0[验证中]多资产合约[进行中]侧链模块,完成单节点调试,正在疏通多节点[完成]经济系统适配改进之后的共识模块[进行中]合约参数传入/出优化[进行中]Issue中的TODO及Bug Fix❏ 区块浏览器[完成]浏览器插件(第二版)[完成]扫链服务调整(支持LIB)[进行中]区块浏览器/钱包适配v0.7.0 90%❏ 测试[进行中]重构模块的单元测试及细节补充-[完成]Blockchain service测试用例完善-[完成]LIB/State manager测试用例完善-[完成]网络同步测试用例完善-[进行中]完善经济系统相关的测试用例[进行中]多节点流程疏通及影响流程的Bug修复-[完成]随着运行时间的增加,执行交易时间逐渐增长的问题-[完成]多节点发交易疏通及问题修复-[验证中]不能正常切换Bestchain[进行中]经济系统回归 80%❏ 其他[完成]根据v0.7.0重新组织监控相关的工程[进行中]脚本兼容windows,使windows用户可以build[进行中]官网改版本周开发计划❏ Release-v0.7.0继续侧链多节点调试及问题修复继续集成测试及修复相关bug❏ 钱包/浏览器继续基于v0.7.0做相关的集成测试原生钱包APP调研及demo❏ 其他实现代码中TODO相关的Issue

March 29, 2019 · 1 min · jiezi

CLR Via读书笔记第一章(3)CLR执行程序集的IL代码

在了解CLR运行之前让我们先简单了解一下IL除了编译器编译的IL代码,IL也是一种汇编语言,也就是说我们可以直接编写IL代码,当然也有对应的IL编译器,值得一提的是对于面向CLR的其他语言,CLR只开放了一部分功能,而IL可以访问CLR的全部功能。前面一章我们介绍了CLR的所有初始工作,最后在调用Main入口方法的时候,CLR需要将程序集中的IL代码转为CPU指令,也就是CLR中JIT(just-in-time)编译器的职责,CLR会即时编译IL代码即时编译:在运行的时候才会进行编译(类似懒加载)当CLR运行并调用方法时做了如下几件事情 1、检测出所有方法中所有被引用的类型,并创建一个内部数据结构进行管理,每个类型的方法都会记录指向名为JITComplier函数的地址, 2、在方法被调用的时候,函数会在与元数据中查找被调用的方法对应的IL代码,对其验证并将代码编译成CPU指令 3、将CPU指令存贮到动态分配的内存中 4、回到内部数据结构中,修改对应方法记录的地址,指向刚才编译好的CPU指令的地址 5、最后函数会回到内存当中去运行CPU指令至此一个方法调用的全部流程就走完了,如果不终止程序(终止会将编译好的cpu指令丢弃),那么CLR在第二次调用方法时,直接在数据结构中找到对应的内存运行CPU指令,省去了上面的2、3、4步骤CLR的JIT编译器以及C#编译器对本机代码的优化C#编译器 :/optimize 关闭 –> 编译出的IL代码会包含许多NOP指令(no-operation 空操作)和跳转执行,vs就是利用的这些指令提供了调试的功能/optimize 开启 –> 优化后的代码会更小,程序集也会相应变小,更方便阅读IL代码(一般估计不会有人去直接阅读IL查找问题吧)JIT编译器:在 /optimize 关闭 的情况下: /debug - 关闭(默认) –> 有优化 /debug (+/full/pdbonly) –> 未优化:编译器会生成PDB文件帮助编译器查找到局部变量并将IL代码映射到源代码方便调试,如果指定的是 /debug : full 开关,编译器还会记录每一条IL指令生成的本机指令,但会使用额外的时间和内存在 /optimize 开启的情况下: /debug (-/+/full/pdbonly) –> 有优化虽然编译器在优化代码的过程中会占用额外的时间和内存,但是在实际运行阶段所带来的收益远远大于这些牺牲,并且性能上远远大于非托管代码,例如: 1、JIT编译器针对不同的CPU优化本机代码 2、会根据机器对特定的判断进行代码优化 3、CLR会根据运行状态对代码评估并重新编译(还未实现)最后再来简单了解一下NGen.exe工具NGen.exe是.net framework提供的工具,它可以将代码提前编译好,这样JIT编译器不需要在运行是编译提升性能,但其实这个工具并不是很实用 1、因为NGen无法对代码进行最优的优化 –> 因为无法确定CPU 2、对服务器提升不明显 –> 因为只是在第一次运行时有帮助,后面运行的时间时相等的 3、可能失去同步 –> 如果当前代码与执行环境不符合,那么就会从新用JIT编译至此关于CLR如何与程序集工作就完成了,下一节我们将介绍.net Framework的Framework 类库以及CTS CLS

March 28, 2019 · 1 min · jiezi

每天学一个算法之归并排序

归并算法(MERGE-SORT)(设按升序排列)1、分冶法—分冶思想的介绍将原始问题分解为几个规模较小但类似于原问题容易解决的子问题,递归地求解这些子问题,然后再合并子问题的解来建立原始问题的解。分冶法在递归时的步骤分解: 原问题分解为子问题,子问题是原问题规模较小的实例。解决: 递归地解决子问题,若子问题足够小,则直接求解。合并: 合并子问题的解。得到原问题解。在归并排序中的体现分解: 分解待排序的n个元素的序列成各自具有n/2个元素的两个子序列。解决: 使用归并排序递归地排序两个子序列。合并: 合并两个已排序的子序列一产生最终已排序的序列。2、 归并排序伪代码描述主过程: MERGE-SORT(A, startIndex, endIndex), A: 待排数组 startIndex: 起始数组下标 endIndex: 截止数组下标MERGE-SORT(A, startIndex, endIndex) if startIndex < endIndex midIndex = (startIndex + endIndex)/2 // midIndex取(startIndex + endIndex)/2值的底; MERGE-SORT(A, startIndex ,midIndex) MERGE-SORT(A, midIndex + 1,endIndex) MERGE(A, startIndex, midIndex, endIndex)子过程: MERGE(A, startIndex, midIndex, endIndex)MERGE(A, startIndex, midIndex, endIndex) n1 = midindex - startIndex + 1 n2 = endIndex - midIndex let L[1..n1+1] and R[1..n2+1] be new arrays for i = 1 to n1 L[i] = A[startIndex + i -1] for j = 1 to n2 R[j] = A[midIndex + j] L[n1+1] = Infinity R[n2+1] = Infinity i = 1 j = 1 for k = startIndex to endIndex if L[i] <= R[j] A[k] = L[i] i = i + 1 else A[k] = R[j] j = j + 1 ...

March 25, 2019 · 1 min · jiezi

每天学一个算法(一、插入排序)

一、插入排序(INSERTION-SORT)(设按升序排列)思路类比思想: 假设桌上有一叠反面朝上的扑克,一个人左手为空,每次右手从这叠扑克的最上边拿一张扑克放到左手。在将扑克插入左手时,都与之前插入的扑克比较大小,插入适当位置,使之按升序排列。当将最后一张牌插入的时候,这叠扑克升序排序结束,得到升序序列。伪代码描述: A为待排序数组。for j = 2 to A.length key = A[j] i = j - 1 while i > 0 and A[i] > key A[i + 1] = A[i] i = i - 1 A[i + 1] = keyC#代码static void InsertionSort<T>(T[] arr) where T: IComparable<T>{ int i, j; T tmp; for (i = 1; i < arr.Length; i++) { tmp = arr[i]; j = i - 1; while (j >= 0 && tmp.CompareTo(arr[j]) < 0 ) //tmp < arr[j] { arr[j + 1] = arr[j]; j–; } arr[j + 1] = tmp; }} ...

March 25, 2019 · 1 min · jiezi

Unity C# 3D世界坐标转2D屏幕坐标

让2D UI跟随3D物体移动或指示3D物体的位置该怎么做呢?关键代码:Camera.main.WorldToScreenPoint(target.position) + new Vector3(-Screen.width / 2, -Screen.height / 2);测试脚本:Position3DTo2DTest.csusing UnityEngine;/// <summary>/// 3D物体转2D屏幕坐标测试/// ZhangYu 2019-03-20/// </summary>public class Position3DTo2DTest : MonoBehaviour { public Transform target; // 3D目标 public Transform ui; // 2D UI private Vector3 originOff; // 当前UI系统(0,0)点 相对于屏幕左下角(0, 0)点的偏移量 private void Start () { originOff = new Vector3(-Screen.width / 2, -Screen.height / 2); Reposition(); } private void Update () { // 需要性能优化 仅在物体移动或相机移动后调用即可 Reposition(); } // 根据目标物体 重定位UI private void Reposition() { Vector3 position = Camera.main.WorldToScreenPoint(target.position) + originOff; position.z = 0; ui.localPosition = position; }}实现效果:血条UI悬浮在人物头顶当摄像机旋转角度时 依然有效 ...

March 20, 2019 · 1 min · jiezi

CLR Via读书笔记第一章(2)程序集和CLR的启动

这一节先简单的讨论一下程序集以及CLR的初始化虽然对应的编译器会生成托管模块,但实际上CLR不与托管模块工作,编译器除了编译还有将生成的托管模块转换为程序集的功能,微软还提供了工具AL.exe(程序集链接器)。程序集:1、是一个或者多个模块或者资源的分组,是安全性、重用、版本控制的最小单元。2、程序集还有一个类似于懒加载的机制,将不常用的类或者资源放在一个单独的文件中,当使用时才会下载,可以节约磁盘和缩短安装时间3、此外程序集还可以自描述,在CLR读取程序集时无需额外信息,因为程序集自带了关于自生的所有信息,所以更容易部署][2]编译器会通过源码生成EXE、DLL文件,但无论哪一种最终都是由CLR运行,但运行CLR之前必须先安装.net Framework,判断是否安装只需检查System32目录中是否含有MSCorEE.dll文件即可,微软还提供了CLRVer.exe检查CLR的版本号工具。但是并不是只要安装了.net Framework就可以运行程序集,需要对应程序集特定的环境才可以运行,例如生成的64位的程序集无法再32位的环境下运行,所以在运行程序集之前需要对检查程序集的信息,可以使用DumpBin.exe和CoreFlags.exe工具查看。程序集的版本是由编辑器设置的,C#的编译器提供了一个/platform开关选项可以设置生成对应的不同程序集以下是对应.platform 开关生成的模块在不同环境下的运营情况![所以在运行程序集之前大致做了如下几个动作:1、先读取程序集的信息2、创建对应环境的进程3、加载MSCorEE.add文件并调用方法初始化程序集4、加载程序集5、调用入口Main方法至此就完成的CLR的所有准备工作,接下来才是重头戏,CLR如何是如何与程序集工作,你会还了解带托管代码的多处优势

March 19, 2019 · 1 min · jiezi

FreeSql 新查询功能介绍

FreeSqlFreeSql 是一个功能强大的 NETStandard 库,用于对象关系映射程序(O/RM),提供了 CodeFirst/DbFirst/CURD/表达式函数/读写分离 等基础封装。支持 .NETCore 2.1+ 或 .NETFramework 4.6.1+。新的查询功能且先看看实体定义:public class Song { [Column(IsIdentity = true)] public int Id { get; set; } public DateTime? Create_time { get; set; } public bool? Is_deleted { get; set; } public string Title { get; set; } public string Url { get; set; } public virtual ICollection<Tag> Tags { get; set; }}public class Song_tag { public int Song_id { get; set; } public virtual Song Song { get; set; } public int Tag_id { get; set; } public virtual Tag Tag { get; set; }}public class Tag { [Column(IsIdentity = true)] public int Id { get; set; } public int? Parent_id { get; set; } public virtual Tag Parent { get; set; } public decimal? Ddd { get; set; } public string Name { get; set; } public virtual ICollection<Song> Songs { get; set; } public virtual ICollection<Tag> Tags { get; set; }}以上定义了三个实体,Song、Tag,以及中间表SongTag。一对一、多对一的查询:var t0 = fsql.Select<Tag>().Where(a => a.Parent.Parent.Name == “粤语”).ToSql();执行转换的SQL语句:SELECT a.Id, a.Parent_id, a__Parent.Id as3, a__Parent.Parent_id as4, a__Parent.Ddd, a__Parent.Name, a.Ddd as7, a.Name as8 FROM Tag a LEFT JOIN Tag a__Parent ON a__Parent.Id = a.Parent_id LEFT JOIN Tag a__Parent__Parent ON a__Parent__Parent.Id = a__Parent.Parent_id WHERE (a__Parent__Parent.Name = ‘粤语’)一对多的查询:var t1 = fsql.Select<Tag>().Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10)).ToSql();执行转换的SQL语句:var t1 = fsql.Select<Tag>().Where(a => a.Tags.AsSelect().Any(t => t.Parent.Id == 10)).ToSql();SELECT a.Id, a.Parent_id, a.Ddd, a.Name FROM Tag a WHERE (exists(SELECT 1 FROM Tag t LEFT JOIN Tag t__Parent ON t__Parent.Id = t.Parent_id WHERE (t__Parent.Id = 10) AND (t.Parent_id = a.Id) limit 0,1))多对多的查询:var t2 = fsql.Select<Song>().Where(s => s.Tags.AsSelect().Any(t => t.Name == “国语”)).ToSql();执行转换的SQL语句:SELECT a.Id, a.Create_time, a.Is_deleted, a.Title, a.Url FROM Song aWHERE(exists(SELECT 1 FROM Song_tag Mt_Ms WHERE(Mt_Ms.Song_id = a.Id) AND(exists(SELECT 1 FROM Tag t WHERE(t.Name = ‘国语’) AND(t.Id = Mt_Ms.Tag_id) limit 0, 1)) limit 0, 1))这个功能不受外建影响,更多前往wiki:《Select查询数据文档》表达式函数var t1 = select.Where(a => new[] { 1, 2, 3 }.Contains(a.testFieldInt)).ToSql();//SELECT a.Id, a.Clicks, a.TestTypeInfoGuid, a.Title, a.CreateTime //FROM Song a //WHERE (a.Id in (1,2,3))查找今天创建的数据var t2 = select.Where(a => a.CreateTime.Date == DateTime.Now.Date).ToSql();SqlServer 下随机获取记录var t3 = select.OrderBy(a => Guid.NewGuid()).Limit(1).ToSql();//SELECT top 1 …//FROM [Song] a //ORDER BY newid()更多前往wiki:《Expression 表达式函数文档》完整特性支持 CodeFirst 迁移;支持 DbFirst 从数据库导入实体类,支持三种模板生成器;采用 ExpressionTree 高性能读取数据;支持深入的类型映射,比如pgsql的数组类型,堪称匠心制作;支持丰富的表达式函数;支持导航属性查询,和延时加载;支持同步/异步数据库操作方法,丰富多彩的链式查询方法;支持读写分离、分表分库,租户设计;支持多种数据库,MySql/SqlServer/PostgreSQL/Oracle/Sqlite; 入门《Select》 \《Update》 \《Insert》 \《Delete》新手《表达式函数》 \《CodeFirst》 \《DbFirst》高手《Repository》 \《UnitOfWork》 \《过滤器》不朽《读写分离》 \《分区分表》 \《租户》 \更新日志快速开始以 .net core 新项目为例,创建新项目dotnet new webapi引入 FreeSql 包dotnet add package FreeSql.Repository在 startup.cs 中定义 IFreeSql 和注入仓储public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) { Configuration = configuration; Fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, @“Data Source=|DataDirectory|/document.db;Pooling=true;Max Pool Size=10”) .UseAutoSyncStructure(true) //自动同步实体结构到数据库 .UseLazyLoading(true) //开启延时加载,导航属性 .UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) //跟踪SQL执行语句 .Build();}public IConfiguration Configuration { get; }public IFreeSql Fsql { get; }public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddSingleton<IFreeSql>(Fsql); var builder = new ContainerBuilder(); builder.RegisterFreeRepository( filter => filter .Apply<ISoftDelete>(“softdelete”, a => a.IsDeleted == false) //开启软删除过滤器,可定义多个全局过滤器 , this.GetType().Assembly //将本项目中所有继承实现的仓储批量注入 ); builder.Populate(services); var container = builder.Build(); return new AutofacServiceProvider(container);}然后在 controller 中就可以像平常一样使用仓储了,如:[Route(“restapi/[controller]”)]public class SongsController : Controller { GuidRepository<Song> _songRepository; public SongsController(GuidRepository<Song> repos1) { _songRepository = repos1; }FreeSql.RepositoryFreeSql.Repository 参考 abp vnext 接口,定义和实现基础的仓储层(CURD)。除此以外,它还实用的全局、局部过滤器功能,分表分方库功能,以及工作单元的实现;过滤器功能不仅可以查询时过滤,连删除/修改/插入时都会进行验证,避免开过过程担心数据安全问题;UnitOfWork 可将多个仓储放在一个单元管理执行,最终通用 Commit 执行所有操作,内部采用了数据库事务;结束语本次更新主要涉及 一对一、多对一、一对多、多对多 的查询,当约定配置不正确的时候使用导航属性,会出现友好的错误提示。感谢您的关注,github:https://github.com/2881099/FreeSql,求给出宝贵的一星,谢谢! ...

March 19, 2019 · 3 min · jiezi

.NETCore 下支持分表分库、读写分离的通用 Repository

首先声明这篇文章不是标题党,我说的这个类库是 FreeSql.Repository,它作为扩展库现实了通用仓储层功能,接口规范参考 abp vnext 定义,实现了基础的仓储层(CURD)。安装dotnet add package FreeSql.Repository可用于:.net framework 4.6+、.net core 2.1+定义var fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, @“Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10”) .UseLogger(loggerFactory.CreateLogger<IFreeSql>()) .UseAutoSyncStructure(true) //自动迁移实体的结构到数据库 .Build();过滤与验证假设我们有User(用户)、Topic(主题)两个实体,在某领域类中定义了两个仓储:var userRepository = fsql.GetGuidRepository<User>();var topicRepository = fsql.GetGuidRepository<Topic>();开发过程中,我总会担心 topicRepository 的数据安全问题,即有可能查询或操作到其他用户的主题。因此在v0.0.7版本进行了改进,增加了 filter lambad 表达式参数。var userRepository = fsql.GetGuidRepository<User>(a => a.Id == 1);var topicRepository = fsql.GetGuidRepository<Topic>(a => a.UserId == 1);在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据;在添加时,使用表达式验证数据的合法性,若不合法则抛出异常;有朋友说这个功能像 abp 的租户,但这是更小单位的过滤+验证,确保数据安全。有朋友说这个功能省事,但我觉得是省心。分表与分库GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装类。var logRepository = fsql.GetGuidRepository<Log>(null, oldname => $"{oldname}_{DateTime.Now.ToString(“YYYYMM”)}");上面我们得到一个日志仓储实例按年月分表,使用它 CURD 最终会操作 Log_201903 表。注意:虽然 FreeSql 支持 CodeFirst 迁移,但不提供迁移分表,开发环境中仍然可以迁移 Log 表。读写分离FreeSql 支持数据库读写分离,本功能是客户端的读写分离行为,数据库服务器该怎么配置仍然那样配置,不受本功能影响,为了方便描术后面讲到的【读写分离】都是指客户端的功能支持。各种数据库的读写方案不一,数据库端开启读写分离功能后,读写分离的实现大致分为以下几种:1、nginx代理,配置繁琐且容易出错;2、中件间,如MySql可以使用MyCat,但是其他数据库怎么办?3、在client端支持;FreeSql 实现了第3种方案,支持一个【主库】多个【从库】,【从库】的查询策略为随机方式。若某【从库】发生故障,将切换到其他可用【从库】,若已全部不可用则使用【主库】查询。出现故障【从库】被隔离起来间隔性的检查可用状态,以待恢复。以 mysql 为例:var connstr = “Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;” + “Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10”;IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.MySql, connstr) .UseSlave(“connectionString1”, “connectionString2”) //使用从数据库,支持多个 .Build();select.Where(a => a.Id == 1).ToOne(); //读【从库】(默认)select.Master().WhereId(a => a.Id == 1).ToOne(); //强制读【主库】其他特性[x] 支持 CodeFirst 迁移;[x] 支持 DbFirst 从数据库导入实体类,支持三种模板生成器;[x] 采用 ExpressionTree 高性能读取数据;[x] 支持深入的类型映射,比如pgsql的数组类型;[x] 支持丰富的表达式函数;[x] 支持导航属性查询,和延时加载;[x] 支持同步/异步数据库操作方法,丰富多彩的链式查询方法;[x] 支持读写分离、分表分库;[x] 支持多种数据库,MySql/SqlServer/PostgreSQL/Oracle/Sqlite;结束语这个点我还没吃晚饭,对今天更新的 v0.1.11 作两个小时的测试。觉得好请献上宝贵一星,谢谢支持!github: https://github.com/2881099/FreeSql ...

March 19, 2019 · 1 min · jiezi

程序员修仙之路--突破内存限制的高性能排序

菜菜的涨工资申请还在待审批中….作为一个技术人员,技术的问题还是要解决。经过线上日志的分析,日志采用小时机制,一个小时一个日志文件,同一个小时的日志文件有多个,也就是说同一时间内的日志有可能分散在多个日志文件中,这也是Y总要合并的主要原因。每个日志文件大约有500M,大约有100个。此时,如果你阅读到此文章,该怎么做呢?不如先静心想2分钟!!问题分析要想实现Y总的需求其实还是有几个难点的:如何能把所有的日志文件按照时间排序日志文件的总大小为500M*100 ,大约50G,所以全部加载到内存是不可能的程序执行过程中,要频繁排序并查找最小元素。那我们该怎么做呢?其中一个解决方案就是它:堆解决方案堆定义堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:堆中某个节点的值总是不大于或不小于其父节点的值堆总是一棵完全二叉树(完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列)对于每个节点的值都大于等于子树中每个节点值的堆,我们叫作“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,我们叫作“小顶堆”。堆实现完全二叉树比较适合用数组来存储(链表也可以实现)。为什么这么说呢?用数组来存储完全二叉树是非常节省存储空间的。因为我们不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。经过上图可以发现,数组位置0为空,虽然浪费了一个存储空间,但是当计算元素在数组位置的时候确非常方便:数组下标为X的元素的左子树的下标为2x,右子树的下标为2x+1。其实实现一个堆非常简单,就是顺着元素所在的路径,向上或者向下对比然后交换位置。添加元素添加元素的时候我们习惯采用自下而上的调整方式来调整堆,我们在数组的最后一个空闲位置插入新元素,按照堆的下标上标原则查找到父元素对比,如果小于父元素的值(大顶堆),则互相交换。如图:删除最大(最小元素)对于大顶堆,堆顶的元素就是最大元素。删除该元素之后,我们需要把第二大元素提到堆顶位置。依次类推,直到把路径上的所有元素都调整完毕。扩展阅读小顶堆的顶部元素其实就是整个堆最小的元素,大顶堆顶部元素是整个堆的最大元素。这也是堆排序的最大优点,取最小元素或者最大元素时间复杂度为O(1)删除元素的时候我们要注意一点,如果采用自顶向下交换元素的方式,在很多情况下造成堆严重的不平衡(左右子树深度相差较大)的情况,为了防止类似情况,我们可以把最后一个元素提到堆顶,然后调整的策略,因为最后一个元素总是在最后一级,不会造成左右子树相差很大的情况。对于有重复元素的堆,一种解决方法是认为是谁先谁大,后进入堆的元素小于先进入堆的元素,这样在查找的时候一定要查彻底才行。另外一种方式是在堆的每个元素中存储一个链表,用来存放相同的元素,原理类似于散列表。不过这样在删除这个元素的时候需要特殊处理一下。删除堆顶数据和往堆中插入数据的时间复杂度都是 O(logn)。不断调整堆的过程其实就是排序过程,在某些场景下,我们可以利用堆来实现排序。asp.net core 模拟代码以下代码经过少许修改甚至不修改的情况下可直接在生产环境应用小顶堆实现代码 /// <summary> /// 小顶堆,T类型需要实现 IComparable 接口 /// </summary> class MinHeap<T> where T : IComparable { private T[] container; // 存放堆元素的容器 private int capacity; // 堆的容量,最大可以放多少个元素 private int count; // 堆中已经存储的数据个数 public MinHeap(int _capacity) { container = new T[_capacity + 1]; capacity = _capacity; count = 0; } //插入一个元素 public bool AddItem(T item) { if (count >= capacity) { return false; } ++count; container[count] = item; int i = count; while (i / 2 > 0 && container[i].CompareTo(container[i / 2]) < 0) { // 自下往上堆化,交换 i 和i/2 元素 T temp = container[i]; container[i] = container[i / 2]; container[i / 2] = temp; i = i / 2; } return true; } //获取最小的元素 public T GetMinItem() { if (count == 0) { return default(T); } T result = container[1]; return result; } //删除最小的元素,即堆顶元素 public bool DeteleMinItem() { if (count == 0) { return false; } container[1] = container[count]; container[count] = default(T); –count; UpdateHeap(container, count, 1); return true; } //从某个节点开始从上向下 堆化 private void UpdateHeap(T[] a, int n, int i) { while (true) { int maxPos = i; //遍历左右子树,确定那个是最小的元素 if (i * 2 <= n && a[i].CompareTo(a[i * 2]) > 0) { maxPos = i * 2; } if (i * 2 + 1 <= n && a[maxPos].CompareTo(a[i * 2 + 1]) > 0) { maxPos = i * 2 + 1; } if (maxPos == i) { break; } T temp = container[i]; container[i] = container[maxPos]; container[maxPos] = temp; i = maxPos; } } }模拟日志文件内容//因为需要不停的从log文件读取内容,所以需要一个和log文件保持连接的包装 class LogInfoIndex : IComparable { //标志内容来自于哪个文件 public int FileIndex { get; set; } //具体的日志文件内容 public LogInfo Data { get; set; } public int CompareTo(object obj) { var tempInfo = obj as LogInfoIndex; if (this.Data.Index > tempInfo.Data.Index) { return 1; } else if (this.Data.Index < tempInfo.Data.Index) { return -1; } return 0; } } class LogInfo { //用int来模拟datetime 类型,因为用int 看的最直观 public int Index { get; set; } public string UserName { get; set; } }生成模拟日志程序 static void WriteFile() { int fileCount = 0; while (fileCount < 10) { string filePath = $@“D:\log{fileCount}.txt”; int index = 0; while (index < 100000) { LogInfo info = new LogInfo() { Index = index, UserName = Guid.NewGuid().ToString() }; File.AppendAllText(filePath, JsonConvert.SerializeObject(info)+ “\r\n”); index++; } fileCount++; } }文件内容如下:测试程序 static void Main(string[] args) { int heapItemCount = 10; int startIndex = 0; StreamReader[] allReader = new StreamReader[10]; MinHeap<LogInfoIndex> container = new MinHeap<LogInfoIndex>(heapItemCount); //首先每个文件读取一条信息 while(startIndex< heapItemCount) { string filePath = $@“D:\log{startIndex}.txt”; System.IO.StreamReader reader = new System.IO.StreamReader(filePath); allReader[startIndex] = reader; string content= reader.ReadLine(); var contentObj = JsonConvert.DeserializeObject<LogInfo>(content); LogInfoIndex item = new LogInfoIndex() { FileIndex= startIndex , Data= contentObj }; container.AddItem(item); startIndex++; } //然后开始循环出堆,入堆 while (true) { var heapFirstItem = container.GetMinItem(); if (heapFirstItem == null) { break; } container.DeteleMinItem(); File.AppendAllText($@“D:\log\total.txt”, JsonConvert.SerializeObject(heapFirstItem.Data) + “\r\n”); var nextContent = allReader[heapFirstItem.FileIndex].ReadLine(); if (string.IsNullOrWhiteSpace( nextContent)) { //如果其中一个文件已经读取完毕 则跳过 continue; } var contentObj = JsonConvert.DeserializeObject<LogInfo>(nextContent); LogInfoIndex item = new LogInfoIndex() { FileIndex = heapFirstItem.FileIndex, Data = contentObj }; container.AddItem(item); } //释放StreamReader foreach (var reader in allReader) { reader.Dispose(); } Console.WriteLine(“完成”); Console.Read(); }结果如下:添加关注,查看更精美版本,收获更多精彩 ...

March 16, 2019 · 3 min · jiezi

程序员修仙之路--把用户访问记录优化到极致

祝愿大家不要像菜菜这般苦逼,年中奖大大滴在没有年终奖的日子里,工作依然还要继续…..一张冰与火的图尽显无奈还记得菜菜不久之前设计的用户空间吗?没看过的同学请进传送门=》设计高性能访客记录系统还记得遗留的什么问题吗?菜菜来重复一下,在用户访问记录的缓存中怎么来判断是否有当前用户的记录呢?链表虽然是我们这个业务场景最主要的数据结构,但并不是当前这个问题最好的解决方案,所以我们需要一种能快速访问元素的数据结构来解决这个问题?那就是今天我们要谈一谈的 散列表散列表散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。散列表其实可以约等于我们常说的Key-Value形式。散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。为什么要用数组呢?因为数组按照下标来访问元素的时间复杂度为O(1),不明白的同学可以参考菜菜以前的关于数组的文章。既然要按照数组的下标来访问元素,必然也必须考虑怎么样才能把Key转化为下标。这就是接下来要谈一谈的散列函数。散列函数散列函数通俗来讲就是把一个Key转化为数组下标的黑盒。散列函数在散列表中起着非常关键的作用。散列函数,顾名思义,它是一个函数。我们可以把它定义成hash(key),其中 key 表示元素的键值,hash(key) 的值表示经过散列函数计算得到的散列值。那一个散列函数有哪些要求呢?散列函数计算得到的值是一个非负整数值。如果 key1 = key2,那hash(key1) == hash(key2)如果 key1 ≠ key2,那hash(key1) ≠ hash(key2)简单说一下以上三点,第一点:因为散列值其实就是数组的下标,所以必须是非负整数(>=0),第二点:同一个key计算的散列值必须相同。重点说一下第三点,其实第三点只是理论上的,我们想象着不同的Key得到的散列值应该不同,但是事实上,这一点很难做到。我们可以反证一下,如果这个公式成立,我计算无限个Key的散列值,那散列表底层的数组必须做到无限大才行。像业界比较著名的MD5、SHA等哈希算法,也无法完全避免这样的冲突。当然如果底层的数组越小,这种冲突的几率就越大。所以一个完美的散列函数其实是不存在的,即便存在,付出的时间成本,人力成本可能超乎想象。散列冲突既然再好的散列函数都无法避免散列冲突,那我们就必须寻找其他途径来解决这个问题。寻址如果遇到冲突的时候怎么办呢?方法之一是在冲突的位置开始找数组中空余的空间,找到空余的空间然后插入。就像你去商店买东西,发现东西卖光了,怎么办呢?找下一家有东西卖的商家买呗。不管采用哪种探测方法,当散列表中空闲位置不多的时候,散列冲突的概率就会大大提高。为了尽可能保证散列表的操作效率,一般情况下,我们会尽可能保证散列表中有一定比例的空闲槽位。我们用装载因子(load factor)来表示空位的多少。散列表的装载因子 = 填入表中的元素个数 / 散列表的长度装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降. 假设散列函数为 f=(key%1000),如下图所示链地址法(拉链法)拉链法属于一种最常用的解决散列值冲突的方式。基本思想是数组的每个元素指向一个链表,当散列值冲突的时候,在链表的末尾增加新元素。查找的时候同理,根据散列值定位到数组位置之后,然后沿着链表查找元素。如果散列函数设计的非常糟糕的话,相同的散列值非常多的话,散列表元素的查找会退化成链表查找,时间复杂度退化成O(n)再散列法这种方式本质上是计算多次散列值,那就必然需要多个散列函数,在产生冲突时再使用另一个散列函数计算散列值,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。建立一个公共溢出区至于这种方案网络上介绍的比较少,一般应用的也比较少。可以这样理解:散列值冲突的元素放到另外的容器中,当然容器的选择有可能是数组,有可能是链表甚至队列都可以。但是无论是什么,想要保证散列表的优点还是需要慎重考虑这个容器的选择。扩展阅读这里需要在强调一次,散列表底层依赖的是数组按照下标访问的特性(时间复杂度为O(1)),而且一般散列表为了避免大量冲突都有装载因子的定义,这就涉及到了数组扩容的特性:需要为新数组开辟空间,并且需要把元素copy到新数组。如果我们知道数据的存储量或者数据的大概存储量,在初始化散列表的时候,可以尽量一次性分配足够大的空间。避免之后的数组扩容弊端。事实证明,在内存比较紧张的时候,优先考虑这种一次性分配的方案也要比其他方案好的多。散列表的寻址方案中,有一种特殊情况:如果我寻找到数组的末尾仍然无空闲位置,怎么办呢?这让我想到了循环链表,数组也一样,可以组装一个循环数组。末尾如果无空位,就可以继续在数组首位继续搜索。关于散列表元素的删除,我觉得有必要说一说。首先基于拉链方式的散列表由于元素在链表中,所有删除一个元素的时间复杂度和链表是一样的,后续的查找也没有任何问题。但是寻址方式的散列表就不同了,我们假设一下把位置N元素删除,那N之后相同散列值的元素就搜索不出来了,因为N位置已经是空位置了。散列表的搜索方式决定了空位置之后的元素就断片了….这也是为什么基于拉链方式的散列表更常用的原因之一吧。在工业级的散列函数中,元素的散列值做到尽量平均分布是其中的要求之一,这不仅仅是为了空间的充分利用,也是为了防止大量的hashCode落在同一个位置,设想在拉链方式的极端情况下,查找一个元素的时间复杂度退化成在链表中查找元素的时间复杂度O(n),这就导致了散列表最大特性的丢失。拉链方式实现的链表中,其实我更倾向于使用双向链表,这样在删除一个元素的时候,双向链表的优势可以同时发挥出来,这样可以把散列表删除元素的时间复杂度降低为O(1)。在散列表中,由于元素的位置是散列函数来决定的,所有遍历一个散列表的时候,元素的顺序并非是添加元素先后的顺序,这一点需要我们在具体业务应用中要注意。Net Core c# 代码有几个地方菜菜需要在强调一下:在当前项目中用的分布式框架为基于Actor模型的Orleans,所以我每个用户的访问记录不必担心多线程问题。我没用使用hashtable这个数据容器,是因为hashtable太容易发生装箱拆箱的问题。使用双向链表是因为查找到了当前元素,相当于也查找到了上个元素和下个元素,当前元素的删除操作时间复杂度可以为O(1)用户访问记录的实体 class UserViewInfo { //用户ID public int UserId { get; set; } //访问时间,utc时间戳 public int Time { get; set; } //用户姓名 public string UserName { get; set; } }用户空间添加访问记录的代码class UserSpace { //缓存的最大数量 const int CacheLimit = 1000; //这里用双向链表来缓存用户空间的访问记录 LinkedList<UserViewInfo> cacheUserViewInfo = new LinkedList<UserViewInfo>(); //这里用哈希表的变种Dictionary来存储访问记录,实现快速访问,同时设置容量大于缓存的数量限制,减小哈希冲突 Dictionary<int, UserViewInfo> dicUserView = new Dictionary<int, UserViewInfo>(1250); //添加用户的访问记录 public void AddUserView(UserViewInfo uv) { //首先查找缓存列表中是否存在,利用hashtable来实现快速查找 if (dicUserView.TryGetValue(uv.UserId, out UserViewInfo currentUserView)) { //如果存在,则把该用户访问记录从缓存当前位置移除,添加到头位置 cacheUserViewInfo.Remove(currentUserView); cacheUserViewInfo.AddFirst(currentUserView); } else { //如果不存在,则添加到缓存头部 并添加到哈希表中 cacheUserViewInfo.AddFirst(uv); dicUserView.Add(uv.UserId, uv); } //这里每次都判断一下缓存是否超过限制 if (cacheUserViewInfo.Count > CacheLimit) { //移除缓存最后一个元素,并从hashtable中删除,理论上来说,dictionary的内部会两个指针指向首元素和尾元素,所以查找这两个元素的时间复杂度为O(1) var lastItem = cacheUserViewInfo.Last.Value; dicUserView.Remove(lastItem.UserId); cacheUserViewInfo.RemoveLast(); } } }添加关注,查看更精美版本,收获更多精彩 ...

March 15, 2019 · 1 min · jiezi

C# ::作用域运算符

什么是作用域(scope)?简单来说,是变量在程序中的起作用范围。一般来说,一个变量只有在特定的区域内是有意义的,那么,限定这个变量的有效区域的代码范围就是这个变量的作用域。作用域的使用能够减少名字冲突,即在不同的作用域可以存在相同名字的变量。在C#中,变量的作用域在他所处的最近的一对花括号内。还有一个被称作全局作用域的,它的范围够覆盖能创建的新文件的全部上下文。作用域运算符:“::”通常情况下,调用变量的时候,最先调用的是距离最近的当前作用域的变量。即,作用域越小优先级越高。如果希望在局部变量的作用域内使用同名的全局变量,可以在该变量前加上“::” ,“::”称为作用域运算符。如果有两个同名变量,一个是全局变量,另一个是局部变量,那么局部变量在其作用域内具有较高的优先权,它将屏蔽全局变量。所以,作用域运算符可以用来解决局部变量与全局变量的重名问题,即在局部变量的作用域内,可用::对被屏蔽的同名的全局变量进行访问。例如,在下面的代码中,Console 解析为 TestApp.Console 而不是 System 命名空间中的 Console 类型。using System;namespace Test{ class TestApp { //定义一个名为“System”的新类来引发问题。 public class System { } //定义一个名为“Console”的常量以引发更多问题。 const int Console = 7; const int number = 66; static void Main() => //以下行导致错误。 它访问TestApp.Console, //这是一个常数。 //Console.WriteLine(); //使用 System.Console 仍会导致错误,因为类 TestApp.System 隐藏了 System 命名空间: // 以下行导致错误。 它访问TestApp.System,没有Console.WriteLine方法。 //System.Console.WriteLine(number); //但是,可以使用 global::System.Console 解决此错误,如下所示: // OK global::System.Console.WriteLine(number);}显然,不建议将自己的命名空间的名称创建为 System,并且不可能会遇到发生此情况的代码。 但是,在大型项目中,很有可能会以一种或另一种形式发生命名空间重复。 在这些情况下,全局命名空间限定符可保证指定根命名空间。参考:如何:使用全局命名空间别名(C# 编程指南)

March 15, 2019 · 1 min · jiezi

【C#】虹软Arc人脸识别 ArcFace 2.0 demo

环境:win7以上 VS2013以上sdk版本:ArcFace v2.0x86 x64平台Debug、Release配置都已通过编译下载地址:https://github.com/ArcsoftEsc…配置过程:到虹软官网下载SDK2.0版本,点击下载解压下载的x86或x64的zip包,本Demo以x64为例选择平台,如:,将libarcsoft_face.dll和libarcsoft_face_engine.dll放到binx64Debug下修改工程下App.config配置文件中的APP_ID和对应的SDKKEY64的值点击启动或者F5启动;其他详细信息请阅读项目中doc目录下的说明文档;常见问题:1.后引擎初始化失败 (1)请选择对应的平台,如x64,x86 (2)删除bin下面对应的asf_install.dat,freesdk_132512.dat; (3)请确保App.config下的appid,和appkey与当前sdk一一对应。 2.SDK支持那些格式的图片人脸检测? 目前SDK支持的图片格式有jpg,jpeg,png,bmp等。 3.使用人脸检测功能对图片大小有要求吗? 推荐的图片大小最大不要超过2M,因为图片过大会使人脸检测的效率不理想,当然图片也不宜过小,否则会导致无法检测到人脸。4.使用人脸识别引擎提取到的人脸特征信息是什么? 人脸特征信息是从图片中的人脸上提取的人脸特征点,是byte[]数组格式。5.SDK人脸比对的阈值设为多少合适? 推荐值为0.8,用户可根据不同场景适当调整阈值。 6.可不可以将人脸特征信息保存起来,等需要进行人脸比对的时候直接拿保存好的人脸特征进行比对? 可以,当人脸个数比较多时推荐先存储起来,在使用时直接进行比对,这样可以大大提高比对效率。存入数据库时,请以Blob的格式进行存储,不能以string或其他格式存储。 7.在.Net项目中出现堆栈溢出问题,如何解决? .Net平台设置的默认堆栈大小为256KB,SDK中需要的大小为512KB以上,推荐调整堆栈的方法为: new Thread(new ThreadStart(delegate { ASF_MultiFaceInfo multiFaceInfo = FaceUtil.DetectFace(pEngine, imageInfo); }), 1024 * 512).Start(); 8.X86模式下批量注册人脸有内存溢出或图片空指针 请增加虚拟内存或每次批量注册人脸控制在20张图片范围内 9.图片中有人脸,但是检测时未检测到人脸 (1)请调整detectFaceScaleVal的值; (2)请确认图片的宽度是否为4的倍数; (3)请确认图片是否通过ImageUtil.ReadBMP方法进行数据调整。感谢 虹软提供免费离线的人脸识别SDK

March 14, 2019 · 1 min · jiezi

PHP比特币开发系列教程汇总

创建一个机器人机器人接受比特币并立即退还用户创建比特币钱包其他编程语言比特币开发教程如下:Python 比特币开发教程Go 比特币开发教程Java 比特币开发教程Node.js 比特币开发教程C# 比特币开发教程开发者资源汇总

March 12, 2019 · 1 min · jiezi

程序员修仙之路-数据结构之设计一个高性能线程池

原因排查经过一个多小时的代码排查终于查明了线上程序线程数过多的原因:这是一个接收mq消息的一个服务,程序大体思路是这样的,监听的线程每次收到一条消息,就启动一个线程去执行,每次启动的线程都是新的。说到这里,咱们就谈一谈这个程序有哪些弊端呢:每次收到一条消息都创建一个新的线程,要知道线程的资源对于系统来说是很昂贵的,消息处理完成还要销毁这个线程。这个程序用到的线程数量是没有限制的。当线程到达一定数量,程序反而因线程在cpu切换开销的原因处理效率降低。无论的你的服务器cpu是多少核心,这个现象都有发生的可能。解决问题线程多的问题该怎么解决呢,增加cpu核心数?治标不治本。对于开发者而言,最为常用也最为有效的是线程池化,也就是说线程池。线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。线程池其中一项很重要的技术点就是任务的队列,队列虽然属于一种基础的数据结构,但是发挥了举足轻重的作用。队列队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列是一种采用的FIFO(first in first out)方式的线性表,也就是经常说的先进先出策略。实现数组队列可以用数组Q[1…m]来存储,数组的上界m即是队列所容许的最大容量。在队列的运算中需设两个指针:head,队头指针,指向实际队头元素+1的位置;tail,队尾指针,指向实际队尾元素位置。一般情况下,两个指针的初值设为0,这时队列为空,没有元素。以下为一个简单的实例(生产环境需要优化):public class QueueArray<T> { //队列元素的数组容器 T[] container = null; int IndexHeader, IndexTail; public QueueArray(int size) { container = new T[size]; IndexHeader = 0; IndexTail = 0; } public void Enqueue(T item) { //入队的元素放在头指针的指向位置,然后头指针前移 container[IndexHeader] = item; IndexHeader++; } public T Dequeue() { //出队:把尾元素指针指向的元素取出并清空(不清空也可以)对应的位置,尾指针前移 T item = container[IndexTail]; container[IndexTail] = default(T); IndexTail++; return item; } }链表队列采用的FIFO(first in first out),新元素总是被插入到链表的尾部,而读取的时候总是从链表的头部开始读取。每次读取一个元素,释放一个元素。所谓的动态创建,动态释放。因而也不存在溢出等问题。由于链表由元素连接而成,遍历也方便。以下是一个实例仅供参考:public class QueueLinkList<T> { LinkedList<T> contianer = null; public QueueLinkList() { contianer = new LinkedList<T>(); } public void Enqueue(T item) { //入队的元素其实就是加入到队尾 contianer.AddLast(item); } public T Dequeue() { //出队:取链表第一个元素,然后把这个元素删除 T item = contianer.First.Value; contianer.RemoveFirst(); return item; } }队列扩展阅读队列通过数组来实现的话有什么问题吗?是的。首先基于数组不可变本质的因素(具体可参考菜菜之前的文章),当一个队列的元素把数组沾满的时候,数组扩容是有性能问题的,数组的扩容过程不只是开辟新空间分配内存那么简单,还要有数组元素的copy过程,更可怕的是会给GC造成极大的压力。如果数组比较小可能影响比较小,但是当一个数组比较大的时候,比如占用500M内存的一个数组,数据copy其实会造成比较大的性能损失。队列通过数组来实现,随着头指针和尾指针的位置移动,尾指针最终会指向第一个元素的位置,也就是说没有元素可以出队了,其实要解决这个问题有两种方式,其一:在出队或者入队的过程中不断的移动所有元素的位置,避免上边所说的极端情况发生;其二:可以把数组的首尾元素连接起来,使其成为一个环状,也就是经常说的循环队列。队列在一些特殊场景下其实还有一些变种,比如说循环队列,阻塞队列,并发队列等,有兴趣的同学可以去研究一下,这里不在展开讨论。这里说到阻塞队列就多说一句,其实用阻塞队列可以实现一个最基本的生产者消费者模式。当队列用链表方式实现的时候,由于链表的首尾操作时间复杂度都是O(1),而且没有空间大小的限制,所以一般的队列用链表实现更简单。当队列中无元素可出队或者没有空间可入队的时候,是阻塞当前的操作还是返回错误信息,取决于在座各位队列的设计者了。简单实用的线程池 //线程池 public class ThreadPool { bool PoolEnable = false; //线程池是否可用 List<Thread> ThreadContainer = null; //线程的容器 ConcurrentQueue<ActionData> JobContainer = null; //任务的容器 public ThreadPool(int threadNumber) { PoolEnable = true; ThreadContainer = new List<Thread>(threadNumber); JobContainer = new ConcurrentQueue<ActionData>(); for (int i = 0; i < threadNumber; i++) { var t = new Thread(RunJob); ThreadContainer.Add(t); t.Start(); } } //向线程池添加一个任务 public void AddTask(Action<object> job,object obj, Action<Exception> errorCallBack=null) { if (JobContainer != null) { JobContainer.Enqueue(new ActionData { Job = job, Data = obj , ErrorCallBack= errorCallBack }); } } //终止线程池 public void FinalPool() { PoolEnable = false; JobContainer = null; if (ThreadContainer != null) { foreach (var t in ThreadContainer) { //强制线程退出并不好,会有异常 //t.Abort(); t.Join(); } ThreadContainer = null; } } private void RunJob() { while (true&& JobContainer!=null&& PoolEnable) { //任务列表取任务 ActionData job=null; JobContainer?.TryDequeue(out job); if (job == null) { //如果没有任务则休眠 Thread.Sleep(10); continue; } try { //执行任务 job.Job.Invoke(job.Data); } catch(Exception error) { //异常回调 job?.ErrorCallBack(error); } } } } public class ActionData { //执行任务的参数 public object Data { get; set; } //执行的任务 public Action<object> Job { get; set; } //发生异常时候的回调方法 public Action<Exception> ErrorCallBack { get; set; } }使用 ThreadPool pool = new ThreadPool(100); for (int i = 0; i < 5000; i++) { pool.AddTask((obj) => { Console.WriteLine($"{obj}__{System.Threading.Thread.CurrentThread.ManagedThreadId}"); }, i, (e) => { Console.WriteLine(e.Message); }); } pool.FinalPool(); Console.Read();添加关注,查看更精美版本,收获更多精彩 ...

March 11, 2019 · 2 min · jiezi

abp+ vue 实战总结

下载官方abp项目vue由于第一次使用vue,完全属于一问三不知的情况,所以点到vue目录,先读ReadMevue介绍# ASP.NET Boilerplate VueJS TemplateThe Vue.js integration for ABP Boilerplate framework. This template is built on Vue+iview+Typescript.## Getting Started### Installingshcd vueyarn install And then startyarn serve## Deploymentshyarn build## Built With* Vue - The Progressive JavaScript Framework* Typescript - Used for static typing* Vuex - Vuex is a state management pattern + library for Vue.js applications. * iView - A High quality and rich functions, friendly APIs, free and flexible UI Toolkit based on Vue.js.了解到运行vue需要Yarn,百度得 yarn 和npm都属于包管理工具 yarn官网下载安装 yarn输入 yarn –version 检测是否安装成功 ...

March 9, 2019 · 1 min · jiezi

跨越逐梦路上的荆棘(程序猿生存篇)

时光荏苒,岁月如梭。曾经的小学生,初中生现如今已是社会的中流砥柱。随着科技时代的迅猛发展,曾经的荒土,已是星罗棋布高楼耸立。其中不乏科技相关的楼宇,俗称互联网公司。也许你,现在就正处于其中。一个互联网公司要正常运作,必定就需要一群足智多谋,思维敏捷,逻辑缜密的程序猿。写的一手好代码,注释都能写出花,懂得算法与结构,调试上线都不怕。你还记得美国动漫《超人》吗?超人虽然拥有近乎无限的超能力,但仍有着很多弱点,怕氪石(绿色水晶)就是其中一个。这群能力超凡的人类,其实也有不堪一击的致命弱点 。或许你会想,技术更新快 学不动? 需求总是改 想狗带? 这些都是工作层面所带来的,其实我想说的是我们的身体。 很多时候,我们为了工作,夜以继日,披星戴月。不辞辛苦的开发,测试。为的就是得到他人的认可,同时证明自己,顺便领取一部分薪酬。但往往有些人认为,it行业高薪就业,为了钱不要怂,就是干,我给钱,你就给我干!但我所希望的是这份钱买来的是快乐和健康,而不是病痛与泪水。我们透支了自己的身体,去换取自己应得的报酬。同时我们也该拥有同等的尊重。该休息,陪伴家人的时候,不要用单位的一些条款和福利来约束我应享有的自由。 为你工作,并不代表为你卖命。 我希望每一位程序员都应该爱护自己的身体,多多锻炼,少少加班。最后我想问一个选择题:换做是你,你会选什么?(能分享分享吗?)let salary = (心理预期值 * 3/4) 举例:7500 = 10K * 3/4A.月薪 = salary && 双休 && 不加班复制代码B.月薪 = salary*(1+1/2) && (996工作制)复制代码C.月薪 = (salary*2+股份) && (997工作制)复制代码D.欢迎补充…

March 9, 2019 · 1 min · jiezi

分布式系统「伸缩性」大招之——「水平&垂直切分」详解

如果第二次看到我的文章,欢迎下方扫码订阅我的个人公众号(跨界架构师)哟本文长度为5389字,建议阅读14分钟。坚持原创,每一篇都是用心之作~没想到这篇文章写了这么长,一时半会没消化完的话,可以收藏一下先。这是「伸缩性」章节的第四篇,先给新来的小伙伴们简单回顾下前三篇的内容。做「伸缩性」最重要的就是先做好「无状态」,如此才可以随心所欲的进行横向“扩展”,而不用担心在多个副本之间切换会产生错乱。《分布式系统关注点——「无状态」详解》聊的就是这个。不过,就算做好了横向扩展,本质上还是一个“大程序”,只是变得「可复制」了而已。如果要消灭“大程序”,那就得“切分”,做好切分必然离不开「高内聚低耦合」的核心思想。《分布式系统关注点——「高内聚低耦合」详解》这篇聊的就是这个。题外话:当你遇到单点单应用支撑不住使用的时候,Z哥给你的普适性建议是:先考虑“扩”,再考虑“切”。这个和写代码一样,“增加”新功能往往比在老功能上改容易。“扩”的话先考虑「垂直扩」(加硬件,钱能解决的都不是问题),再考虑「水平扩」(无状态改造+多节点部署,这是小手术)。“切”的话一般就是「垂直切」(根据业务切分,这是大手术),偶尔会用到「水平切」(其实就是单个应用里的分层,比如前后端分离)。第三篇《分布式系统关注点——弹性架构》我们聊了常见的两种「松耦合」架构模式,为的是让应用程序的「伸缩性」更上一层楼。以上这些呢都是应用程序层面的工作。一般情况下,在应用程序层面做做手术,再配合以缓存的充分运用,就可以支撑系统发展很长时间了。特别是数据量不大,只是请求量大的「CPU密集型」场景。但是,如果所处的工作场景是一个非常成熟且具有一定规模的项目,越发展到后面瓶颈总是出现在数据库这里。甚至会出现cpu长期高负荷、宕机等现象。在如此场景下,就不得不对数据库开刀了。这次Z哥就来和你聊聊做数据库的「伸缩性」有哪些好方法。核心诉求面临数据库需要开刀的时候,整个系统往往已经长成这个样子了。正如前面所说,这时候的瓶颈往往会体现在「CPU」上。因为对数据库来说,硬盘和内存的扩容相对容易,因为它们都可以直接用“增加”的方式进行。CPU就不同了,一旦CPU飙高,最多检查下索引有没有做好,完了之后基本就只能干看着。所以解决这个问题的思路自然就变成了:如何将一个数据库的CPU压力分摊到多个CPU上去。甚至可以做到按需随时增加。那这不就是和应用程序一样做「切分」嘛。也是分布式系统的「分治」思想体现。既然是切分,本质上就和应用程序一样,也分为「垂直切分」和「水平切分」。垂直切分垂直切分有时候也会被称作「纵向切分」。同应用程序一样,它是以「业务」为维度的切分方式,在不同的数据库服务器上跑不同业务的数据库,各司其职。一般情况下,Z哥建议你优先考虑「垂直切分」而不是「水平切分」,为什么呢?你可以随意打开手头项目中的SQL语句看看,我想必然存在着大量的「join」和「transaction」关键字,这种关联查询和事务操作,本质上是一种「关系捆绑」,一旦面临数据库拆分之后,就没法玩了。此时你只有2个选择。要么将不必要的「关系捆绑」逻辑舍弃掉,这需要在业务上作出调整,去除不必要的“批量操作”业务,或者去除不必要的强一致性事务。不过你也知道,肯定有一些场景是去不完的。要么将「合并」,「关联」等逻辑上浮,体现到业务逻辑层甚至是应用层的代码中。最终,不管怎么选择,改动起来都是一个大工程。为了让这个工程尽可能的动作小一些,追求更好的性价比,需要坚持一个原则——“避免拆分紧密关联的表”。因为两个表之间关联越紧密,意味着对「join」和「transaction」的需求越多,所以坚持这个原则可以使得相同的模块,紧密相关的业务都落在同一个库中,这样它们可以继续使用「join」和「transaction」来工作。因此,我们应当优先采用「垂直切分」的方式。做「垂直切分」思路很简单,一般情况下,建议是与切分后的应用程序一一对应就好,不用多也不用少。实际工作中,要做好「垂直切分」主要体现在「业务」的熟悉度上,所以这里就不继续展开了。「垂直切分」的优点是:高内聚,拆分规则清晰。相比「水平切分」数据冗余度更低。与应用程序是1:1的关系,方便维护和定位问题。一旦某个数据库中发现异常数据,排查这个数据库的关联程序就行了。但是这并不是一个「一劳永逸」的方案,因为没人能预料到未来业务会发展的怎么样,所以最明显的缺点就是:对于访问极其频繁或者数据量超大的表仍然存在性能瓶颈。确实需要解决这个问题的话,就需要搬出「水平切分」了。题外话:不到迫不得己,尽量避免进行「水平切分」。看完接下去的内容你就知道原因了。下面Z哥就给你好好聊聊「水平切分」,这才是本文的重点。水平切分想象一下,在你做了「垂直切分」之后,还是在某个数据库中发现了一张数据量超过10亿条的表。这个时候要对这个表做「水平切分」,你会怎么思考这个事情?Z哥教给你的思路是:先找到“最高频“的「读」字段。再看这个字段的实际使用中有什么特点(批量查询多还是单个查询多,是否同时是其它表的关联字段等等)。再根据这个特点选择合适的切分方案。为什么要先找到高频的「读」字段呢?因为在实际的使用中,「读」操作往往是远大于「写」操作的。一般进行「写」之前都得通过「读」来做先行校验,然而「读」还有自己单独的使用场景。所以针对更高频的「读」场景去考虑,产生的价值必然也更大。比如,现在那张10亿数据量的表是一张订单表,结构是这样:order (orderId long, createTime datetime, userId long)下面我们先来看看有哪几种「水平切分」的方式,完了才能明白什么样的场景适合哪种方式。范围切分这是一种「连续式」的切分方式。比如根据时间(createTime)切分的话,我们可以按年月来分,order_201901一个库,order_201902一个库,以此类推。根据顺序数(orderId)切分的话,可以100000~199999一个库,200000~299999一个库,以此类推。这种切分法的优点是:单个表的大小可控,扩展的时候无需数据迁移。缺点也很明显,一般来说时间越近或者序号越大的数据越“新”,因此被访问的频率和概率相比“老”数据更多。会导致压力主要集中在新的库中,而历史越久的库,越空闲。Hash切分与「范围切分」正好相反,这是一种「离散式」的切分方式。它的优点就是解决了「范围切分」的缺点,新数据被分散到了各个节点中,避免了压力集中在少数节点上。同样,缺点与「范围切分」的优点相反,一旦进行二次扩展,必然会涉及到数据迁移。因为Hash算法是固定的,算法一变,数据分布就变了。大多数情况下,我们的hash算法可以通过简单的「取模」运算来进行即可。就像下面这样:假如分成11个库的话,公式就是 orderId % 10。100000 % 10 = 0,分配到db0。100001 % 10 = 1,分配到db1。….100010 % 10 = 0,分配到db0。100011 % 10 = 1,分配到db1。其实,在某些场景下,我们可以通过自定义id的生成(可以参考之前的文章,《分布式系统中的必备良药 —— 全局唯一单据号生成》)来做到既可以通过hash切分来打散热点数据,又可以减少依赖全局表来定位具体的数据。比如,在orderId中加入userId的尾数,以此达到orderId和userId取模结果相等的效果。还是来举个例子:一个用户的userId是200004,如果取一个4bit尾数的话,这里就是4,用0100表示。然后,我们通过自定义id算法生成orderId的前60位,在后面补上0100。于是,orderId % 10和 userId % 10的结果就是一样的了。当然,除了userId之外还想加入其他的因子就不好使了。也就是,可以在不增加全局表的情况下,额外多支持1个维度。提到了两次全局表,那么啥是全局表呢?全局表这种方式就是将用作切分依据的分区Key与对应的每一条具体数据的id保存到一个单独的库或者表中。例如要增加一张这样的表:nodeId orderId 01 100001 02 100002 01 100003 01 100004 …如此一来,的确将大部分具体的数据分布在了不同服务器上,但是这张全局表会给人一种「形散神不散」的感觉。因为请求数据的时候无法直接定位需要的数据在哪台服务器上,所以每一次操作都要先查询一下这张全局表好知道具体的数据被存放在哪里。这种「中心化」的模式带来的副作用就是瓶颈和风险转移到了这张全局表上。但是,胜在逻辑简单。好了,那么这几种切分方案怎么选择呢?Z哥给你的建议是,如果热点数据不是特别集中的场景,建议先用「范围切分」,否则选择另外2种。选择另外两种的时候,数据量越大越倾向选择Hash切分。因为后者在整体的可用性和性能上都比前者好,就是实现成本高一些。「水平切分」真正做到了可以“无限扩展”,但是也存在相应的弊端。1)批量查询、分页等需要做更多的额外工作。特别是当一个表存在多个高频字段用于where、order by或者group by的时候。2)拆分规则不如「垂直切分」那么明确。所以还是多说一句“废话”:没有完美的方案只有合适的方案,要结合具体的场景来选择。(欢迎你在留言区提出你有疑惑的场景,和Z哥来讨论讨论)如何实施当你在具体实施「水平切分」的时候可以在2个层面动刀,可以是「表」层面,也可以是「库」层面。表在同一个数据库下面分表,表名order_0 ,order_1, order_2…..。它可以解决单表数据过大,但并不能解决CPU负荷的问题。所以,当CPU并没多少压力,只是由于表太大,导致执行SQL操作比较慢的话,可以选择这种方式。库这个时候表名可以不变,都叫order,只是分成10个库。那么就是db0-user db1-user db2-user……。我们前面大篇幅都是基于这个模式在聊,就不多说了。表+库也可以既分库又分表,比如先分10个库,然后每个库再分10张表。这其实是个二级索引的思路,通过库来进行第一次定位,减少一定的资源消耗。比如,先按年分库,再按月分表。如此一来,如果需要获取的数据只跨月但不跨年,我们就可以在单个库内做聚合运算来完成,不涉及到跨库操作。不过,不管选择哪种方式来进行,你还是会或多或少面临以下两个问题,逃不掉的。跨库join。全局聚合或者排序操作。解决第一个问题最佳方式还是需要改变你的编程思维。尽量将一些逻辑、关系、约束等体现在应用程序的代码中,避免因为方便而在SQL中做这些事情。毕竟代码是可以写成“无状态”的,可以随时做扩展,但是SQL是跟着数据走的,而数据就是“状态”,天然不利于扩展。当然了,退而求其次,你也可以冗余大量的全局表来应对。只是如此一来,对「数据一致性」工作是个很大的考验,另外,对存储资源也是很大的开销。第二个问题的解决方案就是需要将原本的一次聚合或者一次排序变成两次操作。其中的遍历多个节点可以以「并行」的方式进行。那么数据切分完之后程序如何来使用呢?这又可以分为两种模式,「进程内」和「进程外」。「进程内」的话,可以在封装好的DAL访问框架中做,也可以在ORM框架中做,还可以在数据库驱动中做。这个模式比较知名的解决方案如阿里的tddl。「进程外」的话,就是代理模式,这个模式比较知名的解决方案是mycat、cobar、atlas等等,相对多一些,因为这种模式对应用程序是「低侵入」的,使用起来像“一个数据库”。但是由于多了一道网络通信,性能上会多一些损耗。老规矩,下面再分享一些最佳实践。最佳实践首先分享两个可以不停机做数据切分的小窍门。我们以实施hash法做水平切分的例子来看一下。第一次做切分的时候,你可以以「主-从」的形式将新增的节点作为原始节点的副本,进行全量实时同步。然后在这个基础上删除不属于它的数据。(当然了,不删也没啥问题,就是多占用一些空间)这样就可以不用停机了。第二,随着时间的推移,如果后续支撑不住了,需要二次切分的话,我们可以选择用2的倍数来扩展。如此一来,数据的迁移变得很简单,只需要做局部的迁移,和第一次做切分的思路是一样的。当然了,如果选择的切分方式是「范围切分」的话,就没有二次切分时的困扰,数据自然跑到最新的节点上去了。比如我们按年月分表的话。2019年3月的数据自然就落到了xxxx_201903的表中。到这里,Z哥还是想特别强调的是,能不切分尽量不要切分,可以先使用「读写分离」之类的方案先来应对面临的问题。 如果实在要进行切分的话,务必先「垂直切分」,再考虑「水平切分」。一般来说,以这样的顺序来考虑,性价比更好。总结好了,我们总结一下。这次呢,Z哥先向你介绍了做数据库切分的两种思路。两种思路通俗理解就是:「垂直拆分」等于“列”变“行”不变,「水平拆分」等于“行”变“列”不变。然后着重聊了下「水平切分」的3种实现方式和具体实施的思路。最后分享了一些实践中的经验给你。希望对你有所启发。相关文章:分布式系统关注点——「无状态」详解分布式系统关注点——「高内聚低耦合」详解分布式系统关注点——弹性架构分布式系统中的必备良药 —— 全局唯一单据号生成作者:Zachary出处:https://www.cnblogs.com/Zacha…如果你喜欢这篇文章,可以点一下文末的「赞」。这样可以给我一点反馈。: )谢谢你的举手之劳。▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码。定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。

March 8, 2019 · 1 min · jiezi

海康威视网络摄像头-预览出现绿色移动侦测规则框

最近接触了海康威视网络摄像头的二次开发,需要下载官方提供的SDK选用的二次开发DEMO路径是解压后的: Demo示例/c#开发示例/实时预览示例代码二按照说明把相应的dll和文件夹放到工程对应的bin目录,并成功运行demo之后,预览图像中如果有移动物体,就会显示绿色的方块格状区域, 问询海康威视的工程师后,得知是默认开启了移动侦测框,取消这个设置有两种方式,以下是原文:这个是移动侦测的规则框,是播放库叠加的内容您可以直接关掉移动侦测的功能,调用接口不关闭移动侦测功能只关闭绿色格子显示有两种方式,一种是客户端取消显示,一种是设备端取消叠加:调用NET_DVR_RealPlay_V40传窗口句柄直接预览默认显示该叠加信息,此时调用NET_DVR_GetRealPlayerIndex获取播放库句柄,然后调用播放库接口PlayM4_RenderPrivateData(该接口调用请参考播放库SDK编程指南)可以控制显示或者取消显示。通过NET_DVR_GetDVRConfig(命令:NET_DVR_GET_PICCFG_V40)、NET_DVR_SetDVRConfig(命令:NET_DVR_SET_PICCFG_V40)获取和设置,其中的移动侦测参数NET_DVR_PICCFG_V40—》NET_DVR_MOTION_V40里面的byEnableDisplay设置为0即可,也可以WEB或者或者客户端软件登录设备进配置界面直接手动设置取值动态显示对于使用的C#demo来说,对应的代码如下://声明一个NET_DVR_PICCFG_V40类型的类对象public CHCNetSDK.NET_DVR_PICCFG_V40 m_struPicCfgV40;//以下是函数体内使用的代码uint dwSize = (uint)Marshal.SizeOf(m_struPicCfgV40);//一个指针和对应的结构体,SET和GET Config的时候都是使用指针,但是操作赋值的时候使用的是结构体IntPtr ptrPicCfgV40 = Marshal.AllocHGlobal((Int32)dwSize);Marshal.StructureToPtr(m_struPicCfgV40, ptrPicCfgV40, false);uint dwReturn = 0;int iGroupNo = iChannelNum[(int)iSelIndex]; //通道号 Channel numberif (!CHCNetSDK.NET_DVR_GetDVRConfig(m_lUserID, CHCNetSDK.NET_DVR_GET_PICCFG_V40, iGroupNo, ptrPicCfgV40, dwSize, ref dwReturn)){ iLastErr = CHCNetSDK.NET_DVR_GetLastError(); str = “NET_DVR_GET_PICCFG_V40 failed, error code= " + iLastErr; //获取IP资源配置信息失败,输出错误号 Failed to get configuration of IP channels and output the error code //MessageBox.Show(str);}else{ m_struPicCfgV40 = (CHCNetSDK.NET_DVR_PICCFG_V40)Marshal.PtrToStructure(ptrPicCfgV40, typeof(CHCNetSDK.NET_DVR_PICCFG_V40)); //重点是设置这里的byEnableDisplay为0,取消移动侦测框 m_struPicCfgV40.struMotion.byEnableDisplay = 0; //不要忘记结构体转换成指针 Marshal.StructureToPtr(m_struPicCfgV40, ptrPicCfgV40, true); if (!CHCNetSDK.NET_DVR_SetDVRConfig(m_lUserID, CHCNetSDK.NET_DVR_SET_PICCFG_V40, iGroupNo, ptrPicCfgV40, dwSize)) { uint iLastErr = CHCNetSDK.NET_DVR_GetLastError(); string str = “NET_DVR_SET_PICCFG_V40 failed, error code= " + iLastErr; //MessageBox.Show(str); } else { //MessageBox.Show(“NET_DVR_SET_PICCFG_V40配置成功!” + iGroupNo); CHCNetSDK.NET_DVR_GetDVRConfig(m_lUserID, CHCNetSDK.NET_DVR_GET_PICCFG_V40, iGroupNo, ptrPicCfgV40, dwSize, ref dwReturn); m_struPicCfgV40 = (CHCNetSDK.NET_DVR_PICCFG_V40)Marshal.PtrToStructure(ptrPicCfgV40, typeof(CHCNetSDK.NET_DVR_PICCFG_V40)); //MessageBox.Show(“NET_DVR_GET_PICCFG_V40!” + m_struPicCfgV40.struMotion.byEnableDisplay); }} ...

March 7, 2019 · 1 min · jiezi

.NETCore 下支持分表分库、读写分离的通用 Repository

首先声明这篇文章不是标题党,我说的这个类库是 FreeSql.Repository,它作为扩展库现实了通用仓储层功能,接口规范参数 abp vnext,定义和实现基础的仓储层(CURD)。安装dotnet add package FreeSql.Repository可用于:.net framework 4.6+、.net core 2.1+定义var fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, @“Data Source=|DataDirectory|\document.db;Pooling=true;Max Pool Size=10”) .UseLogger(loggerFactory.CreateLogger<IFreeSql>()) .UseAutoSyncStructure(true) //自动迁移实体的结构到数据库 .Build();过滤与验证假设我们有User(用户)、Topic(主题)两个实体,在某领域类中定义了两个仓储:var userRepository = fsql.GetGuidRepository<User>();var topicRepository = fsql.GetGuidRepository<Topic>();开发过程中,我总会担心 topicRepository 的数据安全问题,即有可能查询或操作到其他用户的主题。因此在v0.0.7版本进行了改进,增加了 filter lambad 表达式参数。var userRepository = fsql.GetGuidRepository<User>(a => a.Id == 1);var topicRepository = fsql.GetGuidRepository<Topic>(a => a.UserId == 1);在查询/修改/删除时附加此条件,从而达到不会修改其他用户的数据;在添加时,使用表达式验证数据的合法性,若不合法则抛出异常;有朋友说这个功能像 abp 的租户,但这是更小单位的过滤+验证,确保数据安全。有朋友说这个功能省事,但我觉得是省心。分表与分库GuidRepository 作为分存式仓储将实现了分表与分库(不支持跨服务器分库)的封装类。var logRepository = fsql.GetGuidRepository<Log>(null, oldname => $"{oldname}_{DateTime.Now.ToString(“YYYYMM”)}");上面我们得到一个日志仓储实例按年月分表,使用它 CURD 最终会操作 Log_201903 表。注意:虽然 FreeSql 支持 CodeFirst 迁移,但不提供迁移分表,开发环境中仍然可以迁移 Log 表。读写分离FreeSql 支持数据库读写分离,本功能是客户端的读写分离行为,数据库服务器该怎么配置仍然那样配置,不受本功能影响,为了方便描术后面讲到的【读写分离】都是指客户端的功能支持。各种数据库的读写方案不一,数据库端开启读写分离功能后,读写分离的实现大致分为以下几种:1、nginx代理,配置繁琐且容易出错;2、中件间,如MySql可以使用MyCat,但是其他数据库怎么办?3、在client端支持;FreeSql 实现了第3种方案,支持一个【主库】多个【从库】,【从库】的查询策略为随机方式。若某【从库】发生故障,将切换到其他可用【从库】,若已全部不可用则使用【主库】查询。出现故障【从库】被隔离起来间隔性的检查可用状态,以待恢复。以 mysql 为例:var connstr = “Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;” + “Initial Catalog=cccddd;Charset=utf8;SslMode=none;Max pool size=10”;IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.MySql, connstr) .UseSlave(“connectionString1”, “connectionString2”) //使用从数据库,支持多个 .Build();select.Where(a => a.Id == 1).ToOne(); //读【从库】(默认)select.Master().WhereId(a => a.Id == 1).ToOne(); //强制读【主库】其他特性[x] 支持 CodeFirst 迁移;[x] 支持 DbFirst 从数据库导入实体类,支持三种模板生成器;[x] 采用 ExpressionTree 高性能读取数据;[x] 支持深入的类型映射,比如pgsql的数组类型;[x] 支持丰富的表达式函数;[x] 支持导航属性查询,和延时加载;[x] 支持同步/异步数据库操作方法,丰富多彩的链式查询方法;[x] 支持读写分离、分表分库;[x] 支持多种数据库,MySql/SqlServer/PostgreSQL/Oracle/Sqlite;结束语这个点我还没吃晚饭,对今天更新的 v0.1.11 作两个小时的测试。觉得好请献上宝贵一星,谢谢支持!github: https://github.com/2881099/FreeSql ...

March 6, 2019 · 1 min · jiezi

FreeSql 如何实现 Sqlite 跨库查询

FreeSql 是 .NetFramework 4.6+、.NetCore 下的 ORM 功能库,提供了丰富的功能,支持五种流行数据库 MySql/SqlServer/PostgreSQL/Oracle/Sqlite。正常的数据库都支持跨库,然而 Sqlite 默认不支持,或者说支持起来较为麻烦,FreeSql 最关心的是通用、易用性,本文介绍 FreeSql 如何实现 Sqlite 跨库操作。故事发生在 CodeFirst 自由开发FreeSql 支持并推荐使用 CodeFirst 方式开发项目,这种开发方式非常自由,如同 FreeSql 的命名一般。如下定义两个实体(文章、评论):class Topic { public Guid Id { get; set; } public string Title { get; set; } public string Content { get; set; } public DateTime CreateTime { get; set; }}[Table(Name = “xxxtb.Comment”)]class Comment { public Guid Id { get; set; } public Guid TopicId { get; set; } public Topic Topic { get; set; } public string Nickname { get; set; } public string Content { get; set; } public DateTime CreateTime { get; set; }}我们希望将 Topic 的数据存到主库,Comment 的数据存到别外一个库(xxxtb)。比如使用 mysql,不指定数据库情况下,将操作当前数据库下的表。其他 ado.net 或 ORM 将面临的问题(默认情况下):1、驱动只打开了一个库,或者需要手工调用驱动的方法对其他库进行附加;2、项目中可能需要定义多个 orm,实现对多个数据库的存储和查询;3、无法使用跨库联表查询;解决用户使用问题使用习惯是 FreeSql 主要攻克的难题,其他数据库都行,【吐槽】就你丫的 Sqlite 奇葩,同连接下默认做不到跨库操作(或者说使用不方便)。好在如今的 .NET 库大多数都已经开源,于是翻阅 System.Data.SQLite.Core 源码做了总结:SQLiteConnection 连接对象在 Open 后,执行如下命令可附加多个数据库;attach database [xxxtb.db] as [xxxtb];于是,第一步:实现一个扩展方法,使用 OpenAndAttach 代替 Open:public static void OpenAndAttach(this DbConnection that, string[] attach) { that.Open(); if (attach?.Any() == true) { var sb = new StringBuilder(); foreach(var att in attach) sb.Append($“attach database [{att}] as [{att.Split(’.’).First()}];\r\n”); var cmd = that.CreateCommand(); cmd.CommandText = sb.ToString(); cmd.ExecuteNonQuery(); }}//异步方法的实现省略…第二步:增加 ConnectionString 参数 AttachsData Source=|DataDirectory|\document.db;Attachs=xxxtb.db;Pooling=true;Max Pool Size=10第三步:SqliteConnectionPool 实施1、解析 Attachs;var att = Regex.Split(_connectionString, @“Attachs\s*=\s*”, RegexOptions.IgnoreCase);if (att.Length == 2) { //此条件说明存在,找到 Attachs 配置的值,并且它支持以逗号分割 var idx = att[1].IndexOf(’;’); Attaches = (idx == -1 ? att[1] : att[1].Substring(0, idx)).Split(’,’);}2、将原有 Open 方法替换成 OpenAndAttach;大功告成编码与实施过程完成,接下来测试结果,仍然使用上方给出的两个实体类型。static IFreeSql sqlite = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, @“Data Source=|DataDirectory|\document.db;Attachs=xxxtb.db;Pooling=true;Max Pool Size=10”) .UseAutoSyncStructure(true) .UseLazyLoading(true) .Build();//秀一波 FreeSql.Repository 扩展包//安装方法:dotnet add package FreeSql.Repositoryvar topicRepository = sqlite.GetGuidRepository<Topic>();var commentRepository = sqlite.GetGuidRepository<Comment>();//添加测试文章Guid topicId = FreeUtil.NewMongodbId();topicRepository.Insert(new Topic { Id = FreeUtil.NewMongodbId(), Title = “文章标题1”, Content = “文章内容1”, CreateTime = DateTime.Now});//添加10条测试评论var comments = Enumerable.Range(0, 10).Select(a => new Comment { Id = FreeUtil.NewMongodbId(), TopicId = topicId, Nickname = $“昵称{a}”, Content = $“评论内容{a}”, CreateTime = DateTime.Now});var affrows = commentRepository.Insert(comments);var find = commentRepository.Select.Where(a => a.Topic.Title == “文章标题1”).ToList();//SELECT a.“Id”, a.“TopicId”, a.“Nickname”, a.“Content”, a.“CreateTime” //FROM “xxxtb”.“Comment” a, “Topic” a__Topic //WHERE (a__Topic.“Title” = ‘文章标题1’)//find 查询出了10条数据然后我们使用 navicat 附加两个数据库查看:其他说明1、FreeUtil.NewMongodbId 是生成有序的 Guid 值,在 FreeSql 中实现,其实可以使用 Guid.NewGuid,这里我承认有秀的嫌疑;2、文章中的代码没有过多的依赖,在 vs2017+.netcore2.2 测试一次运行通过,sqlite 的优势免安装服务;3、FreeSql 支持 CodeFirst,即建好实体运行程序,表就会创建;4、FreeSql.Repository 是扩展包,实现通用 CURD 仓储层功能,文档:https://github.com/2881099/FreeSql/wiki/Repository这个功能其实在 FreeSql 早期就已经实现了,但是一直没能有时间将过程经验整理成文章,今天把文章写完写全,希望能给大家带来一点“惊吓”。FreeSql还有更多细节优化并不是路人一眼能看透,如果觉得写得不错麻烦点个赞,这也是我继续写下去的源动力。一遍看不明白的话,建议多看几遍。谢谢观看! ...

March 6, 2019 · 2 min · jiezi

Unity C# 自定义事件系统

在Unity中 用C#实现自定义的事件系统用法:EventUtil.AddListener(“事件名称”, 回调方法) // 添加事件监听器EventUtil.RemoveListener(“事件名称”, 回调方法) // 移除事件监听器EventUtil.DispatchEvent(“事件名称”, 不定长参数…) // 派发事件实现效果:事件工具EventUtil.csnamespace Pers.ZY.Events { /// <summary> /// 事件工具 /// <para>ZhangYu 2019-03-04</para> /// </summary> public static class EventUtil { /// <summary> 事件派发器 </summary> private static EventDispatcher dispatcher = new EventDispatcher(); /// <summary> 添加事件监听器 </summary> /// <param name=“eventType”>事件类型</param> /// <param name=“eventHandler”>事件处理器</param> public static void AddListener(string eventType, EventListener.EventHandler eventHandler) { dispatcher.AddListener(eventType, eventHandler); } /// <summary> 移除事件监听器 </summary> /// <param name=“eventType”>事件类型</param> /// <param name=“eventHandler”>事件处理器</param> public static void RemoveListener(string eventType, EventListener.EventHandler eventHandler) { dispatcher.RemoveListener(eventType, eventHandler); } /// <summary> 是否已经拥有该类型的事件 </summary> /// <param name=“eventType”>事件类型</param> public static bool HasListener(string eventType) { return dispatcher.HasListener(eventType); } /// <summary> 派发事件 </summary> /// <param name=“eventType”>事件类型</param> public static void DispatchEvent(string eventType, params object[] args) { dispatcher.DispatchEvent(eventType, args); } /// <summary> 清理所有事件监听器 </summary> public static void Clear() { dispatcher.Clear(); } }}事件派发器EventDispatcher.csusing System.Collections.Generic;namespace Pers.ZY.Events { /// <summary> /// 事件派发器 /// <para>ZhangYu 2019-03-05</para> /// </summary> public class EventDispatcher { /// <summary> 事件Map </summary> private Dictionary<string, EventListener> dic = new Dictionary<string, EventListener>(); /// <summary> 添加事件监听器 </summary> /// <param name=“eventType”>事件类型</param> /// <param name=“eventHandler”>事件处理器</param> public void AddListener(string eventType, EventListener.EventHandler eventHandler) { EventListener invoker; if (!dic.TryGetValue(eventType, out invoker)) { invoker = new EventListener(); dic.Add(eventType, invoker); } invoker.eventHandler += eventHandler; } /// <summary> 移除事件监听器 </summary> /// <param name=“eventType”>事件类型</param> /// <param name=“eventHandler”>事件处理器</param> public void RemoveListener(string eventType, EventListener.EventHandler eventHandler) { EventListener invoker; if (dic.TryGetValue(eventType, out invoker)) invoker.eventHandler -= eventHandler; } /// <summary> 是否已经拥有该类型的事件 </summary> /// <param name=“eventType”>事件类型</param> public bool HasListener(string eventType) { return dic.ContainsKey(eventType); } /// <summary> 派发事件 </summary> /// <param name=“eventType”>事件类型</param> public void DispatchEvent(string eventType, params object[] args) { EventListener invoker; if (dic.TryGetValue(eventType, out invoker)) { EventArgs evt; if (args == null || args.Length == 0) { evt = new EventArgs(eventType); } else { evt = new EventArgs(eventType, args); } invoker.Invoke(evt); } } /// <summary> 清理所有事件监听器 </summary> public void Clear() { foreach (EventListener value in dic.Values) { value.Clear(); } dic.Clear(); } }}事件监听器EventListener.csnamespace Pers.ZY.Events { /// <summary> /// 事件监听器 /// <para>ZhangYu 2019-03-05</para> /// </summary> public class EventListener { /// <summary> 事件处理器委托 </summary> public delegate void EventHandler(EventArgs eventArgs); /// <summary> 事件处理器集合 </summary> public EventHandler eventHandler; /// <summary> 调用所有添加的事件 </summary> public void Invoke(EventArgs eventArgs) { if (eventHandler != null) eventHandler.Invoke(eventArgs); } /// <summary> 清理所有事件委托 </summary> public void Clear() { eventHandler = null; } }}事件数据EventArgs.csnamespace Pers.ZY.Events { /// <summary> 事件参数 /// <para>ZhangYu 2019-03-05</para> /// </summary> public class EventArgs { /// <summary> 事件类型 </summary> public readonly string type; /// <summary> 事件参数 </summary> public readonly object[] args; public EventArgs(string type) { this.type = type; } public EventArgs(string type, params object[] args) { this.type = type; this.args = args; } }}事件派发测试using UnityEngine;using Pers.ZY.Events;/// <summary> 派发事件测试 </summary>public class EventDispatchTest : MonoBehaviour { public void Call() { EventUtil.DispatchEvent(EventHandleTest.ON_CLICK); EventUtil.DispatchEvent(EventHandleTest.ON_CLICK2, “参数1”); } }事件接收测试using UnityEngine;using Pers.ZY.Events;/// <summary> 处理事件测试 </summary>public class EventHandleTest : MonoBehaviour { // 定义事件名称 public const string ON_CLICK = “ON_CLICK”; // 定义事件名称 public const string ON_CLICK2 = “ON_CLICK2”; private void Start () { // 添加监听器 if (!EventUtil.HasListener(ON_CLICK)) EventUtil.AddListener(ON_CLICK, OnClick); if (!EventUtil.HasListener(ON_CLICK2)) EventUtil.AddListener(ON_CLICK2, OnClick2); } // 处理点击事件 public void OnClick(EventArgs evt) { print(evt.type); print(evt.args); } // 带参数的点击事件 public void OnClick2(EventArgs evt) { print(evt.type); print(evt.args[0]); } // 移除监听器 private void OnDestroy() { EventUtil.RemoveListener(ON_CLICK, OnClick); EventUtil.RemoveListener(ON_CLICK2, OnClick2); }} ...

March 5, 2019 · 3 min · jiezi

C# 免费离线人脸识别 2.0 Demo

本来打算做个C#版demo,但没用成功。使用虹软最新人脸识别技术开发完成过程如下:1、 传入一张单人脸照片;2、调用检测人脸函数ASFDetectFaces,成功返回人脸信息的指针;3、使用 Marshal.ReadByte(intPtr,offset) 函数读出字节数,发现前16个字节是人脸框范围,第28至31个字节应该是人脸角度,其他信息不太清楚。想了下,最简单的办法是用C++将动态库再封装一次。将封装后的demo及C++代码上传。demo是X86的,只识别单张人脸,有别的需求的同学可自己封装库。https://download.csdn.net/dow…

March 5, 2019 · 1 min · jiezi

ArcFace2.0+红外双目摄像头的活体检测[Windows][C#][.NET][WPF]

废话不多说 直接上图 这个是demo中用到的双目摄像头,一个是红外的,一个是正常的rgb摄像头两个usb接口,在电脑上呈现两路摄像头通道程序检测RGB输出图像,当检测到有人脸时,用RGB人脸的位置到红外画面的位置去检测人脸如果没有检测到,说明当前目标为非活体当在红外画面检测到人脸时,说明当前目标为活体目标再继续使用RGB图像提取特征值下面为demo效果图DEMO源码地址:https://gitee.com/jch/FaceAliveDEMO中用的C#封装库为:https://github.com/Thxzzzzz/A…由于原库有BUG,所以demo中直接附加了修复了bug的源码

March 5, 2019 · 1 min · jiezi

价值6千多美金的C sharp sdk赏金活动已经结束,该召唤神龙了

于2019年1月22日启动的C sharp SDK赏金活动已经结束, wjfree/mixin-csharp-sdk获得50xin token大奖,价值6千多美金。针对另一个也非常优秀的ibigbug/Mixin-SDK-CSharp,CEO cedric特别颁发了感谢奖。通过此次赏金活动,Mixin Network获得高质量的C sharp sdk,下一步将基于sdk开发基于C sharp的教程,来向广大C sharp 程序员展示Mixin Network的特性和优势。至此Mixin Network已经拥有面向7种编程语言的9个sdk,分别是Ruby, Python, PHP, Java, Go lang{ MixinNetwork,MooooonStar}, Nodejs, C# SDK{wjfree, ibigbug}。开发者教程已经覆盖5种语言:Python, PHP, Nodejs, Go lang, JavaMixin Network还将继续为开发者提供更好的文档,教程和服务。但是,明明已经收了7种SDK,说好的神龙在哪呢?

March 4, 2019 · 1 min · jiezi

程序猿修仙之路--数据结构之你是否真的懂数组?

但凡IT江湖侠士,算法与数据结构为必修之课。早有前辈已经明确指出:程序=算法+数据结构 。要想在之后的江湖历练中通关,数据结构必不可少。数据结构与算法相辅相成,亦是阴阳互补之法。开篇说道数组,几乎每个IT江湖人士都不陌生,甚至过半人还会很自信觉的它很简单。的确,在菜菜所知道的编程语言中几乎都会有数组的影子。不过它不仅仅是一种基础的数据类型,更是一种基础的数据结构。如果你觉的对数组足够了解,那能不能回答一下:数组的本质定义?数组的内存结构?数组有什么优势?数组有什么劣势?数组的应用场景?数组为什么大部分都从0开始编号?数组能否用其他容器来代替,例如c#中的List<T>?定义百科所谓数组,是相同的元素序列。数组是在程序设计中,为了处理方便,把具有相同类型的若干元素按无序的形式组织起来的一种形式。正如以上所述,数组在应用上属于数据的容器。不过我还是要补充两点:数组在数据结构范畴属于一种线性结构,也就是只有前置节点和后续节点的数据结构,除数组之外,像我们平时所用的队列,栈,链表等也都属于线性结构。有线性结构当然就有非线性结构,比如之后我们要介绍的二叉树,图 等等,这里不再展开~~~数组元素在内存分配上是连续的。这一点对于数组这种数据结构来说非常重要,甚至可以说是它最大的“杀手锏”。下边会有更详细的介绍。优势和劣势优势我相信所有人在使用数组的时候都知道数组可以按照下标来访问,例如 array[1] 。作为一种最基础的数据结构是什么使数组具有这样的随机访问方式呢?天性聪慧的你可能已经想到了:内存连续+相同数据类型。现在我们抽象一下数据在内存上分配的情景。说到数组按下标访问,不得不说一下大多数人的一个“误解”:数组适合查找元素。为什么说是误解呢,是因为这种说法不够准确,准确的说数组适合按下标来查找元素,而且按照下标查找元素的时间复杂度是O(1)。为什么呢?我们知道要访问数组的元素需要知道元素在内存中对应的内存地址,而数组指向的内存的地址为首元素的地址,即:array[0]。由于数组的每个元素都是相同的类型,每个类型占用的字节数系统是知道的,所以要想访问一个数组的元素,按照下标查找可以抽象为:array[n]=array[0]+sizen以上是元素地址的运算,其中size为每个元素的大小,如果为int类型数据,那size就为4个字节。其实确切的说,n的本质是一个离首元素的偏移量,所以array[n]就是距离首元素n个偏移量的元素,因此计算array[n]的内存地址只需以上公式。论证一下,如果下标从1开始计算,那array[n]的内存地址计算公式就会变为:array[n]=array[0]+size(n-1)对比很容易发现,从1开始编号比从0开始编号每次获取内存地址都多了一次 减法运算,也就多了一次cpu指令的运行。这也是数组从0下标开始访问一个原因。其实还有一种可能性,那就是所有现代编程语言的鼻祖:C语言,它是从0开始计数下标的,所以现在所有衍生出来的后代语言也就延续了这个传统。虽然不符合人类的思想,但是符合计算机的原理。当然也有一些语言可以设置为不从下标0开始计算,这里不再展开,有兴趣的可以去搜索一下。由于数组的连续性,所以在遍历数组的时候非常快,不仅得益于数组的连续性,另外也得益于cpu的缓存,因为cpu读取缓存只能读取连续内存的内容,所以数组的连续性正好符合cpu缓存的指令原理,要知道cpu缓存的速度要比内存的速度快上很多。劣势由于数组在内存排列上是连续的,而且要保持这种连续性,所以当增加一个元素或删除一个元素的时候,为了保证连续性,需要做大量元素的移动工作。举个栗子:要在数组头部插入一个新元素,为了在头部腾出位置,所有的元素都要后移一位,假设元素个数为n,这就导致了时间复杂度为O(n)的一次操作,当然如果是在数组末尾插入新元素,其他所有元素都不必移动,操作的时间复杂度为O(1)。当然这里有一个技巧:如果你的业务要求并不是数组连续有序的,当在位置k插入元素的时候,只需要把k元素转移到数组末尾,新元素插入到k位置即可。当然仔细沉思一下这种业务场景可能性太小了,数组都可以无序,我直接插入末尾即可,没有必要非得在k位置插入把。~~当然还有一个特殊场景:如果是多次连续的k位置插入操作,我们完全可以合并为一次“批量插入”操作:把k之后的元素整体移动sum(插入次数)个位置,无需一个个位置移动,把三次操作的时间复杂度合并为一次。与插入对应的就有删除操作,同理,删除操作数组为了保持连续性,也需要元素的移动。综上所述,数组在添加和删除元素的场景下劣势比较明显,所以在具体业务场景下应该避免频繁添加和删除的操作。数组的连续性就要求创建数组的时候,内存必须有相应大小的连续区块,如果不存在,数组就有可能出现创建失败的现象。在某些高级语言中(比如c#,golang,java)就有可能引发一次GC(垃圾回收)操作,GC操作在系统运行中是非常昂贵的,有的语言甚至会挂起所有线程的操作,对外的表现就是“暂停服务”。数组要求所有元素为同一个类型。在存储数据维度,它可能算是一种劣势,但是为了按照下标快速查找元素,业务中这也是一种优势。仁者见仁智者见智而已。数组是长度固定的数据结构,所以在原始数组的基础上扩容是不可能的,有的语言可能实现数组的“伪扩容”,为什么说是“伪”呢,因为原理其实是创建了一个容量更大的数组来存放原数组元素,发生了数据复制的过程,只不过对于调用者而已透明而已。数组有访问越界的可能。我们按照下标访问数组的时候如果下标超出了数组长度,在现代多数高级语言中,直接就会引发异常了,但是一些低级语言比如C 有可能会访问到数组元素以外的数据,因为要访问的内存地址确实存在。其他很多编程语言中你会发现“纯数组”并没有提供直接删除元素的方法(例如:c#,golang),而是需要将数组转化为另一种数据结构来实现数组元素的删除。比如在golang种可以转化为slice。这也验证了数组的不变性。应用场景我们学习的每个数据结构其实都有对应的适合场景,只不过是场景多少的问题,具体什么时候用,需要我们对该数据结构的特性做深入分析。关于数组的特性,通过以上介绍可以知道最大的一个亮点就是按照下标访问,那有没有具体业务映射这种特性呢?相信很多IT人士都遇到过会员机制,每个会员到达一定的经验值就会升级,怎么判断当前的经验是否到达升级条件呢?我们是不是可以这样做:比如当前会员等级为3,判断是否到达等级4的经验值,只需要array[4]的值判断即可,大多数人把配置放到DB,资源耗费太严重。也有的人放到其他容器缓存。但是大部分场景下查询的时间复杂度要比数组大很多。在分布式底层应用中,我们会有利用一致性哈希方案来解决每个请求交给哪个服务器去处理的场景。有兴趣的同学可以自己去研究一下。其中有一个环节:根据哈希值查找对应的服务器,这是典型的读多写少的应用,而且比较偏底层。如果用其他数据结构来解决大量的查找问题,可能会触碰到性能的瓶颈。而数据按下标访问时间复杂度为O(1)的特性,使得数组在类似这些应用中非常广泛。添加关注,查看更精美版本,收获更多精彩

March 4, 2019 · 1 min · jiezi

C#离线人脸识别 ArcFaceSharp封装库分享

ArcFaceSharpArcFaceSharp 是 ArcSoft 虹软 ArcFace 2.0 SDK 的一个 C# 封装库,为方便进行 C# 开发而封装。欢迎 Start & Fork。使用在 Nuget 搜索 ArcFaceSharp 安装。PM> Install-Package ArcFaceSharp -Version 1.0.2或者下载dll导入。导入 ArcFaceSharp 后,将自己申请到的 ArcFace2.0 SDK 的 dll 文件 (libarcsoft_face.dll 和 libarcsoft_face_engine.dll)放在程序的运行目录下。接口调用的流程可参考官方文档的流程图(http://ai.arcsoft.com.cn/manu… 2.1.5调用流程)主要 API具体参数和含义可以自行查看方法的注释激活及初始化创建 ArcFaceCore对象即可ArcFaceCore arcFaceCore = ArcFaceCore(appId, sdkKey, detectMode, combinedMask,detectFaceOrientPriority, detectFaceMaxNum,detectFaceScaleVal);将 Bitmap 转换成 ImageDataImageData imageData = ImageDataConverter.ConvertToImageData(bitmap);以下方法都是 ArcFaceCore 中的方法人脸检测MultiFaceModel multiFaceModel = arcFaceCore.FaceDetection(imageData);人脸信息检测(年龄/性别/人脸3D角度)最多支持4张人脸信息检测,超过部分返回未知// 人脸信息检测 先调用这个接口才能获取以下三个信息arcFaceCore.FaceProcess(imageData,multiFaceModel);//获取年龄信息List<int> ageList = arcFaceCore.GetAge();// 获取性别信息List<int> genderList = arcFace.GetGender();// 获取人脸角度信息List<Face3DAngleModel> face3DAngleList = arcFace.GetFace3DAngle();人脸特征值提取asfSingleFaceInfo 为人脸检测接口返回的人脸信息中的其中一个人脸信息AsfFaceFeature asfFaceFeature = arcFace.FaceFeatureExtract(imageData, ref asfSingleFaceInfo);人脸对比 float result = arcFace.FaceCompare(asfFaceFeature1, asfFaceFeature2);异常捕获以人脸特征提取为例,当借口返回值不为 0(成功)时,则会抛出 ResultCodeException 异常。try{ AsfFaceFeature asfFaceFeature = arcFace.FaceFeatureExtract(imageData, ref asfSingleFaceInfo);}catch (ResultCodeException e){ Console.WriteLine(e.ResultCode); throw;}代码示例:ArcFaceSharpUnitTestUnitTest1.cs public void TestMethod1() { // SDK对应的 APP_ID SDK_KEY string APP_ID = @“7NK7KSpfgxdqb74r8nvy36kDwH3wVGstr2LHGHBxQ8LY”; string SDK_KEY = @“3fD8vKYMNfPzKHMoqppjA9chGh2aGkWzUQNFiAj7Yq63”; // 加载图片 Bitmap heying = new Bitmap(@“heying.jpg”); Bitmap face1 = new Bitmap(@“ldh0.jpg”); Bitmap face2 = new Bitmap(@“ldh1.jpg”); Bitmap face3 = new Bitmap(@“zxy0.jpg”); // 创建 ArcFaceCore 对象,向构造函数传入相关参数进行 ArcFace 引擎的初始化 ArcFaceCore arcFace = new ArcFaceCore(APP_ID,SDK_KEY,ArcFaceDetectMode.IMAGE, ArcFaceFunction.FACE_DETECT | ArcFaceFunction.FACE_RECOGNITION | ArcFaceFunction.AGE | ArcFaceFunction.FACE_3DANGLE | ArcFaceFunction.GENDER,DetectionOrientPriority.ASF_OP_0_ONLY,50,32); // 将 Bitmap 转换成 ImageData ImageData heyingImgData = ImageDataConverter.ConvertToImageData(heying); // 人脸检测 // 也可直接传入 Bitmap 来调用相关接口 会自动转换成 ImageData,但这里推荐用 ImageData MultiFaceModel multiFaceB = arcFace.FaceDetection(heying); // 传入 ImageData ,推荐使用这个接口 MultiFaceModel multiFace = arcFace.FaceDetection(heyingImgData); // 人脸信息检测(年龄/性别/人脸3D角度)最多支持4张人脸信息检测,超过部分返回未知 这是官方文档的说明 arcFace.FaceProcess(heyingImgData, multiFace); // 获取年龄信息 List<int> ageList = arcFace.GetAge(); // 获取性别信息 List<int> genderList = arcFace.GetGender(); // 获取人脸角度信息 List<Face3DAngleModel> face3DAngleList = arcFace.GetFace3DAngle(); // 将第一张图片的 Bitmap 转换成 ImageData ImageData faceData1 = ImageDataConverter.ConvertToImageData(face1); // 检测第一张图片中的人脸 MultiFaceModel multiFace1 = arcFace.FaceDetection(faceData1); // 取第一张图片中返回的第一个人脸信息 AsfSingleFaceInfo faceInfo1 = multiFace1.FaceInfoList.First(); // 提第一张图片中返回的第一个人脸的特征 AsfFaceFeature faceFeature1 = arcFace.FaceFeatureExtract(faceData1, ref faceInfo1); ImageData faceData2 = ImageDataConverter.ConvertToImageData(face2); // 检测第二张图片中的人脸 MultiFaceModel multiFace2 = arcFace.FaceDetection(faceData2); // 取第二张图片中返回的第一个人脸信息 AsfSingleFaceInfo faceInfo2 = multiFace2.FaceInfoList.First(); // 提第二张图片中返回的第一个人脸的特征 AsfFaceFeature faceFeature2 = arcFace.FaceFeatureExtract(faceData2, ref faceInfo2); // face1 face2 人脸对比,将会返回一个 0-1 之间的浮点数值 float result = arcFace.FaceCompare(faceFeature1, faceFeature2); ImageData faceData3 = ImageDataConverter.ConvertToImageData(face3); // 检测第三张图片中的人脸 MultiFaceModel multiFace3 = arcFace.FaceDetection(faceData3); // 取第三张图片中返回的第一个人脸信息 AsfSingleFaceInfo faceInfo3 = multiFace3.FaceInfoList.First(); // 提第三张图片中返回的第一个人脸的特征 AsfFaceFeature faceFeature3 = arcFace.FaceFeatureExtract(faceData3, ref faceInfo3); // face1 face3 人脸对比,将会返回一个 0-1 之间的浮点数值 float result2 = arcFace.FaceCompare(faceFeature1, faceFeature3); // 释放销毁引擎 arcFace.Dispose(); // ImageData使用完之后记得要 Dispose 否则会导致内存溢出 faceData1.Dispose(); faceData2.Dispose(); // BItmap也要记得 Dispose face1.Dispose(); face2.Dispose(); } 感谢 本项目参考了以下开发者的一些思路和代码,在此表示感谢。C#_Demo_摄像头实时_4线程人脸识别_注册 - Demo 分享 - 虹软人工智能引擎开发者论坛 - Powered by Discuz!https://ai.arcsoft.com.cn/bbs…虹软2.0版本人脸检测C#类库分享 - 第2页 - ArcFace - 虹软人工智能引擎开发者论坛 - Powered by Discuz!https://ai.arcsoft.com.cn/bbs…C#人脸检测与动态人脸识别显示坐标 视频人脸识别WINFORM - ArcFace - 虹软人工智能引擎开发者论坛 - Powered by Discuz! https://ai.arcsoft.com.cn/bbs…另外欢迎打赏哈哈~ ...

March 4, 2019 · 2 min · jiezi

[C#] ArcFace2.0+红外双目摄像头的活体检测

废话不多说 直接上图这个是demo中用到的双目摄像头,一个是红外的,一个是正常的rgb摄像头两个usb接口,在电脑上呈现两路摄像头通道程序检测RGB输出图像,当检测到有人脸时,用RGB人脸的位置到红外画面的位置去检测人脸如果没有检测到,说明当前目标为非活体当在红外画面检测到人脸时,说明当前目标为活体目标再继续使用RGB图像提取特征值下面为demo效果图image.pngDEMO源码地址:https://gitee.com/jch/FaceAliveDEMO中用的C#封装库为:https://github.com/Thxzzzzz/A…由于原库有BUG,所以demo中直接附加了修复了bug的源码

March 1, 2019 · 1 min · jiezi

最新基于虹软免费人脸识别开发经验demo

本来打算做个C#版demo,但没用成功,基于虹软的免费人脸识别技术 过程如下: 1、 传入一张单人脸照片; 2、调用检测人脸函数ASFDetectFaces,成功返回人脸信息的指针; 3、使用 Marshal.ReadByte(intPtr,offset) 函数读出字节数,发现前16个字节是人脸框范围,第28至31个字节应该是人脸角度,其他信息不太清楚。想了下,最简单的办法是用C++将动态库再封装一次。将封装后的demo及C++代码上传。demo是X86的,只识别单张人脸,有别的需求的同学可自己封装库。 https://download.csdn.net/dow…

March 1, 2019 · 1 min · jiezi

C# 虹软人脸识别SDK2.0版实例

虹软SDK推出了2.0版本,这个版本的所有API都集合在一个动态库里面,再通过引擎库调用,比1.2版本相对轻便了很多。了解详情戳这里https://ai.arcsoft.com.cn/ind…小西瓜也迫不及待弄了一个新版本的C#实例,基于VS2013开发的,弄的过程中也遇到很多问题,不过通过论坛的一些大神的反馈和说明,几乎全部解决了,这次封装也有参考dayAndnight2018所封装的SDK加以改进,SDK论坛介绍地址,实例类库用的是.NET FrameWork2.0的库,用低版本的库的原因是因为想要兼容更多的项目,接下来先上图看看项目。几乎所有API都有实现,实例重心在于AsfFace类库中的封装和AsfFace.cs的方法,具体调用可以参照winform中的调用。如果有其他项目需要调用,直接引入AsfFace类库或者生成的动态链接库即可。注意这个实例用的是32位的虹软库,所以项目生成的时候需要注意选择x86,如下图所示:这个实例虽然用的是32位的库,但在64位系统下也能完美运行,如果使用类库发布网站IIS记得应用程序池也相应选择x86。希望这个Demo对C#开发者有所帮助。最后注意要点,使用类库记得替换你的2.0版本的APPid跟SDKKey噢最后,喜欢的话打赏一个,你的支持小西瓜的动力。接下来上百度网盘链接链接:https://pan.baidu.com/s/1QwPa…提取码:1fu8欢迎分享!

March 1, 2019 · 1 min · jiezi

Unity C# 计算导弹抛物线弹道和转向

在三维空间中,利用抛物线公式计算弹道,得到一个发射初速度,让导弹打击到指定地点效果:脚本使用:只需指定目标点即可可以通过Hight调整导弹的飞行高度可以通过Gravity调整导弹的飞行速度通过以下两个脚本实现。工具脚本计算弹道,Missile脚本每帧更新导弹位置PhysicsUtil.csusing UnityEngine;/// <summary> 物理计算工具/// <para>ZhangYu 2018-05-10</para>/// </summary>public static class PhysicsUtil { /**findInitialVelocity * Finds the initial velocity of a projectile given the initial positions and some offsets * @param Vector3 startPosition - the starting position of the projectile * @param Vector3 finalPosition - the position that we want to hit * @param float maxHeightOffset (default=0.6f) - the amount we want to add to the height for short range shots. We need enough clearance so the * ball will be able to get over the rim before dropping into the target position * @param float rangeOffset (default=0.11f) - the amount to add to the range to increase the chances that the ball will go through the rim * @return Vector3 - the initial velocity of the ball to make it hit the target under the current gravity force. * * Vector3 tt = findInitialVelocity (gameObject.transform.position, target.transform.position); Rigidbody rigidbody = gameObject.GetComponent<Rigidbody> (); Debug.Log (tt); rigidbody.AddForce(tt*rigidbody.mass,ForceMode.Impulse); */ public static Vector3 GetParabolaInitVelocity(Vector3 from, Vector3 to, float gravity = 9.8f, float heightOff = 0.0f, float rangeOff = 0.11f) { // get our return value ready. Default to (0f, 0f, 0f) Vector3 newVel = new Vector3(); // Find the direction vector without the y-component /// /找到未经y分量的方向矢量// Vector3 direction = new Vector3(to.x, 0f, to.z) - new Vector3(from.x, 0f, from.z); // Find the distance between the two points (without the y-component) //发现这两个点之间的距离(不y分量)// float range = direction.magnitude; // Add a little bit to the range so that the ball is aiming at hitting the back of the rim. // Back of the rim shots have a better chance of going in. // This accounts for any rounding errors that might make a shot miss (when we don’t want it to). range += rangeOff; // Find unit direction of motion without the y component Vector3 unitDirection = direction.normalized; // Find the max height // Start at a reasonable height above the hoop, so short range shots will have enough clearance to go in the basket // without hitting the front of the rim on the way up or down. float maxYPos = to.y + heightOff; // check if the range is far enough away where the shot may have flattened out enough to hit the front of the rim // if it has, switch the height to match a 45 degree launch angle //if (range / 2f > maxYPos) // maxYPos = range / 2f; if (maxYPos < from.y) maxYPos = from.y; // find the initial velocity in y direction /// /发现在y方向上的初始速度// float ft; ft = -2.0f * gravity * (maxYPos - from.y); if (ft < 0) ft = 0f; newVel.y = Mathf.Sqrt(ft); // find the total time by adding up the parts of the trajectory // time to reach the max //发现的总时间加起来的轨迹的各部分// //时间达到最大// ft = -2.0f * (maxYPos - from.y) / gravity; if (ft < 0) ft = 0f; float timeToMax = Mathf.Sqrt(ft); // time to return to y-target //时间返回到y轴的目标// ft = -2.0f * (maxYPos - to.y) / gravity; if (ft < 0) ft = 0f; float timeToTargetY = Mathf.Sqrt(ft); // add them up to find the total flight time //把它们加起来找到的总飞行时间// float totalFlightTime; totalFlightTime = timeToMax + timeToTargetY; // find the magnitude of the initial velocity in the xz direction /// /查找的初始速度的大小在xz方向// float horizontalVelocityMagnitude = range / totalFlightTime; // use the unit direction to find the x and z components of initial velocity //使用该单元的方向寻找初始速度的x和z分量// newVel.x = horizontalVelocityMagnitude * unitDirection.x; newVel.z = horizontalVelocityMagnitude * unitDirection.z; return newVel; } /// <summary> 计算抛物线物体在下一帧的位置 </summary> /// <param name=“position”>初始位置</param> /// <param name=“velocity”>移动速度</param> /// <param name=“gravity”>重力加速度</param> /// <param name=“time”>飞行时间</param> /// <returns></returns> public static Vector3 GetParabolaNextPosition(Vector3 position, Vector3 velocity, float gravity, float time) { velocity.y += gravity * time; return position + velocity * time; }}Missile.csusing UnityEngine;/// <summary>/// 抛物线导弹/// <para>计算弹道和转向</para>/// <para>ZhangYu 2019-02-27</para>/// </summary>public class Missile : MonoBehaviour { public Transform target; // 目标 public float hight = 16f; // 抛物线高度 public float gravity = -9.8f; // 重力加速度 private Vector3 position; // 我的位置 private Vector3 dest; // 目标位置 private Vector3 velocity; // 运动速度 private float time = 0; // 运动时间 private void Start() { dest = target.position; position = transform.position; velocity = PhysicsUtil.GetParabolaInitVelocity(position, dest, gravity, hight, 0); transform.LookAt(PhysicsUtil.GetParabolaNextPosition(position, velocity, gravity, Time.deltaTime)); } private void Update() { // 计算位移 float deltaTime = Time.deltaTime; position = PhysicsUtil.GetParabolaNextPosition(position, velocity, gravity, deltaTime); transform.position = position; time += deltaTime; velocity.y += gravity * deltaTime; // 计算转向 transform.LookAt(PhysicsUtil.GetParabolaNextPosition(position, velocity, gravity, deltaTime)); // 简单模拟一下碰撞检测 if (position.y <= dest.y) enabled = false; }} ...

February 28, 2019 · 4 min · jiezi

【C#】 ArcFace 2.0版 类库分享

目前只封装了人脸检测部分的类库,供大家交流学习,肯定有问题,希望大家在阅读使用的时候及时反馈,谢谢!使用虹软技术开发完成 戳这里下载SDKgithub:https://github.com/dayAndnigh…目前包含了以下功能:激活:ResultCode result = EngineActivate.ActivateEngine(stringappId, string appKey)–appid和appkey在官网获取– result是一个枚举的状态码获取引擎:IntPtr engine = EngineFactory.GetEngineInstance(uint mode,DetectionOrientPriority orientPriority, int detectFaceScaleVal =12)–engine是引擎–mode可以根据EngineFactory.Video或者EngineFactory.Image设置是图像还是视频,目前只支持图像。– orientPriority是枚举– detectFaceScaleVal可以不填释放引擎:Bool result = EngineFactory.DisposeEngine()人脸个数检测:1.初始化人脸检测器:public FaceDetection(IntPtr hEngine,Bitmap image)– hEngine就是获取的引擎–image,bitmap格式的图片,不需要提前处理图片大小,内部有处理操作2.获取人脸数量public int FindFaceNum()返回人脸数量人脸年龄检测:1.初始化人脸检测器:public FaceDetection(IntPtr hEngine,Bitmap image)– hEngine就是获取的引擎–image,bitmap格式的图片,不需要提前处理图片大小,内部有处理操作2.获取人脸年龄public int GetAge()返回人脸年龄人脸性别检测:1.初始化人脸检测器:public FaceDetection(IntPtr hEngine,Bitmap image)– hEngine就是获取的引擎–image,bitmap格式的图片,不需要提前处理图片大小,内部有处理操作2.获取人脸性别public string GetGender()返回人脸性别人脸相似度对比:方式一:1.初始化人脸检测器:public FaceDetection(IntPtr hEngine,Bitmap image1, Bitmap image2)– hEngine就是获取的引擎–image1,bitmap格式的图片,不需要提前处理图片大小,内部有处理操作–image2,bitmap格式的图片,不需要提前处理图片大小,内部有处理操作2.返回相似度public float Compare()方式二:返回相似度(直接对比)public float Compare(byte[] data1, byte[]data2)–data1是人脸图像数据大小1032–data2是人脸图像数据大小1032

February 28, 2019 · 1 min · jiezi

Unity C# 打包AssetBundle与场景

Unity2018已经把打包过程简化很多了我们只需要关心两个API:1.BuildPipline.BuildAssetBundles() 打包AssetBundle2.BuildPipline.BuildPlayer() 打包场景1.打包AssetBundle先在资源的Inspector面板最下方 填写资源所属的AssetBundle名称和后缀(后缀可以不填)再利用BuildPipeline.BuildAssetBundles()进行打包2.打包Scene利用BuildPipeline.BuildPlayer()进行打包为方便使用 先把要打包的场景放入指定的文件夹 通过脚本批量打包3.脚本批量打包4.打包完毕5.加载测试6.打包和测试脚本AssetBundleBuilder.csusing UnityEngine;using UnityEditor;using System.IO;/// <summary>/// 资源包打包工具/// <para>打包AssetBundle和场景(Unity 2018.2.20)</para>/// <para>ZhangYu 2019-02-26</para>/// </summary>public class AssetBundleBuilder{ [MenuItem(“打包/Windows/资源包和场景”)] public static void BuildAbsAndScenesWindows() { BuildAbsAndScenes(BuildTarget.StandaloneWindows); } [MenuItem(“打包/Android/资源包和场景”)] public static void BuildAbsAndScenesAndroid() { BuildAbsAndScenes(BuildTarget.Android); } [MenuItem(“打包/IOS/资源包和场景”)] public static void BuildAbsAndScenesIOS() { BuildAbsAndScenes(BuildTarget.iOS); } [MenuItem(“打包/Windows/资源包”)] public static void BuildAbsWindows() { BuildAssetBundles(BuildTarget.StandaloneWindows); } [MenuItem(“打包/Android/资源包”)] public static void BuildAbsAndroid() { BuildAssetBundles(BuildTarget.Android); } [MenuItem(“打包/IOS/资源包”)] public static void BuildAbsIOS() { BuildAssetBundles(BuildTarget.iOS); } [MenuItem(“打包/Windows/场景”)] public static void BuildScenesWindows() { BuildScenes(BuildTarget.StandaloneWindows); } [MenuItem(“打包/Android/场景”)] public static void BuildScenesAndroid() { BuildScenes(BuildTarget.Android); } [MenuItem(“打包/IOS/场景”)] public static void BuildScenesIOS() { BuildScenes(BuildTarget.iOS); } // 打包AssetBundle和Scenes public static void BuildAbsAndScenes(BuildTarget platform) { BuildAssetBundles(platform); BuildScenes(platform); } // 打包AssetBundles private static void BuildAssetBundles(BuildTarget platform) { // 输出路径 string outPath = Application.streamingAssetsPath + “/Abs”; if (!Directory.Exists(outPath)) Directory.CreateDirectory(outPath); EditorUtility.DisplayProgressBar(“信息”, “打包资源包”, 0f); BuildPipeline.BuildAssetBundles(outPath, BuildAssetBundleOptions.DeterministicAssetBundle, platform); AssetDatabase.Refresh(); Debug.Log(“所有资源包打包完毕”); } // 打包Scenes private static void BuildScenes(BuildTarget platform) { // 指定场景文件夹和输出路径 string scenePath = Application.dataPath + “/AbResources/Scenes”; string outPath = Application.streamingAssetsPath + “/Abs/”; if (Directory.Exists(scenePath)) { // 创建输出文件夹 if (!Directory.Exists(outPath)) Directory.CreateDirectory(outPath); // 查找指定目录下的场景文件 string[] scenes = GetAllFiles(scenePath, “.unity”); for (int i = 0; i < scenes.Length; i++) { string url = scenes[i].Replace("\", “/”); int index = url.LastIndexOf("/"); string scene = url.Substring(index + 1, url.Length - index - 1); string msg = string.Format(“打包场景{0}”, scene); EditorUtility.DisplayProgressBar(“信息”, msg, 0f); scene = scene.Replace(".unity", “.scene”); Debug.Log(string.Format(“打包场景{0}到{1}”, url, outPath + scene)); BuildPipeline.BuildPlayer(scenes, outPath + scene, BuildTarget.StandaloneWindows, BuildOptions.BuildAdditionalStreamedScenes); AssetDatabase.Refresh(); } EditorUtility.ClearProgressBar(); Debug.Log(“所有场景打包完毕”); } } /// <summary> 获取文件夹和子文件夹下所有指定类型文件 </summary> private static string[] GetAllFiles(string directory, params string[] types) { if (!Directory.Exists(directory)) return new string[0]; string searchTypes = (types == null || types.Length == 0) ? “.*” : string.Join("|", types); string[] names = Directory.GetFiles(directory, searchTypes, SearchOption.AllDirectories); return names; }}LoadTest.csusing UnityEngine;using UnityEngine.SceneManagement;public class LoadTest : MonoBehaviour { private void Start () { LoadAB(); LoadScene(); } // 加载资源包 private void LoadAB() { // 资源包路径 string path = Application.streamingAssetsPath + “/Abs/test.ab”; // WWW下载 Http http = gameObject.AddComponent<Http>(); http.get(path, OnLoadABComplete); } // 加载场景 private void LoadScene() { // 资源包路径 string path = Application.streamingAssetsPath + “/Abs/Test.scene”; // WWW下载 Http http = gameObject.AddComponent<Http>(); http.get(path, OnLoadSceneComplete); } // 加载AssetBundle完毕 private void OnLoadABComplete(WWW www) { // 实例化预制 AssetBundle ab = www.assetBundle; Object prefab = ab.LoadAsset(“Test”); GameObject instance = (GameObject)Instantiate(prefab); DontDestroyOnLoad(instance); } // 加载场景完毕 private void OnLoadSceneComplete(WWW www) { // 必须写www.assetBundle这句 这样场景才能被读取到 AssetBundle ab = www.assetBundle; SceneManager.LoadScene(“Test”); }} ...

February 27, 2019 · 2 min · jiezi

【C#】ArcFace2 视频人脸比对教程

请允许我大言不惭,叫做教程,特希望各位能指正。哦,我用的是vs2017。使用虹软技术一、准备工作1.创建项目2.添加EMGU.CV包3.复制虹软的dll到项目,并设属性“复制到输出目录”为“如果较新则复制准备工作到此结束,按F7切换到代码,然后进入第二步。二、代码using Emgu.CV;using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.Diagnostics;using System.Drawing;using System.IO;using System.Runtime.InteropServices;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;namespace ArcFace2Demo{ public partial class Form1 : Form { #region ArcFaceConst const uint ASF_DETECT_MODE_VIDEO = 0x00000000; //Video模式,一般用于多帧连续检测 const uint ASF_DETECT_MODE_IMAGE = 0xFFFFFFFF; //Image模式,一般用于静态图的单次检测 const uint ASF_NONE = 0x00000000; const uint ASF_FACE_DETECT = 0x00000001; //此处detect可以是tracking或者detection两个引擎之一,具体的选择由detect mode 确定 const uint ASF_FACERECOGNITION = 0x00000004; const uint ASF_AGE = 0x00000008; const uint ASF_GENDER = 0x00000010; const uint ASF_FACE3DANGLE = 0x00000020; /// <summary> /// 结构ASF_FaceRect的长度 /// 32位程序是16,64位程序需要改为32 /// </summary> const int SizeOfASF_FaceRect = 16; #endregion #region ArceDataStructure /// <summary> /// 人脸在图片中的位置 /// </summary> [StructLayout(LayoutKind.Sequential)] internal struct ASF_FaceRect { public int Left; public int Top; public int Right; public int Bottom; public Rectangle GetRectangle() { return new Rectangle(Left, Top, Right - Left, Bottom - Top); } } /// <summary> /// 多人脸信息 /// </summary> [StructLayout(LayoutKind.Sequential)] internal struct ASF_MultiFaceInfo { public IntPtr PFaceRect; public IntPtr PFaceOrient; [MarshalAs(UnmanagedType.I4)] public int FaceNum; } /// <summary> /// 单人脸信息 /// </summary> [StructLayout(LayoutKind.Sequential)] internal struct ASF_SingleFaceInfo { public ASF_FaceRect FaceRect; public int FaceOrient; } /// <summary> /// 人脸特征 /// </summary> [StructLayout(LayoutKind.Sequential)] internal struct ASF_FaceFeature { public IntPtr PFeature; [MarshalAs(UnmanagedType.I4)] public int FeatureSize; } #endregion #region ArcWrapper /// <summary> /// 激活SDK /// </summary> /// <param name=“appId”></param> /// <param name=“sdkKey”></param> /// <returns>0:激活成功,0x16002表示已经激活</returns> [DllImport(“libarcsoft_face_engine.dll”, EntryPoint = “ASFActivation”, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern int ASFActivation(string appId, string sdkKey); /// <summary> /// 初始化引擎 /// </summary> /// <param name=“detectMode”>long会返回scale错误0x16004</param> /// <param name=“orientPriority”></param> /// <param name=“scale”></param> /// <param name=“maxFaceNumber”></param> /// <param name=“combinedMask”></param> /// <param name=“pEngine”></param> /// <returns></returns> [DllImport(“libarcsoft_face_engine.dll”, EntryPoint = “ASFInitEngine”, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern int ASFInitEngine(uint detectMode, int orientPriority, int scale, int maxFaceNumber, uint combinedMask, out IntPtr pEngine); /// <summary> /// 人脸检测 /// </summary> /// <param name=“pEngine”></param> /// <param name=“width”></param> /// <param name=“height”></param> /// <param name=“format”></param> /// <param name=“pImageData”></param> /// <param name=“faceInfo”></param> /// <returns></returns> [DllImport(“libarcsoft_face_engine.dll”, EntryPoint = “ASFDetectFaces”, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern int ASFDetectFaces(IntPtr pEngine, int width, int height, int format, IntPtr pImageData, out ASF_MultiFaceInfo faceInfo); /// <summary> /// 单人脸特征提取 /// </summary> /// <param name=“pEngine”></param> /// <param name=“width”></param> /// <param name=“height”></param> /// <param name=“format”></param> /// <param name=“faceInfo”></param> /// <param name=“faceFeature”></param> /// <returns></returns> [DllImport(“libarcsoft_face_engine.dll”, EntryPoint = “ASFFaceFeatureExtract”, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern int ASFFaceFeatureExtract(IntPtr pEngine, int width, int height, int format, IntPtr pImageData, ref ASF_SingleFaceInfo faceInfo, out ASF_FaceFeature faceFeature); /// <summary> /// 脸特征比对 /// </summary> /// <param name=“pEngine”></param> /// <param name=“faceFeature1”></param> /// <param name=“faceFeature2”></param> /// <param name=“result”></param> /// <returns></returns> [DllImport(“libarcsoft_face_engine.dll”, EntryPoint = “ASFFaceFeatureCompare”, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern int ASFFaceFeatureCompare(IntPtr pEngine, ref ASF_FaceFeature faceFeature1, ref ASF_FaceFeature faceFeature2, out float result); /// <summary> /// 销毁引擎 /// </summary> /// <param name=“engine”></param> /// <returns></returns> [DllImport(“libarcsoft_face_engine.dll”, EntryPoint = “ASFUninitEngine”, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern int ASFUninitEngine(IntPtr engine); #endregion /// <summary> /// 特征库 /// </summary> IntPtr _PFeatureLib; /// <summary> /// 特征库人脸数量 /// </summary> int _FeatureLibFaceCount = 0; /// <summary> /// 特征库人脸ID列表 /// </summary> List<string> _FeatureLibIDList = new List<string>(); /// <summary> /// 人脸特征结构 /// </summary> ASF_FaceFeature _FaceFeature = new ASF_FaceFeature { FeatureSize = 1032 }; /// <summary> /// 人脸识别的结果 /// </summary> class FaceResult { /// <summary> /// 人脸框矩形 /// </summary> public Rectangle Rectangle { get; set; } /// <summary> /// 人脸ID /// </summary> public string ID { get; set; } /// <summary> /// 比对结果 /// </summary> public float Score { get; set; } public override string ToString() { return [ DISCUZ_CODE_0 ]quot;ID:{ID}\r\n结果:{Score}"; } } /// <summary> /// 多人脸识别结果集 /// </summary> ConcurrentDictionary<int, FaceResult> _FaceResults = new ConcurrentDictionary<int, FaceResult>(); /// <summary> /// 检测到的人脸数量 /// </summary> int _DetectedFaceCount = 0; /// <summary> /// 视频捕获 /// </summary> VideoCapture _VideoCapture; Mat _Frame = new Mat(); /// <summary> /// 虹软人脸引擎 /// </summary> IntPtr _PEngine = IntPtr.Zero; /// <summary> /// 比对一次总耗时 /// </summary> long _TotalElapsedMilliseconds = 0; /// <summary> /// 识别任务 /// </summary> Task _TaskMatch; /// <summary> /// 向识别任务发送取消指令的东东 /// </summary> CancellationTokenSource _CTS = new CancellationTokenSource(); /// <summary> /// 图像数据 /// </summary> IntPtr _PImageData; /// <summary> /// 宽、高、图像数据长度 /// </summary> int _ImageWidth, _ImageHeight, _ImageSize; /// <summary> /// 是否要保存当前人脸特征 /// </summary> bool _SaveFlag = false; PictureBox _PictureBox; public Form1() { InitializeComponent(); _PictureBox = new PictureBox(); _PictureBox.SizeMode = PictureBoxSizeMode.StretchImage; _PictureBox.Dock = DockStyle.Fill; this.Controls.Add(_PictureBox); this.Load += Form1_Load; this.FormClosing += Form1_FormClosing; } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (_TaskMatch != null) { _CTS.Cancel(); while (_TaskMatch.Status == TaskStatus.Running) Task.Delay(1000).Wait(); } _VideoCapture.Stop(); if (_PEngine != IntPtr.Zero) ASFUninitEngine(_PEngine); if (_PFeatureLib != IntPtr.Zero) Marshal.FreeCoTaskMem(_PFeatureLib); if (_PImageData != IntPtr.Zero) Marshal.FreeCoTaskMem(_PImageData); } private unsafe void Form1_Load(object sender, EventArgs e) { var ret = ASFActivation(“BKgqTWQPQQbomfqvyd2VJzTUqPp3JD8zjAzDcqsL1jLa”, “2nkDTmnkpS53cpSY42fFS9nEUzg8x4MDGkAubSsebtm1”); if (ret != 0 && ret != 0x16002) { MessageBox.Show(“SDK激活失败:0x” + ret.ToString(“x2”)); return; } ret = ASFInitEngine(ASF_DETECT_MODE_IMAGE, 1, 32, 10, ASF_FACE_DETECT | ASF_FACERECOGNITION, out _PEngine); if (ret != 0) { MessageBox.Show([ DISCUZ_CODE_0 ]quot;人脸识别引擎初始化失败:" + ret.ToString(“x2”)); return; } //初始化识别结果集 for (int i = 0; i < 10; i++) _FaceResults[i] = new FaceResult(); //初始化特征库 _PFeatureLib = Marshal.AllocCoTaskMem(1032 * 1000 + 1032 * 10000 * 20); var bytes = File.ReadAllBytes(“Feature.dat”); var ids = File.ReadAllLines(“Id.txt”); for (int i = 0; i < 20 * 20; i++) { Marshal.Copy(bytes, 0, IntPtr.Add(_PFeatureLib, _FeatureLibFaceCount * 1032), bytes.Length); _FeatureLibIDList.AddRange(ids); _FeatureLibFaceCount += ids.Length; } _VideoCapture = new VideoCapture(); //_VideoCapture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.FrameWidth, 1024); //_VideoCapture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.FrameHeight, 768); _VideoCapture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.Fps, 10); _VideoCapture.Start(); _VideoCapture.ImageGrabbed += (object oo, EventArgs es) => { _VideoCapture.Retrieve(_Frame, 1); using (Graphics g = Graphics.FromImage(_Frame.Bitmap)) { g.DrawString([ DISCUZ_CODE_0 ]quot;比对总耗时{_TotalElapsedMilliseconds}毫秒", this.Font, Brushes.White, 0, 0); for (int i = 0; i < _DetectedFaceCount; i++) { if (_FaceResults.TryGetValue(i, out var faceResult)) { g.DrawRectangle(Pens.Red, faceResult.Rectangle); g.DrawString(faceResult.ToString(), this.Font, Brushes.White, faceResult.Rectangle.Location); } } } this._PictureBox.Image = _Frame.Bitmap; }; _PictureBox.Click += (object oo, EventArgs es) => { if (MessageBox.Show(“您确定要保存人脸特征数据吗?”, “确认信息”, MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes) _SaveFlag = true; }; _ImageSize = _VideoCapture.Width * _VideoCapture.Height * 3; _PImageData = Marshal.AllocCoTaskMem(_ImageSize); _ImageWidth = _VideoCapture.Width; _ImageHeight = _VideoCapture.Height; _TaskMatch = Task.Run(() => { Task.Delay(1000).Wait(); while (!_CTS.IsCancellationRequested) { try { Stopwatch sw = new Stopwatch(); sw.Restart(); Marshal.Copy(_Frame.GetData(), 0, _PImageData, _ImageSize); ret = ASFDetectFaces(_PEngine, _ImageWidth, _ImageHeight, 513, _PImageData, out var faceInfo); if (ret != 0 || faceInfo.FaceNum == 0) { _DetectedFaceCount = 0; continue; } for (int detectedFaceIndex = 0; detectedFaceIndex < faceInfo.FaceNum; detectedFaceIndex++) { float score = 0; string id = “”; ASF_SingleFaceInfo singleFaceInfo = new ASF_SingleFaceInfo { FaceRect = Marshal.PtrToStructure<ASF_FaceRect>(IntPtr.Add(faceInfo.PFaceRect, SizeOfASF_FaceRect * detectedFaceIndex)), FaceOrient = 1// Marshal.ReadInt32(IntPtr.Add(faceInfo.PFaceOrient, i * 4)) }; ret = ASFFaceFeatureExtract(_PEngine, _ImageWidth, _ImageHeight, 513, _PImageData, ref singleFaceInfo, out var faceFeature); if (ret != 0) continue; _FaceResults[detectedFaceIndex].Rectangle = singleFaceInfo.FaceRect.GetRectangle(); if (_SaveFlag) { byte[] bufferSave = new byte[1032]; Marshal.Copy(faceFeature.PFeature, bufferSave, 0, 1032); var newId = DateTime.Now.Ticks.ToString(); FileStream fs = new FileStream(“Feature.dat”, FileMode.Append); fs.Write(bufferSave, 0, 1032); fs.Close(); var streamWriter = File.AppendText(“Id.txt”); streamWriter.Write("\r\n" + newId); streamWriter.Close(); Marshal.Copy(bufferSave, 0, IntPtr.Add(_PFeatureLib, 1032 * _FeatureLibFaceCount), 1032); _FeatureLibIDList.Add(newId); _FeatureLibFaceCount++; if (detectedFaceIndex == faceInfo.FaceNum - 1) { MessageBox.Show(“保存特征数据成功!”); _SaveFlag = false; } continue; } ConcurrentBag<int> needCompareFaceIndexs = new ConcurrentBag<int>(); Parallel.For(0, _FeatureLibFaceCount, faceIndex => { byte* pLib = ((byte*)_PFeatureLib) + 1032 * faceIndex + 8; byte* pCurrent = ((byte*)faceFeature.PFeature) + 8; int count = 0; for (int j = 0; j < 1024; j++) { if (*pLib++ == *pCurrent++) count++; } if (count > 80) needCompareFaceIndexs.Add(faceIndex); }); foreach (var index in needCompareFaceIndexs)//650ms { _FaceFeature.PFeature = IntPtr.Add(_PFeatureLib, index * 1032); ASFFaceFeatureCompare(_PEngine, ref faceFeature, ref _FaceFeature, out var r); if (r > 0.8 && r > score) { score = r; id = _FeatureLibIDList[index]; } } _FaceResults[detectedFaceIndex].Score = score; _FaceResults[detectedFaceIndex].ID = id; } _DetectedFaceCount = faceInfo.FaceNum; sw.Stop(); _TotalElapsedMilliseconds = sw.ElapsedMilliseconds; } catch (Exception ex) { } } }, _CTS.Token); } }}三、下载测试用特征数据(500张人脸)并解压到运行目录 ArcFaceData.zip (463.7 KB, 下载次数: 0) 四、按F5运行点击视频增加当前人脸的特征数据,基本上800毫秒可以从20万人脸中找到你。 ...

February 18, 2019 · 6 min · jiezi

【C#】人脸识别 视频数据转图片数据

因为不会用C#直接打开摄像头,就只能用第三方dll。一开始用Aforge,后来发现有个问题,关闭摄像头老是陷入等待,所以抛弃了。前一阵子开始用封装了OpenCV的Emgu,一路走来也是N声叹息。一、安装Emgu的叹息一开始自己下载并安装了Emgu,然后各种测试,发现还需要这个那个的,最后发现直接安装一个EMGU.CV的NuGet包就OK了。当然要打开视频,还需要引用System.ServiceModel。二、获取视频用Emgu获取视频真心十分方便1.定义两个变量VideoCapture _VideoCapture;Mat _Frame = new Mat();2.初始化视频_VideoCapture = new VideoCapture();//_VideoCapture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.FrameWidth, 1024); //设置宽度//_VideoCapture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.FrameHeight, 768);//设置高度_VideoCapture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.Fps, 10);//设置每秒钟的帧数_VideoCapture.Start();_VideoCapture.ImageGrabbed += _VideoCapture_ImageGrabbed; //视频事件_VideoCapture = new VideoCapture();//_VideoCapture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.FrameWidth, 1024); //设置宽度//_VideoCapture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.FrameHeight, 768);//设置高度_VideoCapture.SetCaptureProperty(Emgu.CV.CvEnum.CapProp.Fps, 10);//设置每秒钟的帧数_VideoCapture.Start();_VideoCapture.ImageGrabbed += _VideoCapture_ImageGrabbed; //视频事件4.获取当前帧用于人脸比对,一般在另外一个线程Mat curFrame=_VideoCapture.QueryFrame();一切十分完美,就是_Frame.Bitmap似乎没有Dispose(后来发现Mat的地址是不变,不会发生内存泄漏),但运行起来也没问题。三、人脸识别的叹息看了一下,人脸识别需要Bitmap,方便Mat curFrame=_VideoCapture.QueryFrame();Bitmap bitmap=curFrame.Bitmap;var bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);int width = (bitmap.Width + 3) / 4 * 4;var bytesCount = bmpData.Height * width * 3;IntPtr pImageData = Marshal.AllocCoTaskMem(bytesCount);if (width == bitmap.Width) CopyMemory(pImageData, bmpData.Scan0, bytesCount);else for (int i = 0; i < bitmap.Width; i++) CopyMemory(IntPtr.Add(pImageData, i * width * 3), IntPtr.Add(bmpData.Scan0, i * bmpData.Stride), bmpData.Stride);bitmap.UnlockBits(bmpData);得到了ArcFace所需的图片数据pImageData,测试一下挺好,能运行。时间一长,报错了:“试读取或写入受保护的内存。这通常指示其他内存已损坏。”估计人脸识别的线程和显示视频的线程冲突了,查看了Emgu的源代码,发现QueryFrame就是封装了Retrieve。好吧,克隆一下,Bitmap bitmap=(Bitmap)curFrame.Bitmap.Clone();问题依旧!查看地址发现Clone没卵用!四、最终解决的办法研究了Mat这个东东,发现GetData()就能返回图片数据,而且不会冲突,最后写成:1.定义IntPtr _PImageData;int _ImageWidth,_ImageHeight,_ImageSize;2.初始化_ImageWidth=_VideoCapture.Width;_ImageHeight=_VideoCapture.Height;_ImageSize = _VideoCapture.Width * _VideoCapture.Height * 3;_PImageData = Marshal.AllocCoTaskMem(_ImageSize);3.转换Marshal.Copy(_Frame.GetData(), 0, _PImageData, _ImageSize);ASFDetectFaces(pEngine,_ImageWidth, _ImageHeight,513,_PImageData, out var faceInfo);一切变得如此简单,长叹一声!五、其他1.视频图片的宽度一般是4的倍数,所以上述方式肯定没问题。2.经常有人问如何获取网络摄像头、ip摄像头的图像,其实就是videoCapture = new VideoCapture(“string filename”);如某tplink的IP摄像头的filename是这样的"rtsp://admin:admin@192.168.0.159/stream1",格式是rstp://用户名:密码@ip地址/…各位C#的亲,你们怎么转换的? ...

February 18, 2019 · 1 min · jiezi

离线人脸识别 ArcFaceSharp -- ArcFace 2.0 SDK C#封装库分享

ArcFaceSharpArcFaceSharp 是 ArcSoft 虹软 ArcFace 2.0 SDK 的一个 C# 封装库,为方便进行 C# 开发而封装。欢迎 Start & Fork。使用在 Nuget 搜索 ArcFaceSharp 安装。PM> Install-Package ArcFaceSharp -Version 1.0.2或者下载dll导入。导入 ArcFaceSharp 后,将自己申请到的 ArcFace2.0 SDK 的 dll 文件 (libarcsoft_face.dll 和 libarcsoft_face_engine.dll)放在程序的运行目录下。接口调用的流程可参考官方文档的流程图(http://ai.arcsoft.com.cn/manu… 2.1.5调用流程)主要 API具体参数和含义可以自行查看方法的注释激活及初始化创建 ArcFaceCore对象即可ArcFaceCore arcFaceCore = ArcFaceCore(appId, sdkKey, detectMode, combinedMask,detectFaceOrientPriority, detectFaceMaxNum,detectFaceScaleVal);将 Bitmap 转换成 ImageDataImageData imageData = ImageDataConverter.ConvertToImageData(bitmap);以下方法都是 ArcFaceCore 中的方法人脸检测MultiFaceModel multiFaceModel = arcFaceCore.FaceDetection(imageData);人脸信息检测(年龄/性别/人脸3D角度)最多支持4张人脸信息检测,超过部分返回未知// 人脸信息检测 先调用这个接口才能获取以下三个信息arcFaceCore.FaceProcess(imageData,multiFaceModel);//获取年龄信息List<int> ageList = arcFaceCore.GetAge();// 获取性别信息List<int> genderList = arcFace.GetGender();// 获取人脸角度信息List<Face3DAngleModel> face3DAngleList = arcFace.GetFace3DAngle();人脸特征值提取asfSingleFaceInfo 为人脸检测接口返回的人脸信息中的其中一个人脸信息AsfFaceFeature asfFaceFeature = arcFace.FaceFeatureExtract(imageData, ref asfSingleFaceInfo);人脸对比 float result = arcFace.FaceCompare(asfFaceFeature1, asfFaceFeature2);异常捕获以人脸特征提取为例,当借口返回值不为 0(成功)时,则会抛出 ResultCodeException 异常。try{ AsfFaceFeature asfFaceFeature = arcFace.FaceFeatureExtract(imageData, ref asfSingleFaceInfo);}catch (ResultCodeException e){ Console.WriteLine(e.ResultCode); throw;}代码示例:ArcFaceSharpUnitTestUnitTest1.cs public void TestMethod1() { // SDK对应的 APP_ID SDK_KEY string APP_ID = @“7NK7KSpfgxdqb74r8nvy36kDwH3wVGstr2LHGHBxQ8LY”; string SDK_KEY = @“3fD8vKYMNfPzKHMoqppjA9chGh2aGkWzUQNFiAj7Yq63”; // 加载图片 Bitmap heying = new Bitmap(@“heying.jpg”); Bitmap face1 = new Bitmap(@“ldh0.jpg”); Bitmap face2 = new Bitmap(@“ldh1.jpg”); Bitmap face3 = new Bitmap(@“zxy0.jpg”); // 创建 ArcFaceCore 对象,向构造函数传入相关参数进行 ArcFace 引擎的初始化 ArcFaceCore arcFace = new ArcFaceCore(APP_ID,SDK_KEY,ArcFaceDetectMode.IMAGE, ArcFaceFunction.FACE_DETECT | ArcFaceFunction.FACE_RECOGNITION | ArcFaceFunction.AGE | ArcFaceFunction.FACE_3DANGLE | ArcFaceFunction.GENDER,DetectionOrientPriority.ASF_OP_0_ONLY,50,32); // 将 Bitmap 转换成 ImageData ImageData heyingImgData = ImageDataConverter.ConvertToImageData(heying); // 人脸检测 // 也可直接传入 Bitmap 来调用相关接口 会自动转换成 ImageData,但这里推荐用 ImageData MultiFaceModel multiFaceB = arcFace.FaceDetection(heying); // 传入 ImageData ,推荐使用这个接口 MultiFaceModel multiFace = arcFace.FaceDetection(heyingImgData); // 人脸信息检测(年龄/性别/人脸3D角度)最多支持4张人脸信息检测,超过部分返回未知 这是官方文档的说明 arcFace.FaceProcess(heyingImgData, multiFace); // 获取年龄信息 List<int> ageList = arcFace.GetAge(); // 获取性别信息 List<int> genderList = arcFace.GetGender(); // 获取人脸角度信息 List<Face3DAngleModel> face3DAngleList = arcFace.GetFace3DAngle(); // 将第一张图片的 Bitmap 转换成 ImageData ImageData faceData1 = ImageDataConverter.ConvertToImageData(face1); // 检测第一张图片中的人脸 MultiFaceModel multiFace1 = arcFace.FaceDetection(faceData1); // 取第一张图片中返回的第一个人脸信息 AsfSingleFaceInfo faceInfo1 = multiFace1.FaceInfoList.First(); // 提第一张图片中返回的第一个人脸的特征 AsfFaceFeature faceFeature1 = arcFace.FaceFeatureExtract(faceData1, ref faceInfo1); ImageData faceData2 = ImageDataConverter.ConvertToImageData(face2); // 检测第二张图片中的人脸 MultiFaceModel multiFace2 = arcFace.FaceDetection(faceData2); // 取第二张图片中返回的第一个人脸信息 AsfSingleFaceInfo faceInfo2 = multiFace2.FaceInfoList.First(); // 提第二张图片中返回的第一个人脸的特征 AsfFaceFeature faceFeature2 = arcFace.FaceFeatureExtract(faceData2, ref faceInfo2); // face1 face2 人脸对比,将会返回一个 0-1 之间的浮点数值 float result = arcFace.FaceCompare(faceFeature1, faceFeature2); ImageData faceData3 = ImageDataConverter.ConvertToImageData(face3); // 检测第三张图片中的人脸 MultiFaceModel multiFace3 = arcFace.FaceDetection(faceData3); // 取第三张图片中返回的第一个人脸信息 AsfSingleFaceInfo faceInfo3 = multiFace3.FaceInfoList.First(); // 提第三张图片中返回的第一个人脸的特征 AsfFaceFeature faceFeature3 = arcFace.FaceFeatureExtract(faceData3, ref faceInfo3); // face1 face3 人脸对比,将会返回一个 0-1 之间的浮点数值 float result2 = arcFace.FaceCompare(faceFeature1, faceFeature3); // 释放销毁引擎 arcFace.Dispose(); // ImageData使用完之后记得要 Dispose 否则会导致内存溢出 faceData1.Dispose(); faceData2.Dispose(); // BItmap也要记得 Dispose face1.Dispose(); face2.Dispose(); }感谢本项目参考了以下开发者的一些思路和代码,在此表示感谢。C#_Demo_摄像头实时_4线程人脸识别_注册 - Demo 分享 - 虹软人工智能引擎开发者论坛 - Powered by Discuz!https://ai.arcsoft.com.cn/bbs…虹软2.0版本人脸检测C#类库分享 - 第2页 - ArcFace - 虹软人工智能引擎开发者论坛 - Powered by Discuz!https://ai.arcsoft.com.cn/bbs…C#人脸检测与动态人脸识别显示坐标 视频人脸识别WINFORM - ArcFace - 虹软人工智能引擎开发者论坛 - Powered by Discuz! https://ai.arcsoft.com.cn/bbs…另外欢迎打赏哈哈~ ...

February 18, 2019 · 2 min · jiezi

ArcFaceDemo 第二版【C#】——视频人脸识别

使用的虹软人脸识别技术 啥话不说,不用跪求,直接给下载地址:http://common.tenzont.com/com… (话说附件的大小不限制,还是说我的文件太大,实际上确实有点大,60M)。几点说明:1.程序是32位的,若您要用64位的需要把arcface的东西换成64位,emug所需的opencv库换成64位的。2.程序自带了500个人脸的特征文件供您测试3.提供了保存视频图片特征值的功能,可以把您的人脸添加进去。

February 15, 2019 · 1 min · jiezi

人脸检测识别,人脸检测,人脸识别,离线检测,C#源码

百度网盘地址微云地址使用虹软最新人脸识别技术完成开发

February 15, 2019 · 1 min · jiezi

【C#】人脸识别 调用2.0的arcface

记录下使用虹软技术的过程中的问题1.初始化 [DllImport(“libarcsoft_face_engine.dll”, EntryPoint = “ASFInitEngine”, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]private static extern int ASFInitEngine(uint detectMode, int orientPriority, int scale, int maxFaceNumber, uint combinedMask, out IntPtr pEngine);开始时,写了个long detectMode,返回错误信息是:0x16004,(detectFaceScaleVal 不支持)正式鬼扯,害我各种调整scale。改成uint就ok了。话说就一个mode,您弄const uint ASF_DETECT_MODE_VIDEO = 0x00000000; //Video模式,一般用于多帧连续检测const uint ASF_DETECT_MODE_IMAGE = 0xFFFFFFFF; //Image模式,一般用于静态图的单次检测这两宝贝,我也是醉了。0/1不行吗?2.人脸检测[DllImport(“libarcsoft_face_engine.dll”, EntryPoint = “ASFDetectFaces”, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]private static extern int ASFDetectFaces(IntPtr pEngine, int width, int height, int format, IntPtr pImageData, out ASF_MultiFaceInfo faceInfo);开始时,弄了张身份证照片,102的宽度,不是4的倍数,返回0x1600F,手动编辑图片,拉伸成104的宽度便告成功。最终写了个方法,自动设成4的倍数,如下:private (int W, int H, IntPtr PImageData) GetImageData(Bitmap bitmap) { var bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); int width = (bitmap.Width + 3) / 4 * 4; var bytesCount = bmpData.Height * width * 3; IntPtr pImageData = Marshal.AllocCoTaskMem(bytesCount); if (width == bitmap.Width) CopyMemory(pImageData, bmpData.Scan0, bytesCount); else for (int i = 0; i < bitmap.Height; i++) CopyMemory(IntPtr.Add(pImageData, i * width * 3), IntPtr.Add(bmpData.Scan0, i * bmpData.Stride), bmpData.Stride); bitmap.UnlockBits(bmpData); return new ValueTuple<int, int, IntPtr>(width, bitmap.Height, pImageData); }踩了这两个坑后,便愉快的运行了。话说2.0的优点也不少:1.特征值缩小了,由原来的20多K变成1032字节(咱就不能变成1024?)2.比对速度快了很多很多,四线程10万次的比对也只要3秒多钟。 ...

February 14, 2019 · 1 min · jiezi

如何在NEO共识节点间分配任务

作者:Alexey Vanin任何计算机系统都有监控操作,可能会发送心跳信息、校验和查询及哈希请求等。这些操作在本文中都被统称为任务。在中心化系统中,通常会有一个受认证的节点或节点群组来完成任务。而去中心化系统可以将任务下发给各个节点,从而灵活拓展,因此效率也显然更高,但这也就导致了相应的问题——到底如何在所选节点间分配任务。我们可以通过以下两种方式解决这个问题:• 节点随机选择要做的任务• 节点使用dBFT之类的共识算法分配任务本文将探讨第二种方法。拜占庭容错任务分配假设有v个任务和n个可随时在系统中工作的节点。每个任务和节点都有一个独一无二的标识符。因此每个节点都可以使用HRW [2]选择任务,使用预先设定的算法来执行任务。任务分配共识实际上确认了所有任务都是在未发生技术故障的前提下完成的。使用dBFT算法,即使网络[1]中高达1/3的节点做出妥协也可以达成共识。举个例子,若某系统n=v=3:该系统有3个任务和3个节点,其中有一个节点有欺诈行为。这些任务统一在各节点中进行分配:如果每个节点承担一个任务,在最坏的情况下会有一个任务可能无法被执行。因此我们使用了冗余来确保所有任务都能被执行。v任务须被n/3+1个节点执行。在此情况下,节点任务池的大小可通过以下公式算出。在此案例中,P(3,3) = 2不管哪个节点做出了妥协,所有任务都仍能被正确执行。图1 不同任务数前提下任务池大小的最大值图1标明,该公式的值趋向于v/3,意味着在任何情况下各节点任务池中的任务都占所有任务的1/3。在负载增加的情况下系统可能不能正常拓展。减小任务池大小若系统的n=3 v=4,任务池大小即为P(3,4) = 2.3 ≈ 3。各节点的任务池大小就可减至2:因此,各任务被执行的几率就都达到了66%。因此任务池大小可以缩减,只要确保一定的精度就可以了。出于研究的目的,我们建立了一个模拟模型进行实验,参数为v=1000。我们尝试在不同n的情况下找到能使任务无法执行的可能性小于0.00001的任务池大小。该模型已开源在github[3]。结果如图2所示:图2 v=1000时的任务池容量实验上图显示,该模型的实验数据实际上处于任务池容量的最大和最小值的区间里。因此当n增加时,任务池所需容量会减小。任务池容量的最大值与实验值之间的差额可以使用R(x)(位于y1轴上,取0-1之间的任意值)公式求得一个近似值。然后任务池容量就可通过以下公式求得。深入研究本文并未考虑网络节点发生故障的可能性,而且所有实验均在最坏假设下进行,做出妥协的节点数也带入了最大值。显然,100个节点中出现33个妥协节点的比例也比3个节点中出现1个妥协节点的几率小。因此可以将“妥协几率”的方程定义为Q(x),而后计算出任务池的大小为P(n,v)⋅R(n)⋅Q(n)。而当妥协节点的数量小于n/3时,R(x)的的行为还需要深入研究。参考文献1.张铮文,区块链拜占庭容错算法:https://docs.neo.org/en-us/ba…2.一致性哈希(最高随机权值):https://en.wikipedia.org/wiki…3.模拟模型库:https://github.com/AlexVanin/…原文翻译自:https://medium.com/@neospcc/t…

February 11, 2019 · 1 min · jiezi

新手入门ROS必备资料

近几年来,ROS机器人操作系统在国内越来越火,学习的资料也越来越多,但机器人是个综合性很强的学科,涉及知识面广。没有专业基础的人学习ROS还是有些困难的,为了帮助新手快速入门ROS,以下这些资料一定要收藏。ROS官方资料:wiki http://wiki.ros.org/http://wiki.ros.org/cn/ apihttp://docs.ros.org/indigo/api/tf: http://docs.ros.org/indigo/ap...  github:https://github.com/ 博客:Exbot:http://blog.exbot.net/archive…史话机器人操作系统ROS:http://blog.exbot.net/archive…ROS 新手问题Q&A粗略合集:http://blog.exbot.net/archive…ROS 通讯层模型:http://blog.exbot.net/archive…机器人操作系统ROS Indigo 入门学习(0)——ROS的UNIX基础http://blog.exbot.net/archive… 老王说ROS:http://blog.exbot.net/archive…拿ROS navigation 玩自主导航攻略(1): http://blog.exbot.net/archive…拿ROS玩移动机器人自主导航攻略(2): http://blog.exbot.net/archive…Aicrobo:http://www.aicrobo.com/blog/2…Bradlucas:http://roboturing.com/blog/ta…古月居:http://blog.csdn.net/hcx25909…http://www.guyuehome.com/知行合一:http://blog.csdn.net/heyijia0327kint_zhao: http://blog.csdn.net/zyh82135…小菜鸟上校: http://blog.csdn.net/xiaocain…东方赤龙曲和政: http://blog.csdn.net/crazyquh…无穷山色: http://blog.csdn.net/dxuehui?…Aicrobo:http://my.phirobot.com/blog/2…step by step:http://blog.csdn.net/yaked/ar…Erva的专栏:http://blog.csdn.net/hawaecho… ROS论坛:RobotOS.net:http://www.robotos.net/forum-…ROS中文:http://www.cnros.org/forum.phpROSWIKI :http://www.roswiki.com/index….硅步:http://ros.gaitech.net/forum.php 国内涉及ROS公司:百度深度学习研究院:http://blog.exbot.net/archive…SLAMTEC:http://www.slamtec.com/Aicrobo:http://www.aicrobo.com/index….以上ROS学习资料,只要你肯花时间,用心学习,相信攻克ROS这门功课指日可待!

February 2, 2019 · 1 min · jiezi

二进制状态码

我们知道计算机中数据都是用二进制数存储。二进制数是一系列0和1的组合,长整型64位,最短的字节型也有8位。其中每一位0和1都可以看做一种状态的开和关,所以就有了这样的一种状态码存储方式:把同一对象的多种状态按位组合到一个整数中。例如我们最最常见的 *nix 文件权限:第9位第8位第7位第6位第5位第4位第3位第2位第1位第0位是否目录所有者读权限所有者写权限所有者执行权限组读权限组写权限组执行权限其余用户读权限其余用户写权限其余用户执行权限0111101101那么这一组状态在程序中表示为:0b0111101101,即八进制的 0o755,十进制的 493。二进制状态码存储的主要好处是节省存储空间,相对于键值对(对象)存储而言可读性较差(当然文件权限这种另说)。这种存储方式仅适用于“一个对象有多种状态,每种状态仅有两种情况”这一情形,请不要对一种状态多种情况的情形使用二进制状态码存储方式,更不要出现十进制的 0 1 10 这种状态码,很蠢。。。使用位运算操作状态码基于这种存储方式,也衍生了一些操作状态码的方式:判断第 x 位状态是否开启(x 以 0 开始,下同):status & (1 << x) == 0打开第 x 位status |= 1 << x关闭第 x 位status &= ~(1 << x)编程语言支持某些编程语言提供了对二进制状态码的一些原生支持。C/C++ 提供了 位域,以及专门的模板库 bitset 用于简化位运算操作。C# 则提供了 Flags 特性标记某个枚举被视作位域另外很重要一点,JavaScript 虽然也支持位运算,但由于 JavaScript 中的 number 类型都是双精度浮点数,在做位运算时会先将数值截断至 32 位长度。例如很著名的数字转整数bug:10000000000 | 0 => 1410065408。所以注意如果后端返回二进制状态码让前端判断,确保后端使用 uint32_t 存储完

February 1, 2019 · 1 min · jiezi

分布式系统关注点——「无状态」详解

如果这是第二次看到我的文章,欢迎下方扫码订阅我的个人公众号(跨界架构师)哟~ ????本文长度为2728字,建议阅读8分钟。坚持原创,每一篇都是用心之作~前面聊完的2个章节「数据一致性」和「高可用」其实本质是一个通过提升复杂度让整体更完善的方式。接下去我们开始聊一些让系统更简单,更容易维护的东西——「易伸缩」,首当其冲的第一篇文章就是「stateless」,也叫「无状态」。z哥带你先来认识一下「状态」是什么。一、初识「状态」之前在「负载均衡」的第四篇(分布式系统关注点——做了「负载均衡」就可以随便加机器了吗?)中提到过一个例子,我们再翻出来一下。开发Z哥对运维Y弟喊:“Y弟,现在系统好卡,刚上了一波活动,赶紧帮我加几台机器上去顶一下。”Y弟回复说:“没问题,分分钟搞定”。然后就发现数据库的压力迅速上升,DBA就吼了:“Z哥,你丫的搞什么呢?数据库要被你弄垮了”。然后客服那边接框也爆炸了,越来越多的用户说刚登陆后没多久,操作着就退出了,接着登陆,又退出了,到底还做不做生意了。这个案例中的问题,产生的根本原因是因为系统中存在着大量「有状态」的业务处理过程。二、「有状态」和「无状态」N.Wirth曾经在它1984年出版的书中将程序的定义经典的概括为:程序=数据结构+算法。(这个概括也是这本书的书名)这是一个很有意思的启发,受它的影响,z哥认为程序做的事情本质就是“数据的移动和组合”,以此来达到我们所期望的结果。而如何移动、如何组合是由“算法”来定的,所以z哥延伸出一个新的定义:数据+算法=成果。通过程序处理所得到的“成果”其实和你平时生活中完成的任何事情所得到的“成果”是一样的。任何一个“成果”都是你通过一系列的“行动”将最开始的“原料”进行加工、转化,最终得到你所期望的“成果”。比如,你将常温的水,通过“倒入水壶”、“通电加热”等工作后变成了100度的水,就是这样一个过程。正如烧水的例子,大多数时候得到一个“成果”往往需要好几道“行动”才能完成。这个时候如果想降低这几道“行动”总的成本(如:时间)该怎么办呢?自然就是提炼出反复要做的事情,让其只做一次。而这个事情在程序中,就是将一部分“数据”放到一个「暂存区」(一般就是本地内存),以提供给相关的“行动”共用。 但是如此一来,就导致了需要增加一道关系,以表示每一个“行动”与哪一个「暂存区」关联。因为在程序里,“行动”可能是「多线程」的。这时,这个“行动”就变成「有状态」的了。题外话:共用同一个「暂存区」的多个“行动”所处的环境经常被称作「上下文」。我们再来深入聊聊「有状态」。「暂存区」里存的是「数据」,所以可以理解为“有数据”就等价于“有状态”。「数据」在程序中的作用范围分为「局部」和「全局」(对应局部变量和全局变量),因此「状态」其实也可以分为两种,一种是局部的「会话状态」,一种是全局的「资源状态」。题外话:因为有些服务端不单单负责运算,还会提供其自身范围内的「数据」出去,这些「数据」属于服务端完整的一部分,被称作「资源」。所以,理论上「资源」可以被每个「会话」来使用,因此是全局的状态。本文聊的「有状态」都指的是「会话状态」。与「有状态」相反的是「无状态」,「无状态」意味着每次“加工”的所需的“原料”全部由外界提供,服务端内部不做任何的「暂存区」。并且请求可以提交到服务端的任意副本节点上,处理结果都是完全一样的。有一类方法天生是「无状态」,就是负责表达移动和组合的“算法”。因为它的本质就是:接收“原料”(入参)“加工”并返回“成果”(出参)为什么网上主流的观点都在说要将方法多做成「无状态」的呢?因为我们更习惯于编写「有状态」的代码,但是「有状态」不利于系统的易伸缩性和可维护性。在分布式系统中,「有状态」意味着一个用户的请求必须被提交到保存有其相关状态信息的服务器上,否则这些请求可能无法被理解,导致服务器端无法对用户请求进行自由调度(例如双11的时候临时加再多的机器都没用)。同时也导致了容错性不好,倘若保有用户信息的服务器宕机,那么该用户最近的所有交互操作将无法被透明地移送至备用服务器上,除非该服务器时刻与主服务器同步全部用户的状态信息。这两个问题在负载均衡的第四篇(分布式系统关注点——做了「负载均衡」就可以随便加机器了吗?)中也有提到。但是如果想获得更好的伸缩性,就需要尽量将「有状态」的处理机制改造成「无状态」的处理机制。三、「无状态」化处理将「有状态」的处理过程改造成「无状态」的,思路比较简单,内容不多。首先,状态信息前置,丰富入参,将处理需要的数据尽可能都通过上游的客户端放到入参中传过来。当然,这个方案的弊端也很明显:网络数据包的大小会更大一些。另外,客户端与服务端的交互中如果涉及到多次交互,则需要来回传递后续服务端处理中所需的数据,以避免需要在服务端暂存。这些改造的目的都是为了尽量少出现类似下面的代码。func(){ return i++;}而是变成:func(i){ return i+1;}要更好的做好这个「无状态」化的工作,依赖于你在架构设计或者项目设计中的合理分层。尽量将会话状态相关的处理上浮到最前面的层,因为只有最前面的层才与系统使用者接触,如此一来,其它的下层就可以将「无状态」作为一个普遍性的标准去做。与此同时,由于会话状态集中在最前面的层,所以哪怕真的状态丢失了,重建状态的成本相对也小很多。比如三层架构的话,保证BLL和DAL都不要有状态,代码的可维护性大大提高。如果是分布式系统的话,保证那些被服务化的程序都不要有状态。除了能提高可维护性,也大大有利于做灰度发布、A/B测试。题外话:在这里,提到做分层的目的是为了说明,只有将IO密集型程序和CPU密集型程序分离,才是通往「无状态」真正的出路。一旦分离后,CPU密集型的程序自然就是「无状态」了。如此也能更好的做「弹性扩容」。因为常见的需要「弹性扩容」的场景一般指的就是CPU负荷过大的时候。最后,如果前面的都不合适,可以将共享存储作为降级预案来运用,如远程缓存、数据库等。然后当状态丢失的时候可以从这些共享存储中恢复。所以,最理想的状态存放点。要么在最前端,要么在最底层的存储层。四、总结任何事物都是有两面性的,正如前面提到的,我们并不是要所有的业务处理都改造成「无状态」,而只是挑其中的一部分。最终还是看“价值”,看“性价比”。比如,将一个以“状态”为核心的即时聊天工具的所有处理过程都改造成「无状态」的,就有点得不偿失了。相关文章:分布式系统关注点——初识「高可用」分布式系统关注点——仅需这一篇,吃透「负载均衡」妥妥的分布式系统关注点——「负载均衡」到底该如何实施?分布式系统关注点——做了「负载均衡」就可以随便加机器了吗?这三招来帮你!分布式系统关注点——99%的人都能看懂的「熔断」以及最佳实践分布式系统关注点——想通关「限流」?只要这一篇分布式系统关注点——让你的系统“坚挺不倒”的最后一个大招——「降级」分布式系统关注点——99%的人都能看懂的「补偿」以及最佳实践作者:Zachary出处:https://www.cnblogs.com/Zacha…如果你喜欢这篇文章,可以点一下底部的「赞????」。这样可以给我一点反馈。: )谢谢你的举手之劳。▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码加入哦~。定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。

January 18, 2019 · 1 min · jiezi

CLR via C# 第四&五章

CLR via C第四章 类型基础所有类型都是从System.Object派生类型转换命名空间和程序集运行时的相互关系4.1 所有类型都从System.Object派生CLR要求所有类型都用System.Object派生。换句话说,所有类型都能够向上递归,显式转换为System.Object类在转换过程中,如果涉及到值类型与引用类型的转换的时候,也同样会发生装箱操作。CLR要求所有对象都通过 new 操作符创建,这里面存在一个new生效的过程计算类型及所有基类型中所有实例字段需要的字节数。分配1中计算的字节数,从而分配内存,将所有分配的字节都设为0初始化对象的类型对象指针和同步索引块调用类型的实例构造器(要一直往上走到System.Object没有与new操作符对应的delete操作符。因此无法 显式 地释放为对象分配的内存(涉及到GC操作4.2 类型转换CLR保证了类型安全。CLR了解运行时的类型时什么是通过由System.Object类中的非虚方法GetType得到的。CLR允许对象转换为它的类型或者它的任何基类型。换句话说,子类型隐式转换为夫类型是CLR所允许的。而当父类型转换成子类型的时候,因为涉及到可能的内存分配问题,所以CLR中只允许进行显式转换。is&as操作符这两个操作符,前者是进行类型检查,返回的是Boolean值后者进行的是赋值操作,返回的是指向该对象的指针or null通过这两个能够快速的在类型安全的情况下进行类型检查与类型检查并将符合的类型进行赋值的操作。4.3 命名空间与程序集命名空间所进行的是 逻辑分组 在程序集层面,实际上命名空间所进行的应当是一个字符串替换的过程因此我们必须要警惕尽量不要进行相同函数命名的过程。在产生命名空间不同但是最后声明了一样的函数名的问题,实际上可以通过向前拓展对应的namespace来使得准确定位到某一个方法。4.4 运行时的相互关系当存在栈内函数的反复调用的时候,对于上一个调用者来说,实际上会在当前的线程栈上预留一个返回的路标,通过这个地址,能够在调用完B程序之后通过这个路标返回到A的线程栈中进行接下来的语句的执行。第五章 基元类型、引用类型和值类型编程语言的基元类型引用类型和值类型对象哈希码dynamic基元类型基元类型实际上就是一种能够被编译器直接理解的短类型,它算是一种define,但是最后的实现还是会落到System库中的对应代码去,如 string 和 String 实际上并没有差别,实际上前者在编译到IL代码之后和后者是一样的。5.2 引用类型和值类型两者最大的区别在于 生存位置 前者生存于托管堆中,后者生存于线程栈中其他的区别大致上有两点1. 不会受GC影响 2. 不会像引用类型一样实际上是通过指针来操作本身引用类型的特点内存必须通过托管堆分配堆上分配的每个对象都有一些额外成员,这些成员必须初始化对象中的其他字节总设为0从托管堆分配对象时,可能强制进行一次gc当声明一个新的引用类型,然后将现有的引用类型赋值给它,这个时候并不会触发一次新的引用类型的生成,因为这样子的名称实际上可以认为是一个存在与线程栈上面的指针,这个指针直接指向了托管堆中该引用类型的内存空间,也就是说,和之前生成的引用类型实际上没有区别,只是多了一种唤醒它的名称而已。对新的名称修改同样会造成本身类型的改变。CLR可以根据使用者指定进行不同的内存排列。5.3 值类型的装箱和拆箱当我们将值类型进行类型转化的时候,一旦转换为了引用类型,这个时候就会显式的发生装箱的操作,装箱结束之后这个值类型会存在一个在托管堆上面的副本,该副本就会变成一个引用类型。而当我们再次将这个副本转换为某个值类型的时候,则就会发生拆箱的操作。因为装箱拆箱操作涉及到大量的内存申请和gc操作,所以实际上会造成非常大的时间浪费。

January 17, 2019 · 1 min · jiezi

任务队列和异步接口的正确打开方式(.NET Core版本)

layout: posttitle: 任务队列和异步接口的正确打开方式(.NET Core版本)category: dotnet coredate: 2019-01-12tags:dotnet coreredis消息队列异步API任务队列和异步接口的正确打开方式什么是异步接口?<h2 id=“asynchronous-operations”>Asynchronous Operations</h2>Certain types of operations might require processing of the request in an asynchronous manner (e.g. validating a bank account, processing an image, etc.) in order to avoid long delays on the client side and prevent long-standing open client connections waiting for the operations to complete. For such use cases, APIs MUST employ the following pattern:For POST requests:Return the 202 Accepted HTTP response code.In the response body, include one or more URIs as hypermedia links, which could include:The final URI of the resource where it will be available in future if the ID and path are already known. Clients can then make an HTTP GET request to that URI in order to obtain the completed resource. Until the resource is ready, the final URI SHOULD return the HTTP status code 404 Not Found.{ "rel": "self", "href": "/v1/namespace/resources/{resource_id}", "method": "GET" }* A temporary request queue URI where the status of the operation may be obtained via some temporary identifier. Clients SHOULD make an HTTP GET request to obtain the status of the operation which MAY include such information as completion state, ETA, and final URI once it is completed.{ "rel": "self", "href": "/v1/queue/requests/{request_id}, "method": "GET" }"For PUT/PATCH/DELETE/GET requests:Like POST, you can support PUT/PATCH/DELETE/GET to be asynchronous. The behaviour would be as follows:Return the 202 Accepted HTTP response code.In the response body, include one or more URIs as hypermedia links, which could include:A temporary request queue URI where the status of the operation may be obtained via some temporary identifier. Clients SHOULD make an HTTP GET request to obtain the status of the operation which MAY include such information as completion state, ETA, and final URI once it is completed.{ "rel": "self", "href": "/v1/queue/requests/{request_id}, "method": "GET" }"APIs that support both synchronous and asynchronous processing for an URI:APIs that support both synchronous and asynchronous operations for a particular URI and an HTTP method combination, MUST recognize the Prefer header and exhibit following behavior:If the request contains a Prefer=respond-async header, the service MUST switch the processing to asynchronous mode.If the request doesn’t contain a Prefer=respond-async header, the service MUST process the request synchronously.It is desirable that all APIs that implement asynchronous processing, also support webhooks as a mechanism of pushing the processing status to the client.资料引自:paypal/API Design Patterns And Use Cases:asynchronous-operations用人话来说简单来说就是请求过来,直接返回对应的resourceId/request_id,然后可以通过resourceId/request_id查询处理结果处理过程可能是队列,也可能直接是异步操作如果还没完成处理,返回404,如果处理完成,正常返回对应数据好像也没什么讲了….全文结束吧.样例代码部分啦实现逻辑创建任务,生成"request-id"存储到对应redis zset队列中同时往redis channel发出任务消息, 后台任务处理服务自行处理此消息(生产者-消费者模式)任务处理服务处理完消息之后,将处理结果写入redis,request-id为key,结果为value,然后从从redis zset从移除对应的"request-id"获取request-id处理结果时:如果request-id能查询到对应的任务处理结果,直接返回处理完的数据; 如果request-id还在sortset队列则直接返回404 + 对应的位置n,表示还在处理中,前面还有n个请求;时序图大概长这样:喜闻乐见代码时间RequestService.cs// RequestService.csusing System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using CorrelationId;using Microsoft.AspNetCore.Mvc;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;using Newtonsoft.Json.Linq;using StackExchange.Redis;using static StackExchange.Redis.RedisChannel;namespace MTQueue.Service{ public class RequestService { private readonly ICorrelationContextAccessor _correlationContext; private readonly ConnectionMultiplexer _redisMultiplexer; private readonly IServiceProvider _services; private readonly ILogger<RequestService> _logger; public RequestService(ICorrelationContextAccessor correlationContext, ConnectionMultiplexer redisMultiplexer, IServiceProvider services, ILogger<RequestService> logger) { _correlationContext = correlationContext; _redisMultiplexer = redisMultiplexer; _services = services; _logger = logger; } public long? AddRequest(JToken data) { var requestId = _correlationContext.CorrelationContext.CorrelationId; var redisDB = _redisMultiplexer.GetDatabase(CommonConst.DEFAULT_DB); var index = redisDB.SortedSetRank(CommonConst.REQUESTS_SORT_SETKEY, requestId); if (index == null) { data[“requestId”] = requestId; redisDB.SortedSetAdd(CommonConst.REQUESTS_SORT_SETKEY, requestId, GetTotalSeconds()); PushRedisMessage(data.ToString()); } return redisDB.SortedSetRank(CommonConst.REQUESTS_SORT_SETKEY, requestId); } public static long GetTotalSeconds() { return (long)(DateTime.Now.ToLocalTime() - new DateTime(1970, 1, 1).ToLocalTime()).TotalSeconds; } private void PushRedisMessage(string message) { Task.Run(() => { try { using (var scope = _services.CreateScope()) { var multiplexer = scope.ServiceProvider.GetRequiredService<ConnectionMultiplexer>(); multiplexer.GetSubscriber().PublishAsync(CommonConst.REQUEST_CHANNEL, message); } } catch (Exception ex) { _logger.LogError(-1, ex, message); } }); } public Tuple<JToken, long?> GetRequest(string requestId) { var redisDB = _redisMultiplexer.GetDatabase(CommonConst.DEFAULT_DB); var keyIndex = redisDB.SortedSetRank(CommonConst.REQUESTS_SORT_SETKEY, requestId); var response = redisDB.StringGet(requestId); if (response.IsNull) { return Tuple.Create<JToken, long?>(default(JToken), keyIndex); } return Tuple.Create<JToken, long?>(JToken.Parse(response), keyIndex); } }}// RedisMQListener.csusing System;using System.Text;using System.Threading;using System.Threading.Tasks;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using Microsoft.Extensions.Logging;using Microsoft.Extensions.Options;using MTQueue.Model;using MTQueue.Service;using Newtonsoft.Json.Linq;using StackExchange.Redis;using static StackExchange.Redis.RedisChannel;namespace MTQueue.Listener{ public class RedisMQListener : IHostedService { private readonly ConnectionMultiplexer _redisMultiplexer; private readonly IServiceProvider _services; private readonly ILogger<RedisMQListener> _logger; public RedisMQListener(IServiceProvider services, ConnectionMultiplexer redisMultiplexer, ILogger<RedisMQListener> logger) { _services = services; _redisMultiplexer = redisMultiplexer; _logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { Register(); return Task.CompletedTask; } public virtual bool Process(RedisChannel ch, RedisValue message) { _logger.LogInformation(“Process start,message: " + message); var redisDB = _services.GetRequiredService<ConnectionMultiplexer>() .GetDatabase(CommonConst.DEFAULT_DB); var messageJson = JToken.Parse(message); var requestId = messageJson[“requestId”]?.ToString(); if (string.IsNullOrEmpty(requestId)) { _logger.LogWarning(“requestId not in message.”); return false; } var mtAgent = _services.GetRequiredService<ZhihuClient>(); var text = mtAgent.GetZhuanlan(messageJson); redisDB.StringSet(requestId, text.ToString(), CommonConst.RESPONSE_TS); _logger.LogInformation(“Process finish,requestId:” + requestId); redisDB.SortedSetRemove(CommonConst.REQUESTS_SORT_SETKEY, requestId); return true; } public void Register() { var sub = _redisMultiplexer.GetSubscriber(); var channel = CommonConst.REQUEST_CHANNEL; sub.SubscribeAsync(channel, (ch, value) => { Process(ch, value); }); } public void DeRegister() { // this.connection.Close(); } public Task StopAsync(CancellationToken cancellationToken) { // this.connection.Close(); return Task.CompletedTask; } }}// RequestsController.csusing System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using CorrelationId;using Microsoft.AspNetCore.Mvc;using MTQueue.Service;using Newtonsoft.Json.Linq;namespace MTQueue.Controllers{ [Route(“v1/[controller]”)] [ApiController] public class RequestsController : ControllerBase { private readonly ICorrelationContextAccessor _correlationContext; private readonly RequestService _requestService; private readonly ZhihuClient _mtAgentClient; public RequestsController(ICorrelationContextAccessor correlationContext, RequestService requestService, ZhihuClient mtAgentClient) { _correlationContext = correlationContext; _requestService = requestService; _mtAgentClient = mtAgentClient; } [HttpGet("{requestId}”)] public IActionResult Get(string requestId) { var result = _requestService.GetRequest(requestId); var resource = $"/v1/requests/{requestId}"; if (result.Item1 == default(JToken)) { return NotFound(new { rel = “self”, href = resource, method = “GET”, index = result.Item2 }); } return Ok(result.Item1); } [HttpPost] public IActionResult Post([FromBody] JToken data, [FromHeader(Name = “Prefer”)]string prefer) { if (!string.IsNullOrEmpty(prefer) && prefer == “respond-async”) { var index = _requestService.AddRequest(data); var requestId = _correlationContext.CorrelationContext.CorrelationId; var resource = $"/v1/requests/{requestId}"; return Accepted(resource, new { rel = “self”, href = resource, method = “GET”, index = index }); } return Ok(_mtAgentClient.GetZhuanlan(data)); } }}完整代码见:https://github.com/liguobao/TaskQueueSample ...

January 13, 2019 · 5 min · jiezi

.NET Core中使用RabbitMQ正确方式

.NET Core中使用RabbitMQ正确方式首先甩官网:http://www.rabbitmq.com/然后是.NET Client链接:http://www.rabbitmq.com/dotnet.htmlGitHub仓库:https://github.com/rabbitmq/rabbitmq-dotnet-client下面直接进入正文,一共是两个主题:消费者怎么写?生产者怎么写?消费者在dotnet core mvc中,消费者肯定不能通过API或者其他的东西启动,理应是跟着程序一起启动的.所以…在dotnet core 2.0以上版本,我们直接用 IHostedService 接口实现..NET Core 中基于 IHostedService 实现后台定时任务Implementing background tasks in .NET Core 2.x webapps or microservices with IHostedService and the BackgroundService class直接上代码.// RabbitListener.cs 这个是基类,只实现注册RabbitMQ后到监听消息,然后每个消费者自己去重写RouteKey/QueueName/消息处理函数Processusing System;using System.Text;using System.Threading;using System.Threading.Tasks;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using Microsoft.Extensions.Logging;using Microsoft.Extensions.Options;using RabbitMQ.Client;using RabbitMQ.Client.Events;namespace Test.Listener{ public class RabbitListener : IHostedService { private readonly IConnection connection; private readonly IModel channel; public RabbitListener(IOptions<AppConfiguration> options) { try { var factory = new ConnectionFactory() { // 这是我这边的配置,自己改成自己用就好 HostName = options.Value.RabbitHost, UserName = options.Value.RabbitUserName, Password = options.Value.RabbitPassword, Port = options.Value.RabbitPort, }; this.connection = factory.CreateConnection(); this.channel = connection.CreateModel(); } catch (Exception ex) { Console.WriteLine($“RabbitListener init error,ex:{ex.Message}”); } } public Task StartAsync(CancellationToken cancellationToken) { Register(); return Task.CompletedTask; } protected string RouteKey; protected string QueueName; // 处理消息的方法 public virtual bool Process(string message) { throw new NotImplementedException(); } // 注册消费者监听在这里 public void Register() { Console.WriteLine($“RabbitListener register,routeKey:{RouteKey}”); channel.ExchangeDeclare(exchange: “message”, type: “topic”); channel.QueueDeclare(queue:QueueName, exclusive: false); channel.QueueBind(queue: QueueName, exchange: “message”, routingKey: RouteKey); var consumer = new EventingBasicConsumer(channel); consumer.Received += (model, ea) => { var body = ea.Body; var message = Encoding.UTF8.GetString(body); var result = Process(message); if (result) { channel.BasicAck(ea.DeliveryTag, false); } }; channel.BasicConsume(queue: QueueName, consumer: consumer); } public void DeRegister() { this.connection.Close(); } public Task StopAsync(CancellationToken cancellationToken) { this.connection.Close(); return Task.CompletedTask; } }}// 随便贴一个子类using System;using System.Text;using Microsoft.Extensions.Options;using Newtonsoft.Json.Linq;using RabbitMQ.Client;using RabbitMQ.Client.Events;using Microsoft.Extensions.DependencyInjection;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.Logging;namespace Test.Listener{ public class ChapterLister : RabbitListener { private readonly ILogger<RabbitListener> _logger; // 因为Process函数是委托回调,直接将其他Service注入的话两者不在一个scope, // 这里要调用其他的Service实例只能用IServiceProvider CreateScope后获取实例对象 private readonly IServiceProvider _services; public ChapterLister(IServiceProvider services, IOptions<AppConfiguration> options, ILogger<RabbitListener> logger) : base(options) { base.RouteKey = “done.task”; base.QueueName = “lemonnovelapi.chapter”; _logger = logger; _services = services; } public override bool Process(string message) { var taskMessage = JToken.Parse(message); if (taskMessage == null) { // 返回false 的时候回直接驳回此消息,表示处理不了 return false; } try { using (var scope = _services.CreateScope()) { var xxxService = scope.ServiceProvider.GetRequiredService<XXXXService>(); return true; } } catch (Exception ex) { _logger.LogInformation($“Process fail,error:{ex.Message},stackTrace:{ex.StackTrace},message:{message}”); _logger.LogError(-1, ex, “Process fail”); return false; } } }}然后,记住….注入到Startup.cs的时候,使用AddHostedService services.AddHostedService<ChapterLister>();消费者就这样玩了.生产者咋玩呢?这个其实更简单.using System;using System.Net;using Newtonsoft.Json.Linq;using RestSharp;using Microsoft.Extensions.Logging;using Microsoft.Extensions.Options;using RabbitMQ.Client;using Newtonsoft.Json;using System.Text;namespace Test.SDK{ public class RabbitMQClient { private readonly IModel _channel; private readonly ILogger _logger; public RabbitMQClient(IOptions<AppConfiguration> options, ILogger<RabbitMQClient> logger) { try { var factory = new ConnectionFactory() { HostName = options.Value.RabbitHost, UserName = options.Value.RabbitUserName, Password = options.Value.RabbitPassword, Port = options.Value.RabbitPort, }; var connection = factory.CreateConnection(); _channel = connection.CreateModel(); } catch (Exception ex) { logger.LogError(-1, ex, “RabbitMQClient init fail”); } _logger = logger; } public virtual void PushMessage(string routingKey, object message) { _logger.LogInformation($“PushMessage,routingKey:{routingKey}”); _channel.QueueDeclare(queue: “message”, durable: false, exclusive: false, autoDelete: false, arguments: null); string msgJson = JsonConvert.SerializeObject(message); var body = Encoding.UTF8.GetBytes(msgJson); _channel.BasicPublish(exchange: “message”, routingKey: routingKey, basicProperties: null, body: body); } }}切记注入实例的时候用单例模式.services.AddSingleton<RabbitMQClient, RabbitMQClient>();全文完… ...

January 13, 2019 · 2 min · jiezi

CLR via C#

CLR是其平台上的语言到达机器上的最后一个经手者,曾经微软是CLR的唯一代码提供者,包括了将IL到机器码,并且将IL中的许多特性进行翻译之后优化执行的功能,与JVM在某些地方存在相似,但是实际上又有多处不同。什么是CLRCLR=公共语言运行时=(Common Language Runtime)在运行的时候实际上它是操作系统上面的一个层级的系统,通过将托管模块中的IL代码能够被翻译成机器代码以供计算机运行。被称为"运行时"实际上就是因为在程序运行的全程,CLR负责将提交操作到系统的这样一个工作。如何工作三个点程序集JIT功能模块首先,支持CLR的编译器将其对应的语言翻译成IL并生成元数据之后,将其封装为托管模块之后,与资源文件一起合并为程序集。紧接着因为IL无法被计算机原生理解,所以CLR会把IL翻译成机器代码,使得机器得以执行当程序运行时,CLR同时负责了不同部分的拓展功能,包括除加载程序集以外的所有模块误区CLR托管代码比native代码要更慢吗通常意义上来说是的,尤其是某一段IL代码没有被加载之前,可以认为其比CLR更慢一些,因为存在一个翻译到内存的过程,但是实际上在日常进行运行的时候,因为其实际上也被翻译成了机器代码,所以速度上并没有太大的劣势CLR与JVM等价吗不,JVM更多的是做一个类似于CLR中的C#->IL->机器代码的过程,CLR同时包括了线程同步等内容,虽然在现在CLR与JVM的类型已经有一点类似,但是在严格意义上来说,CLR应当是在CLR语言中的JVM类型的超集CLR只能运行托管代码并不,实际上可以通过dll等方式连接非托管代码,使得运行与CLR上的语言也可以越过CLR直接操作操作系统的内存与CPU状态或调度等内容

January 8, 2019 · 1 min · jiezi

DAPP 开发直通车-如何基于NEL 轻钱包来开发DAPP

之前做了 DAPP 开发直通车,通讲了一下开发一个DAPP的过程。 但是涉及多工种,多步骤。入手还是非常困难的。经过不懈的努力,做了很多铺垫工作之后,我终于可以告诉你:开发DAPP for NEO,从未如此简单绿谷镇楼。首先请记住NEL的GITHUB首页,这里拥有NEO相关的一大堆开发成果,牛逼我就不吹了,你自己看一看这些项目,你至少可以感受到,我们真的是马不停蹄。唯一的问题是文档化程度很低,我们欢迎有兴趣的同志一起来提升建设NEL这个围绕NEO的中国开发者社区。那么轻钱包在哪里呢?找到这个项目,他还有一个兄弟项目他们分别使用 c# 和 typescript 开发 的轻钱包SDK,提供你开发轻钱包的所需工具。在仔细观察过蓝鲸涛代码、neonjs等代码后,他们均无法完成我们的目标。开发一个开发者使用的轻钱包,于是我们下定决心,绕了很大的弯路,重新建立了这些代码。目前c#的sdk完成度100%,目前在建设例子阶段。Ts的sdk完成度50%,但仅移植c#代码,风险为零,大家只需要等待。这是SDK 对不对,那么轻钱包呢?轻钱包就是SDK的例子,包含在SDK代码中虽然是例子,我们在功能性上面已经能让大部分NEO钱包汗颜,而且还有我们马不停蹄的开发热情,会不断提升开发NEO轻钱包的平均水准。我想用不了多久,开发NEO轻钱包就会变成一件很困难的事情,因为你开发了半天,还要承担着巨大的压力。别人会说:你看那个钱包还不如一个例子。C#的例子钱包长这样Ts的例子钱包长这样钱包和DAPP有什么关系我们的钱包定位是例子,也是开发者工具,他只有一个功能,发交易,但做到啥交易都能发。Nep5 交易,没问题,ico募资,没问题。你会说,NEOGUI难道不是啥都能发么?用户咋用?确实如此,功能强,不代表好用。DAPP就是为了解决这个不好用的问题产生的。那DAPP的最小单位不就正是一个个交易嘛?于是,我们的钱包有一个DAPP模块,你不需要写代码,只需要配置一下,这个DAPP 是由哪几笔交易构成的,帮助用户填个参数,按个按钮。交易就发出去了,不就完成了让用户简单使用的功能了嘛。看DAPP功能在这里钱包的使用之前说过钱包的使用,不再赘述。http://www.cnblogs.com/crazyl…做实验之前先load 一个key 进来。这个钱包保护不是很彻底,我知道出了问题是无法阻止你们发出WTF诅咒的,诅咒完,来github提个issue,甚好。或者直接发个pr帮我们修bug,更好。这是个实验钱包,所以只支持导入一个key。你高兴的话研究一下这个钱包的代码,做点改装。注意这个DAPP区域,我划分出了红黄蓝绿四个区域黄区黄区是选择一个DAPP,对我们这个系统来说,一个DAPP就是一个json,这是跨平台的。到时候我们的网页版使用同样的json,啥也不改,就跨平台了。自动加载dapp路径下所有的json绿区绿区是选择一个dapp之后出现的,我们把dapp 分解为一个个的操作比如Who am I 这个 DAPP 就三个操作,一个是查一个人的名字,输入地址。一个是设置名字,输入地址和名字,设置名字我区分为 test 交易,和发送交易绿区里就是操作和输入蓝区蓝区就是显示你输入的值而已红区红区是用来执行操作和显示结果的Check这个DAPP功能的操作就是从存储区查询,结果就是显示一个string ,给这个地址取得名字是啥。不写代码实现一个DAPP因为我们是用json配置DAPP,那是不用写代码的。当然,如果我们配置的功能无法满足你的需求,你就需要写代码了。让我们开始看起来这是WHO AM I dapp的全貌他有三个功能,在红色区,我折叠了两个,一个一个看title部分json里面的title就指定这个DAPP的名字Consts部分Consts用来配置一些常量,后面可以引用,这样能减少一些直接填值的错误这里consts.base 其实是whoami DAPP的 合约ScriptHash主要是scriptcall 和 scriptparam 需要引用Funcs部分一Funcs部分,看起来很复杂,但是他一共就五个部分Name desc inputs call resultsName 和 desc 对应图上红色,不用解释。Inputs对应图上黄色我们这里指定需要输入一个地址,上面就自动生成了这个UIFuncs部分二这里看起来比较复杂的是Call部分,Type表示这个dapp操作只需要去查一下存储区。查存储区不需要调用合约需要两个参数,脚本hash去const里面拿,地址从输入拿。这个(address)有这个,我们就会把这个string处理成address的scripthash。还有其他的Call type,主要有invokescript,测试执行合约,和sendrawtransaction,发布交易。具体你就可以看代码啦。Funcs部分三Results 配置输出,对于getStorage 只能有一个输出。做过智能合约开发你就知道,getstorage取到的是bytearray,我们这里可以指定类型,会帮你翻译好。一个DAPP功能完成了结果就是通过这样的配置,这里就得到了一个可以方便的查询每个人的名字的小工具,这就是DAPP的意义了。其他部分了解了我们是如何简单的开发DAPP的,其他功能,我就简单说说了SetName(test) 的 calltype 是invokescript,此时还是免费操作,适合做一些查询NEP5余额啦,查询NNS地址啦之类的不需要对区块链产生影响的功能Setname(sendraw) 的calltype 是sendrawtransaction,这是要花gas的(NEO10个gas内的交易免费,基本还是免费的)这里的按钮变成了两个,第一个会把交易发出去。第二个会让你在交易面板看看你刚才生成的交易是啥。我们这个客户端,只有一个功能:发交易。我们把DAPP定义为一种方便的帮助用户发起各种各样交易的辅助工具。根据我们提供的这些功能,你是不是觉得,开发DAPP,也不那么复杂了呢。One more thing还有一个好消息告诉大家,NNS (neo域名服务)TEST版就快放出了。NNS域名服务还会发行代币。其实我们智能合约早就写了,就是没有一个合适的地方放DAPP,总不能让大家都拿着NEOGUI去拼合约吧。万事俱备,才敢告诉你。现在这个未完成的NNS.TEST DAPP其实已经可以注册域名啦。你要是高兴的话,根据我们 nel github docs项目里的NNS白皮书已经可以自己完成这套功能啦。

January 5, 2019 · 1 min · jiezi

用 C# 开发自己的语音识别程序

开发工具:vs 2017AI 平台:http://ai.baidu.com/准备工作1、注册百度账号2、登录百度 AI 开发平台,http://ai.baidu.com/3、在控制台点击“百度语音”服务,点击“创建应用”,填写必填项,勾选额外接口,点击立即创建获取秘钥。在应用列表中查看自己的id用 360 软件管家安装 vs2017 创建自己的项目1、新建项目打开 vs2017,点击文件,新建项目,选择 visual C# –> windows 桌面 –> windows 窗体应用,选择自己的项目地址,点击确定2、添加 baiduai 开发包点击引用 –> 管理 nuGet 程序包,搜索 baiduai,点击下载3、UI 设计直接拖动即可,生成界面如下4、后台功能实现选择文件按钮private void button1_Click(object sender, EventArgs e){ OpenFileDialog fdlg = new OpenFileDialog(); fdlg.Title = “C# Corner Open File Dialog”; //fdlg.InitialDirectory = @“c:/”; //@是取消转义字符的意思 //fdlg.Filter = “All files(.)|.|All files(.)|. “; ///* // * FilterIndex 属性用于选择了何种文件类型,缺省设置为0,系统取Filter属性设置第一项 // * ,相当于FilterIndex 属性设置为1.如果你编了3个文件类型,当FilterIndex =2时是指第2个. // / fdlg.FilterIndex = 2; /// // *如果值为false,那么下一次选择文件的初始目录是上一次你选择的那个目录, // *不固定;如果值为true,每次打开这个对话框初始目录不随你的选择而改变,是固定的 // */ //fdlg.RestoreDirectory = true; if (fdlg.ShowDialog() == DialogResult.OK) { //textBox1.Text = System.IO.Path.GetFileNameWithoutExtension(fdlg.FileName); filePath.Text = System.IO.Path.GetFullPath(fdlg.FileName); }}开始识别按钮// 语音合成按钮private void button2_Click(object sender, EventArgs e){ string value = this.videoType.Text; String filePath = this.filePath.Text; // 设置APPID/AK/SK String APP_ID = “14433392”; String API_KEY = “C7WMYgLeWv3Wm2yogwv5gD08”; String SECRET_KEY = “xcvwiwikALBDBaIcGisNQ6aQImtj3qua”; var client = new Asr(APP_ID, API_KEY, SECRET_KEY); client.Timeout = 60000; // 修改超时时间 client.Timeout = 120000; // 若语音较长,建议设置更大的超时时间. ms FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); byte[] buffur = new byte[fs.Length]; try { fs.Read(buffur, 0, (int)fs.Length); } catch (Exception ex) { Console.Write(ex.StackTrace); } finally { if (fs != null) { //关闭资源 fs.Close(); } } var result = client.Recognize(buffur, value, 16000); Convert.ToString(result); JToken resultStr = null; result.TryGetValue(“result”, out resultStr); Console.WriteLine(“aToken===>"+ resultStr); voiceResult.Text = Convert.ToString(resultStr); Console.Write(result);}开始合成按钮调用 api 中 C# SDK 的语音合成 apihttps://ai.baidu.com/docs#/AS…// 开始合成按钮(语音合成功能)private void synthesisButton_Click(object sender, EventArgs e){ String APP_ID = “14433392”; String API_KEY = “C7WMYgLeWv3Wm2yogwv5gD08”; String SECRET_KEY = “xcvwiwikALBDBaIcGisNQ6aQImtj3qua”; // 获取输入框的值 String value = this.Speech_Synthesis.Text; // 将 value 转成语音文件存放到本地 var client = new Baidu.Aip.Speech.Tts(API_KEY, SECRET_KEY); // 可选参数 var option = new Dictionary<string, object>() { {“spd”, 5}, // 语速 {“vol”, 7}, // 音量 {“per”, 3} // 发音人,4:情感度丫丫童声 }; var result = client.Synthesis(value, option); try { if (result.ErrorCode == 0) { // 或 result.Success File.WriteAllBytes(“E:/prepared/北航/07_工程实践–AI方向/作业/WindowsFormsApplication1/WindowsFormsApplication1/tmp.mp3”, result.Data); } } catch (Exception ex) { Console.Write(ex.StackTrace); } Play();}关注微信公众号[ prepared ],后续会更新一系列有深度的 AI 文章。 ...

January 2, 2019 · 2 min · jiezi

手机直播系统偶尔会需要到的:Windows 下视频采集技术

Windows下视频采集的方法在 Windows 下主要有两种方法来采集视频: 一种是通过 Media Foundation,另一种是通过 DirectShow。 Meida Foundation 是 Windows 从 vista 之后推出的一套全新的 多媒体SDK,简单方便,从 Win7 开始成熟起来。 另一种是 DirectShow,它主要用于 win7 之前的采集视频。使用 DirectShow 编写代码比较麻烦,主要是因为 Windows 工程师按照逻辑电路的思维方式设计了 DirectsShow 的开发接口,引入了什么 filter, pin之类的概念。这些老掉牙的东西现在估计没几个人能搞明白,除非你是从那个时代过来的,哈哈。这也解释了为啥现在很少有人学习 Windows 程序开发了,就是因为跟不上时代。你看人家 Android/iOS做视频采集多简单,你整的这么麻烦,谁还愿意学!Media Foundation的一些概念 DirectShow 方案我们放到以后再分析,今天我们主要讲下 MediaFoundation 如何进行视频采集。 在讲之前,我们先要补充一些基本概念。这些概念大家可以从[Media Foundation Programming Guide][1] 找到。下面的文字基本是翻译的 Windows 的官方文档。MF(MediaFoundation)的整体结构图如下:MF 提供了两种不同的编程模型。第一种是上图的左半部分,媒体数据通过端到端的管道传递。Application首先初始化管道,然后调用相应方法控制管道中的流。第二种如上图的右半部分,Application可以从 Source Reader拉数据,也可以向 Sink Writer 推数据。这种模型对于处理数据非常有用。 Primitives 和 Platfrom 图底部的 Primitives 是一些辅助API:Attributes: 相当于一个 Map, 由 key/value 组成。Media Type: 描述媒体数据流的格式。Media Buffers: 存放一段媒体数据。Media Samples: 存放 Media Buffers 的容器,相当于一个 Buffter List。MF Platform 提供了一些核心功能的API。例如异步调用、工作队列。Media PipelineMedia Pipeline 包括三种类型对象:Media Sources、MFTs(Media Foundation Transfors)、Media Sink。Media Sources: 将数据引入到管道里。数据可以来自本地文件,网络流或都是硬件设备。MFTs: 处理流数据。在 MFTs 里实现了编解码器。Media Sink: 消费数据。显示视频到显示屏上,播放声音或写数据到媒体文件。Media Session 通过管道控制数据流。如质量控制,音频/视频同步,格式的改变。Source Reader 和 Sink WriterSource Reader 和 Sink Writer提供了使用 Media Foundation 的另一种方法(相较于 media source, transforms, media sink)。Source Reader 控制着 media source 和 多个解码器。Sink Writer 控制着 media sink 和 多个编码器。你可以使用 Source Reader 从 media source 获取到压缩或未压缩的数据,并使用 Sinker Writer 编码数据并发送给 media sink。 下面我们就来看看 MF 是如何采集视频数据的。采集视频数据通过上面的介绍,我们基本可以知道 MF 采用 从源采集数据,编解码,输出渲染这种架构来处理多媒体。这种方式通俗易懂,使用起来非常方便。MF采集视频的基本步骤MF采集数据使用的是架构中的第二种编程模型,其步骤如下:初始化 COM 组件。获取视频设备列表。激活某个视频设备,获取该设备的 Media Source。根据请求命令和 Media Source 创建 Source Reader。为 Source Reader 设置 Media Type。通过 Source Reader 从设备中读取 Media Type 格式的视频数据。以上就是 MF 从视频设备采集数所的基本步骤,下面我们来详细介绍每一步。详细分析由于每一步的代码都实分简单,我这里就不做过多的文字描述了,通过下面的代码及其注释大家很容易理解其中的每一步。初始化 COM 组件并启动 MFCoInitializeEx(NULL, COINIT_APARTMENTTHREAD | COINIT_DISABLE_OLEDDE)MFStartup(MF_VERSION)获取所有的视频设备IMFAttributes *videoCmd = NULL; IMFActivate **videoDevices = NULL;UINT32 videoDeviceCount = 0;//设置获取视频设备的命令MFCreateAttributes(videoCmd, 1/表示只分配一项/);videoCmd->setGUID( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, //key MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); //value//获取视频设备列表 MFEnumDeviceSources( videoCmd, &videoDevices, //这里是设备列表 &videoDeviceCount); //这里存放的是设备的个数激活某个视频设备IMFMediaSource *mediaSource = NULL;//激活第一个视频设备,并为该设置备生成逻辑上的媒体源(Media Source)videoDevices[0]->ActivateObject(IID_PPV_ARGS(&mediaSource));创建 Source ReaderIMFSourceReader *soureReader = NULL;//通过媒体源和请求命令,可以获取source reader。(第二种开发模型)MFCreateSourceReaderFromMediaSource( mediaSource, videoCmd, &sourceReader);设置 Media TypeIMFMediaType *mediaType = NULL;MFCreateMediaType(&mediaType);//设置媒体为视频mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);//YUV格式为 I420mediaType->SetGUID(MF_MT_SUBTYPE, WMMEDIASUBTYPE_I420); //每个视频帧的大小为 640 * 480MFSetAttributeSize(mediaType, MF_MT_FRAME_SIZE, 640, 480);sourceReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediaType);读取数据IMFSample *sample = NULL;DWORD index, flags;LONGLONG llVideoTs;while(runing){ sourceReader->ReadSample( MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &index, //实际流的index &flags, //staus flags &llVideoTs, //时间戳 &sample); //存放采集到的视频数据}通过上面简单的几步,就可以轻松的从视频设备里取到视频数据了。MF相对于 DirectShow真是简单太多了。上面介绍的是使用同步方式使用MF采集视频数据,MF还提供了效率更高的异步方式获取视频数据,有兴趣的朋友可以以本篇文章为基础去学习它的异步方式。小结今天向大家介绍了在 Windows下使用 MF 如何采集视频的方法。通过以下 6 步即可做到:初始化 COM 组件。获取视频设备列表。激活某个视频设备,获取该设备的 Media Source。根据请求命令和 Media Source 创建 Source Reader。为 Source Reader 设置 Media Type。通过 Source Reader 从设备中读取 Media Type 格式的视频数据。另外, MF 的采集方案只适用于 Win7 以后的系统,对于之前的系统还是要使用 DirectShow 方案。我也会在后面再为大家介绍如何使用 DirectShow 采集视频。 ...

January 2, 2019 · 2 min · jiezi

分布式系统关注点——99%的人都能看懂的「补偿」以及最佳实践

如果这是第二次看到我的文章,欢迎文末扫码订阅我哟~ ????本文长度为4229字,建议阅读11分钟。这是本系列中既「数据一致性」后的第二章节——「高可用」的完结篇。前面几篇中z哥跟你聊了聊做「高可用」的意义,以及如何做「负载均衡」和「高可用三剑客」(熔断、限流、降级,文末会附上前文连接:))。这次,我们来聊一聊在保证对外高可用的同时,憋出的“内伤”该如何通过「补偿」机制来自行消化。一、「补偿」机制的意义?以电商的购物场景为例:客户端 —->购物车微服务 —->订单微服务 —-> 支付微服务。这种调用链非常普遍。那么为什么需要考虑补偿机制呢?正如之前几篇文章所说,一次跨机器的通信可能会经过DNS 服务,网卡、交换机、路由器、负载均衡等设备,这些设备都不一定是一直稳定的,在数据传输的整个过程中,只要任意一个环节出错,都会导致问题的产生。而在分布式场景中,一个完整的业务又是由多次跨机器通信组成的,所以产生问题的概率成倍数增加。但是,这些问题并不完全代表真正的系统无法处理请求,所以我们应当尽可能的自动消化掉这些异常。可能你会问,之前也看到过「补偿」和「事务补偿」或者「重试」,它们之间的关系是什么?你其实可以不用太纠结这些名字,从目的来说都是一样的。就是一旦某个操作发生了异常,如何通过内部机制将这个异常产生的「不一致」状态消除掉。题外话:在Z哥看来,不管用什么方式,只要通过额外的方式解决了问题都可以理解为是「补偿」,所以「事务补偿」和「重试」都是「补偿」的子集。前者是一个逆向操作,而后者则是一个正向操作。只是从结果来看,两者的意义不同。「事务补偿」意味着“放弃”,当前操作必然会失败。▲事务补偿「重试」则还有处理成功的机会。这两种方式分别适用于不同的场景。▲重试因为「补偿」已经是一个额外流程了,既然能够走这个额外流程,说明时效性并不是第一考虑的因素,所以做补偿的核心要点是:宁可慢,不可错。因此,不要草率的就确定了补偿的实施方案,需要谨慎的评估。虽说错误无法100%避免,但是抱着这样的一个心态或多或少可以减少一些错误的发生。二、「补偿」该怎么做?做「补偿」的主流方式就前面提到的「事务补偿」和「重试」,以下会被称作「回滚」和「重试」。我们先来聊聊「回滚」。相比「重试」,它逻辑上更简单一些。「回滚」Z哥将回滚分为2种模式,一种叫「显式回滚」(调用逆向接口),一种叫「隐式回滚」(无需调用逆向接口)。最常见的就是「显式回滚」。这个方案无非就是做2个事情:首先要确定失败的步骤和状态,从而确定需要回滚的范围。一个业务的流程,往往在设计之初就制定好了,所以确定回滚的范围比较容易。但这里唯一需要注意的一点就是:如果在一个业务处理中涉及到的服务并不是都提供了「回滚接口」,那么在编排服务时应该把提供「回滚接口」的服务放在前面,这样当后面的工作服务错误时还有机会「回滚」。其次要能提供「回滚」操作使用到的业务数据。「回滚」时提供的数据越多,越有益于程序的健壮性。因为程序可以在收到「回滚」操作的时候可以做业务的检查,比如检查账户是否相等,金额是否一致等等。由于这个中间状态的数据结构和数据大小并不固定,所以Z哥建议你在实现这点的时候可以将相关的数据序列化成一个json,然后存放到一个nosql类型的存储中。「隐式回滚」相对来说运用场景比较少。它意味着这个回滚动作你不需要进行额外处理,下游服务内部有类似“预占”并且“超时失效”的机制的。例如:电商场景中,会将订单中的商品先预占库存,等待用户在 15 分钟内支付。如果没有收到用户的支付,则释放库存。下面聊聊可以有很多玩法,也更容易陷入坑里的「重试」。「重试」「重试」最大的好处在于,业务系统可以不需要提供「逆向接口」,这是一个对长期开发成本特别大的利好,毕竟业务是天天在变的。所以,在可能的情况下,应该优先考虑使用「重试」。不过,相比「回滚」来说「重试」的适用场景更少一些,所以我们第一步首先要判断,当前场景是否适合「重试」。比如:下游系统返回「请求超时」、「被限流中」等临时状态的时候,我们可以考虑重试而如果是返回“余额不足”、“无权限”等明确无法继续的业务性错误的时候就不需要重试了一些中间件或者rpc框架中返回Http503、404等没有何时恢复的预期的时候,也不需要重试如果确定要进行「重试」,我们还需要选定一个合适的「重试策略」。主流的「重试策略」主要是以下几种。策略1.立即重试。有时故障是候暂时性,可能是因网络数据包冲突或硬件组件流量高峰等事件造成的。在此情况下,适合立即重试操作。不过,立即重试次数不应超过一次,如果立即重试失败,应改用其它的策略。策略2.固定间隔。应用程序每次尝试的间隔时间相同。 这个好理解,例如,固定每 3 秒重试操作。(以下所有示例代码中的具体的数字仅供参考。)策略1和策略2多用于前端系统的交互式操作中。策略3.增量间隔。每一次的重试间隔时间增量递增。比如,第一次0秒、第二次3秒、第三次6秒,9、12、15这样。return (retryCount - 1) * incrementInterval;使得失败次数越多的重试请求优先级排到越后面,给新进入的重试请求让道。策略4.指数间隔。每一次的重试间隔呈指数级增加。和增量间隔“殊途同归”,都是想让失败次数越多的重试请求优先级排到越后面,只不过这个方案的增长幅度更大一些。return 2 ^ retryCount;策略5.全抖动。在递增的基础上,增加随机性(可以把其中的指数增长部分替换成增量增长。)。适用于将某一时刻集中产生的大量重试请求进行压力分散的场景。return random(0 , 2 ^ retryCount);策略6.等抖动。在「指数间隔」和「全抖动」之间寻求一个中庸的方案,降低随机性的作用。适用场景和「全抖动」一样。var baseNum = 2 ^ retryCount;return baseNum + random(0 , baseNum);3、4、5、6策略的表现情况大致是这样。(x轴为重试次数)为什么说「重试」有坑呢?正如前面聊到的那样,出于对开发成本考虑,你在做「重试」的时候可能是复用的常规调用的接口。那么此时就不得不提一个「幂等性」问题。 如果实现「重试」选用的技术方案不能100%确保不会重复发起重试,那么「幂等性」问题是一个必须要考虑的问题。哪怕技术方案可以确保100%不会重复发起重试,出于对意外情况的考量,尽量也考虑一下「幂等性」问题。幂等性:不管对程序发起几次重复调用,程序表现的状态(所有相关的数据变化)与调用一次的结果是一致的话,就是保证了幂等性。这意味着可以根据需要重复或重试操作,而不会导致意外的影响。对于非幂等操作,算法可能必须跟踪操作是否已经执行。所以,一旦某个功能支持「重试」,那么整个链路上的接口都需要考虑幂等性问题,不能因为服务的多次调用而导致业务数据的累计增加或减少。 满足「幂等性」其实就是需要想办法识别重复的请求,并且将其过滤掉。思路就是:给每个请求定义一个唯一标识。在进行「重试」的时候判断这个请求是否已经被执行或者正在被执行,如果是则抛弃该请求。第1点,我们可以使用一个全局唯一id生成器或者生成服务(可以扩展阅读,分布式系统中的必备良药 —— 全局唯一单据号生成)。 或者简单粗暴一些,使用官方类库自带的Guid、uuid之类的也行。然后通过rpc框架在发起调用的客户端中,对每个请求增加一个唯一标识的字段进行赋值。第2点,我们可以在服务端通过Aop的方式切入到实际的处理逻辑代码之前和之后,一起配合做验证。大致的代码思路如下。【方法执行前】if(isExistLog(requestId)){ //1.判断请求是否已被接收过。 对应序号3 var lastResult = getLastResult(); //2.获取用于判断之前的请求是否已经处理完成。 对应序号4 if(lastResult == null){ var result = waitResult(); //挂起等待处理完成 return result; } else{ return lastResult; } }else{ log(requestId); //3.记录该请求已接收}//do something..【方法执行后】logResult(requestId, result); //4.将结果也更新一下。如果「补偿」这个工作是通过MQ来进行的话,这事就可以直接在对接MQ所封装的SDK中做。在生产端赋值全局唯一标识,在消费端通过唯一标识消重。三、「重试」的最佳实践再聊一些Z哥积累的最佳实践吧(划重点:)),都是针对「重试」的,的确这也是工作中最常用的方案。「重试」特别适合在高负载情况下被「降级」,当然也应当受到「限流」和「熔断」机制的影响。当「重试」的“矛”与「限流」和「熔断」的“盾”搭配使用,效果才是最好。需要衡量增加补偿机制的投入产出比。一些不是很重要的问题时,应该「快速失败」而不是「重试」。过度积极的重试策略(例如间隔太短或重试次数过多)会对下游服务造成不利影响,这点一定要注意。一定要给「重试」制定一个终止策略。当回滚的过程很困难或代价很大的情况下,可以接受很长的间隔及大量的重试次数,DDD中经常被提到的「saga」模式其实也是这样的思路。不过,前提是不会因为保留或锁定稀缺资源而阻止其他操作(比如1、2、3、4、5几个串行操作。由于2一直没处理完成导致3、4、5没法继续进行)。四、总结这篇我们先聊了下做「补偿」的意义,以及做补偿的2个方式「回滚」和「重试」的实现思路。然后,提醒你要注意「重试」的时候需要考虑幂等性问题,并且z哥也给出了一个解决思路。最后,分享了几个z哥总结的针对「重试」的最佳实践。希望对你有所帮助。Question:你之前有哪些时候是通过自己人工来做「补偿」的经历吗?欢迎吐槽~z哥自己就有多次熬到半夜才把“意外”造成的混乱清理干净,刻骨铭心啊????。相关文章:分布式系统关注点——初识「高可用」分布式系统关注点——仅需这一篇,吃透「负载均衡」妥妥的分布式系统关注点——「负载均衡」到底该如何实施?分布式系统关注点——做了「负载均衡」就可以随便加机器了吗?这三招来帮你!分布式系统关注点——99%的人都能看懂的「熔断」以及最佳实践分布式系统关注点——想通关「限流」?只要这一篇分布式系统关注点——让你的系统“坚挺不倒”的最后一个大招——「降级」分布式系统中的必备良药 —— 全局唯一单据号生成作者:Zachary出处:https://www.cnblogs.com/Zacha…▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码加入哦~。定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。 ...

January 2, 2019 · 1 min · jiezi

.NET Core单元测试之搞死开发的覆盖率统计(coverlet + ReportGenerator )

.NET Core单元测试之搞死开发的覆盖率统计这两天在给项目补单元测试,dalao们要求要看一下测试覆盖率翻了一波官方test命令覆盖率倒是有支持了,然而某个更新日志里面写着【“Support for Linux and Mac will be considered separately in a subsequent effort.”】吐血ing。。。8102年都要过去了,微软同学你是不有点过分啊。然后又翻了一堆资料之后发现,GitHub有dalao自己搞了个coverlet来支持测试覆盖率。开源大法拯救世界啊!!!star一个再说。coverlet配置和使用首先安装一下coverlet.dotnet tool install –global coverlet.console或者和我一样懒的话,直接在项目里面引用 “coverlet.msbuild” 这个包也行. <PackageReference Include=“coverlet.msbuild” Version=“2.5.0” />引用之后,执行dotnet test 的时候加多三个参数dotnet test /p:CollectCoverage=true /p:CoverletOutput=’./results/’ /p:CoverletOutputFormat=opencoverCollectCoverage 收集覆盖率CoverletOutput 测试报告数据输出路径CoverletOutputFormat 测试报告格式,支持这些格式json (default)/lcov/opencover/cobertura/teamcity其他参数自己看一下文档说明就好.执行之后大概会看到这些信息.PS:可怜的个位数覆盖率….这个时候Test项目里面的results 文件夹里面就有一个coverage.opencover.xml 文件了.打开这个文件大概长这个样.大概率这不是人看的东西.然后另一个工具又出来了.ReportGeneratorhttps://github.com/danielpalme/ReportGeneratorReportGenerator converts XML reports generated by OpenCover, PartCover, dotCover, Visual Studio, NCover, Cobertura or JaCoCo into human readable reports in various formats.这个工具可以讲上面这些不是人看的XML转换成HTML输出.美滋滋啊美滋滋啊.他们居然还有一个配置指导的页面ReportGenerator/usage真良心!!!我这边简单起见,直接安装 dotnet tool 全局工具算了.dotnet tool install –global dotnet-reportgenerator-globaltool安装好了之后,直接在命令行里面使用 reportgenerator 生成对应的测试报告即可.我这边的命令大概是:reportgenerator ‘-reports:UnitTests/results/*.xml’ ‘-targetdir:UnitTests/results’打开UnitTests/results 下面的index.htm就能看到对应的测试报告了.全文完.明年见!

December 30, 2018 · 1 min · jiezi

gRPC遇见.NET SDK和Visual Studio:构建时自动生成编码

作者:Kirill’kkm’Katsnelson作为微软向其跨平台.NET产品发展的一部分,他们大大简化了项目文件格式,并允许第三方代码生成器与.NET项目的紧密集成。我们一直倾听,现在很自豪地介绍从Grpc.Tools NuGet包的1.17版本开始,.NET C#项目中的Protocol Buffer和gRPC服务.proto文件的集成编译。1.17版本现在可以从Nuget.org获得。你不再需要使用手写脚本从.proto文件生成代码:.NET构建神奇地为你处理此问题。集成工具在调用代码生成器之前,定位proto编译器和gRPC插件,标准Protocol Buffer导入和跟踪依赖关系,以便生成的C#源文件永远不会过时,同时将重新生成保持在最低要求。实质上,.proto文件被视为.NET C#项目中的第一类源。演练在这篇博文中,我们将介绍最简单,且可能是最常见的方案,使用跨平台dotnet命令从.proto文件创建库。我们将基本实现Greeter库的克隆,由C#Helloworld示例目录中的客户端和服务器项目共享。创建新项目让我们从创建新的库项目开始。/work$ dotnet new classlib -o MyGreeterThe template “Class library” was created successfully./work$ cd MyGreeter~/work/MyGreeter$ ls -lFtotal 12-rw-rw-r– 1 kkm kkm 86 Nov 9 16:10 Class1.cs-rw-rw-r– 1 kkm kkm 145 Nov 9 16:10 MyGreeter.csprojdrwxrwxr-x 2 kkm kkm 4096 Nov 9 16:10 obj/观察到dotnet new命令创建了我们不需要的文件Class1.cs,因此将其删除。另外,我们需要一些.proto文件来编译。在本练习中,我们将从gRPC发行版中复制示例文件examples/protos/helloworld.proto。/work/MyGreeter$ rm Class1.cs/work/MyGreeter$ wget -q https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/helloworld.proto(在Windows上,使用del Class1.cs,如果你没有wget命令,只需打开上面的URL,并使用Web浏览器中的“另存为…”命令)。接下来,将必需的NuGet包添加到项目中:/work/MyGreeter$ dotnet add package Grpcinfo : PackageReference for package ‘Grpc’ version ‘1.17.0’ added to file ‘/home/kkm/work/MyGreeter/MyGreeter.csproj’./work/MyGreeter$ dotnet add package Grpc.Toolsinfo : PackageReference for package ‘Grpc.Tools’ version ‘1.17.0’ added to file ‘/home/kkm/work/MyGreeter/MyGreeter.csproj’./work/MyGreeter$ dotnet add package Google.Protobufinfo : PackageReference for package ‘Google.Protobuf’ version ‘3.6.1’ added to file ‘/home/kkm/work/MyGreeter/MyGreeter.csproj’.将.proto文件添加到项目中接下来是一个重要的部分。首先,默认情况下,.csproj项目文件会自动在其目录中找到所有.cs文件,尽管Microsoft现在建议禁止这种通配行为,所以我们也决定不通配.proto文件。因此,必须明确地将.proto文件添加到项目中。其次,将属性PrivateAssets=“All”添加到Grpc.Tools包参考中是非常重要,这样新库的使用者就不会不必要地获取它。这是有道理的,因为程序包只包含编译器、代码生成器和导入文件,这些在.proto文件编译的项目之外是不需要的。虽然,在这个简单的演练中并非严格要求,但始终应该是你的标准做法。因此,编辑文件MyGreeter.csproj以添加helloworld.proto以便将其编译,并将PrivateAssets属性添加到Grpc.Tools包参考中。你生成的项目文件现在应如下所示:<Project Sdk=“Microsoft.NET.Sdk”> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include=“Google.Protobuf” Version=“3.6.1” /> <PackageReference Include=“Grpc” Version=“1.17.0” /> <!– The Grpc.Tools package generates C# sources from .proto files during project build, but is not needed by projects using the built library. It’s IMPORTANT to add the ‘PrivateAssets=“All”’ to this reference: –> <PackageReference Include=“Grpc.Tools” Version=“1.17.0” PrivateAssets=“All” /> <!– Explicitly include our helloworld.proto file by adding this line: –> <Protobuf Include=“helloworld.proto” /> </ItemGroup></Project>构建它!此时,你可以使用dotnet build命令构建项目,以编译.proto文件和库程序集。在本演练中,我们将在命令中添加日志切换开关-v:n,所以我们可以看到编译helloworld.proto文件的命令是在运行。你可能会发现,在第一次编译项目时,总是这样做是个好主意!请注意,下面省略了许多输出行,因为构建输出非常详细。/work/MyGreeter$ dotnet build -v:nBuild started 11/9/18 5:33:44 PM. 1:7>Project “/home/kkm/work/MyGreeter/MyGreeter.csproj” on node 1 (Build target(s)). 1>_Protobuf_CoreCompile: /home/kkm/.nuget/packages/grpc.tools/1.17.0/tools/linux_x64/protoc –csharp_out=obj/Debug/netstandard2.0 –plugin=protoc-gen-grpc=/home/kkm/.nuget/packages/grpc.tools/1.17.0/tools/linux_x64/grpc_csharp_plugin –grpc_out=obj/Debug/netstandard2.0 –proto_path=/home/kkm/.nuget/packages/grpc.tools/1.17.0/build/native/include –proto_path=. –dependency_out=obj/Debug/netstandard2.0/da39a3ee5e6b4b0d_helloworld.protodep helloworld.proto CoreCompile: [ … skipping long output … ] MyGreeter -> /home/kkm/work/MyGreeter/bin/Debug/netstandard2.0/MyGreeter.dllBuild succeeded.如果此时再次调用dotnet build -v:n命令,则不会调用protoc,也不会编译C#源。但是,如果你更改了helloworld.proto源代码,那么在构建期间它的输出将被重新生成,然后由C#编译器重新编译。这是你期望修改任何源文件的常规依赖关系跟踪行为。当然,你也可以将.cs文件添加到同一个项目中:毕竟,它是构建.NET库的常规C#项目。我们在RouteGuide示例中是这样做的。生成的文件在哪里?你可能想知道原型编译器和gRPC插件输出C#文件的位置。默认情况下,它们与其他生成的文件,放在同一目录中,例如对象(在.NET构建用语中称为“中间输出”目录),在obj/目录下。这是.NET构建的常规做法,因此自动生成的文件,不会使工作目录混乱,或意外地置于源代码控制之下。否则,调试器等工具可以访问它们。你也可以在该目录中看到其他自动生成的源:~/work/MyGreeter$ find obj -name ‘.cs’obj/Debug/netstandard2.0/MyGreeter.AssemblyInfo.csobj/Debug/netstandard2.0/Helloworld.csobj/Debug/netstandard2.0/HelloworldGrpc.cs(如果你从Windows命令提示符下执行此演练,请使用dir /s obj .cs)还有更多虽然,在许多情况下最简单的默认行为是足够的,但是有很多方法可以在大型项目中,微调.proto编译过程。如果你发现默认安排不适合你的工作流程,我们建议你阅读文档文件BUILD-INTEGRATION.md,以获取可用选项。该软件包还扩展了Visual Studio的“属性”窗口,因此你可以在Visual Studio界面中为每个文件设置一些选项。“经典”.csproj项目和Mono也有支持。分享你的经验与任何复杂功能的初始版本一样,我们很高兴收到你的反馈。有什么不符合预期的工作?你有不容易用新工具覆盖的场景吗?你是否知道如何改善工作流程?请仔细阅读文档,然后在GitHub上的gRPC代码存储库中提交问题。你的反馈,对于确定构建集成工作的未来发展方向,非常重要! ...

December 20, 2018 · 1 min · jiezi

自定义UiPath Activity实践

开发环境准备:Microsoft Visual Studio with the .NET C# desktop development workload installed.NuGet Package Explorer.自定义Activity分两种,CodeActivity和NativeActivity。简单的区分就是CodeActivity只是执行一段代码,NativeActivity的效果就像内置Activities一样,它们实际上就是不同Activity的父类,实现的时候选择继承哪个类,你的Activity就是属于哪个分类。我们这里是实现CodeActivity,NativeActivity请看开源代码的实现。功能是把特定分隔符连接的字符串分割开,然后随机返回其中的某一个。应用在给选择框一个随机的值。因为主要是学习的目的,所以实际上并没有跟选择框有太大的关联,只是对字符做了处理而已。自定义Activity分两步,首先通过C#语言来编写你的Activity逻辑,编译生成.dll文件,然后通过NuGet Package Explorer打包。下面跟着提示一步一步创建C#项目:Launch Microsoft Visual Studio.Click 文件 > 创建 > 项目 (shortcut: Ctrl + Shift + N). The New Project window is displayed.Click Visual C#. The list of all dependencies using C# is displayed.给你的Activity取个名字, 这里是 “SelectRandomItem”。选择类库(.NET Framework) and click OK. 这样才能把项目导出为 .dll文件。Click 项目 > 添加引用….分别搜索 System.Activities 和 System.ComponentModel.Composition 引用,并勾选。Click the OK button.这样就可以在代码中使用 System.Activities 和 System.ComponentModel.Composition 这两个基础组件了。下面是已添加注释的实现代码:using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Text.RegularExpressions;using System.Activities;using System.ComponentModel; namespace SelectRandomItem{ public class SelectRandomItem : CodeActivity { //参数类型,输入或者输出,或者两者都是 [Category(“Input”)] //必须参数 [RequiredArgument] public InArgument<String> FullText { get; set; } [Category(“Input”)] //参数默认值 [DefaultValue("\r\n")] public InArgument<String> Separator { get; set; } [Category(“Output”)] public OutArgument<String> ChoiceResult { get; set; } /** * Execute是CodeActivity必须重载的方法 * 处理逻辑根据Separator指定的分割符分割FullText * 然后随机返回其中一个 * **/ protected override void Execute(CodeActivityContext context) { //所有的参数取值、赋值都是通过context var fullText = FullText.Get(context); var separator = Separator.Get(context); string[] items = Regex.Split(fullText, separator, RegexOptions.IgnoreCase); Random ran = new Random(); var result = items[ran.Next(items.Length)]; ChoiceResult.Set(context, result); } }}然后点击 生成 > 生成 SelectRandomItem。在输出栏找到SelectRandomItem.dll文件所在位置,准备下一步打包使用。 ...

December 19, 2018 · 1 min · jiezi

【Visual Studio 扩展工具】如何在ComponentOne的DataTree中实现RightToLeft布局

概述C1FlexGrid提供了创建轮廓树的功能,其中可以显示缩进结构,每个节点行旁边都有折叠/展开图标。 然后,用户可以展开和折叠轮廓以查看所需的细节级别。 为此,C1FlexGrid允许您使用其Tree属性和Subtotal方法。现在,如果有任何关于:如何将网格绑定到分层数据源并在子网格中显示细节的想法,ComponentOne已经提供了一个“DataTree”演示,用来实现相同的效果。这个Demo默认存放在这个位置中:Documents ComponentOne Samples WinForms C1FlexGrid CS DataTree。这是通过从C1FlexGrid控件派生控件(C1FlexDataTree)来实现的。 绑定时,控件会检测从属数据源并创建其附加实例以显示子表。但是,如果需要在此分层显示中设置RightToLeft布局,则需要通过代码处理此问题。以下就是具体实现步骤:实现从右到左的布局本文将介绍通过代码处理这些子网格的呈现来实现从右到左布局的步骤。 按照下面提到的两个步骤这将很容易实现:首先,我们将父网格的RightToLeft属性设置为RightToLeft.Yes值。this._flex.RightToLeft = System.Windows.Forms.RightToLeft.Yes;接下来,在C1FlexDataTree.cs的UpdatePosition方法中,子位置和客户端大小计算如下:rc.X = rc.Left - parent.ScrollableRectangle.Width;rc.Y = rc.Bottom;rc.Width = Cols[Cols.Count - 1].Left;rc.Width = Math.Max(Cols[Cols.Count - 1].Left, parent.ScrollableRectangle.Width);点击此处,下载示例DemoComponentOne Enterprise | 下载试用ComponentOne是一款专注于企业应用高性能开发的 .NET 全功能控件套包,包含300余种控件,支持7大平台,涵盖7大功能模块。较于市面上其他同类产品,ComponentOne更加轻盈,功能更加强大,20多年的开发经验,将为您的应用系统带来更为安全的使用体验。纯中文操作界面,一对一技术支持,厂商级的技术服务,共同造就了这款国际顶级控件套包。您对ComponentOne 产品的任何技术问题,都有技术支持工程师提供1对1专业解答,点击此处即可发帖提问>> 技术支持论坛

December 19, 2018 · 1 min · jiezi

【Visual Studio 扩展工具】如何在ComponentOneFlexGrid树中显示RadioButton

概述在ComponentOne Enterprise .NET控件集中,FlexGrid表格控件是用户使用频率最高的控件之一。它是一个功能强大的数据管理工具,轻盈且灵动,以分层的形式展示数据(数据呈现更加直观)。FlexGrid 简介FlexGrid 是业界推崇的 .NET 数据表格,集成于 ComponentOne Enterprise .NET控件集中,可灵活、轻量、快速用于 WPF、WinForm、UWP、MVC、Silverlight、ActiveX平台。分层数据展示在分层数据展示中,FlexGrid 可以使用Node.Checked属性在任何节点行之前显示CheckBox。 然后,父节点之前的这些复选框可用于添加功能,例如启用/禁用或选择/取消选择树中的所有子节点。假设用户想要利用RadioButton来代替这些复选框,并且,需要在对子节点进行“选择/取消”按钮操作时,同时影响父节点状态的功能,利用 FlexGrid 数该如何实现?是否有可能在树中显示单选按钮?答案是肯定的。 诀窍是使用 FlexGrid网格中子节点的Node.Image属性显示RadioButton图像。Node child = c1FlexGrid1.Rows.AddNode(1);child.Image = Image.FromFile("../../Resources/Img_Unchecked.png");我们刚刚展示了效果图。 现在,我们需要在改变“选中/取消”父节点时同时影响子节点状态。 为了满足这一要求,FlexGrid 网格的CellChecked事件将是一个不错的选择。当用户改变父节点当前状态时,它会被触发。private void C1FlexGrid1_CellChecked(object sender, RowColEventArgs e){ //Get the checked/unchecked node row Node node = c1FlexGrid1.Rows[e.Row].Node; //If node row is itself a parent if (node.Parent == null) { //If checked if (node.Checked == CheckEnum.Checked) { //For each child row foreach(Node childNode in node.Nodes) { //Enabled childNode.Row.AllowEditing = true; childNode.Row.StyleNew.BackColor = Color.White; } } //If unchecked else if(node.Checked == CheckEnum.Unchecked) { //For each child row foreach (Node childNode in node.Nodes) { //Disabled childNode.Row.AllowEditing = false; childNode.Row.StyleNew.BackColor = Color.LightGray; } } }}接下来,如果通过网格的MouseDown事件中的代码启用了子节点,它将处理单选按钮的切换。private void C1FlexGrid1_MouseDown(object sender, MouseEventArgs e){ HitTestInfo hti = c1FlexGrid1.HitTest(e.Location); //Get node row corresponding to clicked row Node node = c1FlexGrid1.Rows[hti.Row].Node; Rectangle cellBounds = c1FlexGrid1.GetCellRect(hti.Row, hti.Column); //If it is a child row if(node.Parent != null) { //Only for RadioButton if (hti.Column == 1 && node.Level == 1 && (hti.X >= cellBounds.X + 33) && (hti.X <= cellBounds.X + 43)) { //Check if enabled if(node.Row.AllowEditing) { //Currently unchecked if (Convert.ToInt32(node.Key) == 0) { //Checked state node.Image = Image.FromFile("../../Resources/Img_Checked.png"); node.Key = 1; } //Currently checked else { //Unchecked state node.Image = Image.FromFile("../../Resources/Img_Unchecked.png"); node.Key = 0; } } } }}在上面的代码中,我们在Node.Key属性中存储二进制状态:0表示未检查状态,1表示已检查状态。以上就是关于:如何在ComponentOne FlexGrid树中显示RadioButton的具体做法。与此同时,如果需要显示RadioButton以外的控件,ComponentOne 也同样支持!ComponentOne Enterprise | 下载试用ComponentOne是一款专注于企业应用高性能开发的 .NET 全功能控件套包,包含300余种控件,支持7大平台,涵盖7大功能模块。较于市面上其他同类产品,ComponentOne更加轻盈,功能更加强大,20多年的开发经验,将为您的应用系统带来更为安全的使用体验。纯中文操作界面,一对一技术支持,厂商级的技术服务,共同造就了这款国际顶级控件套包。您对ComponentOne 产品的任何技术问题,都有技术支持工程师提供1对1专业解答,点击此处即可发帖提问>> 技术支持论坛 ...

December 19, 2018 · 1 min · jiezi

关于ComponentOne For WinForm 的全新控件 – DataFilter数据切片器(Beta)

概述数据切片器在电子商务网站上很常见 - 它们可以帮助用户快速过滤所选商品,并且所有过滤选项都可以在一个地方使用,通常包含核心控件类型为:清单,范围栏和单选按钮等。在ComponentOne For WinForm 最新版 2018V3 中,我们推出了数据过滤器的测试版,可以附加到任何数据感知控件中。滤镜布局由手风琴面板组成,它为过滤条件提供了扩展/折叠选项:以下就是ComponentOne For WinForm DataFilter控件的一些用法。使用DataFilter过滤仪表板中的多个控件仪表板是DataFilters的最佳实践,因为屏幕上的所有信息都可以在一个地方以交互方式进行过滤,从而使用户可以通过选择从数据中获取更多信息。 此外,每个视觉所触及之处都可以连接到DataFilter以进一步向下钻取。当产品目录涉及大量有关产品的信息时,数据切片器将会派上用场:将DataFilter控件集成到FlexGrid中在处理Grid或TreeView等数据控件时,切片器可以是一个有用的交互式过滤工具。 当用户需要在多个列上进行过滤时,可以更轻松地将它们全部设置在一个位置:WinForm DataFilter的体系结构DataFilter使用C1CollectionView进行过滤。 C1CollectionView 功能类似于 .NET CollectionView,它支持对集合进行过滤、分组和排序。在系统内部,当数据过滤器根据用户选择创建过滤器表达式时,表达式将传递给C1CollectionView。 C1CollectionView创建数据源的视图,并根据此表达式应用过滤器。WinForm DataFilter的用户界面数据过滤器控件使用的基本布局是“抽屉效果”。 即,每个过滤器项目一个接一个地堆叠,其中每个过滤器项目都可以折叠和展开。在DataFilter中自动选择过滤器类型该控件为不同类型的数据生成不同的过滤器控件:对于布尔数据字段,DataFilter控件生成一个BoolFilter,由复选框表示。对于数字数据字段,控件将生成RangeFilter,它提供范围编辑器和范围滑块以过滤指定范围内的值。对于文本/字符串数据字段,控件生成ChecklistFilter,允许用户从清单中选择和过滤项目。对于DateTime数据字段,控件生成DateRangeFilter,它提供范围编辑器和范围滑块以过滤指定日期范围内的值。异步过滤异步过滤最常见的使用场景是处理大数据时避免阻塞UI。 DataFilter控件中有一个名为ApplyFilterAsync的内置方法,就可以实现异步过滤。保存并加载过滤器DataFilter控件支持通过C1DataFilter类的SaveFilterExpression和LoadFilterExpression方法进行序列化。SaveFilterExpression方法将当前过滤器表达式从C1.Win.DataFilter.C1DataFilter.Filters集合保存到XML文件。 LoadFilterExpression方法从XML文件加载保存的过滤器表达式。设置WinForm DataFilter控件的样式C1DataFilter类提供了一个Styles属性,可用于自定义DataFilter控件及其元素的外观。 在这里,您可以看到如何编辑按钮、过滤器、复选框、编辑器、标题、过滤器标题和滚动条:将 ComponentOne 主题应用于DataFilterDataFilter支持ComponentOne For WinForm中包含的所有主题,包括最近添加的Material和Office 2016主题。 使用C1ThemeController即可在应用程序范围内应用主题:DataFilter在许多应用程序中非常有用。请下载试用最新版 ComponentOne Enterprise .NET控件集,体验产品并分享您的建议和反馈,以帮助我们改进产品体验,并添加在2019年v1即将发布的 ComponentOne 新版本上!ComponentOne Enterprise | 下载试用ComponentOne是一款专注于企业应用高性能开发的 .NET 全功能控件套包,包含300余种控件,支持7大平台,涵盖7大功能模块。较于市面上其他同类产品,ComponentOne更加轻盈,功能更加强大,20多年的开发经验,将为您的应用系统带来更为安全的使用体验。纯中文操作界面,一对一技术支持,厂商级的技术服务,共同造就了这款国际顶级控件套包。您对ComponentOne 产品的任何技术问题,都有技术支持工程师提供1对1专业解答,点击此处即可发帖提问>> 技术支持论坛

December 19, 2018 · 1 min · jiezi

让你的系统“坚挺不倒”的最后一个大招——「降级」

如果这是第二次看到我的文章,欢迎扫描文末二维码订阅我哟本文长度为4069字,建议阅读11分钟。也许你对降级已经有了一些认识,认真看完,我想这篇文章可能会给你带来一些新的收获~前面两篇我们已经聊过了「熔断」(如何在到处是“雷”的系统中「明哲保身」?这是第一招)和「限流」(想通关「限流」?只要这一篇),这次我们聊的就是「高可用三剑客」中剩下的「降级」。不知道这里有多少小伙伴接触过阿里的开放平台。在每次大促的时候,阿里都会发布这样的一个公告。这些调整就是「降级」工作,目的是为了腾出更多资源给核心程序使用,以最大化保证核心业务的可用性,因此就必然需要对非核心业务执行一些降级处理。一、什么是「降级」降级的目的用一句话概括就是:将有限的资源效益最大化。什么样才是效益最大化呢?就像下面这个例子:z哥有3个东西要买,一个3000的A、一个700的B、一个1200的C,对z哥的重要程度A>B>C。但此时,z哥手里只有3000块钱,你说z哥该怎么选才能把钱花的最多?必然是选A咯。根据28原则,我们知道一个系统80%的效益是由最核心的20%的功能产出的。剩下的20%效益需要投入80%的资源才能达到。这就意味着,假如系统平时需要花费100%资源做100%的事情,如果现在访问量增多3倍的话必定扛不住(需要300%的资源)。那么,在不增加资源的情况下,我希望系统不能宕机,依旧能正常工作,必然需要让出那解决剩下20%问题的80%资源。如此一来,理论上这100%的资源就可以支撑原先5倍的访问量。副作用是功能的完整性上受损80%。当然,在实际的场景中不会降级掉80%的功能这么夸张,毕竟还得为用户的体验考虑。举个电商场景典型的例子,在大促的时候,最重要的是什么?转化咯~赚钱咯~ 那么这个时候如果说「评论」功能占用了很多资源,你会怎么处理?其实我们可以选择临时关闭提交评论入口、关闭翻页功能等等,让下单的过程有更多的资源来处理。常见的降级方案表现形式无非以下三种类型。牺牲用户体验为了减少对「冷数据」的获取,禁用列表的翻页功能。为了放缓流量进入的速率,增加验证码机制。为了减少“大查询”浪费过多的资源,提高筛选条件要求(禁用模糊查询、部分条件必选等)。用通用的静态化数据代替「千人千面」的动态数据。甚至更简单粗暴的,直接挂一个页面显示「XX功能在XX时间内暂时关闭」。此类方案虽然或多或少降低了用户的体验,但是在某些时期,有些功能并不是「刚需」。以此换取对系统的保护是笔划算的买卖。牺牲功能完整性还有一些功能是「防御性」的,如果愿意冒险“裸奔”一段时间也会带来可观的资源节约。比如通过临时关闭「风控」、取消部分「条件是否满足」的判断(如,将积分商品添加到购物车时判断积分够不够)等操作,减少这类「验证」动作以释放更多的资源。又或者将原本info、warning级别的日志采集关闭或者直接不采集,仅采集error以及fault级别的日志。牺牲时效性一个事件发生后立马看到效果是一个很符合「思维惯性」的东西。但是根据之前的一篇文章(分布式系统关注点——数据一致性(上篇))我们知道,时效性这个东西一旦涉及到网络传输是不存在真正的“实时”的。但是为了尽可能快的将处理后的结果反映到相关的地方,你会做很多努力。比如库存的及时同步。如果在特殊时期,能够临时降低对时效性的要求(3秒内生效变成30秒生效),也是一个有不错收益的方案。比如原先在商品页会显示当前还剩多少个库存,现在可以调整成固定显示「有货」。以及将一些原本就是异步进行的操作,处理效率放缓,甚至暂缓一段时间。如,送积分、送券等等。讲了这么多,降级具体实施起来要怎么做呢?二、「降级」怎么做主要分为两个环节:定级定序和降级实现。定级定序就像前面的例子中提到的一样,首先我们得先确定每个功能的「重要程度」,它决定了在什么情况下可以抛弃它以保证剩下的功能可用。类似于给日志定义级别一样,比如我们可以定义1~5五个级别,1的级别最高,要拼死保护。5的级别最低最先可以被降级掉。一旦当系统压力过大的时候,先把级别5的功能降级掉。如果还不够再降级别4、级别3,以此类推。但实际上光这样定级还不够,比如被定义为4级的有100个功能,需要降级的时候是一起降级吗?很明显粒度太粗了。如果「定级」好比是横着切蛋糕的话,「定序」就是再来竖着切。我们也可以来定义一些数字,比如序号1~9,序号9最先被降级。然后,你可以以每个程序所支撑的上游程序/功能数量作为一个参考标准。比如,同样是级别5的程序,一个支撑了上游5个功能,一个支撑了10个功能,很显然前者的序号应该更大,更先被降级。当然,根据所支撑的功能数量只是一个「业务无关性」的通用办法。如果想精益求精,还需要对每个功能做「作用」上的分析,毕竟不同功能之间的相对重要性还是有所差异的。(这里可以扩展了解一下Analytic Hierarchy Process,层次分析法,简称AHP)对了,定级定序的时候有一点是需要格外注意的:某个程序所依赖的下游程序的级别不能低于该程序的级别。为什么呢?因为一旦所依赖的程序被降级了,自然会导致其所支撑的所有上游程序不可用。所以,其上游程序的等级再高也是没有意义的。至此,完成了“排兵布阵”,接下来就是“实施运作”了。降级实现首先要制定触发机制。这同熔断、限流一样,什么时候该触发「降级」这个动作也需要依赖提前制定的一些策略。这部分内容和前面两篇(熔断、限流)类似,无非是接口的超时率、错误率,或者系统的资源耗用率等,这里就不重复展开了。当程序发现满足了降级条件进入「降级模式」后,程序该如何处理请求呢?全局变量 int _runLevel = 3; //运行系统级别,默认值5全部变量 int _runIndex = 7; //运行系统序号,默认值9//以下是一个level=4、index=8的功能示例。if(myLevel > _runLevel and myIndex > _runIndex){ // 进入降级模式。}else{ // do something…}题外话:通过Aop+注解(特性)的方式来做上面的if判断是一个爽的事情。虽然处理请求的方式有很多,但特别强调的是,要实现的降级策略要尽可能的简单。因为「边际效应」的存在,为了应对突发状况把事情反而搞复杂了就得不偿失了。那么在实现部分,如果是前端。我们比较常见的是:在返回的http报文中通过Cache-Control的设置,让后续的请求直接走浏览器缓存。页面中原本需要异步加载的数据,直接不加载。禁用部分操作按钮,甚至直接告知“临时关闭”。动态页面的url通过反响代理切换到静态页面返回。这里面除了禁用按钮外,大部分事情都可以在接入层,如nginx中处理掉,这样可以避免对业务项目的代码侵入。如果是后端程序的话,针对「读」类型的操作,可以将“// 进入降级模式”部分代码写成下面的样子:如果是无返回值方法。默认return或者throw一个异常。如果是有返回值方法。默认返回本地mock的数据或者throw一个异常。后端部分如果有使用一些中间件的话,直接在中间件(rpc、mq代理等)中处理掉是极好的(一般会内置一个fallback接口待实现),如此也可以避免对业务代码的侵入。最后我们来聊聊后端程序的「写」问题。缓存是大型系统中的常客,随着系统规模越大,为了在性能和成本上寻求更优,不可避免的会增加复杂度引入多级缓存。如此就会变成:本地缓存 –> 分布式缓存 –> DB/源服务,这样的一个层层递进的关系。平时的代码可能是这样的:if(write数据库(data) == true){ if(write分布式缓存(data) == true){ write本地缓存(data); return success; } else{ rollback数据库(data); return fail; }}else{ return fail;}在高负载时期,我们可以降低对一致性的要求。将耗时的「数据落盘」操作降级为「异步」进行。if(write分布式缓存(data) == true){ write本地缓存(data); pushMessage(data); //发出的消息可以通过集中式的MQ、也可以直接写本地磁盘。 return success;}else{ return fail;}甚至,如果可以的话能做的更彻底,同步到分布式缓存也异步进行。write本地缓存(data); pushMessage(data); //发出的消息可以通过集中式的MQ、也可以直接写本地磁盘。return success;数据库是系统的最后一座堡垒,非非非常极端的情况下,我们可以把一些「写数据」操作在「数据库访问框架」中给禁用了,让给所有资源都给到「读数据」。使得系统从表象上来看至少还是“活着站在那”的,虽然很多功能操作一下就是返回失败(这不也是实在没办法了嘛,面子得要啊,死撑~)。三、总结至此我们聊了做降级的思路以及最常见的一些实现方式,但是真正要把降级最好是一个任重而道远的过程。从方案的角度来说,如果降级的过程需对每个功能/程序逐一进行,那么理论上10个功能点就可以产生P(10,10)= 3628800种方案。再从现实的角度来说,流量又是不可预测的。某些功能可能这次需要作为level2来看待,下次其实作为level3就够了。所以这是一个需要长期不断打磨和调优的过程。最后,希望近期的「高可用三剑客」可以作为你了解「高可用」的起点,可以先收藏防身(当然再分享一下也是极好的:)),欢迎后续一起交流探讨~Question:你曾经是否有遇到过什么场景,当时是通过马上改代码来「降级」呢?欢迎来吐槽~相关文章:如何在到处是“雷”的系统中「明哲保身」?这是第一招想通关「限流」?只要这一篇分布式系统关注点——数据一致性(上篇)作者:Zachary出处:https://www.cnblogs.com/Zacha…▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码加入哦。定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。

December 19, 2018 · 1 min · jiezi

“Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!

概述Material Design设计规范的受欢迎程度和实用性已经引起了 ComponentOne 技术团队的重视。ComponentOne Enterprise 2018V3 版本将全面支持Material specs的功能集。 在此之前,我们已经在ASP.NET MVC和JavaScript控件中添加了 Material 支持。 随着Material Design的日益普及,我们的桌面用户也将可以使用, ComponentOne For WinForm在2018V3中针对材料设计规范增加了全新的主题设计。将材料主题添加到 WinForm 应用程序ComponentOne For WinForm Edition中添加了全新的Material和Material Dark主题。 这些主题即可作为 ComponentOne 主题,也可应用于 WinForm 平台以及大多数常用的Microsoft 框架中。 这两个主题提供两种不同的配色方案,但遵循相同的材料原则和颜色规格,甚至可以作为未来材料主题的基本元素。 与Material Dark相比,Material主题是一个更为轻松的主题。以下是 ComponentOne 控件在默认主题和Material主题中的外观比较:使用WinForm Material Theme Designer创建新主题Material Design颜色系统由主要颜色和次要颜色组成。 这些颜色反映了您的应用程序的主题和样式。 ComponentOne中提供的 Material Designer是一个交互式设计器,可让您为Material主题选择主要和次要风格样式。您可以保存主题并在以后直接将其应用于WinForm应用程序。WinForms Material Designer要更改主题的配色方案,请按照以下简单步骤操作:运行Material Theme Designer示例。单击“设置”选项卡,然后根据您的品牌/主题选择模板和强调颜色。使用C1控件预览主题的外观。单击cog图标以打开应用程序菜单。单击“保存主题”将主题保存在首选位置。请参阅材料主题设计器:将Material Themes应用于WinForm 应用程序材料主题设计器可以在 WinForm 应用程序的设计阶段和运行阶段启动。 您可以在设计时使用“主题控制器”对话框或通过修改“App.config”文件来应用主题。 要在运行时启动,请使用C1ThemeController静态类来应用主题。使用主题控制器对话框在Visual Studio的表单设计器中打开应用程序中的表单。从设计器的工具箱中,拖动C1ThemeController并将其放在表单上。将出现ThemeController对话框。这使您可以选择:应用程序范围的默认主题,当前控制器的默认主题,所有支持控件的主题已经在表单上。在弹出的对话框中,主题最初被指定为“(none)”,适用于表单上已有的控件。这样做可以防止无意中更改这些控件上的属性设置。单击对话框中的全部(默认)按钮,以便在所有控件上设置默认主题。注意:如果您已经自定义了一些控件,则会忽略此控件,并且不会还原默认主题。从可用内置主题列表中选择“材质”。您还可以选择使用Material Theme Designer创建的材质主题。单击上面提到的全部(默认)按钮,在窗体上的所有支持控件上设置默认主题。单击确定按钮以关闭对话框并将指定的主题应用于窗体上的控件。使用App.config文件确保您的产品路径下包含C1.Win.C1Themes.dll。 您可以在App.Config中添加以下应用程序设置以应用基本主题:<configuration> <appSettings> <add key=“C1ApplicationTheme” value=“Material”/> </appSettings></configuration>使用代码确保您的产品路径下包含C1.Win.C1Themes.dll。在加载前在您的应用程序中添加以下代码。C1Theme theme = C1.Win.C1Themes.C1ThemeController.GetThemeByName(“Material”,false);C1ThemeController.ApplyThemeToControlTree(control, theme);自定义材质主题(高级)C1Theme Designer应用程序允许为WinForm Edition中的任何控件轻松设计新主题。 它还允许您编辑/修改现有主题以实现您选择的外观,或与应用程序主题匹配。 您可以进一步使用此应用程序将Material主题调整到控件允许的最精细级别。主题是一个带有.c1theme扩展名的XML文件,它由一组属性及其值组成(它决定了控件的外观)。 主题在内部划分为对应于不同控件的不同部分。 所有其他控件都可以访问“基本主题属性”部分。 此部分包含子部分“材质”,该部分存储可以更改为创建不同材质主题的“材质”属性。例如,下面让我们尝试更改FlexGrid的材质主题,其中标题是主要颜色。如何使用WinForms Material Theme Designer1、 从ComponentOne开始菜单打开ComponentOne主题设计器。 按Ctrl + N或文件>新建。 选择“材质”作为新主题的基本主题。2、 单击BTP编辑器按钮(1)。 这将打开一个新的基本主题属性编辑器。 选择“材料”属性(2)。 在这里,您可以看到材质中使用的不同颜色:3、 转到主题树并展开C1FlexGrid节点。4、 展开C1FlexGrid>样式>固定节点。 此节点将保存固定(标题)单元格的样式。5、 选择ForeColo下拉列表以打开颜色选择器。6、 您可以在选择器下拉列表中选择“参考”选项卡以选择主要颜色。7、 您可以保存此主题并按照“在应用程序中应用材料主题”部分中的说明使用它。 您的FlexGrid现在应该具有基于主要颜色设置的标题颜色。欢迎您下载体验 ComponentOne Enterprise,并与我们分享您是如何设计桌面应用程序以及Material主题如何与您的应用程序协同工作的宝贵经验。ComponentOne Enterprise | 下载试用ComponentOne是一款专注于企业应用高性能开发的 .NET 全功能控件套包,包含300余种控件,支持7大平台,涵盖7大功能模块。较于市面上其他同类产品,ComponentOne更加轻盈,功能更加强大,20多年的开发经验,将为您的应用系统带来更为安全的使用体验。纯中文操作界面,一对一技术支持,厂商级的技术服务,共同造就了这款国际顶级控件套包。您对ComponentOne 产品的任何技术问题,都有技术支持工程师提供1对1专业解答,点击此处即可发帖提问>> 技术支持论坛 ...

December 18, 2018 · 1 min · jiezi

【Visual Studio 扩展工具】使用ComponentOne中的属性保存和还原布局

概述在此前的ComponentOne中,我们为C1FlexGrid(最快,最灵活的.Net数据网格控件)添加了一个非常强大的动态分组功能,这篇技术博客《将动态分组添加到.NET表格控件FlexGrid中》是通过GroupDescriptions属性为我们演示了此功能。 随着ComponentOne Enterprise 2018v3版本的正式发布,ComponentOne For WinForm 中的C1Flexgrid又向前推进了两个新属性:GroupDefinition和SortDefinition。GroupDefinition和SortDefinition的用例目前,C1FlexGrid允许您在GroupPanel上拖动列,实现在运行时执行分组。 但是,如果您希望将网格再次恢复到相同的分组状态,该怎么办? 例如,如果您需要按区域分析销售信息,则可以将Country和City列拖到GroupPanel,对数据进行分类并分析信息。 但是,如果其他人必须执行相同的分析,或者您需要在下次访问时继续从同一状态继续,则您希望C1Flexgrid以特定间隔保存您的分类,然后将网格恢复为相同状态。这就是GroupDefinition属性派上用场的地方。同样,如果需要以动态预定义排序状态设置网格,则SortDefinition是您应该查找的属性。如何使用这两个新属性1.GroupDefinition:获取/设置包含C1FlexGrid分组状态的XML格式的字符串。Save:使用GroupDescriptions属性对网格进行分组或通过在C1FlexGridGroupPanel上拖动列后,可以使用GroupDefinition属性保存C1FlexGrid的分组状态,如下所示:Properties.Settings.Default.GroupInfo = _flexgrid.GroupDefinition;此属性以XML格式保存组信息,如下所示: <GroupDescriptions> < GroupDescription PropertyName = “ShipCountry” SortDirection = “Ascending” Group = “True” /> < GroupDescription PropertyName = “ShipCity” SortDirection = “Ascending” Group = “True” /> < GroupDescription PropertyName = “ShipName” SortDirection = “Ascending” Group = “False” /> </GroupDescriptions>Load:在用户设置等某个位置保存状态后,您可以通过分配“组定义”属性来使用此信息加载相同的组状态,如下所示: _flexgrid.GroupDefinition = Properties.Settings.Default.GroupInfo;2.SortDefinition:获取/设置包含C1FlexGrid排序状态的XML字符串。Save:在对单个/范围的网格列进行排序后,通过设置列的Sort属性或单击列标题,可以使用SortDefinition属性保存C1FlexGrid的排序状态,如下所示:Properties.Settings.Default.SortInfo = _flexGrid.SortDefinition;此属性以XML格式保存组信息,如下所示:<ColumnsSort> < ColumnSort ColumnIndex = “0” ColumnName = “ShipCountry” Sort = “Ascending” /> < ColumnSort ColumnIndex = “1” ColumnName = “ShipCity” Sort = “Ascending” /> < ColumnSort ColumnIndex = “2” ColumnName = “ShipName” Sort = “Ascending” /></ColumnsSort>Load :保存排序信息后,可以使用它将网格恢复为相同的排序状态,如下所示:_flexgrid.SortDefinition = Properties.Settings.Default.SortInfo;我们希望 ComponentOne 中增加的新属性使您更方便地保存和加载组/排序状态,也同样希望 ComponentOne 能为您带来更敏捷的开发体验。ComponentOne Enterprise | 下载试用ComponentOne是一款专注于企业应用高性能开发的 .NET 全功能控件套包,包含300余种控件,支持7大平台,涵盖7大功能模块。较于市面上其他同类产品,ComponentOne更加轻盈,功能更加强大,20多年的开发经验,将为您的应用系统带来更为安全的使用体验。纯中文操作界面,一对一技术支持,厂商级的技术服务,共同造就了这款国际顶级控件套包。您对ComponentOne 产品的任何技术问题,都有技术支持工程师提供1对1专业解答,点击此处即可发帖提问>> 技术支持论坛 ...

December 17, 2018 · 1 min · jiezi

ComponentOne 产品经理:为什么要从C1Report迁移到FlexReport

概述如果你正在使用ComponentOne Enterprise 的Reports for WinForm 报表控件(C1Report),你一定会喜欢更为强大的FlexReport!FlexReport是一个改进的C1Report,使.NET开发人员能够根据应用程序的业务需求解决复杂问题。 借助ComponentOne的2018v2版本,FlexReport提供了创建复杂报表的能力。 FlexReport的架构建立在C1Report之上,因此您将快速掌握C1Report以及其他一些用法。 FlexReport代码是从头开始编写的; 因此,在从C1Report迁移到FlexReport时,您会发现API中的以下重大更改。ComponentOne技术团队现在鼓励C1Report用户将他们的报表迁移到FlexReport,这样就可以获得更快的数据处理能力和更轻量的产品架构。从C1Report迁移到FlexReport的五大理由FlexReport比C1Report快两倍。FlexReport的新架构和后续更新架构有助于支持常见的布局功能,例如并排分页两个子报表等。FlexReport对象模型类似于C1Report,因此针对C1Report的大多数简单代码都可以在FlexReport中使用。FlexReport提供了段落字段、排序、计算字段和添加多个数据源等新功能。 参数更容易添加和编辑。 使用捕捉线、标题、部分轻松添加字段和设计报表 - 这些都在设计器中,因此您无需对其进行任何编码。FlexReport是一种跨平台解决方案。 它不依赖于WinForms / GDI +。 相反,新引擎的渲染基于DirectX / DirectWrite,并且应该相对容易移植到XAML / UWP平台并在这些平台上呈现更好。 FlexReport可在Winform,WPF和UWP平台中使用。FlexReport性能改进我们在许多标准用例上测试了FlexReport和C1Report,以证明性能的提升。 了解有关FlexReport的更多信息,并将其性能与C1Report进行比较。此表提供FlexReport和C1Report功能比较:准备从C1Report迁移到FlexReport?按照这个步骤将基于C1Report的报表迁移到FlexReport吧!ComponentOne Enterprise | 下载试用ComponentOne是一款专注于企业应用高性能开发的 .NET 全功能控件套包,包含300余种控件,支持7大平台,涵盖7大功能模块。较于市面上其他同类产品,ComponentOne更加轻盈,功能更加强大,20多年的开发经验,将为您的应用系统带来更为安全的使用体验。纯中文操作界面,一对一技术支持,厂商级的技术服务,共同造就了这款国际顶级控件套包。您对ComponentOne 产品的任何技术问题,都有技术支持工程师提供1对1专业解答,点击此处即可发帖提问>> 技术支持论坛

December 17, 2018 · 1 min · jiezi

纯静态HTML 与 C# Server 进行WebSocket 连接

TODO: 这篇文章只是写了一个DEMO,告诉你如何使用C#构建一个WebSocket服务器,以便HTML网页可以通过WebSocket与之进行交互。将会使用到的 Package: websocket-sharp Newtonsoft.JSON这个DEMO主要完成的工作是:HTML 连接 WebSocket 并传送一个Json,Json包含两个数字a和b。服务器监听 WebSocket 并解析Json里面的两个数字,将两个数字加起来的和作为结果以Json的形式传送给HTML。HTML 得到返回以后更新显示。10秒之后,服务器主动向浏览器再发送一次消息。准备姿势新建工程首先需要准备两个工程:一个是Web项目,可以是任何Web项目,因为我们只用到HTML。HTML单文件也是没有问题的。这里我用的是vscode live server。另一个是C#命令行项目,当然也可以不是命令行,只是觉得命令行比较方便,DEMO也不需要窗体,如果你需要窗体可以使用WPF或者WinForms。必要依赖在C#项目中,我们需要安装Nuget包:WebSocketSharp (由于这个Nuget包在写文的时候还是rc,所以需要勾选包括抢鲜版才会搜索出来哦)和 Newtonsoft.JSON服务器代码首先我们需要新建一个类,作为一个app,去处理传送来的消息。using Newtonsoft.Json;using Newtonsoft.Json.Linq;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using WebSocketSharp;using WebSocketSharp.Server;namespace WebSocketDemo{ class Add : WebSocketBehavior { protected override void OnOpen() { Console.WriteLine(“Connection Open”); base.OnOpen(); } protected override void OnMessage(MessageEventArgs e) { var data = e.Data; if (TestJson(data)) { var param = JToken.Parse(data); if (param[“a”] != null && param[“b”] != null) { var a = param[“a”].ToObject<int>(); var b = param[“b”].ToObject<int>(); Send(JsonConvert.SerializeObject(new { code = 200, msg = “result is " + (a + b) })); Task.Factory.StartNew(() => { Task.Delay(10000).Wait(); Send(JsonConvert.SerializeObject(new { code = 200, msg = “I just to tell you, the connection is different from http, i still alive and could send message to you.” })); }); } } else { Send(JsonConvert.SerializeObject(new { code = 400, msg = “request is not a json string.” })); } } protected override void OnClose(CloseEventArgs e) { Console.WriteLine(“Connection Closed”); base.OnClose(e); } protected override void OnError(ErrorEventArgs e) { Console.WriteLine(“Error: " + e.Message); base.OnError(e); } private static bool TestJson(string json) { try { JToken.Parse(json); return true; } catch (JsonReaderException ex) { Console.WriteLine(ex); return false; } } }}上面这一段代码中,重点在于OnMessage方法,这个方法就是处理消息的主要流程。在Main函数中,我们加入下面的代码。6690是这次Demo使用的端口号,第二行AddWebSocketService添加了一行路由,使得连接到ws://localhost:6690/add可以导向我们预定义好的App类中的处理逻辑。using System;using WebSocketSharp.Server;namespace WebSocketDemo{ class Program { static void Main(string[] args) { var wssv = new WebSocketServer(6690); wssv.AddWebSocketService<Add>("/add”); wssv.Start(); Console.WriteLine(“Server starting, press any key to terminate the server.”); Console.ReadKey(true); wssv.Stop(); } }}客户端代码<!DOCTYPE html><html> <head> <meta charset=“utf-8” /> <meta http-equiv=“X-UA-Compatible” content=“IE=edge” /> <title>WebSocket DEMO</title> <meta name=“viewport” content=“width=device-width, initial-scale=1” /> <style> ul, li { padding: 0; margin: 0; list-style: none; } </style> </head> <body> <div> a:<input type=“text” id=“inpA” /> b:<input type=“text” id=“inpB” /> <button type=“button” id=“btnSub”>submit</button> </div> <ul id=“outCnt”></ul> <script> let wsc; var echo = function(text) { var echoone = function(text) { var dom = document.createElement(“li”); var t = document.createTextNode(text); dom.appendChild(t); var cnt = document.getElementById(“outCnt”); cnt.appendChild(dom); }; if (Array.isArray(text)) { text.map(function(t) { echoone(t); }); } else { echoone(text); } }; (function() { if (“WebSocket” in window) { // init the websocket client wsc = new WebSocket(“ws://localhost:6690/add”); wsc.onopen = function() { echo(“connected”); }; wsc.onclose = function() { echo(“closed”); }; wsc.onmessage = function(e) { var data = JSON.parse(e.data); echo(data.msg || e.data); console.log(data.msg || e.data); }; // define click event for submit button document.getElementById(“btnSub”).addEventListener(‘click’, function() { var a = parseInt(document.getElementById(“inpA”).value); var b = parseInt(document.getElementById(“inpB”).value); if (wsc.readyState == 1) { wsc.send(JSON.stringify({ a: a, b: b })); } else { echo(“service is not available”); } }); } })(); </script> </body></html>当创建WebSocket对象的时候,会自动进行连接,这个对象可以用onopen,onclose,onmessage分别处理事件。主要通讯的流程也是在onmessage中进行处理。 ...

November 28, 2018 · 2 min · jiezi