一个基于Microsoft Azure、ASP.NET Core和Docker的博客系统

2008年11月,我在博客园开通了个人帐号,并在博客园发表了自己的第一篇博客。当然,我写博客也不是从2008年才开始的,在更早时候,也在CSDN和系统分析员协会(之后名为“希赛网”)个人空间发布过一些与编程和开发相关的文章。从入行到现在,我至始至终乐于与网友分享自己的所学所得,希望会有更多的同我一样的业内朋友能够在事业上取得成功,也算是为我们的软件事业贡献自己的一份力量吧,这也是我在博客园建博客时候的愿景:专业、求是、解惑。因此,我在撰写博客文章的时候,都是以客观严谨的态度来阐述技术知识,并尽可能地以更好的内容组织形式来提高文章的可读性,同时尽可能地回答网友的提问。有很多博客园的粉丝跟我提过意见,有的说我的博客更新太慢,也有的说我有些系列文章有烂尾现象,对于粉丝们的提问,我只有一个回答,那就是业余时间太有限,我没有办法凭靠自己一个人的力量,在有限的业余时间里,在保证文章品质的前提下,为社区提供越来越多的支持。在这一点上,我选择的是宁缺毋滥:宁可发布周期变长,也不希望把没有质量的文章分享出来。另一方面,我也发布了很多开源项目,有些项目属于我自己一些个人小工具的代码备份,也有一些项目,比如Apworks、Byteart Retail、WeText,甚至是我的新博客daxnet.me的源代码项目daxnet-blog,都在我的Github Repo里。说实话我真的没有时间把每个项目中的细节技术以博客的方式一一介绍清楚,因此,一般我基本完成了自己比较满意的开源项目时,我都会写一篇博客来介绍项目的内容和所使用的技术,同时引导读者直接克隆我的项目代码进行参阅,或者直接folk(不用太担心许可协议问题,除了Raspkate项目之外,其它绝大部分项目都是MIT或者Apache的许可协议)。总而言之,不管形式如何,我始终没有放弃过最初的愿景。

也是出于这样的坚持,我希望能够更好地组织我的博客文章,甚至是其它的一些原创作品,以更为集中和高效的方式为读者提供更好的学习交流体验,一直以来我都想过搭建属于自己的博客服务,我也经过了很多尝试。早在2012年,我使用Word Press在一个国外站点建立过博客系统,可是后来因为国外服务供应商的原因,网站没能继续维持下去,之后我也经过好几次的尝试,包括使用BlogEngine.NET等开源项目,可是也都没能做好。出于对技术的热衷与追求,这一次,我终于下定决心,使用自己所学的知识,依托微软的.NET平台,开发并部署了我自己的新博客系统:【http://daxnet.me】。

站点功能

首先简要介绍一下目前的站点功能吧。右图就是本站的主页效果,我做得很简洁,没有用太多花哨的图片,也没有用走马灯。明眼人一看就知道这是基于ASP.NET MVC而开发的Web应用程序,使用了Bootstrap。不错,基本答对!需要强调的是,这个博客站点以及后端的RESTful服务,全部都是基于ASP.NET Core完成的,.NET Core运行时版本为1.1.0,运行在Docker容器中。哎,说着说着又到技术上了,功能还没介绍完呢。说到功能,目前功能很简单:主页列出了我自己原创或者翻译的所有文章,读者可以注册用户帐号,注册用户可以发表评论,也可以在用户管理页面中更改自己的昵称。好了,目前功能就这么多,别看功能少,我可是前前后后陆陆续续花了2个月的时间,才做到目前这个样子。当然,我会继续更新这个站点,让它的功能变得更加完善。

提到ASP.NET Core,有没有吊起你的技术胃口呢?不用着急,接下来我就介绍一下整个站点中各部分的技术选型,看完后,或许你会知道为什么我花了2个月的业余时间,才整出来这么个简单的玩意儿。

站点技术介绍

整体架构

整个网站所采用的所有基础设施全部运行在微软云(Windows Azure)中,使用了部分托管资源,以及一些非托管的Azure VM。大致情况如下:

  • 图片存储服务:由Azure Blob Storage Service托管

  • 数据库系统:由Azure SQL Database托管(未启用Geo-Replication,因为没钱)

  • 邮件服务:由Azure SendGrid Account托管(Pricing Tier为F1,每月可以免费发送25000封邮件)

  • 应用服务器:基于Azure构建的Ubuntu 16.04.1 LTS虚拟机,运行了两个Docker容器:blog-web和blog-service,分别托管前端Web站点和后端RESTful服务。后端RESTful API服务没有做任何认证和授权,Web站点通过内部子网访问RESTful API服务,Docker容器运行在非托管环境中

  • 持续集成系统:Jenkins,基于Azure构建的Windows Server 2012 R2一台(Master),和一台Ubuntu 16.04.1 LTS(Slave)。站点的前端和后端都在后者(Ubuntu)中完成编译、打包以及Docker镜像的发布,实现了一步到位的部署方式

  • 代码库:Github

有人会问:为什么使用了非托管的Azure VM环境运行应用系统?我也考虑过这个问题,理论上讲,基于云的系统架构最好选用托管的PaaS服务,这样不仅可以得到纯天然的高可用性(包括灾备,比如AWS的跨AZ部署,某些服务跨区域的可用性,以及负载均衡),而且还可以得到专业的技术支持。只有当存在老系统向云迁移的需求,并需要迎合老系统的特定运行环境要求时,才考虑使用IaaS服务。虽然虚拟机等这些资源是由Azure负责创建并运行的,在这一层面Azure可以保证虚机的可用性,但虚机内部运行的任何程序的状态,以及所使用的数据,Azure等云服务是无从得知的,对这部分东西的监控也会变得很麻烦。出于安全考虑,通常云服务供应商是不会,也不应该获得类似虚机内部的客户程序的运行数据的,使用虚拟机服务所产生的程序运行风险,客户需要自己承担。这也就是著名的责任共担原则。

看起来用虚拟机运行应用不是太靠谱嘛,然而我却选择这么使用了。有几个原因:

  • 为何不使用Azure Web App?一方面Jenkins做自动化部署,直接把编译好的应用推送到Azure Web App中好像不是太顺手,要写一些PowerShell的代码,可是我的编译系统是Linux,不过现在已经有Linux版的PowerShell了,而且Azure SDK Command Line Interface也有Linux版,所以这个理由有点牵强,更合理的解释是:劳资不会!另一方面,我没有在服务端做认证和授权,仅通过子网向外界提供服务,所以我希望我的Web App也运行在子网内部,然后向外暴露80端口供外界访问。这样一来,Azure Web App又如何部署到我自己的子网内?这是一个技术问题,我相信一定有解决方案,但是我也没太多时间和精力去细究如何实现,自己的第一反应也无非是将前后端全部部署在Azure Web App中,然后打开后端的认证机制。但这样做又要花一些额外的工夫。好吧,还是这个理由:劳资不会

  • 为何不使用Azure Container Service?Azure Container Service会在你指定的Resource Group(资源组)中创建一整套网络部署,包括好几台虚拟机、公网IP、两个负载均衡器等等,我想你一定知道我为什么没有选择Azure Container Service了,原因就是:劳资没钱

理由够充分吧?微软Windows Azure提供的这些服务都很赞,我没选不是说它们不好用,而是出于自己的实际情况考虑:

  1. 某些服务的学习成本

  2. 经济成本

  3. 暂时没必要做到99.99999%的高可用率

  4. 即使应用挂了,恢复的成本很小:数据完全不需要恢复,托管的SQL Database、Blob Storage会保证我的数据不丢失,应用程序恢复也很简单:重新运行Docker容器就完事儿

OK,从整体架构上看,我的选择即是如此而已,这样的选择当然不一定完全正确,但我觉得至少合适,仅供参考。下面附上本站点的整体架构图。

作几点注解:

  1. 三台VM位于同一个Virtual Network的subnet中,每台VM的虚拟网卡上都套有独立的Network Security Group(NSG),在NSG上设置了Inbound/Outbound Endpoints,严格限制了端口访问的IP地址。三台VM之间使用subnet IP地址访问

  2. Windows Server 2012 VM宿主了Jenkins Master,以及Seq日志服务。它向公网暴露8080端口和5342端口,分别用于访问Jenkins服务和Seq管理界面

  3. 第一台Ubuntu VM运行了Jenkins Slave,它不向公网暴露任何端口,仅向Jenkins Master机器暴露22端口,用于Jenkins Slave Agent的执行调度

  4. 第二台Ubuntu VM运行了博客系统的两个Docker容器:前端应用程序blog-web和后端RESTful API服务程序blog-service。web通过子网IP地址访问service,VM仅向公网暴露80端口,后台service无法从公网访问

  5. 两个Docker容器所运行的应用(blog-web和blog-service)都可以访问托管的Azure SQL database、Azure Storage blob和SendGrid Account服务

  6. 整个部署的拓扑结构有可能不太合理,比如没有做负载均衡,没有使用托管的应用宿主服务(比如Azure Web App、Container Service等),没有使用Scaleset。因为目前没必要而且没钱

接下来,回到代码上,我向大家介绍一些框架的技术选型,以及几个ASP.NET Core可用的开源库项目。

前端

如今的前端技术日新月异,各种Javascript的框架和JSX的技术,使得前端开发变得更加方便高效,所获得的用户体验也变得越来越好。例如Angular JS(包括1和2两个版本)、React + Redux、Knockout.JS、Backbone等等。在实际项目中,我们也运用了这其中绝大部分技术,然而,在我的这个博客系统中,我没有使用单页面应用的解决方案,而是继续使用前端Razor+后端C#代码的方式,对啦,这就是ASP.NET Core MVC!我没有使用任何MVVM的框架,只是简单地使用了Bootstrap和jQuery,对我来说,这样选择的原因有以下几个:

  1. 相对而言对ASP.NET MVC比较熟悉,更容易尽快完成开发任务

  2. 本身站点逻辑不是太复杂,暂时没有必要使用这些前端框架

  3. 打算体验一下ASP.NET Core的新特性

当然,为了实现一些特定的功能,我还是选用了一些开源代码和框架,现给大家大致介绍一下。

关于首页的分页实现

首页实现了博客文章的服务端分页,每次仅向服务器请求有限量的数据。分页控件是自己写的一套算法实现的,并套用了Bootstrap的pager样式,实现了响应式用户体验。分页控件使用了ASP.NET Core MVC中新的Tag Helper技术,从算法上根据每页的大小和总博客数量,对页号进行分段处理,使得整个分页功能有个很好的用户体验。

关于验证码生成

验证码的生成在经典的ASP.NET应用程序中能够非常容易地实现。经典ASP.NET应用程序基于Full .NET Framework,运行于Windows的IIS上,依赖于Windows的图形库,可以很方便地产生图片。然而,ASP.NET Core应用程序则完全不同,为了实现跨平台,就无法使用System.Drawing命名空间下的类型(当然你可以指定你的ASP.NET Core应用程序使用net45,但是这样无法跨平台)。在这里我使用了CoreCompact.System.Drawing这个库,可以通过nuget搜索到 。它会依赖于Microsoft.Win32.Primitives库,这个库定义了一些与Drawing相关的数据结构,但是没有提供任何图形库的实现。有兴趣的读者不妨一试。

关于回复编辑器

没什么好说的,使用了著名的CKEditor作为编辑器,当然,我选择性地启用/禁用了某些功能。

关于博客文章中的代码高亮

使用了著名的Alex Gorbatchev的SyntaxHighlighter,博客园也是使用的这个库,不过我用的可能不是最新版本。

关于回复中的时间信息

在每篇博客文章后面会显示网友的回复内容。这些内容会显示回复时间与当前时间的关系信息,比如:

上图显示这则回复内容是发表于25天前的。可别小看了这个部分的实现,我是采用了一个叫做Humanizer的库。这个库很有意思,它能提供一些非常实用的API,比如给它一个英文名词,它可以返回复数形式;给它一个日期,它能返回一个更贴近人类自然语言的表述。它还有很多其它的有趣的功能,大家可以去了解一下。

关于博客发布的MetaWeblog API

博客系统支持使用Windows Live Writer发布博客,它通过Shawn Wildermuth提供的WilderMinds.MetaWeblog实现了MetaWeblog API。通过Windows Live Writer可以直接将站点添加到帐号中:

基本上前端所使用的一些技术和第三方框架就如上所述。下面来看看后台的一些技术选型。

后台

数据库与数据访问组件

正如上所述,新博客系统后台使用Azure SQL Database,也就是托管的SQL Server关系型数据库。为什么选择SQL Server而不选择MongoDB等目前流行的NoSQL方案?作为一个博客网站,我没有找到选择NoSQL的理由,Azure上也有托管的MongoDB服务,仅管它是委托由Bitnami负责运维的。另一方面,虽然我选择了Azure SQL Database,但我没有使用任何第三方的数据访问框架,没有使用ORM,包括目前流行的Dapper。没有选择ORM的理由,一方面感觉ORM在这个场景里还是太重,另一方面,截止我进行技术选型时,Entity Framework Core无法满足我的需求,至少它无法从领域模型的角度去支持多对多的映射。那为何又没有选择Dapper呢?主要原因还是一样:无法满足我的需求。原生的Dapper类库需要写一些SQL脚本,虽然轻量了,但失去了对代码重构的支持,Dapper.Contrib增加了一些更友好的API,但仍然无法满足自己的需求。

几番思考,我决定自己写一个小框架,既可以支持自己定义的简单领域模型,又可以支持基于Lambda的语法、支持数据库事务、支持异步API、支持多种类型的关系型数据库。这个小框架的代码位于DaxnetBlog.Common.Storage命名空间下,使用了一些非常巧妙的技巧,比如,开发者可以使用Lambda表达式来定义查询条件,框架会通过ExpressionVisitor(访问者模式)将Lambda表达式转换成SQL语句。下面的代码正是这个框架的使用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var  rowsAffected = await this .storage.ExecuteAsync(async (connection, transaction, cancellationToken) =>
{
     var  account = (await this .accountStore.SelectAsync(connection,
         acct => acct.UserName == userName,
         transaction: transaction,
         cancellationToken: cancellationToken)).FirstOrDefault();
     if  (account == null )
     {
         throw  new  ServiceException(HttpStatusCode.NotFound, Reason.EntityNotFound, $ "未能找到帐号名称为{userName}的用户帐号。" );
     }
     account.DateLastLogin = DateTime.UtcNow;
     return  await this .accountStore.UpdateAsync(account,
         connection,
         acct => acct.UserName == userName,
         new  Expression<Func<Account, object >>[] { acct => acct.DateLastLogin },
         transaction, cancellationToken);
});

这段代码用于更新指定帐号名称的用户的登录时间,代码中没有穿插SQL语句,而是使用Lambda表达式进行表述。代码中storage对象指代关系型数据库的实体,而accountStore则表示对某种实体(在此处是帐号实体)的存储,有点像领域驱动设计中的Repository的概念。这样的设计是为了实现职责分离:accountStore不会依赖于storage(也就是关系型数据库类型)的实现。

日志

无论是前端还是后端,我都使用了Serilog作为日志框架,并将日志推送到Seq系统。具体做法我会在另外的博客文章中详细介绍,在此就不多介绍了。下图就是本博客的日志输出,为了省钱,在Docker容器启动时,通过环境变量将日志级别设置为Warning。

API文档

不多说,Swagger。具体实现方式我也会在另外的文章中介绍。

缓存

暂时未使用缓存,下一步会增加。

好了,整个博客的架构以及前后端技术大概就介绍这么多,如果要深入技术实践的每一个细节,我想,估计几个系列文章都讲不完。还是如本文最开始的时候所述,博客代码开源,大家可以学习交流。今后我仍然会争取多写一些文章来介绍相关技术。

我还会继续在博客园发表博客吗?

当然会!博客园一直是我与大家交流的主要场所,将来也是。可以理解,为了向大家提供更多高质量的“干货”,博客园对博主们所发文章都会有一些限制,博客主题行文也会有一些约束。作为我本人来说,在博客这种形式下,我或许应该可以以更多的方式来表现我的技术生涯,甚至是自己的一些对生活中事物的思考,这或许对他人的技术发展也会是一种启发,在获得大家的反馈和回复以后,我也能继续提高自己。与这些相关的内容,我会发表在自己的博客中,当然,我想,我自己的博客仍然会以技术类文章为主吧。

目前这个新博客显示了我曾经在博客园发表的博客(当然只是为了充数,使得主页不显得那么单调,所有图片中还是保留博客园的链接)。我打算给这个新博客定下三个月的试运营阶段,这个过程准备考察一下系统的运行状况,并总结一下微软Azure云的使用心得,当然最重要的是衡量一下自己能否支付得起运营的这笔开销。整个试运营阶段我还会继续往系统加入更多功能。

如果运营失败,也请大家多多包涵,权当是我为社区多贡献了一个开源项目吧。

总结

本文首先阐述了我对社区贡献的一些实际情况,并由此引出我自己全手工打造的基于ASP.NET Core实现的博客系统;接下来介绍了这个系统的整体架构和部署,以及前后端的一些技术选型;最后对大家可能提出的问题进行了简要解答。马上又要进入新的一年了,也快到了自己MVP Renew的时间,无论Renew是否成功(去年贡献量感觉不是太高),我仍将继续坚持为社区多做贡献,真正做到“专业、求是、解惑”。

原文地址: http://www.cnblogs.com/daxnet/p/6139317.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327566.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

聊聊并发-Java中的Copy-On-Write容器

转载自 聊聊并发-Java中的Copy-On-Write容器 Copy-On-Write简称COW&#xff0c;是一种用于程序设计中的优化策略。其基本思路是&#xff0c;从一开始大家都在共享同一个内容&#xff0c;当某个人想要修改这个内容的时候&#xff0c;才会真正把内容Copy出去形成一个新的内容然后…

集成SpringSecurity---SpringBoot

集成SpringSecurity 安全简介 在 Web 开发中&#xff0c;安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求&#xff0c;但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题&#xff0c;就可能陷入一个两难的境地&#xff1a;一方面&am…

大新闻!Magic Leap造假,HoloLens即将入华商用

昨天微软搞了大新闻&#xff0c;Terry和Alexi到了深圳&#xff0c;在WinHEC大会上宣布了2017上半年HoloLens正式入华商用。 而唯一竞争对手Magic Leap今天也被曝光其设备造假&#xff0c;各大科技媒体纷纷报道&#xff0c;部分相关报道如下&#xff1a; 【新浪科技】Magic Lea…

集成Swagger(API)---SpringBoot

集成Swagger(API) 学习目标: 了解Swagger的概念及作用掌握在项目中集成Swagger自动生成API文档Swagger简介 前后端分离 前端 -> 前端控制层、视图层后端 -> 后端控制层、服务层、数据访问层前后端通过API进行交互前后端相对独立且松耦合产生的问题 前后端集成,前端或者…

win10硬盘修复工具使用教程

DiskGenius 下载地址https://www.diskgenius.cn/download.php https://baijiahao.baidu.com/s?id1651410139210648102&wfrspider&forpc win10硬盘修复工具使用教程 小白一键重装系统 发布时间&#xff1a;19-11-2810:17惠州市早点科技官方帐号&#xff0c;优质创作者…

.NET Core Tools转向使用MSBuild项目格式

微软之前为了让.NET Core和ASP.NET Core能够支持Windows Visual Studio之外的开发平台&#xff0c;创建了基于project.json格式的项目系统。不过可惜&#xff0c;这种格式与之前的.csproj/MSBuild无法兼容。来自微软的Rich Lander近期宣布&#xff0c;.NET Core Tools将在最新的…

android:background大小,小Demo小知识-android:foreground与android:background

-----------------------------------------------前言君--------------------------------------------------正好碰到了这个foreground属性平时没怎么用到过。这次用到&#xff0c;就特意的去看了下。在这里记录一下。------------------------------------------------正文君…

微软推出《我的世界》“编程一小时”免费教程,携手Code.org普及计算机科学教育

为了继续支持全球计算机科学教育周期间的“编程一小时”年度全球活动&#xff08;12月5日至11日&#xff09;&#xff0c;微软和Code.org共同发布了针对学生和教育工作者的《我的世界》造物主版编程教程。网页版全新教程现已免费上线&#xff0c;初学者可以通过教程中简单的游戏…

android 处理http状态码,OkHttp(Retrofit)对于http状态码202的处理

http code 202 :The request has been accepted for processing, but the processing has not been completed.这时候&#xff0c;服务器给你的body是空的&#xff0c;如果你使用去解析为json&#xff0c;那么&#xff0c;恭喜你java.io.EOFException: End of input at line 1 …

2.Idea分支的merge

1.选择需要merge到的版本 比如说develop是主板本。temp是紧急分支版本的话。 name要选到develop这个版本进行操作。 2.进行merge 1.选择“merge changes” image.png 2.选择对应的要merge的版本 image.png 3.记得PUSH代码 0人点赞 IDEA git使用 作者&#xff1a;了凡_850…

在Linux上编译dotnet cli的源代码生成.NET Core SDK的安装包

.NET 的开源&#xff0c;有了更多的DIY乐趣。这篇博文记录一下在新安装的 Linux Ubuntu 14.04 上通过自己动手编译 dotnet cli 的源代码生成 .net core sdk 的 deb 安装包。 1&#xff09;安装一个现有版本的 .net core sdk sudo sh -c echo "deb [archamd64] https://apt…

android菱形imageview,ios – 在UICollectionView中,UIImageView应该是圆形视图而不是菱形...

我有UICollectionView和UIImageView(如图所示).我想把它作为圆形视图.但是,当我运行应用程序时,它显示为菱形(下图).在(UICollectionViewCell *)collectionView&#xff1a;(UICollectionView *)collectionView中cellForItemAtIndexPath&#xff1a;(NSIndexPath *)indexPath {…

Java多线程-BlockingQueue-ArrayBlockingQueue-LinkedBlockingQueue

转载自 Java多线程-BlockingQueue-ArrayBlockingQueue-LinkedBlockingQueue 前言&#xff1a; BlockingQueue很好的解决了多线程中&#xff0c;如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类&#xff0c;为我们快速搭建高质量的多线程程序带来极大的便利…

java实现遍历树形菜单方法——设计思路【含源代码】

开发工具&#xff1a;MyEclipse 10 后台框架&#xff1a;Hibernate Struts2 数据库&#xff1a;Oracle 11g 前台框架&#xff1a;EasyUi 浏览器&#xff1a;谷歌 在开发中我们经常会遇到左边是树形菜单&#xff0c;右边是一个显示列表&#xff0c;单击左边的树形菜单项时&…

软件工程技术的未来

“云、架构即代码、具有API和反脆弱系统的联邦架构&#xff0c;这些软件系统开发技术正迅速成为关注焦点”。这是Mary Poppendieck在GOTO Berlin 2016大会上做“软件工程技术的未来”演讲时所提出的。 当数据量大到无法被单机所管理时&#xff0c;有两个解决方案&#xff0c;即…

Springboot 传递 List「Long」 IdList

/*** 删除企业排污口详细信息** return*/ DeleteMapping("/bathDelete") public Result bathDelete(RequestBody IdListDto idListDto){Integer count iOutfallEnterpriseService.bathDelete(idListDto.getIds());return ResponseUtil.getSuccess(count); } Data p…

SpringBoot(笔记)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VBJy5yv1-1610191443991)(C:\Users\王东梁\AppData\Roaming\Typora\typora-user-images\image-20210106103928696.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yoU…

鸿蒙os更新要求,华为鸿蒙OS即将迎来升级 手机版本或仍需时间

在2019年的华为开发者大会上&#xff0c;华为消费者业务CEO余承东正式对外发布了HarmonyOS。时隔一年后&#xff0c;华为开发者大会2020即将拉开帷幕。此次大会&#xff0c;HarmonyOS无疑仍会是重头戏之一&#xff0c;这个被寄予厚望的操作系统或将迎来新的升级。正如华为所说&…

【新书推荐】《微软开源跨平台移动开发实践》带你走近微软开源开源跨平台技术

上周收到本书作者李争送的一本12月份的新书《微软开源跨平台移动开发实践——利用ASP.NET Core 1.0 、Apache Cordova、Xamarin和Azure快速构建移动应用解决方案》。这本书的名字超长。这本书也是超薄&#xff0c;只有220页&#xff0c;一个周末时间就读完了&#xff0c;但是这…

Java集合(实现类线程安全性)

转载自 Java集合&#xff08;实现类线程安全性&#xff09; 1、集合和Map 下图是Java集合的Collection集合体系的继承树&#xff1a; 下图是Java的Map体系的继承树&#xff1a; 对于Set、List、Queue和Map四种集合&#xff0c;最常用的是HashSet、TreeSet、ArrayList、ArrayQu…