SegmentFault 最新的文章 |
- 动辄“耗资过亿”的表格工具,究竟难在哪儿?丨思否专访
- GPL3.0许可证软件著作权纠纷案例解析
- XTask 一个拓展性极强的Android任务执行框架
- 谁动了我的代码!(协同仓库该有的规范)
- 网络编程懒人入门(十四):到底什么是Socket?一文即懂!
- 首屏时间,你说你优化了,那你倒是计算出给给我看啊!
- React 源码解析系列 - React 的 render 异常处理机制
- VuePress 博客如何开启本地 HTTPS 访问
- Spring Boot 发送邮件,端口号大有玄机!
- 【高并发】深入解析Callable接口
Posted: 17 Feb 2022 07:53 PM PST 棋盘上的麦粒问题,相信每个人都听说过。那小小的 64 个棋盘格,因为一个简单的逻辑设定,便能装下全世界的的麦粒。表格工具作为办公场景中最基础也最常用的业务支撑工具,同样具有着无限的可能。 近年来,互联网、大数据、云计算、人工智能、区块链等技术加速创新,日益融入经济社会发展各领域全过程。数字经济发展速度之快、辐射范围之广、影响程度之深前所未有,正在成为重组全球要素资源、重塑全球经济结构、改变全球竞争格局的关键力量。 软件和信息服务业作为数字经济的先导性、基础性和战略性产业,是未来数字世界的构建者和引领者。作为知名的软件开发技术和低代码平台提供商,葡萄城专注开发技术和工具四十余年,一直以来引领着国内控件技术和数据分析工具的发展。 本期 SegmentFault 思否 x 技术人专访,采访嘉宾为西安葡萄城技术布道师姚尧。由他来为我们分享表格工具这一看似简单、却动辄"耗资过亿"的产品,技术究竟难在哪儿?究竟解决了什么问题?在我们的日常工作、生活甚至数字经济浪潮中,会扮演什么样的角色? 采访对象介绍: 姚尧,西安葡萄城技术布道师,SegmentFault D-Day 技术讲师,微软开发者峰会讲师。姚尧毕业于西北工业大学计算机系,一直聚焦于前端电子表格在各行业信息化中的使用,积累了丰富的前端技术和行业解决方案经验,为腾讯、阿里、华为以及平安等企业提供技术咨询服务。 https://v.qq.com/x/page/y3324... 以下为采访 Q&A 的文字内容,为方便阅读略有删改。 Q1:能否用一句话来介绍 SpreadJS?提炼三个关键词,又会是哪三个?用一句话的话,我会这么说 —— "葡萄城开发的纯前端表格控件 SpreadJS 可以用不到 10 行代码,将传统 Excel 的功能和使用体验完美嵌入到在线文档系统中。"提炼三个关键词的话我会选择匠人、匠心、匠术。 首先,SpreadJS 是一款在界面和功能上都与 Excel 高度类似的开发控件,提供了表格文档协同编辑、 数据填报和类 Excel 报表设计的功能支持,可帮助软件厂商和系统集成商有效应对数据处理、数据共享和数据有效性甄别等业务需求。除了强大的功能外,正如我的一句话介绍,SpreadJS 可帮助开发者在不依赖任何 Excel 组件的情况下,实现"用不到 10 行代码,将 Excel 的功能和使用体验完美嵌入到在线文档系统中。" 之所以能做到这些,在于葡萄城从 1993 年推出支持微软 Visual Basic 的控件产品,再到与微软展开持续深入的合作,应时而变推出前端开发控件和快速开发平台,目标便是打造出完美兼容 Excel 的功能和使用体验并高度匹配在线办公场景需求的 SpreadJS 控件。 通过 30 年的精耕细作以及对行业的理解沉淀,这背后是一群匠人,凭借匠心打磨出的一款工匠级别的产品与技术。 Q2:SpreadJS 现阶段主要的应用场景有什么?主要用来解决哪些问题?很多企业的 IT 业务都是从一张表格开始的。不仅仅是数据存储,团队中的信息共享也依赖于表格这一表现形式。文档、报告、凭证以及基础数据的汇总计算,大部分是在表格的形式中完成分析与决策的。即便表格的应用场景已经十分广泛,但随着业务的发展,用户对于表格产品的性能和系统兼容度还是能提出更高的需求,这也迫使表格产品不断的优化迭代。 现阶段,SpreadJS 主要聚焦于未来的智慧办公,应用于表格文档线上协同、数据填报、以及类 Excel 报表设计这三大应用场景。借助 SpreadJS 提供的 API 与二次扩展能力,可以让数据处理不再受硬件、操作系统与使用环境的限制,帮助企业实现更为高效的数据处理应用。 比如 SpreadJS 中全新设计的 TableSheet 能力,除了排序、筛选、样式、行和列置顶以及单元格编辑等 WorkSheet 基础功能之外,还提供了关系数据管理、结构化公式和数据分组等实用功能。 Q3:表格工具在用户端看起来似乎很简单、很容易实现,但业内也有人表示这是一个"耗资数亿"才能做出的产品。对于一个表格工具而言,技术层面最大的技术难点是什么?西安葡萄城在表格领域已经深挖数十年,您认为最重要且最坚实的技术积淀是什么?B/S 作为 Web 兴起之后的一种应用模式,统一了客户端,将系统功能实现的核心部分集中到服务器上。 但随之而来的问题是多浏览器差异、浏览器沙箱机制、内存访问受限、客户端性能低下等。作为数据载体的表格,最直接的影响就是经常会被"吐槽"卡顿,UI 界面"假死",界面操作不流畅等。 引起这些问题的症结在于浏览器渲染引擎的基础原理:当界面元素越多,浏览器的渲染时间会显著增长,内存消耗会越大。这对于强计算逻辑的表格工具来说,无疑是棘手的难题。 由此可见,开发一款前端表格控件需要攻克这四个技术难点:性能、内存消耗、可靠性和操作体验。 当然,开发一款前端表格控件最难的不只是技术,还有对表格产品的熟悉程度。因为纯技术的问题,开发者靠时间与精力的投入总能弥补。然而,一款真正优秀的产品最重要的一点,则是对于应用场景,以及用户使用体验的细节把控,这也是 SpreadJS 最坚实的技术沉淀之一。 Q4:在过去几年里,SpreadJS 备受华为、明源云、远光软件、腾讯、网易等知名企业青睐,丰富的应用场景和解决方案已经覆盖了十几个行业众多头部客户的项目。对于客户而言,表格工具有哪些是最基础最核心的硬需求,又有哪些功能是"额外的惊喜"?如前面所说,团队沟通中的信息共享大量依赖于表格这一展现形式,而伴随着企业数字化转型的迫切需要,远程办公模式已正式开启,纯在线的表格产品俨然成为了很多企业必备的工具之一。 以某知名保险公司为例,各级机构需要定期向上汇报业务数据,机构人员根据业务将从系统获得的基础数据进行分析汇总,以表格和图表的方式呈现给上级单位。传统的 Excel 线下方式费时费力容易出错;人员流动性较大的基层机构又无法使用繁琐的 BI 系统。而使用 SpreadJS 开发的自助报表系统,可以让基层业务人员按照传统的 Excel 方式在线设计维护数据报表的模板,通过表格数据绑定实现报表的定时发送。自助式报表系统降低人力成本同时也降低了使用人员的门槛,对于报表的查看人员可以直接看到嵌入在邮件中报表结果,或者通过链接进入在线报表查看实时数据。 但随着表格工具的发展,企业和用户对文档协同工具的需求也从「好用」变成了「适用」,如何满足不同场景下的用户需求,是市场对 Saas 企业和系统供应商们提出的挑战。 提到额外的惊喜,除了对于表格依赖较多的金融保险等行业,SpreadJS 还悄悄改变了很多行业的信息化实现方式。在计量检定行业中,吉林省科图科技有限公司是信息化的先行者,公司提供的计量检测云服务 SaaS,其核心证书模块便是和 SpreadJS 产品线一起打磨而成。从最初的证书在线制作,到证书的在线打印,以及后续的证书批量制作。在计量行业,在线证书设计、预览,证书内容自动填充,特殊符号矢量支持,批量 PDF 证书生成等功能,SpreadJS 已经全面覆盖。 Q5:很多企业的 IT 业务都是从一张表格开始的。不仅仅是数据存储,团队中的信息共享也需依赖于表格这一数据结构。在未来的智慧办公场景及数字经济中,一份表格还可以承载哪些需求?可以担任什么样的角色?了解表格工具的朋友会知道,表格工具的迭代历程,其实正是一部用户需求的演化史。而表格工具保持旺盛生命力的原因,正是因为人们对数据处理的需求始终旺盛。随着互联网的发展,在智慧办公场景中人们对于数据表格工具有着更多的期待,比如基于云服务的在线功能以及企业级的协同需求,就是现阶段的一个重点。尤其是 2022 年数字中国概念的提出,有力推进了线上办公进程,使得表格技术的在线协同能力上升到了新的高度。 未来十年的表格工具,一定会要具备"云端、智能化、数据共享"这几个特点,链接多元的业务数据场景,提升企业的生产力。 对于表格工具担任的角色,我想用"脚踏实地,仰望星空"来形容。表格工具作为最有力的底层支持工具,一定是脚踏实地的发展技术、深入行业,成为业务创新、技术探索最有力的支撑。但同样凭借链接数据的能力,也有可能成为带动办公场景变革、企业生产模式变革的一把钥匙。 今日,葡萄城将践行"赋能开发者"使命,携最新前沿电子表格技术举办"葡萄城表格技术研讨会暨表格产品发布会",旨在分享先进表格技术功能特性,发掘表格技术最佳实践,推动未来办公领域数字化发展。 思否小姐姐邀您一起,走近表格工具里的无穷宇宙。 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 17 Feb 2022 07:32 PM PST 作者:陈元熹律师,海华永泰律师事务所 合伙人 工信部 《"十四五"软件和信息技术服务业发展规划的通知》提出了"繁荣国内开源生态"的目标,我国将大力发展国内开源基金会等开源组织,完善开源软件治理规则,普及开源软件文化。开源软件的治理与生态建设,离不开法律对开源软件作者著作权的保护。 本文将通过分析最近中国法院有关GPL3.0许可证的实践判例,初步探讨中国法下开源许可证的性质与法律效力,开源软件侵权纠纷赔偿等问题,以期能为开源软件开发者与使用者保护自身权利、避免纠纷提供参考。 一、 开源软件作者判定与主体适格问题1. 开源软件受著作权法律保护计算机软件是《著作权法》明确规定的作品,作品不论是否发表,作者都应享有著作权。开源软件作为一种已经发布在网络平台上,可供用户访问使用的软件,作者享有包括发表、署名、修改、复制、网络信息传播等著作权。所以,开源软件的作者作为著作权人,可以根据法律保护自己的合法权益,并在受到侵权时要求侵权人承担相应的责任。 2. 署名者即为软件作者根据我国现行的《著作权法》,如无相反证据证明,在作品上署名的即为作者。《计算机软件保护条例》也规定,计算机软件的著作权属于软件开发者,如无响应证明,在软件上署名的自然人、法人或者其他组织为开发者。 除了署名以外,作者可以通过其他方式证明自己的权利。根据《最高人民法院关于审理著作权民事纠纷案件适用法律若干问题的解释》第七条规定:"当事人提供的涉及著作权的底稿、原件、合法出版物、著作权登记证书、认证机构出具的证明、取得权利的合同等,可以作为证据。"其中,作者可以在中国版权保护中心(国家版权登记门户网)提交著作权登记申请,取得登记证书,明确作品的名称、完成日期、发表日期、地点、作者身份等权利归属,并在主张权利时作为拥有权利的初步证明。 在(2018)京民终471号判决书,即数字天堂(北京)网络技术有限公司诉柚子(北京)科技有限公司等侵犯计算机软件著作权纠纷一案(以下简称"数字天堂案")中,数字天堂公司对其开发的HBuilder软件中的三个插件,即代码输入法功能插件、真机运行功能插件、边改边看功能插件分别进行了著作权登记并取得了登记证书,法院认可数字天堂公司有权主张著作权。 3. Github项目人作为诉讼主体是否适格Github的规则要求项目人允许任意用户查看与复制仓库内容。根据GitHub网站的规则,开源软件项目人将软件源代码上传到网站后,如果将页面和仓库设为公开显示,则表示项目人向每个用户授予非独占、全球许可,允许他们通过 GitHub 服务使用、显示和执行内容。除了Github默认的规则要求外,项目人还可以采用许可证的形式,进一步授予用户更多的权利。 Github规则与开源软件的特性允许这些用户在修复开源软件的bug或增加功能,将修改后的源代码反馈给项目人,向项目人发出pull request,如果项目人同意,修改后的源代码就正式merge到该开源软件的源代码中,成为一个新的版本。这些用户在Github网站上被标记为贡献者。 在(2019)粤73知民初207号判决书,即济宁市罗盒网络科技有限公司诉广州市玩友网络科技有限公司等侵害计算机软件著作权纠纷(以下简称"玩友案")中,法院认为罗盒公司起诉不需要另外32位贡献者的同意。法院认为现有证据无法认定VirtualApp属于合作作品,即使属于合作作品,由于贡献者分布于世界各地,如果要求必须经过所有贡献者的授权才能提起诉讼,那么将导致开源软件维权无从提起。法院认定玩友公司侵犯的是该软件开源版的著作权,同时认为如果其他贡献者可以向罗盒公司主张分割赔偿款。 对此,本文作者认为存在一定争议:罗盒公司主张的是其取得登记证书的VirtualApp开源版的著作权被侵犯,其中已经包含了32位贡献者修改的代码本案的争议作品的发布与修改都是通过Github网站进行的,同样也可以通过该网站或者其他方式取得贡献者的同意,并没有证据证明贡献者与项目人签署协议授权项目人代表其诉讼,也没有任何证据表明罗盒公司对此进行了努力。法院认为贡献者并非著作权人,同时允许贡献者主张分割赔偿款,本文作者认为存在一定矛盾。通过认定超过百分之90的代码由项目人提供,否定贡献者对开源软件独创性的贡献,将项目人认为单一著作权人,将大大打击贡献者的积极性,不利于开源社区生态的发展。实践中,我们发现大型开源项目常常会签署贡献者授权协议来解决授权问题。 二、 GPL开源许可证的法律性质GNU通用公共许可协议,英文GNU General Pubic License, 缩写为GNU GPL或GPL,是开源软件领域最受欢迎的软件许可证之一。 GPL3.0许可证作为一种Copyleft许可证,具有"高传染性"。GPL3.0许可证允许开源软件用户可以对开源软件进行自由的复制、分发、修改及再发布,但要求用户应同样使用GPL3.0许可证公布相应的源代码。所以,作者通过使用GPL3.0许可证,不仅将部分著作权授予用户,也为用户设定了以相同条件开源的义务。 我国判例倾向于将GPL3.0许可证认定为附条件的合同。玩友案中,广州知识产权法院采用"要约说",认为GPL3.0许可证属于软件权利人与用户之间订立的合同,是一种非典型、通过行为订立的书面格式合同,其中开源软件发布视为要约,用户使用视为承诺,在使用时合同即成立。首先,根据《民法典》及早前实行的相关法律规定,可以认为开源软件作者使用GPL3.0许可证的行为属于一种附条件的民事法律行为;其次,在(2019)粤03民初3928号判决书,即济宁市罗盒网络科技有限公司诉福建风灵创景科技有限公司等侵害计算机软件著作权纠纷一案(以下简称"风灵案")中,深圳市中级人民法院认为,GPL3.0许可证的内容与形式都具备合同的特征。 三、 违反许可证的后果与侵权赔偿1. 违反GPL3.0许可证的后果"风灵案"中法院认为"GPL3.0协议规定的使用条件(如开放源代码、标注著作权信息和修改信息等)系授权人许可用户自由使用的前提条件,亦即协议所附的解除条件。一旦用户违反了使用的前提条件,将导致GPL3.0协议在授权人与用户之间自动解除,用户基于协议获得的许可即时终止。" 本文作者认为,根据GPL3.0许可证的文本,违反该许可证的任何传播或修改作品的企图都是无效的,并将自动中止用户通过该许可证获得的权利;但是许可证同时规定了当用户不再违反许可证时,该授权可以恢复。所以,虽然我们可以将GPL3.0许可证作为中国法下附条件的合同,但是当用户违反许可证的前提条件时,许可证这一合同自动中止,而非直接终止而导致合同权利义务消灭。 不论是法院认为的许可终止,或者是可以恢复的中止,违反GPL3.0许可证后,用户对开源软件的复制、使用、发布、修改等行为就失去了权利来源,构成著作权侵权。 2. 侵权损害赔偿根据2010年版《著作权法》,著作权侵权损害赔偿金额按照:1. 实际损失;2. 违法所得确定,包括合理开支或由法院在五十万元以内酌定赔偿。 我国于2020年对《著作权法》进行了修订,其中对著作权侵权损害赔偿进行了较大修改:1. 法定赔偿中增加了可以参照权利使用费给与赔偿;2. 引入了惩罚性赔偿的机制,情节严重的可以参照法定赔偿金额一到五倍赔偿;适用惩罚性赔偿须符合:侵权人故意侵犯著作权、情节严重,实际损失、违法所得与权利使用费难以计算;3. 酌定赔偿的金额从五十万元提升为五百元以上五百万元以下。 由于案件事实均发生在2020年以前,在"数字天堂案","玩友案"与"风灵案"中,法院均判决被告需赔付原告总金额五十万元。这几个案件几乎都是按照当时实行法律的最高限额判决。可以预见,未来开源软件著作权侵权损害赔偿将很有可能按照五百万元进行判决,甚至可能让被告付出远超违法所得的惩罚性赔偿。 四、结语:开源软件的发布、共享、修改与再发布,甚至商用等行为,都是离不开开源协议这一基石。GPL3.0许可证作为数以百计的开源协议的一种,已经得到中国法院的承认与保护。在中国法院的判例中,我们看到了知名开源软件项目方在使用开源协议中的混乱行为,而开源软件用户侵犯著作权,违反开源协议的行为也得到了判罚。作为开源软件的参与者,开发者与开源软件用户都应该重视开源协议的选择、使用等问题。 开源软件的企业用户更应主动建立合规体系并做好开源合规,了解各类开源协议的权利义务,做好事前、事中与事后审查,对企业内部使用开源软件进行审查,避免出现无意中侵犯开源软件著作权的行为;如果发现侵权,应及时进行弥补,采取与作者和解、购买商业版本许可等补救措施,以免加重自身责任,付出高额代价。 参考资料: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 17 Feb 2022 10:42 AM PST 背景很早之前接触了RxJava的任务流操作,觉得这种将复杂业务流通过一个个操作符拆解开来,形成一条条条理清晰的function, 让人写起来直呼过瘾.其实这就是责任链模式的一种应用. 但是RxJava的功能实在是太强大了, 如果仅仅是使用它来处理这些业务流我觉得还是有些大材小用了. 之前也做过一段时间的应用性能优化, 其中当然就包括应用冷启动优化, 中间有涉及过启动器的概念, 当时也查阅了一些现有的开源框架, 也使用过其中一些, 但是总觉得并不是很好用, 用起来不是很顺手. 作为一名资深Android开源框架卷王, 当时我脑海里就萌发一种想法, 为啥我不自己写一个任务流执行的框架呢?想到这, 我就抽出了我的一部分业余时间(女朋友都不陪了), 撸出了这个XTask框架, 自我感觉非常nice, 在这分享给大家. 简介XTask是一个拓展性极强的Android任务执行框架。 可自由定义和组合任务来实现你想要的功能,尤其适用于处理复杂的业务流程,可灵活添加前置任务或者调整执行顺序。例如:应用的启动初始化流程。 项目地址特征
设计思想框架主体使用责任链的设计模式,辅以建造者模式、工厂模式、适配器模式、组合模式、外观模式以及代理模式来实现。 组成结构
日志一览集成指南添加Gradle依赖1.先在项目根目录的
2.然后在dependencies添加:
使用方法XTask作为对外统一的API入口,所有常用的方法都能从中找到。 打开调试模式当需要定位问题,需要进行调试时,可打开调试模式,这样便可开启框架的日志。
XTask的API介绍
如何执行一条任务链下面是一整个完整的例子:
1.创建一条任务链.(必须)
2.设置任务链的初始化参数.(可选)
3.创建多个任务,并向任务链中添加.(必须)
【注意】对于任务执行完成,需要注意以下两点:
4.设置任务链执行回调.(可选) 调用setTaskChainCallback设置任务链执行回调。
5.任务链执行.(必须) 调用start执行任务链。
任务创建创建任务有两种方式:
通过XTask创建通过XTask.getTask, 传入对应的属性进行构建
通过继承创建通过继承
任务执行原则每一个任务都是依托于任务链进行流程控制。任何任务都需要遵循以下原则:
TaskCommand手动通知执行结果在通过XTask.getTask传入TaskCommand构建Task的时候,设置
SimpleTaskStep手动通知执行结果重写
参数传递
线程控制设置任务的threadType类型,即可完成对任务运行线程的控制。目前支持6种线程处理方式。
任务组目前共有串行任务组(SerialGroupTaskStep)和并行任务组(ConcurrentGroupTaskStep) 串行任务组串行任务组是按顺序依次执行,和任务链的处理方式类似。使用XTask.getSerialGroupTask获取。
并行任务组并行任务组是组内所有任务同时执行,待所有任务都完成后才视为任务组完成。使用XTask.getConcurrentGroupTask获取。
最后如果你觉得这个项目对你有所帮助, 你可以点击star进行收藏或者将其分享出去, 让更多的人知道这个项目! 我是xuexiangjys,一枚热爱学习,爱好编程,致力于Android架构研究以及开源项目经验分享的技术up主。获取更多资讯,欢迎微信搜索公众号:【我的Android开源之旅】 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 14 Feb 2022 03:51 AM PST 前言公司一个老项目,没有做代码提交前的校验,我拿到后,想着老项目嘛也没时间帮它弄这些,反正就是改一点点小东西;尽量跟着它的代码风格写,写完提交就行; 直到某一天,又有一个人加入了进来。好家伙,直接干出事了。 当你某一天 fetch 代码时,发现很多文件是这样的 👆 你是不是很崩溃? 下面我将带大家一起,先通过 eslint+prettier这里我为该项目适配了一套 eslit 规则,按照这套规则提交的代码不会有冲突。 🦄 在这篇文章中,主要讲解如何在团队协同工作时,在 git 提交代码更改前,对不规范的代码和提交信息进行校验,修复,并限制不规范的提交。 husky首先要介绍的是 具体方法首先我们将
安装完后,我们需要在当前项目中创建一个 使用以下命令快速创建 👇
为了让其他人在此项目中安装依赖后也能自动创建 使用以下命令快速添加 👇
使用以下命令快速创建 👇
完成后可以看到 这里我用的是 当然,这里的报错问题只是由于缩进不规范引起的,类似这种的问题还有引号,句尾分号,换行符等等...都可以通过 说到换行符,这里我们需要了解的是:在 Windows 上默认的是回车换行(Carriage Return Line Feed, CRLF),然而,在 Linux/MacOS 上则是换行(Line Feed, LF)。 我们可以试一下将原先换行符为 可以看到最终 LF 换行符还是被 CRLF 转化了; 如果你们不会跨平台协作(都在 Mac/Linux,或者都在 Windows 上协同),只需要在当前项目中通过 为了保险起见,你需要新建一个
文件内容如下 👇
可以看到使用
🥰 到这里,一个最简单的代码风格限制方法就已经实现了。 既然做了,就肯定要做一套完整的,且好用的。下面我们来继续完善其他功能 ~ lint-staged什么是 每次提交一两个文件,却都要 使用方法我们将
然后在 package.json 中添加以下代码,
添加玩上述代码后,我们通过测试,将两个文件的缩进改为不符合规范的情况,然后将其中一个文件暂存后,我们运行 当所有暂存区代码都符合规范时 👇,才会通过校验执行提交。
commitizenCommitizen 是一个撰写符合上面 Commit Message 标准的一款工具。通过它可以实现交互式撰写规范的 Commit Message。 如果只在本仓库使用 👇
如果你想全局都用 commitizen 来帮你做 commit
安装完成后,一般我们都采用符合 Angular 的 Commit message 格式的提交规范(当然也可以自定义,后面会讲到~),运行以下命令生成符合 Angular 提交规范格式的 Commit message。 如果你项目用的是 npm 👇
如果你项目用的是 yarn 👇
运行了上述命令后,它将为你项目安装 cz-conventional-changelog 适配器模块,把 config.commitizen 的密钥添加到文件的根目录添加到 可以在
完成后,通过命令 限制 commitlint由于 commitizen 并不是强制使用的,仍然可以通过 首先我们需要安装
使用以下命令快速创建 git hooks 的 commit-msg 钩子 👇
然后我们创建一个 commitlint 配置文件到项目根目录 👇
以上将会在项目目录中生成
然后我们在终端进行测试
如果你执行上面这个行命令后出现了以上这种报错。 将文件更改问 UTF-8 的格式即可解决;这个问题目前已经在 👉 Issues中,有不少人遇到了(我也是 😂)。 解决以上问题后,我们再测试一下,可以看到,不符合规范的 commit-msg 是会导致报错的,也就 commit 不了了,说明我们的 commitlint 已经生效了~ 👏👏👏 到此,commit-msg 的校验也已经完成 ✔ 如果,你想自定义 commitlint 的交互文本(不用 feat,fix...,很多人都喜欢在 commit message 前面加一个 emoji 表情符号),当然也可以。 我们需要安装 运行以下命令 👇
在项目根目录,创建一个 当然,你也可以用我写好的:
创建完
关于 最后我们将之前创建过的
或者你也可以在
到这里,自定义的 commit message 的校验也 ok 了 ✅ 最后提醒:项目的代码风格和规则要和团队一起制定哦 ~ 至此,在团队协同的项目中,不符合规范的提交就被扼杀在摇篮里面了。我们大家不管是从书写代码还是提交代码最好都要规范哦~ 不给自己惹麻烦的同时,也不会给他人或公司带来麻烦。这就是本篇的全部内容啦~如果对你有帮助,记得点赞鼓励 ~
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
网络编程懒人入门(十四):到底什么是Socket?一文即懂! Posted: 15 Feb 2022 09:27 PM PST 本文由cxuan分享,原题"原来这才是 Socket",有修订。 1、引言本系列文章前面那些主要讲解的是计算机网络的理论基础,但对于即时通讯IM这方面的应用层开发者来说,跟计算机网络打道的其实是各种API接口。 本篇文章就来聊一下网络应用程序员最熟悉的Socket这个东西,抛开生涩的计算机网络理论,从应用层的角度来理解到底什么是Socket。 对于 Socket 的认识,本文将从以下几个方面着手介绍: 1)Socket 是什么; 特别说明:本文中提到的"Socket"、"网络套接字"、"套接字",如无特殊指明,指的都是同一个东西哦。 学习交流:
(本文已同步发布于:http://www.52im.net/thread-38...) 2、Socket 是什么一个数据包经由应用程序产生,进入到协议栈中进行各种报文头的包装,然后操作系统调用网卡驱动程序指挥硬件,把数据发送到对端主机。 整个过程的大体的图示如下: 我们大家知道,协议栈其实是位于操作系统中的一些协议的堆叠,这些协议包括 TCP、UDP、ARP、ICMP、IP等。 通常某个协议的设计都是为了解决特定问题的,比如: 1)TCP 的设计就负责安全可靠的传输数据; 应用程序比如浏览器、电子邮件、文件传输服务器等产生的数据,会通过传输层协议进行传输。而应用程序是不会和传输层直接建立联系的,而是有一个能够连接应用层和传输层之间的套件,这个套件就是 Socket。 在上面这幅图中,应用程序包含 Socket 和解析器,解析器的作用就是向 DNS 服务器发起查询,查询目标 IP 地址(关于DNS请见《理论联系实际,全方位深入理解DNS》)。 应用程序的下面:就是操作系统内部,操作系统内部包括协议栈,协议栈是一系列协议的堆叠。 操作系统下面:就是网卡驱动程序,网卡驱动程序负责控制网卡硬件,驱动程序驱动网卡硬件完成收发工作。 在操作系统内部有一块用于存放控制信息的存储空间,这块存储空间记录了用于控制通信的控制信息。其实这些控制信息就是 Socket 的实体,或者说存放控制信息的内存空间就是Socket的实体。 这里大家有可能不太清楚所以然,所以我用了一下 netstat 命令来给大伙看一下Socket是啥玩意。 我们在 Windows 的命令提示符中输入: netstat-ano
我的计算机会出现下面结果: 如上图所示: 1)每一行都相当于一个Socket; 所以,一个Socket就是五元组: 1)协议; PS:有的时候也被叫做四元组,四元组不包括协议。 我们来解读一下上图中的数据,比如图中的第一行: 1)它的协议就是 TCP,本地地址和远程地址都是 0.0.0.0(这表示通信还没有开始,IP 地址暂时还未确定)。 2)而本地端口已知是 135,但是远程端口还未知,此时的状态是 LISTENING(LISTENING 表示应用程序已经打开,正在等待与远程主机建立连接。关于各种状态之间的转换,大家可以阅读《通俗易懂-深入理解TCP协议(上):理论基础》)。 3)最后一个元组是 PID,即进程标识符,PID 就像我们的身份证号码,能够精确定位唯一的进程。 3、Socket 是如何创建的通过上节的讲解,现在你可能对 Socket 有了一个基本的认识,先喝口水,休息一下,让我们继续探究 Socket。 现在我有个问题,Socket 是如何创建的呢? Socket 是和应用程序一起创建的。 应用程序中有一个 socket 组件,在应用程序启动时,会调用 socket 申请创建Socket,协议栈会根据应用程序的申请创建Socket:首先分配一个Socket所需的内存空间,这一步相当于是为控制信息准备一个容器,但只有容器并没有实际作用,所以你还需要向容器中放入控制信息;如果你不申请创建Socket所需要的内存空间,你创建的控制信息也没有地方存放,所以分配内存空间,放入控制信息缺一不可。至此Socket的创建就已经完成了。 Socket创建完成后,会返回一个Socket描述符给应用程序,这个描述符相当于是区分不同Socket的号码牌。根据这个描述符,应用程序在委托协议栈收发数据时就需要提供这个描述符。 4、Socket 是如何连接的Socket创建完成后,最终还是为数据收发服务的。但是,在数据收发之前,还需要进行一步"连接"(术语就是 connect),建立连接有一整套过程。 这个"连接"并不是真实的连接(用一根水管插在两个电脑之间?不是你想的这样。。。)。 实际上这个"连接"是应用程序通过 TCP/IP 协议标准从一个主机通过网络介质传输到另一个主机的过程。 Socket刚刚创建完成后,还没有数据,也不知道通信对象。 在这种状态下:即使你让客户端应用程序委托协议栈发送数据,它也不知道发送到哪里。所以浏览器需要根据网址来查询服务器的 IP 地址(做这项工作的协议是 DNS),查询到目标主机后,再把目标主机的 IP 告诉协议栈。至此,客户端这边就准备好了。 在服务器上:与客户端一样也需要创建Socket,但是同样的它也不知道通信对象是谁,所以我们需要让客户端向服务器告知客户端的必要信息:IP 地址和端口号。 现在通信双方建立连接的必要信息已经具备,可以开始"连接"过程了。 首先:客户端应用程序需要调用 Socket 库中的 connect 方法,提供 socket 描述符和服务器 IP 地址、端口号。 以下是connect的伪码调用: connect(<描述符>、<服务器IP地址和端口号>) 这些信息会传递给协议栈中的 TCP 模块,TCP 模块会对请求报文进行封装,再传递给 IP 模块,进行 IP 报文头的封装,然后传递给物理层,进行帧头封装。 之后通过网络介质传递给服务器,服务器上会对帧头、IP 模块、TCP 模块的报文头进行解析,从而找到对应的Socket。 Socket收到请求后,会写入相应的信息,并且把状态改为正在连接。 请求过程完成后:服务器的 TCP 模块会返回响应,这个过程和客户端是一样的(如果大家不太清楚报文头的封装过程,可以阅读《快速理解TCP协议一篇就够》)。 在一个完整的请求和响应过程中,控制信息起到非常关键的作用: 1)SYN 就是同步的缩写,客户端会首先发送 SYN 数据包,请求服务端建立连接; 由于网络环境的复杂多变,经常会存在数据包丢失的情况,所以双方通信时需要相互确认对方的数据包是否已经到达,而判断的标准就是 ACK 的值。 上面的文字不够生动,动画可以更好的说明这个过程: (PS:这个"连接"的详细理论知识,可以阅读《理论经典:TCP协议的3次握手与4次挥手过程详解》、《跟着动画来学TCP三次握手和四次挥手》,这里不再赘述。) 当所有建立连接的报文都能够正常收发之后,此时套接字就已经进入可收发状态了,此时可以认为用一根管理把两个套接字连接了起来。当然,实际上并不存在这个管子。建立连接之后,协议栈的连接操作就结束了,也就是说 connect 已经执行完毕,控制流程被交回给应用程序。 另外:如果你对Socket代码更熟悉的话,可以先读读这篇《手把手教你写基于TCP的Socket长连接》。 5、Socket 是如何收发数据的当控制流程上节中的连接过程回到应用程序之后,接下来就会直接进入数据收发阶段。 数据收发操作是从应用程序调用 write 将要发送的数据交给协议栈开始的,协议栈收到数据之后执行发送操作。 协议栈不会关心应用程序传输过来的是什么数据,因为这些数据最终都会转换为二进制序列,协议栈在收到数据之后并不会马上把数据发送出去,而是会将数据放在发送缓冲区,再等待应用程序发送下一条数据。 为什么收到数据包不会直接发送出去,而是放在缓冲区中呢? 因为只要一旦收到数据就会发送,就有可能发送大量的小数据包,导致网络效率下降(所以协议栈需要将数据积攒到一定数量才能将其发送出去)。 至于协议栈会向缓冲区放多少数据,这个不同版本和种类的操作系统有不同的说法。 不过,所有的操作系统都会遵循下面这几个标准: 1)第一个判断要素:是每个网络包能够容纳的数据长度,判断的标准是 MTU,它表示的是一个网络包的最大长度。最大长度包含头部,所以如果单论数据区的话,就会用 MTU - 包头长度,由此的出来的最大数据长度被称为 MSS。 2)另一个判断标准:是时间,当应用程序产生的数据比较少,协议栈向缓冲区放置数据效率不高时,如果每次都等到 MSS 再发送的话,可能因为等待时间太长造成延迟。在这种情况下,即使数据长度没有到达 MSS,也应该把数据发送出去。 但协议栈并没有告诉我们怎样平衡这两个因素,如果数据长度优先,那么效率有可能比较低;如果时间优先,那又会降低网络的效率。 经过了一段时间。。。。。。 假设我们使用的是长度有限法则:此时缓冲区已满,协议栈要发送数据了,协议栈刚要把数据发送出去,却发现无法一次性传输这么大数据量(相对的)的数据,那怎么办呢? 在这种情况下,发送缓冲区中的数据就会超过 MSS 的长度,发送缓冲区中的数据会以 MSS 大小为一个数据包进行拆分,拆分出来的每块数据都会加上 TCP,IP,以太网头部,然后被放进单独的网络包中。 到现在,网络包已经准备好发往服务器了,但是数据发送操作还没有结束,因为服务器还未确认是否已经收到网络包。因此在客户端发送数据包之后,还需要服务器进行确认。 TCP 模块在拆分数据时,会计算出网络包偏移量,这个偏移量就是相对于数据从头开始计算的第几个字节,并将算好的字节数写在 TCP 头部,TCP 模块还会生成一个网络包的序号(SYN),这个序号是唯一的,这个序号就是用来让服务器进行确认的。 服务器会对客户端发送过来的数据包进行确认,确认无误之后,服务器会生成一个序号和确认号(ACK)并一起发送给客户端,客户端确认之后再发送确认号给服务器。 我们来看一下实际的工作过程: 首先:客户端在连接时需要计算出序号初始值,并将这个值发送给服务器。 接下来:服务器通过这个初始值计算出确认号并返回给客户端(初始值在通信过程中有可能会丢弃,因此当服务器收到初始值后需要返回确认号用于确认)。 同时:服务器也需要计算出从服务器到客户端方向的序号初始值,并将这个值发送给客户端。然后,客户端也需要根据服务器发来的初始值计算出确认号发送给服务器。 至此:连接建立完成,接下来就可以进入数据收发阶段了。 数据收发阶段中,通信双方可以同时发送请求和响应,双方也可以同时对请求进行确认。 请求 - 确认机制非常强大:通过这一机制,我们可以确认接收方有没有收到某个包,如果没有收到则重新发送,这样一来,但凡网络中出现的任何错误,我们都可以即使发现并补救。 上面的文字不够生动,动画可以更好的理解请求 - 确认机制: 网卡、集线器、路由器(见《史上最通俗的集线器、交换机、路由器功能原理入门》)都没有错误补救机制,一旦检测到错误就会直接丢弃数据包,应用程序也没有这种机制,起作用的只是 TCP/IP 模块。 由于网络环境复杂多变,所以数据包会存在丢失情况,因此发送序号和确认号也存在一定规则,TCP 会通过窗口管理确认号,我们这篇文章不再赘述,大家可以阅读《通俗易懂-深入理解TCP协议(下):RTT、滑动窗口、拥塞处理》来寻找答案。 PS:另一篇《我们在读写Socket时,究竟在读写什么?》中用动画详细说明了这个过程,有兴趣可以读一读。 6、Socket 是如何断开连接的当通信双方不再需要收发数据时,需要断开连接。不同的应用程序断开连接的时机不同。 以 Web 为例:浏览器向 Web 服务器发送请求消息,Web 服务器再返回响应消息,这时收发数据就全部结束了,服务器可能会首先发起断开响应,当然客户端也有可能会首先发起(谁先断开连接是应用程序做出的判断),与协议栈无关。 无论哪一方发起断开连接的请求,都会调用 Socket 库的 close 程序。 我们以服务器断开连接为例:服务器发起断开连接请求,协议栈会生成断开连接的 TCP 头部,其实就是设置 FIN 位,然后委托 IP 模块向客户端发送数据,与此同时,服务器的Socket会记录下断开连接的相关信息。 收到服务器发来 FIN 请求后:客户端协议栈会将Socket标记为断开连接状态,然后,客户端会向服务器返回一个确认号,这是断开连接的第一步,在这一步之后,应用程序还会调用 read 来读取数据。等到服务器数据发送完成后,协议栈会通知客户端应用程序数据已经接收完毕。 只要收到服务器返回的所有数据,客户端就会调用 close 程序来结束收发操作,这时客户端会生成一个 FIN 发送给服务器,一段时间后服务器返回 ACK 号。至此,客户端和服务器的通信就结束了。 上面的文字不够生动,动画可以更好的说明这个过程: ▲ 上图引用自《跟着动画来学TCP三次握手和四次挥手》 7、Socket的删除上述通信过程完成后,用来通信的Socket就不再会使用了,此时我们就可以删除这个Socket了。 不过,这时候Socket不会马上删除,而是等过一段时间再删除。 等待这段时间是为了防止误操作,最常见的误操作就是客户端返回的确认号丢失,至于等待多长时间,和数据包重传的方式有关,这里我们就深入展开讨论了。 关于Socket操作的全过程,如果从系统的角度来看,可能会更深入一些,建议可以深入阅读张彦飞的《深入操作系统,从内核理解网络包的接收过程(Linux篇)》一文。 8、系列文章本文是系列文章中的第14篇,本系列文章的大纲如下: [1] 网络编程懒人入门(一):快速理解网络通信协议(上篇) 9、参考资料[1] TCP/IP详解 - 第17章·TCP:传输控制协议 (本文已同步发布于:http://www.52im.net/thread-38...) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 16 Feb 2022 07:36 PM PST 前言大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心 背景当我们在做项目的性能优化的时候,优化首屏时间是一个避不过去的优化方向,但是又有多少人想过这两个东西的区别呢:
并且这两个时间的计算方式又有什么区别呢?接下来我就给大家讲一下吧! 白屏时间是什么?白屏时间指的是:页面开始显示内容的时间。也就是:浏览器显示第一个字符或者元素的时间 怎么算?我们只需要知道浏览器开始显示内容的时间点,即页面白屏结束时间点即可获取到页面的白屏时间。 因此,我们通常认为浏览器开始渲染
为什么不直接用生命周期?有些小伙伴会说:为啥不直接在App.vue的 为什么不直接用nextTick?
怎么算?我们需要利用
以上DOM结构的分数为: 1.5 + 2 + 2.5 + 2.5 = 8.5(分) 其实在首屏的加载中,会涉及到DOM的增加、修改、删除,所以会触发多次 首屏时间实践现在我们开始计算首屏时间吧! 前置准备
observerData当我们一切准备就绪后运行代码,我们获得了 计算首屏时间我们怎么根据 很多人会问了,为什么不是取最后一项的时间来当做首屏时间呢?大家要注意了:首屏并不是所有DOM都渲染,我就拿刚刚的代码来举例吧,我们渲染完了列表,然后再去增加一个li,那你是觉得哪个时间段算是首屏呢?应该是渲染完列表后算首屏完成,因为后面只增加了一个li,分数的涨幅较小,可以忽略不计 所以我们开始计算吧:
计算出首屏时间 总结我这个计算方法其实很多漏洞,没把删除元素也考虑进去,但是想让大家知道计算首屏时间的计算思想,这才是最重要的,希望大家能理解这个计算思想 结语我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜欢前端,想学习前端,那咱们可以交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
React 源码解析系列 - React 的 render 异常处理机制 Posted: 16 Feb 2022 06:32 PM PST 系列文章目录(同步更新)
本系列文章均为讨论 React v17.0.0-alpha 的源码 错误边界(Error Boundaries)在解释 React 内部实现前,我想先从一个 React API —— 错误边界(Error Boundaries) 这一 React 异常处理机制 的"冰山一角"开始介绍。 错误边界是什么在 React 16 之前,React 并没有对开发者提供 API 来处理组件渲染过程中抛出的异常:
而 React 16 带来了 错误边界 这一全新的概念,向开发者提供一种能力来更精细地处理组件维度抛出的异常。 错误边界就像一堵 防火墙 (命名上也有点像),我们可以在整个组件树中"放置"若干这样的"防火墙",那么一旦某个组件出现异常,该异常会被离它最近的错误边界给拦截住,避免影响组件树的其它分支;而我们也可以通过错误边界来渲染更"用户友好"的 UI 界面。 什么样的组件才能被称为错误边界错误边界 也是一个组件(目前只支持 类组件 ),因此我们可以插入任意数量的错误边界到组件树中的任意位置。 错误边界包含两个 API :类组件静态方法 贴一段 React 官方文档的示例:
错误边界能达到什么效果早期版本的错误边界只有 getDerivedStateFromError
由于 componentDidCatch
在早期还没有 React 的 render 异常处理机制之所以优先介绍"错误边界",一方面是因为这是直接面向开发者的 API ,更好理解;另一方面则是 React 为了实现这样的能力,让 render 异常处理机制变得更复杂了,不然直接用 异常是如何产生的上文中提到,错误边界处理的是组件渲染过程中抛出的异常,其实这本质上也是 React 的 render 异常处理机制所决定的;而其它诸如事件回调方法、 什么样的异常会被 render 异常处理机制捕获简单来说,类组件的 render 方法、函数组件这样的会在 render 阶段被同步执行的代码,一旦抛出异常就会被 render 的异常处理机制捕获(无论是否有错误边界)。举一个实际开发中很常遇到的场景:
在 React 的 render 过程中,上述两个函数组件先后会被执行,而当执行到
组件本身抛异常的具体位置以下内容需要你对 React 的 render 过程有一定的了解,请先阅读《React 源码解析系列 - React 的 render 阶段(二):beginWork》 在 beginWork 方法中,若判断当前 Fiber 节点无法 bailout (剪枝),那么就会创建/更新 Fiber 子节点:
ClassComponent 抛异常的位置从上面 beginWork 这代码段可以看到执行了 updateClassComponent 方法,并且传入了名为 循着 updateClassComponent,我们可以看到执行了
在 finishClassComponent 中,我们可以看到 在后续过程中,React 会根据这一 ReactElement 对象来创建/更新 Fiber 子节点,但这不是本文所关心的;我们关心的是,这里执行了 FunctionComponent 抛异常的位置接下来我们来定位与 FunctionComponent 抛异常的位置:有了 ClassComponent 的经验,我们一路循着 updateFunctionComponent 到 renderWithHooks ,在该方法中,我们可以看到 如何捕获 render 异常当我们被问到"如何捕获异常",本能就会回答"用
为什么不能直接使用 try/catch 呢React 原先就是直接使用
为了解决这个问题,React 需要提供一套满足以下条件的异常捕获方案:
如何不使用 try/catch 来捕获 render 异常当 JavaScript 运行时错误(包括语法错误)发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror() 。 上述这段描述出自 MDN GlobalEventHandlers.onerror 的文档,这便是除 但这样做的话,岂不是也捕获到许多与 React 组件渲染过程无关的异常?其实,我们只需要在执行 React 组件渲染前监听 error 事件,而在组件结束渲染后取消监听该事件即可:
那是不是这样就大功告成了呢?且慢!这套方案的原意是要在开发环境取代原先使用的
而使用上述的
显而易见, 如何像 try/catch 一样不影响后续代码执行前端领域总是有各种各样的骚套路,还真让 React 开发者找到这样的方法: EventTarget.dispatchEvent ;那么,为什么说 dispatchEvent 就能模拟并替代 dispatchEvent 能够同步执行代码与浏览器原生事件不同,原生事件是由 DOM 派发的,并通过 event loop 异步调用事件处理程序,而 上文出自 dispatchEvent 的 MDN 文档,由此可见: dispatchEvent 能够同步执行代码 ,这意味着在事件处理方法执行完成前,可以阻塞 dispatchEvent 抛的异常不冒泡这些 event handlers 运行在一个嵌套的调用栈中:他们会阻塞调用直到他们处理完毕,但是异常不会冒泡。 准确来说,是:通过 dispatchEvent 触发的事件回调方法,异常不会冒泡;这意味着,即便抛出异常,也只是会终止事件回调方法本身的执行,而
从上述 DEMO 可以看出,尽管 dispatchEvent 的事件处理函数抛了异常,但依然还是能够继续执行 dispatchEvent 后续的代码(即 DEMO 中的 实现一个简易版的 render 异常捕获器接下来,让我们把 GlobalEventHandlers.onerror 和 EventTarget.dispatchEvent 结合起来,就能够实现一个简易版的 render 异常捕获器:
React 源码中具体是如何捕获 render 异常的上文介绍完捕获 render 异常的原理,也实现了个简易版 DEMO ,下面就可以来具体分析 React 源码了。 捕获目标:beginWork上文提到, React 组件渲染维度的异常是在 beginWork 阶段抛出,因此我们捕获异常的目标显然就是 beginWork 了。 对 beginWork 进行包装React 针对开发环境对 beginWork 方法进行了一个封装,添上了 捕获异常 的功能:
invokeGuardedCallback接下来看
invokeGuardedCallbackImpl这个
以上是我精简后的 处理异常上文介绍了异常是怎么产生的,也介绍了异常是怎么被捕获的,下面就来简单介绍一下异常被捕获到后是怎么处理的:
React 中处理异常的源码实现上文说到在(开发环境)封装的
handleError下面来介绍 handleError :
throwException下面来介绍 throwException , throwException 主要做了以下事情:
createRootErrorUpdate 和 createClassErrorUpdate当遇到无错误边界能处理的致命异常时,会调用 createRootErrorUpdate 方法来创建一个状态更新任务,该任务会将根节点置为
当发现有错误边界可以处理当前异常时,会调用 createClassErrorUpdate 方法来创建一个状态更新任务,该更新任务的
completeUnitOfWork上面讲完了
在之前的文章中,我们已经介绍过 completeUnitOfWork 方法了,但介绍的是正常的流程,直接把异常处理的流程给忽略了,下面我们来补上这一块:
这里还有个问题:为什么要重新 render 可以处理异常的节点 呢?我们不看后续的操作其实就能猜到 React 的做法:假设这个 可以处理异常的节点 是一个错误边界,在上文介绍的 throwException 中已经根据 getDerivedStateFromError 执行后返回的 state 值来创建了一个更新任务,那么后续只需要更新错误边界的 state ,根据 state 卸载掉抛异常的组件并渲染错误提示的组件,那这不就是一个很正常的 render 流程了吗。
unwindWork这里介绍一下
在
重新 render 错误边界 Fiber 节点在 completeUnitOfWork 方法中,我们通过
上面这都是正常 render 一个 ClassComponent 的过程,首先我们需要关注到 updateClassInstance ,在这个方法中,会针对当前节点的更新任务,来更新节点的 state ;还记得在 createClassErrorUpdate 中根据类组件静态方法 getDerivedStateFromError 返回的 state 值来创建的一个更新任务吗,该更新任务还被赋予了最高优先级: 然后,我们进入到 finishClassComponent 方法的逻辑里,本方法针对异常处理其实就做了两个事情:
如何强制重新渲染子节点在介绍 finishClassComponent 时我们提到可以用 forceUnmountCurrentAndReconcile 方法,与正常的 render 逻辑类似,该方法中也会调用 reconcileChildFibers ,但却非常巧妙地调用了两次:
至于为什么要这么做呢, React 官方的解释是"从概念上来说,处理异常时与正常渲染时是不同的两套 UI ,不应该复用任何子节点(即使该节点的特征 —— key/props 等是一致的)";简单来理解的话,就是"一刀切"避免复用到异常的 Fiber 节点吧。
写在最后以上便是对 React render 异常处理机制的介绍,通过本文,补全了前面几篇介绍 render 的文章的疏漏(前文仅介绍了 render 的正常流程),让我们在开发过程中做到对异常处理"心里有数",快给你的应用加几个错误边界吧(笑)。 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 16 Feb 2022 02:26 AM PST 前言在 《一篇带你用 VuePress + Github Pages 搭建博客》中,我们使用 VuePress 搭建了一个博客,最终的效果查看:TypeScript 中文文档。 如果我们在本地运行项目,运行地址类似于 开启 HTTPS在 VuePress 官方文档里,我们并没有搜到直接的答案,但我们可以在 StackOverflow 搜到一个回答,其实可以直接在
我们试一下,访问地址,会出现不安全提示: 我们点击 「高级」里的 「继续前往localhost(不安全)」,可以访问到页面,只是地址栏会显示一个"不安全": HTTPS 原理想想确实如此,我们都没有 SSL 证书,怎么就能判断为安全连接呢? 那如何才能让浏览器判断为安全连接呢?我们先简单复习一下 HTTPS 的原理: 首先是 CA,英文全称:Certificate Authority,中文翻译为:数字证书认证机构,是负责发放和管理数字证书的权威机构,是受到信任的第三方机构。电脑系统、浏览器里会内置 CA 颁发的根证书。 然后是 HTTPS 建立的过程,当客户端向服务端发起一个 HTTPS 连接的时候,服务器会将自己的证书发给客户端,证书中包含公钥,客户端会寻找是否有这个证书签发的 CA 的根证书,如果有,再对证书进行解密验证,防止证书被篡改,如果通过,客户端会生成一个随机串,然后使用服务器证书中的公钥进行加密,然后发送给服务器,服务器利用私钥进行对这个密文进行解密,得到随机串,然后两端使用这个随机值进行加密通信。 所以对于服务器来说,需要有两个东西,一个是包含公钥的服务器证书,一个是私钥。 对于客户端来说,则需要 CA 根证书。 mkcert为了实现本地 HTTPS 连接,我们可以借助 mkcert 这个工具来实现证书的配置: mkcert 是一个用于创建本地信任的开发证书的便捷工具。在本地开发环境中使用真实的CA(Certificate Authority,证书颁发机构)签发的证书,是非常困难的,特别是对于像 example.net、localhost 或者 127.0.0.1 这样的主机来说,使用真实的CA签发的证书是不可能的。在这样的情况下,自签发的证书可能是唯一的选择。mkcert 可以生成自签发的证书,并把本地 CA 安装到系统根证书库中。 1. 安装 mkcert
2. 创建本地 CA
生成后,在 Mac 中,我们可以通过 「钥匙串访问」查看到这个证书: 3. 生成证书
这会在当前目录下生成两个证书文件: 4. 修改 config.js然后我们将这两个文件拷贝到
5. 重新运行项目然后重新运行项目,你就会看到: 如果证书显示有效,但依然显示不安全连接,浏览器重启或者开一个隐私窗口试试。 系列文章博客搭建系列是我至今写的唯一一个偏实战的系列教程,预计 20 篇左右,讲解如何使用 VuePress 搭建、优化博客,并部署到 GitHub、Gitee、私有服务器等平台。本篇为第 22 篇,全系列文章地址:https://github.com/mqyqingfeng/Blog 微信:「mqyqingfeng」,加我进冴羽唯一的读者群。 如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 15 Feb 2022 07:45 PM PST @[toc] 今天想和大家聊一下 SMTP 服务器的端口问题,这个也是一个小伙伴提的问题,SMTP 服务器有众多端口:25、465、587 各自间有什么区别?可以随意使用吗?希望今天这篇文章能给你答案。 1. 什么是 SMTPSMTP 代表简单邮件传输协议,简而言之,它是通过 Internet 发送电子邮件的过程。计算机端口是个人计算机连接到网络并完成数据传输的方式。SMTP 端口是两者的组合:设计用于通过网络向其收件人发送电子邮件的端口。 下图展示了 SMTP 协议在邮件发送过程的作用: 当然,就像有多个计算机端口一样,可以使用的 SMTP 端口也有很多。 2. SMTP Port2.1 251982 年,南加州大学向 Internet 工程任务组 (IETF) 提交了一份提案,即 Request For Comments (RFC) 821,将端口 25 建立为 Internet 电子邮件的默认传输通道。40 年过去了,如今我们依然可以使用 25 这个端口在两个邮件服务器之间传输邮件。 不过最初的设计没有考虑安全问题,在 1998 年 12 月,R. Gellens 和 J. Klensin 提交了 RFC2476,在这个规范中,RFC 提议将传统的消息提交和消息中继概念分开,RFC 定义消息提交应通过端口 587 进行(即我们通过邮件客户端等工具提交邮件的时候,应该使用 587 端口),以确保新的策略和安全要求不会干扰消息中继端口 25 上的传统中继流量。 这么一拆分,端口 25 就主要用于 SMTP 中继,也就是将邮件从一个电子邮件服务器传输到另一个电子邮件服务器。 在大多数情况下,SMTP 电子邮件客户端(Foxmail、Microsoft Outlook、Mail、Thunderbird 等)不应使用 25 端口,以遏制垃圾邮件的数量,所以这个 25 端口和我们个人使用的关系就不大。 2.2 587这是默认的邮件提交端口,当用户提交一封电子邮件到邮件服务器时,可以使用该端口,我们自己通过 Java 代码发送邮件,也可以使用该端口。 端口 587 与 TLS 加密相结合,可确保安全提交电子邮件并遵循 IETF 制定的指导方针。 2.3 465那按理说我们发送邮件的时候就该使用 587 端口呀,465 又是干嘛的? IETF 从未将端口 465 发布为官方 SMTP 传输或提交端口,然而维护大部分核心互联网基础设施的 IANA 为 SMTPS 分配了端口 465。目的是为 SMTP 建立一个端口,以便使用安全套接字层 (SSL) 进行操作,这样使得邮件发送更加安全。 所以 465 和 587 其实都是为了邮件安全,但是两者的思路不一样,465 是 SSL,587 则是 TLS,SSL 和 TLS 有啥区别呢?这个就说来话长了,简单一句话就是:TLS(传输层安全)是更为安全的升级版 SSL,TLS 是 SSL 标准化后的产物。 按理说 465 应该被撤销,大家都用 587,但是由于 465 曾经被 IANA 认定为有效,因此可能存在仅能够使用此端口连接的遗留系统,所以该端口并没有被废弃,也可以使用。 2.4 小结好啦,这就是这几个端口的区别。一般来说,我们用 Spring Boot 发送邮件的时候,465 和 587 都能用,但是不建议使用 25。另外在使用 465 或者 587 的时候,有的个别邮箱如 139 邮箱需要配置如下属性:
3. 号外可能还有小伙伴不懂 Spring Boot 邮件发送,再来回顾下。 邮件发送其实是一个非常常见的需求,用户注册,找回密码等地方,都会用到,使用 JavaSE 代码发送邮件,步骤还是挺繁琐的,Spring Boot 中对于邮件发送,提供了相关的自动化配置类,使得邮件发送变得非常容易,接下来我们就来一探究竟!看看使用 Spring Boot 发送邮件的 5 中姿势。 3.1 邮件基础我们经常会听到各种各样的邮件协议,比如 SMTP、POP3、IMAP ,那么这些协议有什么作用,有什么区别?我们先来讨论一下这个问题。 SMTP 是一个基于 TCP/IP 的应用层协议,江湖地位有点类似于 HTTP,SMTP 服务器默认监听的端口号为 25 。看到这里,小伙伴们可能会想到既然 SMTP 协议是基于 TCP/IP 的应用层协议,那么我是不是也可以通过 Socket 发送一封邮件呢?回答是肯定的。 生活中我们投递一封邮件要经过如下几个步骤:
这是一个缩减版的生活中邮件发送过程。这三个步骤可以分别对应我们的邮件发送过程,假设从 aaa@qq.com 发送邮件到 111@163.com :
邮件投递大致就是这个过程,这个过程就涉及到了多个协议,我们来分别看一下。 SMTP 协议全称为 Simple Mail Transfer Protocol,译作简单邮件传输协议,它定义了邮件客户端软件与 SMTP 服务器之间,以及 SMTP 服务器与 SMTP 服务器之间的通信规则。 也就是说 aaa@qq.com 用户先将邮件投递到腾讯的 SMTP 服务器这个过程就使用了 SMTP 协议,然后腾讯的 SMTP 服务器将邮件投递到网易的 SMTP 服务器这个过程也依然使用了 SMTP 协议,SMTP 服务器就是用来收邮件。 而 POP3 协议全称为 Post Office Protocol ,译作邮局协议,它定义了邮件客户端与 POP3 服务器之间的通信规则,那么该协议在什么场景下会用到呢?当邮件到达网易的 SMTP 服务器之后, 111@163.com 用户需要登录服务器查看邮件,这个时候就该协议就用上了:邮件服务商都会为每一个用户提供专门的邮件存储空间,SMTP 服务器收到邮件之后,就将邮件保存到相应用户的邮件存储空间中,如果用户要读取邮件,就需要通过邮件服务商的 POP3 邮件服务器来完成。 最后,可能也有小伙伴们听说过 IMAP 协议,这个协议是对 POP3 协议的扩展,功能更强,作用类似,这里不再赘述。 3.2 准备工作目前国内大部分的邮件服务商都不允许直接使用用户名/密码的方式来在代码中发送邮件,都是要先申请授权码,这里以 QQ 邮箱为例,向大家演示授权码的申请流程:首先我们需要先登录 QQ 邮箱网页版,点击上方的设置按钮: 然后点击账户选项卡: 在账户选项卡中找到开启POP3/SMTP选项,如下: 点击开启,开启相关功能,开启过程需要手机号码验证,按照步骤操作即可,不赘述。开启成功之后,即可获取一个授权码,将该号码保存好,一会使用。 3.3 项目创建接下来,我们就可以创建项目了,Spring Boot 中,对于邮件发送提供了自动配置类,开发者只需要加入相关依赖,然后配置一下邮箱的基本信息,就可以发送邮件了。
创建完成后,项目依赖如下:
项目创建成功后,接下来在 application.properties 中配置邮箱的基本信息:
配置含义分别如下:
如果不知道 smtp 服务器的端口或者地址的的话,可以参考 腾讯的邮箱文档 做完这些之后,Spring Boot 就会自动帮我们配置好邮件发送类,相关的配置在
从这段代码中,可以看到,导入了另外一个配置
可以看到,这里创建了一个 做完如上两步,邮件发送的准备工作就算是完成了,接下来就可以直接发送邮件了。 具体的发送,有 5 种不同的方式,我们一个一个来看。 3.3.1 发送简单邮件简单邮件就是指邮件内容是一个普通的文本文档:
从上往下,代码含义分别如下:
最后执行该方法,就可以实现邮件的发送,发送效果图如下: 3.3.2 发送带附件的邮件邮件的附件可以是图片,也可以是普通文件,都是支持的。
注意这里在构建邮件对象上和前文有所差异,这里是通过 javaMailSender 来获取一个复杂邮件对象,然后再利用 MimeMessageHelper 对邮件进行配置,MimeMessageHelper 是一个邮件配置的辅助工具类,创建时候的 true 表示构建一个 multipart message 类型的邮件,有了 MimeMessageHelper 之后,我们针对邮件的配置都是由 MimeMessageHelper 来代劳。 最后通过 addAttachment 方法来添加一个附件。 执行该方法,邮件发送效果图如下: 3.3.3 发送带图片资源的邮件图片资源和附件有什么区别呢?图片资源是放在邮件正文中的,即一打开邮件,就能看到图片。但是一般来说,不建议使用这种方式,一些公司会对邮件内容的大小有限制(因为这种方式是将图片一起发送的)。
这里的邮件 text 是一个 HTML 文本,里边涉及到的图片资源先用一个占位符占着,setText 方法的第二个参数 true 表示第一个参数是一个 HTML 文本。 setText 之后,再通过 addInline 方法来添加图片资源。 最后执行该方法,发送邮件,效果如下: 在公司实际开发中,第一种和第三种都不是使用最多的邮件发送方案。因为正常来说,邮件的内容都是比较的丰富的,所以大部分邮件都是通过 HTML 来呈现的,如果直接拼接 HTML 字符串,这样以后不好维护,为了解决这个问题,一般邮件发送,都会有相应的邮件模板。最具代表性的两个模板就是 3.3.4 使用 Freemarker 作邮件模板首先需要引入 Freemarker 依赖:
然后在
接下来,将邮件模板渲染成 HTML ,然后发送即可。
需要注意的是,虽然引入了 调用该方法,发送邮件,效果图如下: 3.3.5 使用 Thymeleaf 作邮件模板推荐在 Spring Boot 中使用 Thymeleaf 来构建邮件模板。因为 Thymeleaf 的自动化配置提供了一个 TemplateEngine,通过 TemplateEngine 可以方便的将 Thymeleaf 模板渲染为 HTML ,同时,Thymeleaf 的自动化配置在这里是继续有效的 。 首先,引入 Thymeleaf 依赖:
然后,创建
接下来发送邮件:
调用该方法,发送邮件,效果图如下: 好了,这就是我们今天说的 5 种邮件发送姿势,不知道你掌握了没有呢? 本文案例已经上传到 GitHub:https://github.com/lenve/javaboy-code-samples。 有问题欢迎留言讨论。 参考资料: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 15 Feb 2022 05:43 PM PST 大家好,我是冰河~~ 本文纯干货,从源码角度深入解析Callable接口,希望大家踏下心来,打开你的IDE,跟着文章看源码,相信你一定收获不小。 1.Callable接口介绍Callable接口是JDK1.5新增的泛型接口,在JDK1.8中,被声明为函数式接口,如下所示。
在JDK 1.8中只声明有一个方法的接口为函数式接口,函数式接口可以使用@FunctionalInterface注解修饰,也可以不使用@FunctionalInterface注解修饰。只要一个接口中只包含有一个方法,那么,这个接口就是函数式接口。 在JDK中,实现Callable接口的子类如下图所示。 默认的子类层级关系图看不清,这里,可以通过IDEA右键Callable接口,选择"Layout"来指定Callable接口的实现类图的不同结构,如下所示。 这里,可以选择"Organic Layout"选项,选择后的Callable接口的子类的结构如下图所示。 在实现Callable接口的子类中,有几个比较重要的类,如下图所示。 分别是:Executors类中的静态内部类:PrivilegedCallable、PrivilegedCallableUsingCurrentClassLoader、RunnableAdapter和Task类下的TaskCallable。 2.实现Callable接口的重要类分析接下来,分析的类主要有:PrivilegedCallable、PrivilegedCallableUsingCurrentClassLoader、RunnableAdapter和Task类下的TaskCallable。虽然这些类在实际工作中很少被直接用到,但是作为一名合格的开发工程师,设置是秃顶的资深专家来说,了解并掌握这些类的实现有助你进一步理解Callable接口,并提高专业技能(头发再掉一批,哇哈哈哈。。。)。
PrivilegedCallable类是Callable接口的一个特殊实现类,它表明Callable对象有某种特权来访问系统的某种资源,PrivilegedCallable类的源代码如下所示。
从PrivilegedCallable类的源代码来看,可以将PrivilegedCallable看成是对Callable接口的封装,并且这个类也继承了Callable接口。 在PrivilegedCallable类中有两个成员变量,分别是Callable接口的实例对象和AccessControlContext类的实例对象,如下所示。
其中,AccessControlContext类可以理解为一个具有系统资源访问决策的上下文类,通过这个类可以访问系统的特定资源。通过类的构造方法可以看出,在实例化AccessControlContext类的对象时,只需要传递Callable接口子类的对象即可,如下所示。
AccessControlContext类的对象是通过AccessController类的getContext()方法获取的,这里,查看AccessController类的getContext()方法,如下所示。
通过AccessController的getContext()方法可以看出,首先通过getStackAccessControlContext()方法来获取AccessControlContext对象实例。如果获取的AccessControlContext对象实例为空,则通过调用AccessControlContext类的构造方法实例化,否则,调用AccessControlContext对象实例的optimize()方法返回AccessControlContext对象实例。 这里,我们先看下getStackAccessControlContext()方法是个什么鬼。
原来是个本地方法,方法的字面意思就是获取能够访问系统栈的决策上下文对象。 接下来,我们回到PrivilegedCallable类的call()方法,如下所示。
通过调用AccessController.doPrivileged()方法,传递PrivilegedExceptionAction。接口对象和AccessControlContext对象,并最终返回泛型的实例对象。 首先,看下AccessController.doPrivileged()方法,如下所示。
可以看到,又是一个本地方法。也就是说,最终的执行情况是将PrivilegedExceptionAction接口对象和AccessControlContext对象实例传递给这个本地方法执行。并且在PrivilegedExceptionAction接口对象的run()方法中调用Callable接口的call()方法来执行最终的业务逻辑,并且返回泛型对象。
此类表示为在已经建立的特定访问控制和当前的类加载器下运行的Callable类,源代码如下所示。
这个类理解起来比较简单,首先,在类中定义了三个成员变量,如下所示。
接下来,通过构造方法注入Callable对象,在构造方法中,首先获取系统安全管理器对象实例,通过系统安全管理器对象实例检查是否具有获取ClassLoader和设置ContextClassLoader的权限。并在构造方法中为三个成员变量赋值,如下所示。
接下来,通过调用call()方法来执行具体的业务逻辑,如下所示。
在call()方法中同样是通过调用AccessController类的本地方法doPrivileged,传递PrivilegedExceptionAction接口的实例对象和AccessControlContext类的对象实例。 具体执行逻辑为:在PrivilegedExceptionAction对象的run()方法中获取当前线程的ContextClassLoader对象,如果在构造方法中获取的ClassLoader对象与此处的ContextClassLoader对象是同一个对象(不止对象实例相同,而且内存地址也相同),则直接调用Callable对象的call()方法返回结果。否则,将PrivilegedExceptionAction对象的run()方法中的当前线程的ContextClassLoader设置为在构造方法中获取的类加载器对象,接下来,再调用Callable对象的call()方法返回结果。最终将当前线程的ContextClassLoader重置为之前的ContextClassLoader。
RunnableAdapter类比较简单,给定运行的任务和结果,运行给定的任务并返回给定的结果,源代码如下所示。
TaskCallable类是javafx.concurrent.Task类的静态内部类,TaskCallable类主要是实现了Callable接口并且被定义为FutureTask的类,并且在这个类中允许我们拦截call()方法来更新task任务的状态。源代码如下所示。
从TaskCallable类的源代码可以看出,只定义了一个Task类型的成员变量。下面主要分析TaskCallable类的call()方法。 当程序的执行进入到call()方法时,首先将task对象的started属性设置为true,表示任务已经开始,并且将任务的状态依次设置为State.SCHEDULED和State.RUNNING,依次触发任务的调度事件和运行事件。如下所示。
接下来,在try代码块中执行Task对象的call()方法,返回泛型对象。如果任务没有被取消,则更新任务的缓存,将调用call()方法返回的泛型对象绑定到Task对象中的ObjectProperty<V>对象中,其中,ObjectProperty<V>在Task类中的定义如下。
接下来,将任务的状态设置为成功状态。如下所示。
如果程序抛出了异常或者错误,会进入catch()代码块,设置Task对象的Exception信息并将状态设置为State.FAILED,也就是将任务标记为失败。接下来,判断异常或错误的类型,如果是Exception类型的异常,则直接强转为Exception类型的异常并抛出。否则,将异常或者错误封装为Exception对象并抛出,如下所示。
记住:你比别人强的地方,不是你做过多少年的CRUD工作,而是你比别人掌握了更多深入的技能。不要总停留在CRUD的表面工作,理解并掌握底层原理并熟悉源码实现,并形成自己的抽象思维能力,做到灵活运用,才是你突破瓶颈,脱颖而出的重要方向! 最后,作为一名合格(发际线比较高)的开发人员或者资深(秃顶)的工程师和架构师来说,理解原理和掌握源码,并形成自己的抽象思维能力,灵活运用是你必须掌握的技能。 好了,今天就到这儿吧,我是冰河,我们下期见~~ |
You are subscribed to email updates from SegmentFault 最新的文章. To stop receiving these emails, you may unsubscribe now. | Email delivery powered by Google |
Google, 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States |
No comments:
Post a Comment