关于asp.net:快速理解ASPNET-Core的认证与授权

ASP.NET Core 的认证与受权曾经不是什么新鲜事了,微软官网的文档对于如何在 ASP.NET Core 中实现认证与受权有着十分具体深刻的介绍。但有时候在开发过程中,咱们也往往会感觉无从下手,或者因为一开始没有进行认证受权机制的设计与布局,使得前期呈现一些凌乱的状况。这里我就尝试联合一个理论的例子,从0到1来介绍 ASP.NET Core 中如何实现本人的认证与受权机制。 当咱们应用 Visual Studio 自带的 ASP.NET Core Web API 我的项目模板新建一个我的项目的时候,Visual Studio 会问咱们是否须要启用认证机制,如果你抉择了启用,那么Visual Studio 会在我的项目创立的时候,退出一些辅助依赖和一些辅助类,比方退出对Entity Framework 以及ASP.NET Identity 的依赖,以帮忙你实现基于 Entity Framework 和 ASP.NET Identity 的身份认证。如果你还没有理解过 ASP.NET Core 的认证与受权的一些根底内容,那么当你关上这个由 Visual Studio 主动创立的我的项目的时候,必定会一头雾水,不知从何开始,你甚至会狐疑主动创立的我的项目中,真的是所有的类或者办法都是必须的吗? 所以,为了让本文更加简略易懂,咱们还是抉择不启用身份认证,间接创立一个最简略的 ASP.NET Core Web API 应用程序,以便后续的介绍。新建一个 ASP.NET Core Web API 应用程序,这里我是在 Linux 下应用 JetBrains Rider 新建的我的项目,也能够应用规范的 Visual Studio 或者 VSCode 来创立我的项目。创立实现后,运行程序,而后应用浏览器拜访 /WeatherForecast 端点,就能够取得一组随机生成的天气及温度数据的数组。你也能够应用上面的 curl 命令来拜访这个 API: 1curl -X GET "http://localhost:5000/WeatherForecast" -H "accept: text/plain" ...

January 7, 2022 · 4 min · jiezi

关于asp.net:AgileConfig-轻量级配置中心-15-发布-支持多环境配置

AgileConfig 从公布到当初,收到不少同学的 issue 说须要多环境的反对。也就是一个利用在不同的环境下能够配置不同的配置项。这是一个十分有用的性能,就跟咱们开发的时候会设置多个 appsettings.json 文件一样,比方 appsettings.development.json 、appsetting.production.json 等等。那么这次 1.5 版本就为大家带来了这个性能。 上面介绍下如何应用多环境配置性能。 运行控制台节点拉取最新的 latest 或者 release-1.5.0 的 docker 镜像,运行控制台节点即可反对多环境配置。 sudo docker run \--name agile_config \-e adminConsole=true \-e db:provider=sqlite \-e db:conn="Data Source=agile_config.db" \-p 5000:5000 \-v /etc/localtime:/etc/localtime \#-v /your_host_dir:/app/db \-d kklldog/agile_config:release-1.5.0节点运行起来后,在配置项治理界面的右上角即可切换环境。 自定义环境AgileConfig 默认内置了 DEV, TEST, STAGING, PROD 四个罕用的环境,如果用户感觉不够用或者不想要那么多环境的话能够进行本人定义。找到数据库的 agc_setting 表,对其中 id = environment 的行进行批改。配置名称之间应用英文输出状态的逗号分隔。 为环境独自配置数据库AgileConfig 默认状况下会把所有的配置项都存储在 db:conn 指定的数据库上面。然而对于多环境来说,集中式的配置存储显然不太适合。特地是对于生产环境来说不太可能跟开发测试环境都部署在同一个数据库上。AgileConfig 反对对某个环境配置独自的数据库。 在启动节点的时候为某个环境独自配置数据库: -e db:env:TEST:provider=mysql \-e db:env:TEST:conn= "Database=agile_config_test;Data Source=192.168.0.111;User Id=dev;Password=dev@123;port=3306" \-e db:env:PROD:provider=mysql \-e db:env:PROD:conn= "Database=agile_config_prod;Data Source=192.168.0.1111;User Id=dev;Password=dev@123;port=3306" \客户端为配合 AgileConfig 1.5 版本请应用 AgileConfig.Client 1.2 及以上版本。 ...

December 20, 2021 · 1 min · jiezi

关于asp.net:ApacheCN-AspNET-译文集-20211126-更新

ASP.NET Core2 基础知识 零、前言一、搭建舞台二、控制器三、视图四、模型五、验证六、路由七、RestBuy八、增加性能、测试和部署ASP.NET Core3 和 Angular9 零、前言一、筹备二、环顾四周三、前端和后端交互四、实体框架外围的数据模型五、获取和显示数据六、表单和数据验证七、代码调整和数据服务八、后端和前端调试九、ASP.NET Core 和 Angular 单元测试十、认证和受权十一、渐进式 Web 利用十二、Windows 和 Linux 部署ASP.NET Core5 初学者指南 零、序言第一局部:匍匐 一、ASP.NET Core 5 简介二、跨平台设置三、依赖注入四、Razor 视图引擎五、Blazor 入门第二局部:步行 六、摸索 Blazor Web 框架七、API 和数据拜访八、在 ASP.NET 中应用身份九、Docker 入门第三局部:跑步 十、部署到 AWS 和 Azure十一、浏览器和 Visual Studio 调试十二、与 CI/CD 集成十三、开发云原生利用十四、答案ASP.NET Core5 和 React 零、序言第一局部:开始 一、理解 ASP.NET 5 React 模板二、创立解耦的 React 和 ASP.NET 5 利用第二局部:应用 React 和 TypeScript 构建前端 三、开始应用 React 和 TypeScript四、应用 Emotion React 组件定义款式五、将 React 路由用于路由六、应用表单七、应用 Redux 治理状态第三局部:构建 ASP.NET 后端 ...

December 8, 2021 · 4 min · jiezi

关于asp.net:ASPNET-Core-MVC-入门到精通-1-开发必备工具-2021

环境: .NET 5ASP.NET Core MVC1. .NET 5作为一个资深.NET工程师,说句实话,.NET败落了,在国内更加的败落。之前做过8年node.js/前端,现如今又转回了.NET。也心愿.NET能有更好的前景吧。集体感觉.NET还是有肯定的市场的(当然,国内算小众了),有几点起因: .NET5开始,真正做到的跨平台(之前叫.NET CORE), 而且把那个'CORE'字也拿掉就,就叫.NET5, 可见微软的巨大指标;开发效率,.NET软件开发,不论是web还是winForm,效率的确高跨平台,从.net core开始,终于跨平台了....性能大大晋升。 .NET core性能大幅晋升,尤其是跑在Linux上的时候;有些已有的基于.NET的产品,还须要持续保护、开发,不会换个语言重写;微软、以及和微软严密单干的公司,也提供了不少.NET的利用场景;工欲善其事必先利其器,既然要接着用.NET,那么下文将列一下开发必备工具,供参考。 2. IDE2.1. Visual Studio这个没的说,而且要用最新版的VS! 2.2. Visual Studio Code微软的开源软件,同时是以后最风行的Web前端开发IDE。 作为备选,反对C#开发,对于简略的代码,或者测试一些语法、看代码什么的,这个足够,而且占用资源很少。 举荐插件: Code Spell Checker, 查看代码中单词拼写错误。申明个变量,起初发现单词拼写错了,难堪了,应用这个插件,即时提醒拼写错误C#, 装置此插件反对C#我的项目GitLens,如果应用git,肯定要装这个,不便查问提交历史,某一行上次代码的提交人、提交备注等;Markdown All in One, markdown文件的插件,很弱小,主动生成目录、编号等等。3. 代码比拟3.1. Beyond Compare免费软件,但性能的确是弱小。 3.2. WinDiff免费版,将就着用; 4. 图片解决4.1. paint.net(这个也是基于.NET开发的哦,因为咱们机器必然有.net framework,所以这是还是很小的);免费版,性能也很弱小,占用资源很少。对于非专业的美工,足够用了! 5. 其余Web工具5.1. node.js/npm做前端web开发,不论是否用node.js,环境还是要有的; 5.2. API 测试 - Postman不便测试API,免费版足够用了,反对账号同步,导入导出等等; 5.3. Color Pick疾速准确地从任何中央取色。 6. 其余工具6.1. Linux客户端:MobaXterm还在用putty? 试试MobaXterm吧,免费版反对保留20个session(节点、机器),足够用了; 集成了sftp,间接拖拽上传、下载文件自带windows编辑器,不必vi了多窗口模式很好很弱小... ...6.2. 近程连贯: Remote Desktop Connection Manager如果有大量的虚机、PC等须要近程连贯应用,那这个工具就是很必要的。微软的工具,虽说没什么更新了,然而很实用: 分组治理、保留用户、明码,反对继承用户名、明码(实验室机器个别用户名、明码都是一样的吧)

June 8, 2021 · 1 min · jiezi

关于asp.net:ASPNET-路由跳转到-HTML-页面

在默认状况下,ASP.NET 中的路由是不可能指向一个 HTML 页的,如: routes.MapPageRoute("route1", "{*placeholder}", "~/index.html");这段代码在默认状况下会被 IIS 拦截,并抛出一段谬误音讯,相似“没有为扩展名 ".html" 注册的生成提供程序。...”,前面就是疏导你去 Web.config 中注册一个,最终在 Web.config 中的 compilation 节点上面增加这样一段就能够了: <buildProviders> <add extension=".html" type="System.Web.Compilation.PageBuildProvider" /></buildProviders>

April 17, 2021 · 1 min · jiezi

关于asp.net:谁还不是凡尔赛了LEARUNNET框架实力不容低调

不晓得从什么时候开始,就被满眼的“凡尔赛”惊住了,如果不是长年混迹于互联网,怕是早就认为那是巴黎凡尔赛大宫殿了。 如果论起凡尔赛鼻祖,这几位可是当之无愧: 普通家庭马化腾,不知妻美刘强东;悔创阿里杰克马,北大还行撒贝宁;一亿指标王健林,再次平凡特朗普。 然而,最近的梗好玩: 1.爱因斯坦:我在发表《广义相对论》的第一篇论文时,一篇参考文献都找不到,不像当初动不动就有几百篇论文献可看,真是艳羡嫉妒恨。 2.说进去挺不好意思的,我是最近才晓得鸡蛋有壳的,以前都是吃管家剥好的,始终认为鸡蛋都是红色的软软的。 3.我对象不晓得从哪里学到的坏毛病,一喝多就给我转账,上次间接转了6位数。我每次都说我不须要,我本人一个月工资够转给他好屡次,他还是不听。唉,男人就是这样。 就像我以前,始终认为软件开发很简略,因为平时都是使劲软,不写什么代码,拖拖拽拽零碎就好一大半,直到最近我才晓得,竟有那么多苦逼的码农还在一个个敲代码。 所以,从今天起,为了给大家更好的推介LEARUN.NET神器的高效技能,当前请叫我凡尔赛.力软.PLUS。 你如果说这是“用最低调的话,炫最高调的耀。”,那么...嘿嘿,你说对了,实力不容许我低调,奥利给!!! 可能大家听出了隐隐的得意,但这可不是嘚瑟,拿出一个流程,请让我用一分钟工夫来展现这款暗藏的软件界凡尔赛。 第一步,找到流程 第二步,新增流程(销假为例) 第三步,设置权限 第四步,左侧拖拽节点,并双击节点进行设置 实现保留即可,所实现的性能在新版中可独自接入已有零碎。 你说,这够凡尔赛不? LR.

December 7, 2020 · 1 min · jiezi

关于asp.net:ASPNET界面开发环境配置看看你的VSSQL是否正确对应

点击获取工具>> 如下图所示,绿色Yes代表反对,红色No代表不反对。对于有些人感觉装了DevExpress后,VS工具箱没有,个别都是以下两大问题: DevExpress的版本不反对你以后的VS版本,没有很失常。我的项目的.net版本并不反对以后版本DevExpress,批改我的项目的.net版本即可。另外如果以上都没有问题的话,还有其余两种状况: 请记住装置程序,先装VS,后装DevExpress。以上都没问题的话,工具箱还是没有DevExpress控件的话,然而VS工具栏有DevExpress选项卡,则能够抉择Repair Tool(重置工具箱:能够右击工具箱,或者抉择DevExpress选项卡),如果VS工具栏上没有DevExpress选项卡的话,请到装置门路找到exe修复DevExpress。ASP.NET MVC框架 DevExpress ASP.NET MVC扩大反对以下ASP.NET MVC和.NET Framework版本。 ASP.NET MVC 框架 ASP.NET .NET框架 jQuery DevExpress ASP.NET MVC扩大反对jQuery version 3.x。 留神:必须装置Microsoft .NET Framework 4和MVC 3能力运行DevExpress MVC演示,如果应用MVC 4,则须要稍作批改DevExpress MVC演示我的项目。 IDE 应用以下IDE开发具备DevExpress ASP.NET MVC扩大的MVC应用程序。 留神:从v18.2开始,DevExpress源代码面向C#6.0。 您仍能够在Visual Studio 2012和2013中应用咱们的预编译程序集。然而,无奈在这些Visual Studio版本中从新编译DevExpress源代码。 SQL Server 要在本地运行DevExpress ASP.NET MVC扩大演示,必须装置以下版本的Microsoft SQL当中的一个。 Microsoft SQL Server 2005 Express Edition (SP3)Microsoft SQL Server 2008 Express Edition (SP1)Microsoft SQL Server 2012 ExpressMicrosoft SQL Server 2012 Express LocalDBMicrosoft SQL Server 2014Microsoft SQL Server 2016须要Microsoft SQL Server,因为DevExpress演示应用本地部署的.mdf文件作为数据源。 ...

November 30, 2020 · 1 min · jiezi

关于asp.net:NETCore31Vuejs打造的低代码工作流引擎

简介:JNPF采纳支流的两大技术.NETCore/JAVA开发,是一套低代码开发平台,可视化开发环境,拖拽式疾速设计表单,PC、Pad、手机多端适配,灵便的权限配置、SaaS服务,弱小的接口对接,随心可变的工作流引擎,一站式开发多端应用Web、Android、IOS、微信小程序,并且有以构建业务流程、逻辑和数据模型等所需的性能;为企业我的项目节俭80%的反复工作,让开发者将重心放在业务逻辑,不用懊恼底层架构设计,可短时间开发出如ERP、OA、CRM、HR、MIS以及电信、银行、政府、企业等各行业的企业应用零碎。 介绍指标:让操作更加简略,让性能更加全面的低代码开发平台。 基于JAVA、.NET CORE3.1构建的PC端,并提供由Vue.js开发的APP。 从15年研发至今,引迈JNPF已成长为成熟的“企业级利用疾速开发平台”。 软件架构基于JAVA、.NET CORE3.1开发,反对MySQL/Oracle/SQLServer数据库和国产数据库 前后端拆散架构,前端对立为Vue+ElementUI JAVA后端SpringBoot+SpringCloud+Mybatis Plus+Redis .NET后端.NETCore3.1+Ocelot+Entityframework Core+Redis JNPF是一个只需利落拽开发的低代码平台,性能的开发齐全能够脱离代码,一站式跨多端开发Web、APP,节俭开发过程中反复写代码的工夫。 性能JNPF领有残缺的性能开发流程,包含性能开发、挪动开发、报表开发、大屏开发、门户开发等。 欠缺的系统管理性能,包含系统配置、系统调度、零碎缓存、系统日志、系统监控等 JNPF针对的开发信息化管理系统,其主体业务为工作流零碎,并自主开发了JNPF.WF工作流引擎,次要性能为权限治理+表单流程。 JNPF.WF的权限治理的外围为“角色”,用户能够通过表演的角色进行权限调配。 图解工作流权限管理机制 表单流程的开发

October 29, 2020 · 1 min · jiezi

关于asp.net:国内最好用的短网址推荐

国内最好用的短网址推荐 目前市面上支流短链接有百度、腾讯、淘宝和新浪,他们的前缀域名别离是:http://dwz.cn、http://url.cn、http://tb.cn、http://t.cn,上面各自剖析下他们的优缺点 百度短网址(http://dwz.cn) 百度短网址是免费我的项目,相对来说性能多一点,官网是这样介绍的:百度短网址服务能够帮忙你把一个长网址缩短,不便你在社交网络和第三方平台上分享链接,投放广告等等。 百度具备超简略的形式应用短网址服务:拜访百度短网址首页https://dwz.cn,生成对应的短网址。你还能够调用百度短网址服务API服务,查看数据统计与剖析! 之前的百度短网址是须要大站能力申请,起初变成了都能够但须要2个小时的审核工夫,当初是即时的 毛病:因为刚开始做,根底有余,市面不吃这套,改封还是封,在腾讯系app中简直没有防封成果 腾讯短链接(http://url.cn、http://w.url.cn)url短链接最开始是为了反抗http://t.cn推出的网址压缩服务,起初其微博开张,官网并没有进行http://url.cn的解析,但也没有对外开放接口。 长处:稳定性好,故障率极低,很少出现异常,微信里被封概率比拟低 毛病:因为是一家的,具备查问平安核心的权限,检测绝对容易,QQ中被封概率较高,即便是图片也能够通过AI人工智能辨认图片后再判断,而且会呈现间接封短链的状况,即便在浏览器中关上也是提醒被封 淘宝短链接(http://tb.cn) 权限限度:只服务于阿里系自家电商平台,其余链接无奈应用 新浪短网址(http://t.cn) 国内最早做短链接业务,因为市场保有量微小,腾讯系不敢轻易动刀,比拟吃得开,只是因为使用量太大,过来呈现过几次无奈应用的状况,http://t.cn不会被随便封杀,所以说新浪短网址是一款包容性最好,市场兼容性最好的短链接最佳抉择,尽管官网进行了对外的api接口服务,但很多第三方工具站依然能够生成。 然而这些大平台的短网址,缓缓的曾经不凋谢给个人用户应用,这样对于很多小白或者游客类型的用户来说,就十分不不便了,而市面上还有很多短网址服务,根本都是免费的,并且很多免费短网址服务对游客来说十分不敌对,因为付费疏导暗藏的很深,当链接生效时,付费疏导将会显示进去,而游客转换的短链接随时生效,对于很多游客,须要的仅仅是将一个网址转换为短链接的服务。首页中,不存在任何揭示、阐明。 面对这样的状况,我把本人平时应用到比拟好用并且收费的短网址给做一次举荐 Dwurl.cn Dwurl.com 从首页能够看出,这家短网址服务商走的是极客格调,主打简略,稳固,牢靠,应用起来非常容易上手,没有很多的用户限度,不须要很多性能的用户,能够抉择。 urlicu.cn urlicu.com icuurl.com ggurl.net icuurl.cn ttturl.cn ttturl.com weiurl.net 以上的这批短链接都是我集体收藏,应用起来极度舒服,大家能够去试试,祝你好运!

September 30, 2020 · 1 min · jiezi

NET-Core中使用OOM框架AutoMapper的使用介绍

(一)什么是OOM:OOM顾名思义,Object-Object-Mapping实体间相互转换,AutoMapper其意义在于帮助你无需手动的转换简单而又麻烦的实体间关系。 (二)AutoMapper是什么:AutoMapper是基于对象到对象约定的映射工具,常用于(但并不仅限制于)把复杂的对象模型转为DTO,一般用于ViewModel模式和跨 服务范畴。 (三)在.NET Core项目中如何使用它:1.通过Nuget安装AutoMapper到项目:Install-Package AutoMapper2.定义好Model类和DTO类:`//Model类public class ProjectEntity{ public int ID { get; set; }public string ProjectName { get; set; }public string ProjectImg { get; set; }public string ProjectCreateDate { get; set; }public List<ProjectTaskEntity> Tasks { get; set; }}` `//DTO类public class ProjectDto{ public int ProjectID { get; set; }public string ProjectName { get; set; }public string ProjectImg { get; set; }public string ProjectCreateDate { get; set; }public List<ProjectTaskDto> Tasks { get; set; }}` ...

November 4, 2019 · 1 min · jiezi

Actor模型是解决高并发的终极解决方案

写在开始一般来说有两种策略用来在并发线程中进行通信:共享数据和消息传递。使用共享数据方式的并发编程面临的最大的一个问题就是数据条件竞争。处理各种锁的问题是让人十分头痛的一件事。 传统多数流行的语言并发是基于多线程之间的共享内存,使用同步方法防止写争夺,Actors使用消息模型,每个Actor在同一时间处理最多一个消息,可以发送消息给其他Actor,保证了单独写原则。从而巧妙避免了多线程写争夺。和共享数据方式相比,消息传递机制最大的优点就是不会产生数据竞争状态。实现消息传递有两种常见的类型:基于channel(golang为典型代表)的消息传递和基于Actor(erlang为代表)的消息传递。 Actor简介Actor模型(Actor model)首先是由Carl Hewitt在1973定义, 由Erlang OTP 推广,其 消息传递更加符合面向对象的原始意图。Actor属于并发组件模型,通过组件方式定义并发编程范式的高级阶段,避免使用者直接接触多线程并发或线程池等基础概念。Actor模型=数据+行为+消息。 Actor模型是一个通用的并发编程模型,而非某个语言或框架所有,几乎可以用在任何一门编程语言中,其中最典型的是erlang,在语言层面就提供了Actor模型的支持,杀手锏应用RabbitMQ 就是基于erlang开发的。 更加面向对象Actor类似面向对象编程(OO)中的对象,每个Actor实例封装了自己相关的状态,并且和其他Actor处于物理隔离状态。举个游戏玩家的例子,每个玩家在Actor系统中是Player 这个Actor的一个实例,每个player都有自己的属性,比如Id,昵称,攻击力等,体现到代码级别其实和我们OO的代码并无多大区别,在系统内存级别也是出现了多个OO的实例 class PlayerActor { public int Id { get; set; } public string Name { get; set; } }无锁在使用Java/C# 等语言进行并发编程时需要特别的关注锁和内存原子性等一系列线程问题,而Actor模型内部的状态由它自己维护即它内部数据只能由它自己修改(通过消息传递来进行状态修改),所以使用Actors模型进行并发编程可以很好地避免这些问题。Actor内部是以单线程的模式来执行的,类似于redis,所以Actor完全可以实现分布式锁类似的应用。 异步每个Actor都有一个专用的MailBox来接收消息,这也是Actor实现异步的基础。当一个Actor实例向另外一个Actor发消息的时候,并非直接调用Actor的方法,而是把消息传递到对应的MailBox里,就好像邮递员,并不是把邮件直接送到收信人手里,而是放进每家的邮箱,这样邮递员就可以快速的进行下一项工作。所以在Actor系统里,Actor发送一条消息是非常快的。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWwNZ5t0-1570964270753)(https://timgsa.baidu.com/timg...] 这样的设计主要优势就是解耦了Actor,数万个Actor并发的运行,每个actor都以自己的步调运行,且发送消息,接收消息都不会被阻塞。 隔离每个Actor的实例都维护这自己的状态,与其他Actor实例处于物理隔离状态,并非像 多线程+锁 模式那样基于共享数据。Actor通过消息的模式与其他Actor进行通信,与OO式的消息传递方式不同,Actor之间消息的传递是真正物理上的消息传递。 天生分布式每个Actor实例的位置透明,无论Actor地址是在本地还是在远程机器上对于代码来说都是一样的。每个Actor的实例非常小,最多几百字节,所以单机几十万的Actor的实例很轻松。如果你写过golang代码,就会发现其实Actor在重量级上很像Goroutine。由于位置透明性,所以Actor系统可以随意的横向扩展来应对并发,对于调用者来说,调用的Actor的位置就在本地,当然这也得益于Actor系统强大的路由系统。 生命周期每个Actor实例都有自己的生命周期,就像C# java 中的GC机制一样,对于需要淘汰的Actor,系统会销毁然后释放内存等资源来保证系统的持续性。其实在Actor系统中,Actor的销毁完全可以手动干预,或者做到系统自动化销毁。 容错说到Actor的容错,不得不说还是挺令人意外的。传统的编程方式都是在将来可能出现异常的地方去捕获异常来保证系统的稳定性,这就是所谓的防御式编程。但是防御式编程也有自己的缺点,类似于现实,防御的一方永远不能100%的防御住所有将来可能出现代码缺陷的地方。比如在java代码中很多地方充斥着判断变量是否为nil,这些就属于防御式编码最典型的案例。但是Actor模型的程序并不进行防御式编程,而是遵循“任其崩溃”的哲学,让Actor的管理者们来处理这些崩溃问题。比如一个Actor崩溃之后,管理者可以选择创建新的实例或者记录日志。每个Actor的崩溃或者异常信息都可以反馈到管理者那里,这就保证了Actor系统在管理每个Actor实例的灵活性。 劣势天下无完美的语言,框架/模型亦是如此。Actor作为分布式下并发模型的一种,也有其劣势。 由于同一类型的Actor对象是分散在多个宿主之中,所以取多个Actor的集合是个软肋。比如在电商系统中,商品作为一类Actor,查询一个商品的列表在多数情况下经过以下过程:首先根据查询条件筛选出一系列商品id,根据商品id分别取商品Actor列表(很可能会产生一个商品搜索的服务,无论是用es或者其他搜索引擎)。如果量非常大的话,有产生网络风暴的危险(虽然几率非常小)。在实时性要求不是太高的情况下,其实也可以独立出来商品Actor的列表,利用MQ接收商品信息修改的信号来处理数据一致性的问题。在很多情况下基于Actor模型的分布式系统,缓存很有可能是进程内缓存,也就是说每个Actor其实都在进程内保存了自己的状态信息,业内通常把这种服务成为有状态服务。但是每个Actor又有自己的生命周期,会产生问题吗?呵呵,也许吧。想想一下,还是拿商品作为例子, 如果环境是非Actor并发模型,商品的缓存可以利用LRU策略来淘汰非活跃的商品缓存,来保证内存不会使用过量,如果是基于Actor模型的进程内缓存呢,每个actor其实就是缓存本身,就不那么容易利用LRU策略来保证内存使用量了,因为Actor的活跃状态对于你来说是未知的。分布式事物问题,其实这是所有分布式模型都面临的问题,非由于Actor而存在。还是以商品Actor为例,添加一个商品的时候,商品Actor和统计商品的Actor(很多情况下确实被设计为两类Actor服务)需要保证事物的完整性,数据的一致性。在很多的情况下可以牺牲实时一致性用最终一致性来保证。每个Actor的mailBox有可能会出现堆积或者满的情况,当这种情况发生,新消息的处理方式是被抛弃还是等待呢,所以当设计一个Actor系统的时候mailBox的设计需要注意。升华一下通过以上介绍,既然Actor对于位置是透明的,任何Actor对于其他Actor就好像在本地一样。基于这个特性我们可以做很多事情了,以前传统的分布式系统,A服务器如果想和B服务器通信,要么RPC的调用(http调用不太常用),要么通过MQ系统。但是在Actor系统中,服务器之间的通信都变的很优雅了,虽然本质上也属于RPC调用,但是对于编码者来说就好像在调用本地函数一样。其实现在比较时兴的是Streaming方式。由于Actor系统的执行模型是单线程,并且异步,所以凡是有资源竞争的类似功能都非常适合Actor模型,比如秒杀活动。基于以上的介绍,Actor模型在设计层面天生就支持了负载均衡,而且对于水平扩容支持的非常好。当然Actor的分布式系统也是需要服务注册中心的。虽然Actor是单线程执行模型,并不意味着每个Actor都需要占用一个线程,其实Actor上执行的任务就像Golang的goroutine一样,完全可以是一个轻量级的东西,而且一个宿主上所有的Actor可以共享一个线程池,这就保证了在使用最少线程资源的情况下,最大量化业务代码。搜索公众号:架构师修行之路,领取福利,获取更多精彩内容

October 13, 2019 · 1 min · jiezi

BDEX知识科普之跨链到底解决了什么问题

本文引自和数传媒。 从字面上理解就是将不同的区块链连接起来,就像两岛之间建桥相连一样。简单来看,跨链就是解决如何让一条链上的Token转移到另一条链上。从记账角度,单一区块链解决的是如何精确记账的问题。跨链解决的是两个分布式账本针对同一用户或不同用户控制的账户发生Token转移时,如何在两个账本中精确记账的问题。 并非协议 很多白皮书或者技术文章中将跨链称为“协议”,信息传输协议只需要确何接收到信息,发送方得到反馈不用考虑信息重复发送的问题。 但在账本之间同步数据,就需要确何两个账本变动一致,否则就会出现双重支付或者价值丢失的问题。 由于区块链技术快速发展,每一条链记账机制、共识机制都有区别,建立通用的跨链协议就变得更加复杂。跨链“协定”更像是双边和多边贸易协定,在实现过程中个性化更加明显,大范围统一更加困难。需要通过技术方法实现两个账本的跨账本精确记账问题。 一种机制 分析至此,跨链实际上是一种机制。跨链本质上是一套链和链之间清算机制,而清算的本质是精确记账。 跨链实现后最重要的应用场景是分布式交易、中心化竞价撮合与分布式清结算混合模式的交易所等多种模式。

September 10, 2019 · 1 min · jiezi

程序员过关斩将来自于静态方法和实例方法的联想翩翩

这两周没有妹子来找我问问题,有点小伤感,所以耽误更新了。哈哈,别当真,因为菜菜这两周周末都有事(你可以认为去公司加班了),实在是没有精力,忘各位见谅!!以下为菜菜自己观点,不代表任何妹子的观点,请轻喷面向对象作为一个久经考验并得到业界肯定的编程思想莫过于面向对象编程思想了。 面向对象(Object Oriented,OO)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。谈到面向对象思想,首先你得有一个对象才可以。所以计算机天才在语言角度发挥抽象能力,在编程中把对象抽象创建了出来,典型的代表作就是java/c# 中的类(class)。把每一个class的类型看做现实世界中的一类对象,然后根据class 可以创建出来多个class的实例,把这些实例看做是面向的具体对象。 为了完美的支持面向对象,大多数语言都支持了特性:封装,继承,多态。这也是诸多蛋疼的面试题中的常见题型。应用场景引入实例化方法概念是面向对象概念出现以后的事情了,区分静态方法和实例化方法不能单单从性能上去理解,创建c++,java,c#这样面向对象语言的大师引入实例化方法一定不是要解决什么性能、内存的问题,而是为了让开发更加模式化、面向对象化。这样说的话,静态方法和实例化方式的区分是为了解决模式的问题。 说的白话一点,到底是使用实例方法还是静态方法取决于业务的场景,当你的业务中每个对象都有自己的状态,或者行为,这些状态和行为是只属于当前对象的,那你的行为可以设计成实例方法。举一个很简单的例子:一个游戏的项目中,每个玩家(player)都有自己的状态,比如玩家有一个行为:跳跃,不同的玩家跳的距离可能不同,所以这个跳跃的行为体现到代码上就是一个player类型实例的方法。 至于静态方法,一般的定义成类型的行为和状态。因为类型是所有实例共享的,所以通常用作全局共享用途。实际项目中会发现有很多的helper类里边都是静态方法,因为这些方法和具体对象,和具体对象的行为状态没有任何关系。因为和具体实例没有连接,所以这类型的静态方法几乎都是线程安全的。举个很简单的例子:项目中有很多加密的方法,这些方法的作用就是给一个参数,返回一个结果,没有任何自己的状态,所以这些方法被设计成静态方法。 在多数项目中,实例方法的使用量要大于静态方法,为什么呢?因为在多数系统中充斥着各种对象的设计,各种XX设计模式的使用,而这些最终都使用了面向对象的思想。举一个最简单的mvc例子,无论是java中还是c#的 mvc框架,controller中的方法都是实例方法,因为每个http请求都有自己的状态,像header头信息,body信息等,这些状态是属于当前http请求的,所以这些controller必须是实例方法才行。 几乎现代所有的流行编程语言都提供了类型实例的继承和多态,统统都是为了更好的服务面向对象这个理念。为什么不提供类型的继承和多态呢?小伙伴们可以留言! 常见问题静态方法是类型的方法,实例方法是每个实例的方法(每个语言形式不太一样): class Bird { //静态方法 static bool IsAnimal() { return true; } //实例方法 bool IsCanFly() { return true; } }静态方法比实例方法快?菜菜认为这是错误的。一个方法的代码被加载到内存中,然后被cpu去执行,执行的速度快慢和是不是静态方法没有任何关系。但是有一个特殊的场景,那就是GC。实例化太多对象在java/c#这类带有GC的编程语言中会引发垃圾回收操作,当垃圾回收进行的时候会挂起所有的线程,所以在这个短暂的时间里,程序会卡顿。 静态方法常驻内存?在一个类型第一次被使用的时候,会把静态方法和静态变量载入内存,直到进程被销毁。说道常驻内存,也算是一种误解,正确的说法是只有在被使用之后才会加载进入内存。当然在一些语言中可以手动卸载当前类型。 静态方法没有线程安全问题菜菜认为是错的。有没有线程安全问题不是是不是静态所决定的,一个类型也可以有自己的状态和行为,只不过在一个进程中只有一份而已。当一个类型中的状态被多个线程修改的时候,就会有资源竞争问题,就会有线程安全问题。当一个类型的状态只有读的情况下,可以认为读这个方法是线程安全的。 自己运行一下以下程序的结果 class Program { static void Main(string[] args) { for (int i = 0; i < 20; i++) { Thread t = new Thread(() => { for (int i2 = 0; i2 < 100000; i2++) { Add(); } }); t.Start(); } //为了模拟程序一直运行 while (true) { Console.WriteLine($"Num的值:"+Num); Thread.Sleep(1000); } Console.Read(); } public static int Num; public static void Add() { Num= Num+1; } }至于实例方法的线程安全问题,原理类似。有没有线程安全问题取决于状态有没有被多个线程并发修改,有没有资源竞争,和是否静态完全没关系。 ...

July 15, 2019 · 1 min · jiezi

程序员过关斩将小小的分页引发的加班血案

问题分析通过以上的对话,身为程序员的你是否也遇到过妹子这样的问题呢?传统的而且网上到处充斥着的也是这类方式,客户端根据自己的滚动不断的更新pagesize和pageindex两个参数,然后上传给服务端接口获取数据,而且网络上也很少说明这种方式是否有问题,那到底有没有问题呢? 谈到分页,无论程序怎样写,分页这个业务的核心动作是根据开始位置和结束位置来获取一段数据,无论你的排序规则有多复杂,最终的目的总是获取总列表数据中一段连续的数据。无论你是直接用的sql语句分页,还用的搜索引擎(比如es),最终在客户端体现的效果就是下一页的数据展现。 当然体现在客户端的UI上的交互操作可以有很多样式 如果是瀑布流或者app段滚动展示的方式,或者其他不需要数据总个数的情况下,菜菜认为服务端千万不要查询这个总个数数据,展示方完全可以以下一页有无数据作为是否继续拉取下一页数据的依据。话题回归,如果客户端依据pagesize和pageindex参数来进行分页需求,有没有问题呢?当然有,要不然菜菜写这篇文章意义何在,我又不是一个喜欢爱扯淡的程序员~~ 问题所在这里以最简单也是最基本的sql 语句分页为例,假如现在数据库现有数据为 1,2,3,4,5,6,7排序的规则是按照大小倒序,即数据的全部列表为: 7,6,5,4,3,2,1假如现在是获取第二页数据,pagesize为2,pageindex为2,正确结果为 “5,4” 。这无可厚非,在数据未发生改变的情况下,正确结果确实如此,那如果数据发生的变化呢,假如现在新加入一条数据 8,列表数据会变为 8,7,6,5,4,3,2,1那依据以上分页原则,第二页获取的数据就变为了“6,5”,聪明的你是不是发现了问题,这也可能是D妹子引发加班的原因。 分页的操作是建立在动态数据上的操作解决问题分页操作的数据源是动态变动的,有时候变动的部分正好发生在你获取的数据范围内,就会发生数据重复或者错误的情况。那怎么解决呢? 客户端作为数据的需求方和展示方,客户端需要记住已经加载的数据的主键列表,如果某条数据已经展示过,根据业务需求来确定是否要重复展示,一般情况下需要去重。 如果数据量非常大,客户端维护一个数据池的方案其实也不够理想服务端服务端分页接口参数新增上一页最后一条数据id参数lastId,去掉pageindex参数,因为在多数情况下,pageindex参数在服务端的作用是确定数据的起点而已,如果有了lastid,pageinde在很多情况下其实已经不需要了。服务端把所有的数据做缓存,这样动态数据在一定时间内静态化,但是这样也是治标不治本。如果业务上对于排序无要求的话,服务端可以采用顺序分页,把获取的数据落在不会变动的数据段上服务端要想把动态的数据搞成静态有点难度业务方无论程序怎么优化也改变不了数据是在不停变动的本质,如果业务方(产品,运营)能够接受数据在偶尔情况下能重复的现象,那能大幅度减少程序员的工作了。 有时候你认为的数据bug,在其他业务部门不一定是什么重大问题添加关注公众号:架构师修行之路,获取更多精彩内容

July 15, 2019 · 1 min · jiezi

C给枚举加自定义特性

通常我们需要定义一组特定值。采用枚举再好不过了。它可以让我们很方便直观的管理一组固定的值。如果我们需要对应输出枚举值的汉语意思或者颜色样式等,我们可以这样实现: 首先定义一个枚举类enum @enum{ Update = 1, Insert = 2}方式一(if语句)if (@enum == 1){ Console.Write("更新");}else (@enum == 2){ Console.Write("新增");}方式二(switch语句)switch(@enum){ case 1: Console.Write("更新"); break; case 2: Console.Write("新增"); break;}方式三(采用字典)Dictionary<int, string> dic = new Dictionary<int, string>{ [1] = "更新", [2] = "新增"};Console.Write(dic[@enum]);现在我们用一种更加优雅的方式来实现,给枚举加特性在System.ComponentModel命名空间下有一个特性Description,用来指定属性或事件的描述。 enum @enum{ [Description("修改")] Update = 1, [Description("新增")] Insert = 2}我们可以写一个扩展,用来获取Description特性,这用反射来读取Description的值,这个是复用性的/// <summary>/// 获取特性 (DescriptionAttribute) 的说明;如果未使用该特性,则返回枚举的名称。可指定的默认值。/// </summary>/// <param name="enum"></param>/// <param name="def">默认值</param>/// <returns></returns>public static string Description(this Enum @enum, string def = ""){ Type enumType = @enum.GetType(); int value = int.Parse(Enum.Format(enumType, Enum.Parse(enumType, @enum.ToString()), "d")); FieldInfo fieldInfo = enumType.GetField(Enum.GetName(enumType, value)); if (fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute), false) is DescriptionAttribute descriptionAttribute) { return descriptionAttribute.Description; } return def != "" ? def : @enum.ToString();}那么在使用的时候我们将可以很方便的获取枚举值对应的描述信息 ...

June 19, 2019 · 1 min · jiezi

开源-FreeSql-配套工具基于-Razor-模板实现最高兼容的生成器

FreeSql 经过半年的开发和坚持维护,在 0.6.x 版本中完成了几大重要事件: 1、按小包拆分,每个数据库实现为单独 dll; 2、实现 .net framework 4.5 支持; 3、同时支持 MySql.Data、MySqlConnector 的实现; 4、自定义导航属性关系的配置; 5、配套工具 FreeSql.Tools 发布; 本文主要讲解第5项《FreeSql.Tools》,大主角往往在最后才出现!!! 拆分小包在此之前一直被吐槽 FreeSql 臃肿,没有小包开发理念。其实我是一点也不承认这种评价,虽然刚开始只有一个 FreeSql.dll,但是在开发和规划上简单了很多。 有一条开发原则这样讲道:过早优化是恶梦! 大概意思是无论做什么项目,不要想着一开始就过度系统的、规范的执行。从外界来看是正规了,但是进度和稳定性会大大折扣。可以不信我,但是请一定要相信前人的总结啊!!! 从之前的一个 dll 到拆分成小包,我们总共耗时两天,虽然都在一个项目内开发,但其实耦合性并不高,so easy!! 车到山前必有路,时机到了自然会拆。这个时机也是奠定 FreeSql 走出了稳定关键的一步。这样会有更多人愿意加入 FreeSql 阵营。 各数据库单独包、延时加载包;FreeSql.Extensions.LazyLoadingFreeSql.Provider.MySqlFreeSql.Provider.PostgreSQLFreeSql.Provider.SqlServerFreeSql.Provider.SqliteFreeSql.Provider.Oracle支持 .netframework 4.5早期 FreeSql 主要是在 .net core 最方便的 ORM!NETStandard 是新的标准,然而前段时间微软又说 ..net5 将合并。。。变化真的太快。 在实现拆分小包后,其实 FreeSql 的模块更加清淅,并且依赖项非常之少,然后比较容易的做出了 4.5 framework 的适配。 目前支持的版本: Package NameVersionFreeSql.Provider.MySqlNETStandard2.0、net452FreeSql.Provider.PostgreSQLNETStandard2.0、net45FreeSql.Provider.SqlServerNETStandard2.0、net451FreeSql.Provider.SqliteNETStandard2.0、net45FreeSql.Provider.OracleNETStandard2.0、net45FreeSql.Extensions.LazyLoadingNETStandard2.0、net45MySqlConnector 的实现mysql 是一个神奇的流行数据库,在 .net 阵营中使用量排名老二。mysql 的版本五花八门,从 5.6 开始有了不同的分支,分支的出现使得 ado.net 驱动不通用。 很多人不推荐使用 MySql.Data 官方驱动,但是 FreeSql 一直在使用官驱,并且支持了所有 5.6 类型,包括 enum/set 等。 ...

June 4, 2019 · 2 min · jiezi

程序员过关斩将论商品促销代码的优雅性

背景介绍据我所知,几乎所有的互联网公司都带有和电商有关的项目,而且在大多数公司里面还是举足轻重的重头戏,比如京东,淘宝。既然有电商项目,必然会涉及到商品,一旦有商品就会有各种促销活动,比如 满100减20,三八妇女节9折等等类似活动。作为一个coder怎么才能在实现产品狗的需求下,最小改动代码,最优雅的实现呢。今天菜菜不才,就D妹子的问题献丑一番。以下以.netCore c#代码为例,其他语言类似。 D妹子版本首先D妹子有一个商品的对象,商品里有一个价格的属性,价格的单位是分 class Product { //其他属性省略 public int Price { get; set; } }下面有一个满100减20的活动,在结算价格的时候代码是这样的 public int GetPrice() { Product p = new Product(); int ret = p.Price; if (p.Price >= 100*100) { ret = ret - 20 * 100; } return ret; }有问题吗?按照需求来说没有问题,而且计算的结果也正确。但是从程序艺术来说,其实很丑陋。现在又有一个全场9折的活动,恰巧有一个商品参与了以上两个活动,而且还可以叠加使用(假设活动参与的顺序是先折扣后满减)。这时候D妹子的代码就变成了这样 public int GetPrice() { Product p = new Product(); //9折活动 int ret = p.Price * 90 / 100; //满减活动 if (ret >= 100 * 100) { ret = ret - 20 * 100; } return ret; }假如现在又来一个类似活动,那这块代码还需要修改,严重违反了开放关闭原则,而且频繁修改已经上线的代码,bug的几率会大大增高。这也是D妹子领导骂她并且让她codereview的原因。 ...

June 3, 2019 · 3 min · jiezi

程序员过关斩将请不要随便修改基类

初级版本这是玩家的抽象基础类,这个设计很好,把一些玩家共有的特性抽象出来 //玩家的基础抽象类 abstract class Player { //玩家的级别 public int Level { get; set; } //其他属性代码省略一万字 }这是新加需求:10级可以跳跃,具体跳跃动作是客户端做处理 //玩家的基础抽象类 abstract class Player { //玩家的级别 public int Level { get; set; } //其他属性代码省略一万字 //新加玩家跳跃动作,由于需要到达10级所以需要判断level public virtual bool Jump() { if (Level >= 10) { return true; } return false; } }这种代码初级人员很容易犯,有什么问题呢? 跳跃的动作被添加到了基类,那所有的子类就都有了这个行为,如果子类机器人玩家不需要这个跳跃的行为呢?为了新需求,修改了基类,如果每次需求都需要修改基类,时间长了,项目大了,这个是比较要命的。优化版本由于需求是增加玩家一个行为,根据上一节的介绍,我们应该了解到,行为在代码级别更倾向于用接口来表示。而且不是所有的玩家类型都需要附加跳跃这个行为。据此优化如下: //玩家跳跃的行为 interface IJump { bool Jump(); } //玩家的基础抽象类 abstract class Player { //玩家的级别 public int Level { get; set; } //其他属性代码省略一万字 } //真实玩家 class PersonPlayer : Player, IJump { public bool Jump() { if (Level >= 10) { return true; } return false; } }不错,到此我们已经避免了初级人员所犯的错误了,每种玩家类型可以根据需要自行去扩展行为,改天产品狗在加一个10级玩家可以飞的行为,顶多在加一个IFly的行为接口,然后实现即可。但是这样的设计就没有问题了吗?有,当然有 ...

May 31, 2019 · 2 min · jiezi

画流程图的软件专门做结构流程图的软件

亿图流程图制作软件是一款用于绘制各种流程图,同时兼具跨平台,云储存,分享功能的专业流程图制作软件。操作简单,功能强大,非常容易实现可视化、分析和交流复杂信息。软件内置海量精美的流程图模板与图库,帮助你轻松绘制项目管理流程图,程序流程图,工作流程图,过程流程图等。如何用来绘制一个流程图呢 第一步 选择从模板创建或者创建一个新页面方法一:创建一个新的页面点击文件-新建-流程图。双击模板下的流程图选择需要绘制的种类,进入编辑状态。 方法二:使用模板创建程图点击文件-新建-流程图。当找到需要的模板时,双击模板或者点击右上角预览窗口下的创建导按钮,即可成功创建一个含有预设内容的流程图。 第二步 添加图形方法一:用图形的浮动按钮添加从左侧模板库中拖出一个流程形状。点击四周的浮动按钮。 方法二: 从库里拖放添加从界面左边的符号库里拖动一个图形。把拖动的图形移动到要吸附的标题旁,松开鼠标会自动链接。 第三步 排版和连接线样式排版十分灵活,可以智能的调整大小和对齐,还可以根据已经存在图形的位置标出对齐线,其自动性为我们带来便捷。 第四步 添加文本和其他内容添加文本双击流程图图形。输入文本。点击绘图页面的任意空白区域或者按 ESC 键完成输入文字。 另外流程图制作软件不仅可以添加文件,还可以添加超链接,附件、图释和其他内容以提供上下文信息。 第五步 美化功能流程图软件不仅在实用性上下足功夫,同时也相当注重美观设计,更改主题形状可以起到美化导图的作用。点击页面布局中的主题,我们可以根据自己的喜好选择,点击即可应用。 也可以点击页面布局的颜色按钮,打开更改主题格式下拉框,可以设置主题边框的颜色和填充的颜色。 还可以点击界面下方的填充条,或者按F4在界面的右侧将会显示更多的样式。 第六步 一键导出到Word,PPT作为专业实用性软件,和其他的软件有很好的兼容性,从导出格式上就可以看出来,支持12种导出格式,完全满足的日常办公的常用需求。制作好流程图。点击文件>导出。即可打开导出格式列表。 流程图制作软件不仅可以制作流程图,还可以通过其在线服务将制作的流程图共享给其他人。可以免费注册账号,通过账户登录即可使用在线服务。

May 27, 2019 · 1 min · jiezi

程序员过关斩将你的面向接口编程一定对吗

妹子开始抱怨起来业务背景妹子的游戏是个对战类的游戏,其中有一个玩家的概念,玩家可以攻击,这个业务正是妹子开始挠头的起点 第一次需求产品经理:玩家有很多属性,例如:身高,性别 blalalala ,玩家可以攻击其他玩家。YY妹子写程序也是很利索,一天就把程序搞定了,而且还抽象出一个palyer的基类出来,堪称高级程序员必备技能。 //玩家的基础抽象类 abstract class Player { public string Name { get; set; } //. //. //. //玩家的攻击 public abstract void Attack(); } //真实玩家 class PersonPlayer : Player { public override void Attack() { //to do something return; } }第二次需求产品经理:游戏里我需要增加机器人玩家来增加游戏在线的人数,机器人属性和真实玩家一样,但是攻击不太一样这个需求修改还是难不住YY妹子,没过几天代码改好了,增加了一个机器人玩家的类,用到了OO的继承。在这里为玩家抽象类点赞 class RobotPlayer : Player { public override void Attack() { //修改攻击内容等 to do something return; } }第三次需求产品经理:我要创建一批类似玩家的怪物,没有真实玩家的那些属性,但是和真实玩家一样有攻击行为这个时候YY妹子终于意识到攻击是一种行为了,需要抽象出接口来了。 //攻击接口 interface IAttack { void Attack(); } //玩家的基础抽象类 abstract class Player { //其他属性代码省略一万字 } //真实玩家 class PersonPlayer :Player, IAttack { public void Attack() { //to do something return; } } //机器人玩家 class RobotPlayer :Player, IAttack { public void Attack() { // to do something return; } } //怪物玩家 class MonsterPlayer : IAttack { public void Attack() { // to do something return; } }到了这里,我们遇到了大家耳熟能详的面向接口编程,没错,这个做法是对的。这也是设计的一大原则:程序依赖接口,不依赖具体实现。这里要为YY继续点赞。顺便说一下,在多数情况下,很多同学就到此为止了第四次需求产品经理:我现在要设计玩家的攻击方式了,目前有远程攻击,近程攻击,贴身攻击这三类,其他需求 blalalalala。据说此刻YY妹子的心里是一万头羊驼飘过的状态。这次要怎么设计呢?这也是菜菜要说的重点部分。现在我们需要静下心来思考一番了,为什么我们使用了面向接口编程,遇到这次需求,程序还是需要修改很多东西呢? ...

May 26, 2019 · 1 min · jiezi

FreeSql-aop功能介绍

前言FreeSql 是一个功能强大的 .NETStandard 库,用于对象关系映射程序(O/RM),支持 .NETCore 2.1+ 或 .NETFramework 4.6.1+(QQ群:4336577)。 据了解,用户使用很少问问题,编码过程中,因业务阻塞,情有可原;因框架使用问题阻塞,得不偿失。我们的口号:做 .net 最方便的 ORM!愿每一位开发者嘴角上扬????! 整体功能IFreeSql 是核心,提供原始用法;FreeSql.DbContext 是扩展包,提供面向对象的用法(像EF);FreeSql.Repository 也是扩展包,提供仓储+工作单元用法(实际上和 DbContext 是一个扩展包);FreeSql.Connection.Extensions 也是扩展包,提供像 Dapper 一样的用法;源码地址:https://github.com/2881099/FreeSql,可从这里链向上面介绍的各个仓库。 fsql= new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|\document.db;Attachs=xxxtb.db;Pooling=true;Max Pool Size=10") .UseAutoSyncStructure(true) //自动迁移(CodeFirst) .Build();AOP 功能今天上场的是 AOP 已有的功能介绍,未来为会根据用户需求不断增强。 审计 CRUD马云说过,996是修福报。对于多数程序员来说,加班是好事。。。起码不是闲人,不会下岗。 当如果因为某个 sql 骚操作耗时很高,没有一个相关的审计功能,排查起来可以说无从下手,福报与你紧紧相随(哈哈)。 FreeSql 支持简单的类似功能: fsql.Aop.CurdAfter = (s, e) => { if (e.ElapsedMilliseconds > 200) { //记录日志 //发送短信给负责人 }};是的,只需要一个事件,就可以对全局起到作用。 除了 CurdAfter,还有一个 CurdBefore (在执行 sql 之前触发)。 审计迁移脚本FreeSql 自带迁移功能,那么迁移的 SQL 语句长啥样,你可能会好奇。 ...

May 24, 2019 · 1 min · jiezi

业务流程图怎么画wps流程图怎么画

说到流程图的绘制,第一个浮现在大家脑海中的应该就是微软的Visio吧。作为绘图软件的“龙头老大”,MS Visio具有开发历史久远、公司技术实力雄厚、绘图专业、方便快捷等众所周知的优点,因此是用户(尤其是专业用户、大公司等)在流程图绘制上的合适工具。然而,它也有一个让许多人不得不放弃的“特点”——购买价格高昂,而且需要付费升级。Visio标准版的2422元和专业版的4763元,对于大部分人来说是一个不小的负担。另外,越来越多的人用的电脑系统是Mac、或者Linux,Visio目前只支持Windows系统的欠缺又让许多用户无法使用。因此,寻找一款和Visio类似且专业好用的流程图绘制软件,也许是作为流程图用户的您,花费大量时间与精力在做的事情。 今天,终于不用再去苦苦找寻了。让我来为大家介绍一款高性价比的流程图软件——亿图图示专家。虽是国产软件,亿图图示却丝毫不逊色于Visio。在绘图功能、内置符号和模板数量、文件导入导出、系统支持等方面,都有可以相媲美,甚至更胜一筹的地方。 专业流程图软件——亿图图示 亿图图示是一款跨平台、多功能、同时支持云储存、分享功能的专业图形图表绘制软件。除了各种流程图以外,亿图图示同时也支持绘制思维导图、工业设计、组织结构、建筑平面图、计算机网络、信息图等,集200种以上的绘图工具于一身。软件提供矢量基础的12000个以上的符号、800个以上的模板和例子以便用户绘图使用。同时,亿图图示也支持多种类型文件的导入导出,导入如Visio、SVG,导出如Visio、SVG、HTML、PS、JPG、PNG、PDF、Office文件等等。 那凭什么说亿图图示是一款超高性价比的流程图软件呢?亿图图示和Visio相比,具体有什么优点呢? 以亿图图示8.6版本和Visio 2016版本为基准,我们用一张表格来详细对比一下。相信看完下面这张表格,大家就能找到答案了。 亿图图示 VS Visio 看完这个比较后,你找到答案了吗? 总结来说,亿图图示的特点在于以下8个方面: 1. 拥有矢量基础的流程图符号,包括基本流程图符号、业务流程建模与标记符号、跨职能流程图符号、数据流程图符号等; 2. 丰富的流程图模板和例子供您免费使用; 3。通过快捷菜单,图形自动生成并连接; 4。一键导出,支持Visio、Html、PNG、PDF、office文件等多种格式; 5。支持高清晰度打印; 6。支持云保存与团队共享; 7。价格合理适中,在大部分人的承受范围之内; 8。一个软件同时支持流程图、思维导图、工业设计、组织结构、建筑平面图、计算机网络、信息图等200种以上的图表绘制。 总结 其实,就我个人看来,作为多功能绘图软件的“老大”,Visio在用户心中的地位并不那么容易被动摇。实际上也并没有哪一款软件能够真正百分百地和Visio一样。只是,倘若有更多可供选择的好用的绘图软件出现,那对于用户来说无疑是有百益而无一害的。寻找Visio相似软件,是用户对软件功能、性价比、便利性的一个不断的向上的追求。而亿图图示正好符合这样一个要求。不论在价格,还是功能,亦或是使用操作的方便性上,都是一个高性价比的流程图绘制工具。因此,如果你有绘制流程图、思维导图、工业设计、组织结构、建筑平面图、计算机网络、信息图等的需要,那何妨不试一下亿图图示呢?或许它会给你带来不同于Visio的体验。 点击亿图图示下载,立即免费体验!

May 22, 2019 · 1 min · jiezi

FreeSql-新功能介绍贪婪加载五种方法

前言FreeSql 在经过6个月的开发和朋友们的工作实践,不断的改进创新,目前拥有1500个左右单元测试方法,且每个方法内又复盖不同的测试面。 今天介绍 FreeSql 各种贪婪加载的姿势,作下总结。本节内容对应的还有【延时加载】,贪婪加载和他本该在一起介绍,开发项目的过程中应该双管齐下,才能写出高质量的程序。有关延时加载,日后有空再单独编写。 FreeSql是一个功能强大的NETStandard库,用于对象关系映射程序(O/RM),便于开发人员能够使用 .NETStandard 对象来处理数据库,不必经常编写大部分数据访问代码。 [√] 支持 CodeFirst 迁移;[√] 支持 DbFirst 从数据库导入实体类,支持三种模板生成器;[√] 采用 ExpressionTree 高性能读取数据;[√] 支持深入的类型映射,比如pgsql的数组类型,堪称匠心制作;[√] 支持丰富的表达式函数;[√] 支持导航属性查询,和延时加载;[√] 支持同步/异步数据库操作方法,丰富多彩的链式查询方法;[√] 支持读写分离、分表分库,租户设计;[√] 支持多种数据库,MySql/SqlServer/PostgreSQL/Oracle/Sqlite;贪婪方法一:Dto 映射查询Select<Tag>().Limit(10).ToList(a => new TestDto { id = a.Id, name = a.Title });Select<Tag>().Limit(10).ToList(a => new TestDto());Select<Tag>().Limit(10).ToList(a => new TestDto { });Select<Tag>().Limit(10).ToList(a => new TestDto() { });Select<Tag>().Limit(10).ToList<TestDto>();像这种映射支持单表/多表。 查找规则,查找属性名,会循环内部对象 _tables(join 查询后会增长),以 主表优先查,直到查到相同的字段。 如: A, B, C 都有 id,Dto { id, a1, a2, b1, b2 },A.id 被映射。也可以指定 id = C.id 映射。 ...

May 16, 2019 · 2 min · jiezi

推荐一个接口文档自动生成工具Swagger

本文包括两个部分: webapi中使用swagger修改webapi的路由和默认参数WebApi中使用swagger新建一个webapi项目 项目打开之后,选择 引用,右键,管理NuGet程序包 浏览,搜索swagger,选择第一个swashbuckle,安装 安装好之后,右键项目,选择属性,生成,在下面的输出那里勾选:XML文档文件,如果没有自动填充好路径,需要自己填写一下,文件名可以自己取。 打开App_Start文件夹下的SwaggerConfig.cs文件,新增一个如下方法:private static string GetXmlCommentsPath(){ return System.String.Format(@"{0}\bin\WebApiDemo.xml", System.AppDomain.CurrentDomain.BaseDirectory);}其中WebApiDemo.xml这个文件名要和自己在前一步填写的文件名一致 搜索GetXmlCommentsPath,下面能搜到已经注释了,自己把注释放开,要是没搜到,就自己手动写一下c.IncludeXmlComments(GetXmlCommentsPath());注意要写在register方法里面 打开valuescontroller,自己写一些注释 运行项目,在根路径后面直接加swagger,就会自动跳转到文档,如:http://localhost:8970/swagger,能看到我们写的一些注释 修改webapi的路由和默认参数在实际应用中,完全使用webapi的restful风格的api设计是比较少见的,请求方式一般也只使用get请求和post请求,所以我们做一些修改,使用的是类似restful风格的api设计,修改一下webapi的路由配置 把valuescontroller做一些修改 /// <summary>/// ValuesController的注释/// </summary>public class ValuesController : ApiController{ /// <summary> /// 获取列表 /// </summary> /// <returns></returns> [HttpGet] public IEnumerable<string> GetList(int pageIndex, int pageSize, string search = "") { return new string[] { "value1", "value2" }; } /// <summary> /// 设置键值对 /// </summary> /// <param name="value"></param> [HttpPost] public string PostData([FromBody]string key, [FromBody]string value = "value") { return "{\"" + key + "\":\"" + value + "\"}"; }}重新运行,能看到文档变成了如下,必填的参数显示required,非必填的参数可以不用填,post请求的参数也显示在文档里 ...

April 28, 2019 · 1 min · jiezi

程序猿修仙之路算法之快速排序到底有多快

<img src="https://www.cnblogs.com/image...; width="100%" hegiht="20%" align=center /> 分治思想关于排序,江湖盛传有一种分治思想,能大幅度提高排序心法的性能。所谓分治,即:化大为小,分而治之。达到治小而治大的成效。多年来基于分治思想衍生出多种排序心法,然万变不离其宗!虽然江湖上算法内功繁多,但是好的算法小编认为必须符合以下几个条件,方能真正提高习练者实力。 时间复杂度(运行时间)在算法时间复杂度维度,我们主要对比较和交换的次数做对比,其他不交换元素的算法,主要会以访问数组的次数的维度做对比。其实有很多修炼者对于算法的时间复杂度有点模糊,分不清什么所谓的 O(n),O(nlogn),O(logn)...等,也许下图对一些人有一些更直观的认识。 空间复杂度(额外的内存使用)排序算法的额外内存开销和运行时间同等重要。 就算一个算法时间复杂度比较优秀,空间复杂度非常差,使用的额外内存非常大,菜菜认为它也算不上一个优秀的算法。结果的正确性这个指标是菜菜自己加上的,我始终认为一个优秀的算法最终得到的结果必须是正确的。就算一个算法拥有非常优秀的时间和空间复杂度,但是结果不正确,导致修炼者经脉逆转,走火入魔,又有什么意义呢?原理基本思想:选取一个元素作为分割点,通过遍历把小于分割点的元素放到分割点左边,把大于分割点的元素放到分割点元素右边。然后再按此方法对两部分数据分别排序,以此类推,直到分割的数组大小为1。 整个排序过程可以递归进行,以此达到整个数据变成有序序列。过程实现快速排序的方式有很多,其中以类似指针移动方式最为常见,为什么最常见呢?因为它的空间复杂度为O(1),也就是说是原地排序。 我们从待排序的记录序列中选取一个记录(通常第一个)作为基准元素(称为key)key=arr[left],然后设置两个变量,left指向数列的最左部,right指向数据的最右部。 key首先与arr[right]进行比较,如果arr[right]<key,则arr[left]=arr[right]将这个比key小的数放到左边去,如果arr[right]>key则我们只需要将right--,right--之后,再拿arr[right]与key进行比较,直到arr[right]<key交换元素为止。 如果右边存在arr[right]<key的情况,将arr[left]=arr[right],接下来,将转向left端,拿arr[left ]与key进行比较,如果arr[left]>key,则将arr[right]=arr[left],如果arr[left]<key,则只需要将left++,然后再进行arr[left]与key的比较。 然后再移动right重复上述步骤 最后得到 {23 58 13 10 57 62} 65 {106 78 95 85},再对左子数列与右子数列进行同样的操作。最终得到一个有序的数列。{23 58 13 10 57 62} 65 {106 78 95 85}{10 13} 23 {58 57 62} 65 {85 78 95} 10610 13 23 57 58 62 65 78 85 95 106性能特点关于复杂度相关O(n)等公式,我这里需要强调一点,公式代表的是算法的复杂度增长的趋势,而不是具体计算复杂度的公式。比如:O(n²)和O(n)相比较,只是说明 O(n²)增长的趋势要比o(n)快,并不是说明O(n²)的算法比O(n)的算法所用时间一定就要多。时间复杂度快速排序平均时间复杂度为O(nlogn),最好情况下为O(nlogn),最坏情况下O(n²) 空间复杂度基于以上例子来实现的快排,空间复杂度为O(1),也就是原地排序。 稳定性举个例子:待排序数组:int a[] ={1, 2, 2, 3, 4, 5, 6}; ...

April 27, 2019 · 2 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

iPhone手机怎么投屏到电脑 airplay怎么用

其实想要小屏转大屏,方法很简单,简单几步就可以操作,下面简单几步教大家iPhone手机怎么投屏到电脑。使用工具:Iphone&电脑操作方法:1、如果想要把手机本地的照片或视频投屏到电脑上,可以先通过屏幕镜像,原来叫Airplay,让手机和电视同屏显示。2、操作起来很简单,以iOS11为例,上滑屏幕,点击屏幕镜像。3、如果手机和处于同一个无线网络环境,就能直接看到电脑的名称,所以在投屏之前,手机电脑要连接同一个wifi线路。4、在手机上看到电脑名称,随后点击连接之后手机的画面就出现在电视上了,然后打开图片视频就能随意播放了。5、投屏的时候,如果有想要录制成视频的小伙伴,可以点击左上角的红色圆圈,点击开始录屏就可以了,结束载点击一次就完成视频的录制了。看完了iPhone手机怎么投屏到电脑的全部步骤,是不还感觉是实用又简单呢!如果感兴趣的话,那就动手起来吧!

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

已实现乐观锁功能,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

FreeSql.Repository 通用仓储层功能

前言好多年前,DAL 作为数据库访问层,其实是非常流行的命名方式。不知道从什么时候开始,仓储层成了新的时尚名词。目前了解到,许多人只要在项目中看见 DAL 就会觉得很 low,但是比较可笑的一点是,多数的仓储层与 DAL 实质在做同样的事情。本文正要介绍这种比较 low 的方式,来现实通用的仓储层。参考规范与其他规范标准一样,仓储层也有相应的规范定义。FreeSql.Repository 参考 abp vnext 代码,定义和实现基础的仓储层(CURD),应该算比较通用的方法吧。IBasicRepository.cs 增删改接口using System.Threading.Tasks;namespace FreeSql { public interface IBasicRepository<TEntity> : IReadOnlyRepository<TEntity> where TEntity : class { TEntity Insert(TEntity entity); Task<TEntity> InsertAsync(TEntity entity); void Update(TEntity entity); Task UpdateAsync(TEntity entity); IUpdate<TEntity> UpdateDiy { get; } void Delete(TEntity entity); Task DeleteAsync(TEntity entity); } public interface IBasicRepository<TEntity, TKey> : IBasicRepository<TEntity>, IReadOnlyRepository<TEntity, TKey> where TEntity : class { void Delete(TKey id); Task DeleteAsync(TKey id); }}IReadOnlyRepository.cs 查询接口using System.Threading.Tasks;namespace FreeSql { public interface IReadOnlyRepository<TEntity> : IRepository where TEntity : class { ISelect<TEntity> Select { get; } } public interface IReadOnlyRepository<TEntity, TKey> : IReadOnlyRepository<TEntity> where TEntity : class { TEntity Get(TKey id); Task<TEntity> GetAsync(TKey id); TEntity Find(TKey id); Task<TEntity> FindAsync(TKey id); }}IRepository.cs 仓储接口using System;using System.Linq.Expressions;using System.Threading.Tasks;namespace FreeSql { public interface IRepository { //预留 } public interface IRepository<TEntity> : IReadOnlyRepository<TEntity>, IBasicRepository<TEntity> where TEntity : class { void Delete(Expression<Func<TEntity, bool>> predicate); Task DeleteAsync(Expression<Func<TEntity, bool>> predicate); } public interface IRepository<TEntity, TKey> : IRepository<TEntity>, IReadOnlyRepository<TEntity, TKey>, IBasicRepository<TEntity, TKey> where TEntity : class { }}现实 BaseRepository.cs 通用的仓储基类using System;using System.Linq;using System.Linq.Expressions;using System.Threading.Tasks;namespace FreeSql { public abstract class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class { protected IFreeSql _fsql; public BaseRepository(IFreeSql fsql) : base() { _fsql = fsql; if (_fsql == null) throw new NullReferenceException(“fsql 参数不可为空”); } public ISelect<TEntity> Select => _fsql.Select<TEntity>(); public IUpdate<TEntity> UpdateDiy => _fsql.Update<TEntity>(); public void Delete(Expression<Func<TEntity, bool>> predicate) => _fsql.Delete<TEntity>().Where(predicate).ExecuteAffrows(); public void Delete(TEntity entity) => _fsql.Delete<TEntity>(entity).ExecuteAffrows(); public Task DeleteAsync(Expression<Func<TEntity, bool>> predicate) => _fsql.Delete<TEntity>().Where(predicate).ExecuteAffrowsAsync(); public Task DeleteAsync(TEntity entity) => _fsql.Delete<TEntity>(entity).ExecuteAffrowsAsync(); public TEntity Insert(TEntity entity) => _fsql.Insert<TEntity>().AppendData(entity).ExecuteInserted().FirstOrDefault(); async public Task<TEntity> InsertAsync(TEntity entity) => (await _fsql.Insert<TEntity>().AppendData(entity).ExecuteInsertedAsync()).FirstOrDefault(); public void Update(TEntity entity) => _fsql.Update<TEntity>().SetSource(entity).ExecuteAffrows(); public Task UpdateAsync(TEntity entity) => _fsql.Update<TEntity>().SetSource(entity).ExecuteAffrowsAsync(); } public abstract class BaseRepository<TEntity, TKey> : BaseRepository<TEntity>, IRepository<TEntity, TKey> where TEntity : class { public BaseRepository(IFreeSql fsql) : base(fsql) { } public void Delete(TKey id) => _fsql.Delete<TEntity>(id).ExecuteAffrows(); public Task DeleteAsync(TKey id) => _fsql.Delete<TEntity>(id).ExecuteAffrowsAsync(); public TEntity Find(TKey id) => _fsql.Select<TEntity>(id).ToOne(); public Task<TEntity> FindAsync(TKey id) => _fsql.Select<TEntity>(id).ToOneAsync(); public TEntity Get(TKey id) => Find(id); public Task<TEntity> GetAsync(TKey id) => FindAsync(id); }}如何使用?1、安装dotnet add package FreeSql.Repository2、声明 FreeSql,为单例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();ps: FreeSql 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite。3、创建实体public class Song { [Column(IsIdentity = true)] public int Id { get; set; } public string Title { get; set; }}4、创建仓储层public class SongRepository : BaseRepository<Song, int> { public SongRepository(IFreeSql fsql) : base(fsql) { }}解释:<Song, int> 泛值第一个参数Song是实体类型,第二个参数int为主键类型至此,通过继承 BaseRepository 非常方便的实现了仓储层 SongRepository,他包含比较标准的 CURD 现实。参考资料:https://github.com/2881099/FreeSql/wiki/Repository结束语FreeSql.Repository 的版本号目前与 FreeSql 同步更新,查看更新说明;FreeSql 特性CodeFirst 迁移。DbFirst 从数据库导入实体类,支持三种模板生成器。采用 ExpressionTree 高性能读取数据。类型映射深入支持,比如pgsql的数组类型,匠心制作。支持丰富的表达式函数。支持导航属性查询,和延时加载。支持同步/异步数据库操作方法,丰富多彩的链式查询方法。支持事务。支持多种数据库,MySql/SqlServer/PostgreSQL/Oracle/Sqlite。Github:https://github.com/2881099/FreeSql ...

March 19, 2019 · 2 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

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

祝愿大家不要像菜菜这般苦逼,年中奖大大滴在没有年终奖的日子里,工作依然还要继续…..一张冰与火的图尽显无奈还记得菜菜不久之前设计的用户空间吗?没看过的同学请进传送门=》设计高性能访客记录系统还记得遗留的什么问题吗?菜菜来重复一下,在用户访问记录的缓存中怎么来判断是否有当前用户的记录呢?链表虽然是我们这个业务场景最主要的数据结构,但并不是当前这个问题最好的解决方案,所以我们需要一种能快速访问元素的数据结构来解决这个问题?那就是今天我们要谈一谈的 散列表散列表散列表(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

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

原因排查经过一个多小时的代码排查终于查明了线上程序线程数过多的原因:这是一个接收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

.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

Java程序员一年的成长和蜕变

那年7月份到深圳,信心满满的以为自己找工作会一帆风顺,谁知这成了一部血泪史,带着一份servlet+jdbc做的毕业设计到深圳,简历上三三两两的写了“项目经验”:图书管理系统、养老院管理系统,当时还完全不会SSH(struts2+spring+hibernate),然后自信满满的到求职网上投简历,投递了两天,发现并没有任何用……这家公司(下面称为公司)也是运气进来的,当时是拿着自己的电脑,简历,一份毕业设计《养老院管理系统》,非SSH框架技术,面试我的是公司的前端大师,也得亏运气好吧,当时做的界面和公司的后台系统是差不多类型。大概10天后,可能出于培养和解决当前用人问题把我招进来。那年-07-28,正式进入公司,由一位同事给我介绍公司的整体业务,把整个主要流程讲了一遍,然后就算入职了。9月份,公司接了一个大项目,来了四个人,那气势真是足,提出的需求真的非常多,非常苛刻,公司也是给其可以实现。我参与了进来,当时的项目经理也是非常好,看我刚毕业,也不会SSH,交给我一个非常简单的模块,还记得是几个复选框添加到界面,然后让操作的时候这复选框要有作用。当我看了需求文档,真心不知道如何下手,然而项目经理以为我可能一两天搞定,结果搞了一个星期。公司框架导下来各种报错,不会框架方面调试!但也只能硬着头皮上,虽然那些基本的编程能力还是有的,毕竟也是独立的写完毕业设计,但是SSH开发真的不会,到后来几乎都是旁边的大神帮忙搞定的,真的汗颜。不过也通过他帮忙,对整个流程稍微了解了,总算是交了差。经过几天的时间,也算是熟悉了。后来获取了项目经理的一点信任,交给一个完整模块,可以从页面到后台交互都能完整的操作一遍,欣然接受了,花了个把小时把这个功能的需求写出来,交给项目经理,审核通过,然后交给我去做。九月份还有半个月我就在这上面耗着了,后来也是大体将这个功能完成了,也算松了一口气。九月底安排去武汉出差,到武汉来帮忙打杂,做一些项目的修修补补工作。当然,个人也乐意这样的安排,反正都是学技术,还有补贴。首先把环境搭建好,然后将项目跑通,这里说一下,公司的项目,从svn上导下来之后,还需要很多步骤的修改,才能跑通,真的很头疼(现在这家公司,导下来直接跑通,方便很多),就开始了正式的SSH学习之旅。在武汉这边,任务是做报表,写周报,将所用技术总结成文,写的每一个步骤都非常详细,也包括所使用的工具的使用等,因为公司周报要求不是很规范,只要有内容就行,但是我敢肯定,我的周报是当时开发人员里面写的最好最多最完全的。因为周报,能把所用技术不断的巩固,知识点不断的复习,把SSH框架的整体流程完全掌握,并且在CSDN发表了自己的简单的技术文章,自己也算是在技术路上启程了!在武汉的这段时间,因为是做很简单的报表,趁着写周报的时间,把接触到的所有用到的技术:struts2、spring、hibernate、oracle、润乾报表、Dreamweaver、 Secure Shell client、mysql、tomcat等操作流程,接触到的业务需求,全部整理成周报,然后提交给公司专门收周报的工作人员,而正是这段时间,技术积累飞速提升。正是从这周报总结上获得了巨大的收益,希望都能有总结的好习惯!不断总结,技术才能变成自己的!从武汉出差回来之后,在那年度年终大会上,代表着公司新人上台发言了。还抽奖中了公司一等奖(后来我听说是公司技术老总看我工资偏低,然后在抽奖环节做了“手脚”,感动ing)后来进入新项目开发,其实就是二次开发,只是把数据库,系统后台的结构全部进行改变。不管如何,这也算是一次机遇,无论项目大小,让自己知道要做什么,要去学习什么,要去获得什么,所以就非常激动的进入项目组,开始自己的零项目经验的开发!最初,项目经理安排了一个订单修改的功能模块。公司订单这块真的非常的复杂,我敢说,很多的公司可能都没有这家公司的业务复杂。修改一个订单,非常的头疼,这才发现原来业务这块真的不擅长,非常缺乏经验。不知道要做什么,而且说得一些“概念化”的东西,听得一知半解,听完之后,就默默的点头,然后就去总结从项目经理那里讨论来的业务需求,然后一步一步的开始开发了。当时,我们寻常思维的订单,无非就是根据产品的一种销售状态或者销售记录来获取数据库中的订单状态,这个订单的修改呢,其状态用四个表示数量的数据库字段来计算得出,然后又有各种组合产品、单独产品、组合产品+单独产品的信息修改,操作,真心非常之复杂,这过程就不再阐述。总之,通过这个功能学习到了:▌ 1、通过业务融入到技术开发中,排开业务来开发的功能、项目,都是不现实的。初期不懂的业务、功能点一定要向同事、项目经理求助,然后理清自己的思路再进行开发,这样才能事倍功半!▌ 2、技术层次,因为是个独立的新模块,这个功能涉及到了前端、后台的整体操作,不仅将后台的整体流程,从配置到开发独立完成,再到前端界面操作各种js(虽然很浅,但是非常的实用),jquery操作,然后将所用技术全部进行总结,这些再度成为我个人实际增长的技术点和能力,因此再次感谢自己的总结习惯!▌ 3、开发过程中不要怕犯错。如果不犯错说明进步不大,亦或是在做重复的工作。只有犯错了,然后解决错误了,这样才能成长,技术才能得到提升。通过总结所有的东西都是自己的!这个是项目中独立的一个开发模块,但是因为其业务的复杂,以及在前段后台的界面,逻辑操作,让自己得到了很大的提升(一个功能做了两个月),还好当初没有因为觉得难而放弃。参与项目之后,因为项目组就四个实际开发的(后续走了一个,还有一个成了接受需求、测试等工作的同事),基本上开发由自己和另外一个同事在做,这个期间,还是不太理解完整的业务逻辑,虽然已经做过简化了,但是从自己做完订单修改功能后,才开始慢慢的了解,这期间也了解到了至少有9部分(线上、线下)的关联来实现主要的业务逻辑,因此自己的后续项目也围绕着这5部分来进行业务、需求讨论,并根据这些业务/需求来进行开发。当时负责了9部分中其中的4,5个模块,然后参与了其他的模块的需求分析,因为都是关联在一起的,因此或多或少都在其他模块去开发过。首先去完成的是线下功能,得去了解业务逻辑,得知道这是干嘛用的(再次强调熟悉业务),因为里面的逻辑太复杂了,在项目经理的谆谆教诲,抱着一知半解的心态,然后去做了,因为是二次开发,但是逻辑也有相应的变动,因此在以前的几千行代码上进行修改,当时哪里看过这么长的代码,然后里面各种调用其他类中方法,简直是受罪。但是呢,随着时间流逝,自己也从最初的完全看不进,到后来整个的那一块的模块读懂,并且修改了大部分的业务逻辑,现在想想也是挺有成就感的,这块的逻辑据说是公司的大佬级程序员几个负责不断修复完成的,然后我们就这样毫不留情的改动了。改完之后,肯定要先单元测试,整了几个所谓“程序员能通过的测试用例”,然后去测试,很成功,没有问题(这种情况下很多情况是坑)!程序员的测试用例,简直到后来把自己坑的很惨。把写好的功能交给测试部,然后测试部以用户角度去测试的啊,然后各种组合进行测试,提出一堆bug,搞的头都大的不行,这绝对是个血的经验教训。经过了两三个月的开发,总算也是项目进入尾声了,也终于把自己做的这个项目的整体流程,从需求–pm参与需求讨论、制定开发任务–开发者得到任务–开发–编码–单元测试–测试部测试,整个流程参与了下来,自己也是非常的受益匪浅,开发经验飞一样的上升(记住,我之前就说了我是一个爱总结的人,因此我的收获非常大)。几个方面,这里就不多说了,反正一句话,完整的参与了一个项目,并且全身心投入进来,那么你的收获将会是非常大的。以上基本上为自己在公司所经历的了,作为一年工作经验开发人员,最后再总结一下:1、业务为主,理解业务,就能让自己思维清晰2、初期不要嫌累和麻烦,做的越多,收获越多3、不断总结,做的多不总结只能得到最常用的收获,但是做得多总结了,将全盘收获!这里是程序员秘密聚集地,各位还在架构师的道路上挣扎的小伙伴们速来。“

February 21, 2019 · 1 min · jiezi

直击六大会场 | 洞察100+创新实践,2018TOP100summit圆满落幕!

北京时间11月30日-12月3日,由msup和中国国际人才交流基金会联合主办的第七届全球软件案例研究峰会(简称:TOP100summit)在北京国家会议中心圆满落幕。TOP100summit是科技界一年一度的案例研究峰会,每年甄选有学习价值的100件技术创新实践,分享他们在本年度最值得的总结、盘点的实践启示。今年,本届大会以“释放AI生产力,让组织向智能化演进”为主方向,来自全球范围内的100+年度优秀软件研发实践案例对2018年的行业发展进行了一次整体复盘。此外,数百位行业翘楚也给技术人带来了新一年的规划和参照。开幕式释放AI生产力,让组织向智能化演进_在11月30日举办的开幕式上,中国国际人才交流基金会主任苏光明先生,msup创始人兼CEO刘付强先生,京东副总裁、大数据平台负责人翁志先生,华为云首席产品专家汪维敏先生 ,搜狗语音交互技术中心语音首席科学家陈伟先生,yelp产品总监杨光先生,网易有道首席科学家段亦涛先生,快手产品副总裁徐欣先生,腾讯区块链高级产品总监秦青先生,分别从产品演进、团队管理、大数据、AI应用 、区块链技术等方面,对2018年软件研发现状及未来发展趋势发表了各自的观点,给诸多参会者带来了深入的思考。具体开幕式详情可点击:释放AI生产力,让组织向智能化演进 | 2018TOP100summit开幕式成功举办!本届大会分为产品、团队、架构、测试、运维及人工智能6个专场,在2018年软件研发行业的大背景下,人工智能、区块链、大前端等话题成为大会的新亮点。TOP100组委会历时125天,3000小时,180000分钟,邀请到了20+国外的讲师,百余位国内讲师共同为1500位参会者呈现出4天的技术盛会。产品创新/体验设计/运营增长_本专场由三位联席主席策划出品:快手产品副总裁徐欣、新浪微博用户运营总经理陈福云、阿里巴巴高级体验设计专家朱斌。纵观壹佰案例产品场榜单 ,不难发现今年甄选的案例全部围绕着互联网产品各个运营细节进行的讨论,也正是为了顺应当下产品的发展趋势,我们邀请了全球产品及运营大咖。本次峰会上,小米物流系统产品经理李宽、网易云信&七鱼市场总监卡爷、个推大数据产品咨询总监沈都分别为我们讲述了B端产品和C端产品根本性的区别、作为一个B端产品经理应该具备的技能以及具体的案例经验分享。卡爷在演讲中提到,数据需要不断测试持续优化 、要重视所有用户的营销价值、以客户为中心。随着AI的发展,人工智能技术已经逐渐在大部分企业落地,如何将人工智能技术结合到业务,形成真正的人工智能产品成为了他们新的诉求,我们邀请了Google产品经理何周舟从智能化产品管理的新挑战出发,带大家理解智能产品管理和直觉型以及经典型产品管理的差别,了解并填补相关鸿沟;“数字化转型”是近几年被高频提到的词汇,喜茶CTO陈霈霖作为众多案例中,唯一的零售餐饮企业为参会者分享了喜茶数字化转型过程中的核心逻辑——数字化三支柱;在无界零售时代的大潮中,京东无人超市产品负责人高颖运用京东X事业部“无人黑科技”的技术实现无人结算,为零售行业降本增效。腾讯CDC 高级设计师 and 产品经理魏仁佳&欧龙为我们深度讲述了To G 项目业务体系的复杂度,结合数字广东在推进政务服务不同触点的迭代阶段,缔造了“互联网+政务”体系。在用户增长领域,主要涉及到用户增长体系的搭建,滴滴、网易、slack、宜人贷、中原集团也分享了各自团队在用户增长方面的实践。滴滴出行体验战略负责人冯伟伦提到,体验需要可衡量、可监控、成体系、能比较。不同的商业模式和阶段,需要体验管理体系不断的迭代和更新。除了产品创新和运营增长外,体验设计也是产品经理离不开的话题,阿里、Spotify、微软、喜马拉雅、有赞的讲师也分别讲述了产品背后的设计思考。诸位产品经理、产品负责人演讲的议题涵盖了产品方法、产品设计、团队管理等核心方法论和进阶理论,结合了各自领域内一线实操经验,涉及了AI、新零售、小程序、增长黑客、社交零售、小程序、企业服务等多个互联网热点领域及方向。组织发展/研发效能/团队管理_本专场由三位联席主席策划出品:阿里巴巴钉钉技术管理负责人,资深技术专家杨威、美团·大众点评技术学院院长刘江、百度工程效率部总监李涛。技术的发展是无休止的,同时技术也存在繁多的问题:技术挑战,技术多样性导致协同困难;人才挑战,缺少跨界领域转接;交付挑战,业务复杂交付难度;组织挑战,协作壁垒严重阻碍合作进度;能力挑战,创新型企业缺少资金和市场能力等。作为技术团队的管理者,如何正确认识和把握团队的战略发展?敏捷、OKR、转型及项目管理的实践方式仍然是最普遍且一直持续使用的方法。本届峰会,来自阿里、美团、百度、平安银行、Trend Micro等企业的讲师分别从企业转型及研发效能方面分享敏捷和OKR的实践细节和操作经验。经过了几年的刷新变革,微软的市值翻番,超过了互联网泡沫以来的高点。微软Windows中国工程团队首席研发总监邹欣为我们讲述了在微软探索复兴之路的过程中,微软文化的重塑、软件工程的创新在管理上的变化。千人规模的研发团队,不同产品部门间既有共同产品规划目标,如何实现研发团队项目化管理,数字化和透明化呈现研发过程?用友开发管理总监侯洪志给出了答案 ;小游戏的项目管理在时间紧的情况下,如何快速地确定目标、规划好版本,制定可落地执行的计划?多人并行开发,如何保证人力最优?腾讯高级项目经理徐州分享了他的实践经验;伴随招银网络科技的发展,组织规模迅速扩大,如何快速提升工程师幸福感?招银网络科技文化牵头人苏妮分享了“Best Brain”项目打造组织技术氛围。作为技术管理者,不止需要协同协作,创新及流程化管理也是企业发展和技术变革的基础,同样将新的技术及思路融合进来,才能取得更十足的进步。爆款架构/数据平台/工程实践_本专场由三位联席主席策划出品:新浪微博研发中心研发总监李庆丰、前趣店集团总架构师&技术总监徐章健、京东副总裁、大数据平台负责人翁志。在数据时代,不少企业学会以数据驱动决策。但是,谈及实践,不少企业又犯了难:如何从海量数据中选择对业务增长有价值的部分?如何清洗并分析数据以驱动决策?如何不让庞大的数据降低整体计算性能?杨波从Uber的数据平台、对Apache Spark的使用,应用挑战和改进三方面全面介绍了Spark在Uber内部的架构设计及大规模实践。数字化转型时代,新技术正在逐渐颠覆传统行业。传统企业要想扭转颓势,必须了解数字化转型的特点以快速适应迅速变化的市场,Liberty Mutual资深产品经理吴疆介绍了Liberty Mutual从技术选型、开发流程等多维度开展数字化转型,并为参会者带来了新的启示。WeiboMesh源自于微博内部对异构体系服务化的强烈需求,同时像大多服务一样,都有历史包袱,网红微博搜索架构师丁振凯在现场为我们解读了现有服务如何高效平滑完成跨语言服务方案,并结合业务实例分析了WeiboMesh的独到之处。随着电商业务快速发展,各个业务系统已经演变成分布式、微服务化平台,因此理清各业务系统的链路关系,快速准确定位系统链路的性能瓶颈变得愈发困难。京东、当当、阿里、咸鱼等电商企业从技术选型到生产环境落地,对持续进化过程中遇到的问题进行了讲述。人工智能/AI驱动/AI实践_本专场由三位联席主席策划出品:网易传媒技术副总经理刘彦东、爱奇艺资深科学家王涛、小米首席架构师、人工智能与云平台副总裁崔宝秋。目前人工智能技术已经进入4.0时代,面对数字时代的飞速发展,前沿技术已悄然改变了人类的生活方式、沟通方式和商业架构。说到人工智能,机器学习作为人工智能的核心技术,是各个行业实践人工智能的基础。360商业产品事业部高级技术经理潘尧振演讲的《360易投放-用机器学习让广告投放进入“自动驾驶”时代》,通过使用机器学习技术帮助中小广告主自动生成和优化创意、快速搭建推广计划,实现“一站式”投放。近年来人工智能不仅正在各个生活场景中加速落地,还可以处理分析一些复杂的数据信息,来自腾讯互娱、来也、Keep、LG、Uber等公司的讲师带来对当下人工智能的相关技术原理和应用成果,并且延伸到在不同场景下的应用即对未来人工智能发展趋势的展望。测试实践/测试工具链建设/大前端&移动端_本专场由两位联席主席策划出品:甲骨文研发总监许峰兵、阿里巴巴淘宝技术质量负责人青灵。在测试技术的领域中,关于Weex、模糊测试、MBT及移动端自动化测试仍然是最热门的话题。本次峰会,深度链接 - 客户端测试效率提升之蹊径、手自一体化的移动云测试平台建设方案、基于机器学习的模糊测试在大型系统产品中的应用、Weex生态质量保障方案等演讲主题为参会者带来了新的启示。大前端技术,自从诞生以来就受到人们的广泛关注,经过几年的发展,这类技术开始落地。但由于大前端的演进与火爆,前端工程师的责任也越来越大,未来对前端岗位的要求也越来越高。同时,业务发展变化迅速,页面变来变去,前端人员忙不过来,但却职业提升缓慢?爱奇艺高级经理段金辰给出了答案;海量的用户规模、跨端用户环境、全球业务部署,给前端监控带来不少新的挑战,任职于阿里巴巴的前端技术专家彭伟春,分享了分前端监控最前沿的思考,到前端监控系统的架构,再到具体的案例分析,体系化地讲述前端监控的点点滴滴。运维体系/AIOps&DevOps/区块链_本专场由三位联席主席策划出品:蘑菇街技术总监赵成、腾讯区块链业务总经理蔡弋戈、JFrog中国区首席架构师王青。运维,是2018年最火的一个话题,也是最有前途的一个话题。随着运维收到越来越多的重视,运维业务多样化和业务规模也在持续增长,并开启了运维自动化向智能化的转型之路。成本、效率和质量是现在通用的运维主要面临的三大问题,三七互娱、百度、虎牙、腾讯、JFrog等团队分别分享了各自在运维平台整体架构建设方面的心得与经验。任职于三七互娱的运维开发负责人童传江分享了他在解决任何计算机问题的过程中,如何把复杂的问题抽象为简单的模块。近两年,DevOps正在成为大家所熟知的实践方法和文化价值观,对DevOps的采用已是主流趋势。京东的研发支持团队负责人唐洪山通过成熟的研发理念和DevOps实践,助力了企业效能提升和业务发展。随着近年来区块链技术的兴起,许多的企业也开始跟随时代开始着手研发,但在面对如此复杂的区块链知识时,一些问题与挑战也开始出现。腾讯、360、华为、中国民生银行等公司的区块链技术专家带来了自己的心得,和运维体系/AIOps&DevOps/区块链专场的每一位参会者共同交流区块链技术在企业中的应用。讲师晚宴_为打造更加深入的参会体验,本届TOP100summit于12月1日晚举办了讲师晚宴活动。多位讲师与部分参会者欢聚一堂,共同就软件研发行业的热点展开思考与共鸣。茶歇&展区互动_开放的茶歇场地,为参会者和大会讲师提供了自由的交流空间,同时由msup、buzz等多家合作伙伴精装打造的特色展位组成的用户体验区域,也为参会嘉宾带来了别样的感受,让参会嘉宾在玩味前沿技术更获得愉悦放松。本届2018TOP100summit讲师阵容中,国际化比例显著提升,大会不仅邀请了国内第一阶梯大厂的领袖,还邀请了来自 Google、yelp、微软、Linkedln、Uber、亚马逊等国际巨头企业负责人。他山之石可以攻玉,这些来自全球各地的不同的产品视角和产品方法,为参会用户带来了不一样的视野和收获。同时,我们在大会设置了反馈表环节,发现今年的评分远远高于往年,整体获得了9.2分以上的好评,在此,TOP100组委会感谢各位联席主席、讲师以及业内同仁的积极参与!此外,还要特别鸣谢华为、个推、来也、亿美软通、JFrog、APICloud、环信、图灵、华章、博文视点、青云、网易云信、猎聘等赞助伙伴及IT168等合作媒体伙伴的大力支持!

December 29, 2018 · 1 min · jiezi

百度云曲显平:AIOps时代下如何用运维数据系统性地解决运维问题?

本文是根据百度云智能运维负责人曲显平10月20日在msup携手魅族、Flyme、百度云主办的第十三期魅族技术开放日《百度云智能运维实践》演讲中的分享内容整理而成。内容简介:本文主要从百度运维技术的发展历程、如何做智能运维、故障管理场景、服务咨询场景和面对的挑战等几个方面介绍了百度云智能运维实践。百度运维技术的三个阶段第一阶段:基础运维平台 2008年2012年2008年,在百度运维部建立之前,还没有一个标准而统一的运维平台。例如,搜索、广告、贴吧都有各自的运维平台。存在的问题:技术和平台能力无法复用,业务之间需要交互时比较复杂。解决方法:①为帮助业务解决问题,我们把各个分散在不同业务的运维平台整合起来做成一套标准化运维平台;②有了统一运维平台后,运维部门内的角色就分为了两个,即标准的运维工程师和运维平台研发工程师。第二阶段:开放的运维平台 2012年2014年第一阶段仍然存在的问题: ①个性化需求很多,统一平台很难全部解决②PaaS出现之后,运维平台和PaaS的关系解决方法:①开放运维平台,即全部API化。②通过提供标准化的监控数据的采集、计算、报警能力,最基础的程序分发、数据分发、任务调度能力,解决自身平台的需求。③利用PaaS方法,把一些研发的技术平台和运维技术平台整合在一起,解决重复造轮子的问题。第三阶段:AIOps阶段 2014年开始百度从2014年就开始了智能运维的实践。最早的时候,我们更多是通过完善底层的大数据平台能力,提供一些数据分析和挖掘的算法和工具,解决运维数据没有得到合理运用,运维人工效率低等问题,这是偏大数据的方法。百度对于AIOps的理解在2015年,AI变得异常火热,百度也是想将自身先进的机器学习算法应用到运维领域之中,于是我们和百度的大数据实验室、深度学习实验室进行了合作。运维研究人员把需求和归整好的数据提交给实验室的人员,然后他们会根据数据训练模型,最终提供一些库和方法供业务使用。2016年,Gartner提出了AIOps这个词,也就是我们说的智能运维,这和百度的实践是不谋而合的。三个核心内容随着智能运维的发展,百度也是把数据、工程和策略三个,作为最核心内容来系统地解决运维行业的应用。从数据角度来讲,首先要构建一个完整的数据仓库,接着要建设运维知识库。知识库是在数据仓库上抽象进行的。从工程角度,一方面,分析数据和训练算法模型需要大数据平台和框架,另一方面,运维业务研发人员还做了一套运维工程研发框架,用以解决标准化、可扩展和复用的问题。这个框架十月份刚刚开源,感兴趣的朋友可以看下。在百度内部,一致的运维“语言”非常关键。我们要统一不同的工具和平台,形成一致的运维模式。所以不管是故障感知、故障诊断决策、弹性伸缩决策还是运维操作和执行,只有统一起来才能解决这个问题。一致不仅是数据一致、工程一致,还需要策略本身的一致性。自动驾驶分级在构建整个百度智能运维体系的过程中,我们重点参考了自动驾驶里的分级理论。百度是有这样两个部门的,一个叫L3,一个叫L4。L3部门重点在做类似于辅助驾驶或者高度辅助驾驶;L4部门做的是高度完全自动驾驶。下图是关于自动驾驶的分级。运维能力分级自动化运维能力分级当时我们团队参照这个自动驾驶分级,构建出了一个自动化运维能力的分级标准,用以评估我们各个方向的自动化水平,一共分为六个能力等级,即人工、工具辅助、部分自动化、有条件的自动化、高速自动化和完全自动化。关键点:决策规划由运维系统做出,而不是人人负责:制定优化目标(比如,可用性、效率、成本等)运维系统负责:根据其对待处理的需求、待解决的问题的理解,以及对运维对象的认知(经验),自主做出解决方案(规划)并在控制执行过程中根据目标和运维对象的状态反馈来适时调整执行规划。智能化运维能力分级在自动化能力分级之中,我们还细化出了一个智能化运维能力分级(我们始终认为智能运维是实现完全自动化运维的一种手段)。实现智能化能力,重点解决的是在运维感知和决策过程中,人工效率低和准确率不足的问题。关键点:决策规划由运维系统做出,而不是人人负责:制定优化目标(比如,可用性、效率、成本等)运维系统负责:根据其对待处理的需求、待解决的问题的理解,以及对运维对象的认知(经验),自主做出解决方案(规划)并在控制执行过程中根据目标和运维对象的状态反馈来适时调整执行规划。如何做运维我们希望每一个运维工具都像一个小型的运维机器人一样,解决运维的问题。运维工程师需要把每一个运维工具抽象化,同时也要像一个标准框架一样,可以在代码库里克隆,把框架代码复制下来。通过三个基本核心,感知、决策和执行来进行编写执行器,接着可以通过配置实现一些具体任务调度的配置或者并发执行的配置;每一个运维工程师要实现感知逻辑、决策逻辑、执行逻辑,利用运维核心解决可靠性的问题。在测试方面,要在线下建立看代码的逻辑去验证。结合这个看代码,把比较核心的运维故障抽象出来,再把一些常见的故障模拟出来,具体的情况可以在这里面运行;写完一个运维工具或者算法,需要直接在上面运行,从而检测出是否有效。故障处理场景百度内部如何解决故障处理场景故障处理场景一般分四个主要阶段:故障发现、服务止损、服务恢复、故障总结。在服务止损方面,核心是如何让用户感知不到这个故障,对于运维来讲,更多用的方法是隔离、降级,而非从代码BUG入手解决的问题。在服务恢复方面,这个一般是在服务止损或者说故障被隔离之后,很大程度上需要运维和研发共同合作,比如定位代码的BUG,最终要决定如何把线上的问题真正解决掉。恢复,更多用的是修复来解决。在百度,大多数的故障都是可以用隔离和降级解决的,只有那些极特殊的case,才会通过程序回滚来恢复。回滚风险很大,而且效率很低。在整个解决故障处理场景的阶段,每一个阶段都可以结合智能运维的方法。从开始服务部署、监控添加、故障发现、止损决策、止损操作、根因诊断、恢复操作,最后报告自动生成。把AIOps应用到故障处理最核心的基础是,全面覆盖监控。在百度,做的最全面的是云上的监控,所以包含这四个维度的监控:系统监控、业务监控、内网监控和外网监控。系统监控主要的监控对象是机器/容器和服务的动态内容;业务监控针对业务和用户的访问日志等;内网监控则针对IDC内网设备和内网链路;外网监控为了保障用户、运营商链路到百度IDC中间的状态。有了全面的监控之后,才能开始现在业界常提到的一个智能运维技术,自动异常检测。典型的异常检测场景有关异常检测场景,我为大家举三个典型的例子,第一个,周期波动的数据。上图中的蓝、绿、黄三条线分别代表着今天、昨天、上周的时间线,蓝线比较明显,后面还有绿线和黄线。它们相对来说周期性体现得特别强。这种数据很难用传统的计算方法设置阈值。针对这种场景,我们会使用不同类的算法,专门解决这种问题。第二个,关心突变的数据。突变的数据也是一个比较典型的场景,周期性数据更多参考的是天级和周级的数据,而这个场景更多说的是某一个细节层面,可以理解为它是对一小块数据的放大。第三个,关心是否超出了一定波动范围的数据。这种场景是我们用普通的监控方法很难覆盖的,很多情况下,其均值或基线不会有特别明显的变化,但系统现在确实出现了很大的不同状态,可能仅仅是波动更剧烈了,对于这类场景,我们更多的是去看波动的情况,就是除基线以外的一些特征。今年八月份,百度云开源了一个数据标注的工具-Curve 。我们始终觉得算法虽然很重要,但远没有数据本身重要。做机器学习时,数据的建设才是最需要花时间解决的问题,百度的运维工程师也是重点在解决数据标准和数据获取的问题。如何应对报警风暴当出现大规模报警时,手机可能会直接被打爆。异常检测重点解决的是故障感知的问题。当故障被感知后,需要通知给运维工程师。首先,做逐级通告,对报警进行分级。接着做数据的整理,整理出每一个数据,最后抽象化数据的特征,按照每个维度或特征进行报警的归并。完成前两步之后,报警会有一定改善。最后要用数据分析方法或者机器学习的方法处理。数据的特征已经被抽象化,所以有很多方法可以解决,第一种方法是传统数据挖掘,比如关联分析,频繁项集挖掘是最被广泛使用到的方法,它可以有效将同类报警进行合并。第二种方法是机器学习,因为前面抽象出了特征,那做分类聚类都是比较直接的事情。从我们的实践情况看,最后的效果两者相差不大,大家都可以尝试。报警产生后,就相当于感知阶段结束,之后就到达故障处理阶段。接下来,我分享几个百度内部觉得效果最好的处理方法。第一个方法,多维度定位。这个更多偏业务问题的定位。业务都有访问日志,日志由各个不同维度的数据组成。一个故障的出现可能有不同维度,运维工程师需要通过访问日志的数据进行计算分析,分析出真正影响故障的维度。在这个基础上,可以做可视化。这是一类结合业务特征的可视化方法,如上图,这是一个模块拓扑图,很多圈圈,很多研发,这里有健康度、响应时间等等各种维度的展示。像模块响应时间,又可能会分很多类、很多维度或者很多模块,底下是每一个不同的模块,都可能产生对应的一些情况。接下来,百度现在大部分在用的是基于信息熵的维度特征推荐。例如,一个出现故障问题的指标,大的流量下降,可能有不同的维度。运维工程师会对每一个维度里的子维度数据进行分析,分析下降的程度,以及对于现在整个流量总体的下降程度的不同占比,然后做一个排序,就可以得到故障影响较高的某几个维度,从而帮助工程师尽快定位到这个问题或者缩小问题的范围。第二个方法,基于服务拓扑或者服务关联做定位。这是内部比较重要的故障判断基础和指导意见。百度运维倾向于把一个问题的分析分成六个维度:①时间维度,缩小时间范围;②网络拓扑模型,缩小空间范围,区分整体和局部故障;③服务管理模型,推导异常集群、实例或者机器;④变更关联模型,定位程序、配置、数据、运营活动上线;⑤模块关联模型,上下游关联服务的异常传播链;⑥多维度模型,维度关联层级分析,缩小业务范围。上图是一类典型的故障诊断框架。我们可能有很多故障的分类,比如有网络故障,细分一点是有交换机故障、链路故障,可能有系统故障,业务问题、操作问题等各种各样的,都是属于假说生成,可能都是备选故障问题。中间有一个证据评分,相当于基于前面的模型拓扑关系,对不同的故障做评分,把拓扑关系的线做权重,然后做置信计算和排序,最后给出最优决策判断。有关自愈的问题· 故障自愈 通过自动化、智能化处理故障节省人力投入,通过预设定的处理流程和只能化判断策略,提高故障处理可靠性,同时降低故障时间,为业务可用性保驾护航。· 智能自愈①感知:通过监控系统获取业务运行指标、智能异常检测、网络异常事件多种触发方式②决策:根据不同感知方式可以配置不同决策模型③执行:在单机执行基础上,提供集群级别、分布式的处理方式在执行故障自愈过程中,并不止是一个工具的执行,而是包括了调度、伸缩、隔离预案处理甚至多个不同业务的联动。自愈本身的核心并非自动化过程,更多是决策的过程。举一个典型案例叫单机房故障自愈。单机房,不仅仅指机房网络故障,更多指的是故障范围只要限定在一个IDC内部,不管这个故障是代码BUG,还是外面流量接入出了问题,还是机房整个掉电,只要故障范围是在一个IDC内都可以解决。基础能力达标后,我们要设计一个故障自愈系统,核心部分是外网流量调度止损决策器和内网流量调度止损决策器。外网比较简单,而内网则涉及到一些负载均衡策略、弹性伸缩策略、主备切换策略等。盲测验收最后讲一下盲测验收。有了故障自愈的系统后,怎么证明你的方案好用呢?在不通知业务的情况下,我们会和IDC同事进行配合,拔网线或是制造网络拥塞,这时候才能进行完整的切换,从而可以证明基础能力是否达标。百度现在单机房故障自愈已经覆盖了所有核心业务线,自愈时效控制在5分钟内,并且对于非数据库依赖的业务,可以做到1-2分钟完成机房级自愈。咨询服务场景服务咨询的场景可分为以下三种:①通过聊天窗口(IM软件、浏览器等)实时查询业务状态,用户可视化、可追查各种问题;②通过聊天窗口(IM软件、浏览器等)实时触发运维操作,运维工程师可远程上线、启停任务等;③当运维操作完成,出现状态变化或异常等情况时,运维工程师可主动发送相关通知,或按照策略自动进行后续操作。在百度内部,我们将这种场景称为ChatOps:•“放心”:分级发布和可用性干预、保障•“贴心”:监控、部署一站式集成,信息主动推送和确认•“省心”:高度自动化,减少人工介入和等待•“开心”:助力业务发展,如迭代效率提升•将运维人员从日渐琐碎、枯燥、疲惫、低价值、高事故率的工作中解放出来•实现运维人员的转型和增值AIOps的挑战最后说一下AIOps的挑战。现有的AIOps技术,比如指标异常检测、故障自愈等,更多解决的是数据本身的特征和问题,还没抽象到服务、程序本身的特征这个层次上,也就是说,我们并没有真正地了解和解决问题本身。比如,不同类的服务所产生的故障和表征是不一样的,我们希望让数据更多、业务场景可扩展,而非针对几个横向的场景;在业务运营方面,我们不仅仅局限在IDC、操作系统、机器,而是注重资源和性能优化,运维还可以继续拓展。对内,可以做系统优化、成本优化;对外,帮助所有用户做云服务资源池优化,让大家更好的节约成本,提升服务能力。以上内容来自曲显平老师的分享。声明:本文是由msup原创,转载请联系 meixu.feng@msup.com.cn

December 29, 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