<?xml version="1.0" encoding="utf-8"?><rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>东隅已逝/桑榆非晚</title><link>https://h1z3y3.me/</link><description>专注于服务端开发，喜欢折腾，喜欢最新科技产品</description><generator>Hugo 0.101.0 https://gohugo.io/</generator><language>en</language><managingEditor>gmzhaoyang@gmail.com (h1z3y3)</managingEditor><webMaster>gmzhaoyang@gmail.com (h1z3y3)</webMaster><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><lastBuildDate>Fri, 01 Jul 2022 11:40:58 +0800</lastBuildDate><atom:link rel="self" type="application/rss+xml" href="https://h1z3y3.me/rss.xml"/><item><title>业务消息传递(AKA Enterprise Messaging) 和 事件流（AKA Event Streaming）的区别</title><link>https://h1z3y3.me/posts/event-streaming-vs-enterprise-messaging/</link><guid isPermaLink="true">https://h1z3y3.me/posts/event-streaming-vs-enterprise-messaging/</guid><pubDate>Sun, 29 May 2022 23:31:00 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>业务消息传递技术（AKA Enterprise Messaging），如 IBM MQ、RabbitMQ 和 ActiveMQ，在应用程序内和跨应用程序间提供异步通信技术已经很多年了。
最近，事件流技术（AKA Event Streaming，如 Apache Kafka）越来越流行，它们也提供异步通信。&lt;/p>
&lt;p>开发人员和架构师可能错误地认为在这两种技术之间进行切换的改动会很小。
但在很多场景下，一旦深入了解并真正理解这两种技术存在的意义，以及它们适用的场景，
就会明白它们实际上它们是&lt;em>互补&lt;/em>的两种技术，而不是相互竞争。&lt;/p>
&lt;p>在本文中，我们将参考两种异步用例：&lt;/p>
&lt;ul>
&lt;li>处理请求(request for processing)&lt;/li>
&lt;li>访问业务数据(access enterprise data)&lt;/li>
&lt;/ul>
&lt;p>以帮助了解更多关于业务消息传递技术和事件流技术的信息。
然后，我们再讨论在为异步通信解决方案做技术选型时要考虑的关键因素。&lt;/p>
&lt;h2 id="异步使用场景">异步使用场景&lt;/h2>
&lt;p>为了了解如何进行技术选型，让我们先了解两种异步场景：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>处理请求(request for processing)&lt;/strong>: 这个场景是一个应用程序向另一个系统或服务发出请求来完成一个操作，然后将响应消息返回给请求者。
这种模式自从有互联网就存在了，而且将来也可能会一直存在。对于同步的、低质量的服务通信，自然而然就会 HTTP，而对于关键任务的通信，首选是异步消息。
要进一步了解何时使用同步或异步通信，请参阅文章 “ &lt;a href="https://developer.ibm.com/articles/introduction-apis-and-messaging">APIs 和 Messaging 的介绍&lt;/a> ”。&lt;/li>
&lt;li>&lt;strong>访问业务数据(access enterprise data)&lt;/strong>: 在这个场景，业务中的组件可以发送描述其当前状态的数据，该数据通常不会直接包含让另一个系统完成某个操作的指令。
相反，组建让其他系统了解它们的数据和状态，这可能是分发和消费使用业务数据的强大机制。&lt;/li>
&lt;/ul>
&lt;h3 id="处理请求的场景">处理请求的场景&lt;/h3>
&lt;p>业务消息传递技术擅长 &lt;em>处理请求&lt;/em> 的场景，包括许多常见的功能：&lt;/p>
&lt;p>&lt;strong>会话式消息传递&lt;/strong>：使用消息传递技术完成 请求/响应交互 的能力。这允许应用程序以 &lt;em>仅请求（即发即弃）&lt;/em> 或 &lt;em>请求/响应模式&lt;/em> 进行交互，选择最适合该场景的方式进行交互。&lt;/p>
&lt;p>&lt;strong>有针对性的可靠交付&lt;/strong>：当发送一条消息时，会以处理这条消息的特定实体为目标。
可以使用不同等级的消息可靠性，取决于应用程序和消息的重要性。
在关键任务通信的情况下，它应该是一次性、有保证的交付。
在 最多一次 或 至少一次 的交付场景下，可以考虑使用 &lt;em>接受丢失&lt;/em> 或 &lt;em>接受重复&lt;/em> 的系统。&lt;/p>
&lt;p>&lt;strong>短暂的数据持久化&lt;/strong>：数据仅会存储到被消费者消费或数据过期。
数据不需要持久化超过要求的时间，而且从系统资源占用的角度来看，一直持久保存没有好处。&lt;/p>
&lt;h3 id="访问业务数据的场景">访问业务数据的场景&lt;/h3>
&lt;p>在 &lt;em>访问业务数据&lt;/em> 这个场景下核心是发布/订阅（pub/sub）引擎，发布程序发布数据到 topic，然后订阅程序注册消费一个或多个 topic 以接收来自发布程序的数据。
pub/sub 引擎负责分发以确保所有人都能收到想要的数据。它也充当抽象层，使发布和订阅程序解耦，使其具有不同的可用性。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/difference-between-events-and-messages/publish-subscribe-messaging.png" alt="">&lt;/p>
&lt;p>pub/sub 引擎已经存在很多年了，例如首次发布 Apache Kafka 的 2011 年很多年之前， 1998 年发布的 JMS 1.0 规范就已经包含了这个能力。
业务消息技术和事件流技术都提供 pub/sub 引擎，所以可能对于给定的项目到底用哪个更合适会产生混淆。&lt;/p>
&lt;p>许多事件流技术提供 pub/sub 引擎但是也会包含一些其他适合特定场景的附加功能，
可以帮助在区分什么时候应该用业务消息的解决方案。比如，Apache Kafka 擅长：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>流历史（Stream history）&lt;/strong>：Apache Kafka 会保存 topic 的中的事件，只有当它们过期或达到系统资源限制时才会被移除。
这让订阅者可以重放事件，而不是仅仅只能获取到最近发布的事件。这是消息传递技术 Transient Data Persistence 特性的镜像。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>可扩展订阅（Scalable subscriptions）&lt;/strong>：流历史允许 Apache Kafka 可以通过轻量级的方法扩展订阅者的数量。
每个订阅者在流历史记录中都有一个指针来代表它已经消费的位置，这极大限度地减少了新订阅者的开销。
这也让 Apache Kafka 能以最小的影响支持上千，甚至上万的订阅者同时订阅同一个 topic。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="在业务消息传递技术和事件流技术之间进行选择">在业务消息传递技术和事件流技术之间进行选择&lt;/h1>
&lt;p>就像上面所提到的，业务消息传递技术和事件流技术有不同所擅长的功能，但是也有一些相同的功能。
所以关键是为解决方案选择合适的技术，而不是强制进行配合。&lt;/p>
&lt;p>为了促进本次评估，在为解决方案选择合适的技术时，需要考虑以下关键的标准：&lt;/p>
&lt;ul>
&lt;li>事件历史&lt;/li>
&lt;li>细粒度的订阅&lt;/li>
&lt;li>可扩展的消费&lt;/li>
&lt;li>事务操作&lt;/li>
&lt;/ul>
&lt;h2 id="事件历史">事件历史&lt;/h2>
&lt;p>解决方案是否需要能够在正常或故障的情况下取回历史事件？
在消息传递 pub/sub 模型中，事件会被发布到一个 topic。
一旦当它被订阅者收到，topic 就有责任存储此信息以备未来使用。
在某些情况下， pub/sub 模型可以保留最后的发布，但是让消息传递技术存储历史事件不太常见。
而对于 Apache Kafka，保存事件历史是架构的基础，唯一的问题是要保存多少和保存多久。
在许多用例场景下，存储历史记录至关重要，但是在一些其他用例场景中，从安全和系统资源的角度来看，这可能并不是最好的方案。&lt;/p>
&lt;h2 id="细粒度的订阅">细粒度的订阅&lt;/h2>
&lt;p>当一个 topic 被 Apache Kafka 创建之后，会创建一个或多个 partition 。
partition 是 Apache Kafka 基本架构的概念，并提供了扩展解决方案以处理大量事件的能力。
每一个 partition 独自占用资源，通常建议将单个集群中的 topic 数量限制为几百或几千个。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/difference-between-events-and-messages/kafka-partition.png" alt="">&lt;/p>
&lt;p>业务消息传递 pub/sub 技术有一个更灵活的机制，topic 是可以分层的结构，比如 &lt;code>/travel/flights/airline/flight-number/seat&lt;/code>，
每个层次都可以订阅。这让订阅程序可以更细粒度地选择事件。此外，业务消息传递 pub/sub 技术可以用于进一步细化感兴趣的事件。&lt;/p>
&lt;p>订阅业务消息 pub/sub 系统的程序接收与他们无关的事件的可能性要小得多，
然而对于订阅 Apache Kafka 且可能只需要一小部分事件的程序，在刚开始处理时需要过滤程序来过滤掉不需要的事件。&lt;/p>
&lt;h2 id="可扩展的消费">可扩展的消费&lt;/h2>
&lt;p>如果 100 个订阅者订阅了一个 topic 中的所有事件，业务消息传递技术需要为每个发布事件都创建 100 条消息。
如果要求它们的每一个都要存储而且持久化保存到磁盘，对于 Apache Kafka 来说，事件只会被写入一次，每个消费者都有一个对应于所在事件历史位置的索引。
而对于 IBM MQ 等消息传递技术可以有更高的可扩展性，所以根据事件数量和订阅者的数量这些因素可能可以决定最适合的技术，但也需要根据实际情况再进行分析。&lt;/p>
&lt;h2 id="事务操作">事务操作&lt;/h2>
&lt;p>IBM MQ 等业务消息传递技术和 Apache Kafka 等事件流技术都提供了事务操作 API 来处理事件。
然而，这两种实现的工作方式不同，因此不能相互兼容。
IBM MQ 提供原子性（Atomicity），一致性（Consistency），隔离性（Isolation）和 持久性（Durability）的 ACID 属性，
但是这些在 Apache Kafka 中并不能得到保证。
通常在 pub/sub 解决方案中，IBM MQ 特定的事务性操作不像处理请求的场景那么重要，所以了解其中的差异非常重要。&lt;/p>
&lt;p>了解更多事务需要考虑的因素，可以看这篇文章
“ &lt;a href="https://medium.com/@andrew_schofield/does-apache-kafka-do-acid-transactions-647b207f3d0e">Does Apache Kafka do ACID transactions?&lt;/a> ”。&lt;/p>
&lt;h1 id="总结以及扩展阅读">总结以及扩展阅读&lt;/h1>
&lt;p>总而言之，虽然业务消息传递技术和事件流技术可能最初看起来是重叠的，但适用的用例和场景其实不同。
消息传递技术更擅长请求处理的场景，而事件流技术专门提供具有流历史的 pub/sub 引擎。
这两个技术确实天然互补，所以客户通常对两者都有需求。&lt;/p>
&lt;p>想要更深入的了解这两个技术，请看这个在 2020 年 TechCon 大会的演示文稿
“ &lt;a href="https://ibmhybridcloud.lookbookhq.com/c/m23-mq-and-kafka-wha?x=_lDVGR">MQ and Kafka, what&amp;rsquo;s the difference?&lt;/a> ”(或者看视频回放)。&lt;/p>
&lt;p>原文：&lt;a href="https://developer.ibm.com/articles/difference-between-events-and-messages/">Why enterprise messaging and event streaming are different&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/message/">Message</category><category domain="https://h1z3y3.me/tags/event-streaming/">Event Streaming</category><category domain="https://h1z3y3.me/tags/mq/">MQ</category><category domain="https://h1z3y3.me/tags/kafka/">Kafka</category></item><item><title>low-code 和 serverless – 冰与火？</title><link>https://h1z3y3.me/posts/lowcode-and-serverless/</link><guid isPermaLink="true">https://h1z3y3.me/posts/lowcode-and-serverless/</guid><pubDate>Sat, 19 Mar 2022 16:08:22 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>对于业务应用程序的要求不断提高，但是 IT 部门困于大量的应用程序积压的工作而不可能很快的完成工作。而且，目前面临的挑战是，IT 需要重新思考提高交付速度的方法。有两个解决方案：low-code 和 serverless。&lt;/p>
&lt;p>但什么是 low-code 和 serverless？还有更重要的是，这一切又与 IoT 有什么关系？放轻松，我最终会解释。但是首先，让我们看一下 low-code 和 serveless，如果你还不熟悉这些技术，能让你快速熟悉起来。&lt;/p>
&lt;h2 id="冰与火之歌">冰与火之歌&lt;/h2>
&lt;p>low-code 和 serverless 技术同样都是为了简化应用开发流程而设计的，从而加速新应用程序的交付。serverless 通过减轻开发人员的服务器管理负担来做到这一点。虽然 serverless 的名字可能暗示没有任何服务器，但是还是需要服务器的。只是从开发者的角度来看，他只需要简单地专注于应用的开发而不是要担心部署、管理和扩容服务器。&lt;/p>
&lt;p>另一方面，low-code 通过从代码中抽象出开发人员来简化应用程序开发。思路是，如果开发人员可以拖放 GUI 组建来创建用户界面，然后用类似流程图的方式来创建业务逻辑，他将更快的交付应用程序。&lt;/p>
&lt;p>两个技术的存在都是为了解决同样的问题：加速应用程序开发。然而，两项技术背后的公司可能会采取截然不同的方法，导致 serverless 和 low-code 看起来像是冰与火。&lt;/p>
&lt;p>公有云供应商比如 AWS、Google、Azure 和 IBM 都提供 serverless 选择，但是对于他们的大多数，他们只关注于低等级功能，大多数组织无法基于这些技术解决复杂的问题。直接与这些供应商合作的组织可以更好的控制输出，但是这需要更多的开发工作。&lt;/p>
&lt;p>同时，传统的 low-code 供应商通过让商业用户可以访问应用程序的开发，来预示着“全民开发者“的兴起。鉴于大多数商业用户都没有计算机相关学位，low-code 的方法非常适合他们。不像 serverless 那样，low-cdoe 让应用程序交付更快，但是以可控性为代价 &amp;ndash; 开发者在供应商设置的 low-code 环境下可做的事情受到很大限制。&lt;/p>
&lt;h2 id="异性相吸冰与火的结合">异性相吸：冰与火的结合&lt;/h2>
&lt;p>随着找到应用程序开发挑战解决方案的压力越来越大，这些技术没有理由不能共存。只是传统 low-code 供应商早于 serverless 的概念，并使用需要应用服务器的传统技术。是的，应用服务器被认为是过时的技术，即使他们是开源的！而且给全民开发者提供 low-code 的方法也不是 AWS、Google 或 IBM 的 DNA。 Microsoft 有一点不同，但其业务开发工作目前并未与 serverless 相关联。&lt;/p>
&lt;p>所以，这回带来什么问题？对于寻找 low-code 选项的人来说，他们应该仔细考虑系统的架构。这可能会很难，因为供应商喜欢到处乱用技术名称，那将会让调研变得复杂。不幸的是，实际上很多传统的 low-code 供应商都依赖较旧的技术。相比于 serverless，你可以认为他们是单体架构 &amp;ndash; 那意味着你没有对于 设计、开发、测试、部署、独立扩展能力的可灵活性。&lt;/p>
&lt;p>好消息是，现在有很多 low-code 的可选项是基于 serverless 的。它们采取不同的方法，通过关注于提高专业开发人员的生产力而不是将应用程序开发人员的责任转移给全民开发者。本质上，它们旨在通过使用现有的工具和流程设计的通用 web 技能为开发人员提供更高级别的控制。&lt;/p>
&lt;h2 id="serverlesslow-code-和-iot-应用程序经验">Serverless、low-code 和 IoT 应用程序经验&lt;/h2>
&lt;p>所以，为什么这一切对于 IoT 很重要？传统的 low-code 吹捧支持 IoT 应用程序的能力。但是他们仅限于调用打包的服务（比如 分析）和在应用程序中使用服务。&lt;/p>
&lt;p>另一方面，serverless 对于 IoT 来说是非常好的架构，因为 event-based 工作方式在 serverless 环境运行的非常好。而且 event-based 是 IoT 应用程序的关键，因为来自传感器数据分析的事件可以实现更自然的、中断驱动的应用体验。这允许应用程序代表用户执行操作，从而最大限度地减少用户必须做的工作。&lt;/p>
&lt;p>因此，如果您正在考虑 low-code ，请确保要仔细考虑系统的架构，而不是只关注拖放式 UI —— 它可以对最终产品产生重大影响。&lt;/p></description><category domain="https://h1z3y3.me/tags/low-code/">low-code</category><category domain="https://h1z3y3.me/tags/serverless/">serverless</category></item><item><title>2021 北京 Gopher 大会</title><link>https://h1z3y3.me/posts/gophercon-china-2021/</link><guid isPermaLink="true">https://h1z3y3.me/posts/gophercon-china-2021/</guid><pubDate>Mon, 28 Jun 2021 09:01:11 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/GopherCon-China-2021/logo.png" alt="">&lt;/p>
&lt;p>托 &lt;a href="https://fukun.org/">团队 leader&lt;/a> 的福，白嫖了今年 GopherCon China 的票，和历届大会相比，第二天分了两个分会场，
分享不同的内容，参会者可以根据个人兴趣自行选择参加。&lt;/p>
&lt;p>然而不得不说，本次和之前几次的分享安排相比，多了一些针对自己公司产品的推广。
但不管怎么说，收获还是有的，印象比较深的 talk 有 PingCAP CEO 黄东旭分享的《全链路可观测性：从应用到 Go Runtime》，比较有趣；还有曹大曹春晖分享的《Go 语言的抢式调度》，内容确实很卷很深入。&lt;/p>
&lt;p>大会提到的很多技术其实我们团队也早已经实践使用，比如鸟窝在《深入探究 Go Module》中分享如何使用 GOPRIVATE 从而使用自己的私有仓库；
Grab 公司在分享《Improving Go Backend Developer Experience In Grab》中提到的 CI/CD 我们也已经在我们大多数项目中使用。&lt;/p>
&lt;p>我总结了讲师提到最多的关键词，一方面也能体现业内目前在流行什么：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>K8S&lt;/strong>：毋庸置疑，云原生时代的王者&lt;/li>
&lt;li>&lt;strong>API Gateway&lt;/strong>、&lt;strong>L7 Banlancer&lt;/strong>：处理南北向流量，两者定位不同又有一部分功能重合，APISIX、BFE 最近两年大家讨论比较多的开源项目&lt;/li>
&lt;li>&lt;strong>Service Mesh&lt;/strong>：处理东西向流量，Envoy、xDS协议&lt;/li>
&lt;li>&lt;strong>微服务&lt;/strong>：不再赘述&lt;/li>
&lt;li>&lt;strong>Tracing&lt;/strong>、&lt;strong>Metrics&lt;/strong>、&lt;strong>Logging&lt;/strong>：及时发现服务问题&lt;/li>
&lt;li>&lt;strong>CI/CD&lt;/strong>：加快应用的部署速度，解放开发者双手不可或缺的步骤&lt;/li>
&lt;li>&lt;strong>Rust&lt;/strong>：没错，Gopher 大会确实多次提到了 Rust 语言&lt;/li>
&lt;/ul>
&lt;p>我针对我自己参加的几个分享谈一谈我自己的一些感受和思考。&lt;/p>
&lt;p>大会的 PPT，可以在这里获取：&lt;a href="https://mp.weixin.qq.com/s/734ac0JeQtSrzcmZ1-so8w">https://mp.weixin.qq.com/s/734ac0JeQtSrzcmZ1-so8w&lt;/a>&lt;/p>
&lt;h2 id="generic-in-go">《Generic in go》&lt;/h2>
&lt;p>大会第一场是跟老外连线，但是直播断断续续，我这蹩脚听力更听不懂了，体验不太好。但是针对泛型这个主题，在社区中有很多讨论，
也有一些争议，主要是泛型的支持者，和认为加入泛型破坏 golang 简单特性的反对者，
不过今年官方正式提出将泛型特性加入 Go 的
proposal &lt;a href="https://blog.golang.org/generics-proposal">https://blog.golang.org/generics-proposal&lt;/a> &lt;a href="https://github.com/golang/go/issues/43651">https://github.com/golang/go/issues/43651&lt;/a> 。
而且，golang 官方已经在 master 分支实现了泛型，鸟窝也在早些时间给出了 Go 泛型尝鲜的方法：https://colobu.com/2021/03/22/try-go-generic/&lt;/p>
&lt;h2 id="mosn在云原生的探索和实践">《MOSN在云原生的探索和实践》&lt;/h2>
&lt;p>MOSN 是一款网络代理软件，可以与任何支持 xDS API 的 Service Mesh 集成，也具备南北向流量代理的功能。
不得不佩服 MOSN 对 CGO 的调研和使用深度，其中介绍的 MOE（Mosn on Envoy）使得 MOSN 底层网络具备 C 同等处理能力的同时，
又能使上层业务可高效复用 MOSN 的处理能力及 Golang 高效的开发效率。&lt;/p>
&lt;h2 id="浅谈全链路可观测性从应用到go-runtime">《浅谈全链路可观测性：从应用到Go Runtime》&lt;/h2>
&lt;p>我个人认为很不错的分享，由浅入深带领大家如何 Trace In Go Runtime，PPT 风格还有讲解风格都很有趣。
最终的实现方式我也没想到和我最近翻译的这篇文章相关
（ &lt;a href="https://h1z3y3.me/posts/demysitifying-pprof-labels-with-go/">深入剖析 Golang Pprof 标签&lt;/a> ），
翻译时也没想到还能这么玩，当时听了确实也令人眼前一亮。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/GopherCon-China-2021/profile-label.png" alt="">&lt;/p>
&lt;h2 id="improving-go-backend-developer-experience-in-grab">《Improving Go Backend Developer Experience in Grab》&lt;/h2>
&lt;p>算是 Grab 公司的经验分享，关于整个软件的设计、开发和交付流程。和 Go 的关系并不是很大，很多经验经验也很难进行复制。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/GopherCon-China-2021/build-times.png" alt="">&lt;/p>
&lt;h2 id="利用夜莺扩展能力打造全方位监控体系">《利用夜莺扩展能力打造全方位监控体系》&lt;/h2>
&lt;p>夜莺也是国内应用很火的监控系统，和 Prometheus 相比，有一些优势吧，比如有自身的告警平台，
我们团队内部使用的基于 Prometheus 又另外开发的一套报警系统，目前也是开源，地址是 &lt;a href="https://github.com/Qihoo360/doraemon">https://github.com/Qihoo360/doraemon&lt;/a> 。
更多夜莺和Prometheus的对比可以看这篇分析文章，&lt;a href="https://www.yuque.com/ictc/manual/nr798n">《夜莺与Prometheus的对比》&lt;/a> 。
但是干货太少，全程介绍产品功能。&lt;/p>
&lt;h2 id="如何构建易于拆分的单体应用">《如何构建易于拆分的单体应用》&lt;/h2>
&lt;p>由于项目的特殊性，我们组内大多数应用都是单体应用，也在尝试做一些微服务的拆分工作。这个分享虽然没讲 DDD，
但从现实例子出发，分析一个应用如何设计、开发，同时实战讲解了如何使用 go-kit 构建一个简单的应用，还是有些收获。&lt;/p>
&lt;h2 id="深入探索go-module-实践技巧和陷阱">《深入探索Go Module: 实践、技巧和陷阱》&lt;/h2>
&lt;p>内容充实还不错，一些比较常见的用法我们团队也一直在用，我们的也是去年开始从 dep 全部换到了 go module，
所以积累了一些 go module 的使用经验，所以整个分享听下来也比较轻松。我之前也写了一篇文章介绍如何让 go module
使用 gitlab 私有仓库：&lt;a href="https://h1z3y3.me/posts/go-private-git-repository/">https://h1z3y3.me/posts/go-private-git-repository/&lt;/a> 。
鸟窝还介绍了一些之前基本没用过的子命令，比如 &lt;code>go mod graph&lt;/code>、&lt;code>go mod why&lt;/code>，收获最大的是终于弄明白了 go module 拉取依赖包的策略。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/GopherCon-China-2021/go-module-history.png" alt="">
&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/GopherCon-China-2021/go-module-xxx.png" alt="">&lt;/p>
&lt;h2 id="深入理解-bfe">《深入理解 BFE》&lt;/h2>
&lt;p>也算是推广分享，介绍 BFE 产品，没什么干货。&lt;/p>
&lt;p>我们 19 年也开源了一个七层转发的代理 &lt;a href="https://github.com/Qihoo360/HTTPSLayer">「HTTPS Layer」&lt;/a> ，
和 BEF 相比，功能基本类似，甚至可以说 80% 相同。最大的区别就是 BFE 基于 Go 生态，我们基于 Openresty 生态。
或许缺少了维护和宣传，我们并没有获得很高的关注，比较可惜。其实 HTTPS Layer 在我们公司内部也得到了生产环境的验证，
做了一些分布式部署之后，目前我们单机房峰值 QPS 也能达到 10w QPS，当然还预留了一些 buffer 的情况下。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/GopherCon-China-2021/why-bfe.png" alt="">&lt;/p>
&lt;h2 id="go-语言的抢占式调度">《Go 语言的抢占式调度》&lt;/h2>
&lt;p>曹大曹春晖的分享，很卷，讲解了 GMP 模型以及 Golang 的抢占式调度，干货又太干了，有点吸收不了。
主要内容是 golang 1.14 版本前后两种不同的抢占式调度模型，深入源码，通过编译后的汇编进行讲解，挺有深度的，之后会再仔细研究这方面的知识。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/GopherCon-China-2021/GMP.png" alt="">
&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/GopherCon-China-2021/tangping.png" alt="">&lt;/p>
&lt;h2 id="k8s私有云建设实践">《K8S私有云建设实践》&lt;/h2>
&lt;p>公司的经验分享，不可复制，因为太客制化了。为了迎合公司现有平台，抛弃了很多 k8s 的原生用法，我个人觉得很鸡肋。
针对私有云建设，360 其实走在比较前面，之前还开源了 k8s 的多集群管理平台
&lt;a href="https://github.com/Qihoo360/wayne">Wayne&lt;/a> ，开源初期获得很多关注，但是后续也不没有持续更新，也比较可惜。&lt;/p>
&lt;h2 id="go-如何助力企业进行微服务转型">《Go 如何助力企业进行微服务转型》&lt;/h2>
&lt;p>go-zero 的维护者万俊峰分享，虽然经验之谈多一些，关于 Go 的讨论少，但是整体分享是好分享，收获很多，
也有些之后可借鉴的点。不多赘述，PPT 可以下载下来看，里面挺详细。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/GopherCon-China-2021/monolith-to-microservice.png" alt="">&lt;/p>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>从整体来看，本次 Gopher 大会还算成功，有些小插曲，但是瑕不掩瑜。只是希望之后能少一些对于产品的推广和介绍，
对于产品功能完全可以去看各自产品的文档，我能想到他们和 Go 唯一相关的就是这个产品是 Go 写的。
作为技术人花了钱到这里想看到的还是对于 Go 的一些可借鉴和可复制的实践经验，这个其实看下国外的 gopher 大会就能体会到了。&lt;/p>
&lt;p>不管怎么说，虽然周末两天都没睡懒觉，7点早起就往会场赶，但是最后还是心满意足。我看大会全程有录像，
大家可以参考我的思考，去翻翻对应的 PPT 或者视频，相信也能有所收获！&lt;/p></description><category domain="https://h1z3y3.me/tags/golang/">Golang</category></item><item><title>Go 简单而强大的反向代理（Reverse Proxy）</title><link>https://h1z3y3.me/posts/simple-and-powerful-reverse-proxy-in-golang/</link><guid isPermaLink="true">https://h1z3y3.me/posts/simple-and-powerful-reverse-proxy-in-golang/</guid><pubDate>Thu, 10 Jun 2021 21:01:11 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>在本文中，我们将了解反向代理，它的应用场景以及如何在 Golang 中实现它。&lt;/p>
&lt;p>反向代理是位于 Web 服务器前面并将客户端（例如 Web 浏览器）的请求转发到 Web 服务器的服务器。
它们让你可以控制来自客户端的请求和来自服务器的响应，然后我们可以利用这个特点，
可以增加缓存、做一些提高网站的安全性措施等。&lt;/p>
&lt;p>在我们深入了解有关反向代理之前，让我们快速看普通代理（也称为正向代理）和反向代理之间的区别。&lt;/p>
&lt;p>在&lt;strong>正向代理&lt;/strong>中，代理代表原始客户端从另一个网站检索数据。
它位于客户端（浏览器）前面，并确保没有后端服务器直接与客户端通信。
所有客户端的请求都通过代理被转发，因此服务器只与这个代理通信（服务器会认为代理是它的客户端）。
在这种情况下，代理可以隐藏真正的客户端。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/studygolang/gctt-images2/master/20210525-Simple-and-Powerful-ReverseProxy-in-Go/forward-proxy.png" alt="">&lt;/p>
&lt;p>另一方面，&lt;strong>反向代理&lt;/strong>位于后端服务器的前面，确保没有客户端直接与服务器通信。
所有客户端请求都会通过反向代理发送到服务器，因此客户端始终只与反向代理通信， 而从不会直接与实际服务器通信。
在这种情况下，代理可以隐藏后端服务器。
几个常见的反向代理有 Nginx， HAProxy。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/studygolang/gctt-images2/master/20210525-Simple-and-Powerful-ReverseProxy-in-Go/reverse-proxy.png" alt="">&lt;/p>
&lt;h2 id="反向代理使用场景">反向代理使用场景&lt;/h2>
&lt;p>负载均衡（Load balancing）：
反向代理可以提供负载均衡解决方案，将传入的流量均匀地分布在不同的服务器之间，以防止单个服务器过载。&lt;/p>
&lt;p>防止安全攻击：
由于真正的后端服务器永远不需要暴露公共 IP，所以 DDoS 等攻击只能针对反向代理进行，
这能确保在网络攻击中尽量多的保护你的资源，真正的后端服务器始终是安全的。&lt;/p>
&lt;p>缓存：
假设你的实际服务器与用户所在的地区距离比较远，那么你可以在当地部署反向代理，它可以缓存网站内容并为当地用户提供服务。&lt;/p>
&lt;p>SSL 加密：
由于与每个客户端的 SSL 通信会耗费大量的计算资源，因此可以使用反向代理处理所有与 SSL 相关的内容，
然后释放你真正服务器上的宝贵资源。&lt;/p>
&lt;h2 id="golang-实现">Golang 实现&lt;/h2>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/http&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/http/httputil&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/url&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// NewProxy takes target host and creates a reverse proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// NewProxy 拿到 targetHost 后，创建一个反向代理
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">targetHost&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ReverseProxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">targetHost&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewSingleHostReverseProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ProxyRequestHandler handles the http request using proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ProxyRequestHandler 使用 proxy 处理请求
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="nf">ProxyRequestHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">proxy&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ReverseProxy&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ServeHTTP&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// initialize a reverse proxy and pass the actual backend server url here
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 初始化反向代理并传入真正后端服务的地址
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NewProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;http://my-api-server.com&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">panic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// handle all requests to your server using the proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 使用 proxy 处理所有请求到你的服务
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nf">ProxyRequestHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">proxy&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ListenAndServe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;:8080&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>是的没错！这就是在 Go 中创建一个简单的反向代理所需的全部内容。
我们使用标准库 &lt;code>net/http/httputil&lt;/code> 创建了一个单主机的反向代理。
到达我们代理服务器的任何请求都会被代理到位于 &lt;code>http://my-api-server.com&lt;/code>。
如果你对 Go 比较熟悉，这个代码的实现一目了然。&lt;/p>
&lt;h2 id="修改响应">修改响应&lt;/h2>
&lt;p>&lt;code>HttpUtil&lt;/code> 反向代理为我们提供了一种非常简单的机制来修改我们从服务器获得的响应，
可以根据你的应用场景来缓存或更改此响应，让我们看看应该如何实现：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// NewProxy takes target host and creates a reverse proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">targetHost&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ReverseProxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">targetHost&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewSingleHostReverseProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ModifyResponse&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">modifyResponse&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">modifyResponse&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Header&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;X-Proxy&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Magical&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>可以在 &lt;code>modifyResponse&lt;/code> 方法中看到 ，我们设置了自定义 Header 头。
同样，你也可以读取响应体正文，并对其进行更改或缓存，然后将其设置回客户端。&lt;/p>
&lt;p>在 &lt;code>modifyResponse&lt;/code> 中，可以返回一个错误（如果你在处理响应发生了错误），
如果你设置了 &lt;code>proxy.ErrorHandler&lt;/code>, &lt;code>modifyResponse&lt;/code> 返回错误时会自动调用 &lt;code>ErrorHandler&lt;/code> 进行错误处理。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// NewProxy takes target host and creates a reverse proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">targetHost&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ReverseProxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">targetHost&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewSingleHostReverseProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ModifyResponse&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">modifyResponse&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ErrorHandler&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">errorHandler&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">errorHandler&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">req&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Got error while modifying response: %v \n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">modifyResponse&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;response body is invalid&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="修改请求">修改请求&lt;/h2>
&lt;p>你也可以在将请求发送到服务器之前对其进行修改。
在下面的例子中，我们将会在请求发送到服务器之前添加了一个 Header 头。
同样的，你可以在请求发送之前对其进行任何更改。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// NewProxy takes target host and creates a reverse proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">targetHost&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ReverseProxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">targetHost&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewSingleHostReverseProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalDirector&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Director&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Director&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">originalDirector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">modifyRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ModifyResponse&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">modifyResponse&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ErrorHandler&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">errorHandler&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">modifyRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Header&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;X-Proxy&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Simple-Reverse-Proxy&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="完整代码">完整代码&lt;/h2>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;errors&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/http&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/http/httputil&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/url&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// NewProxy takes target host and creates a reverse proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">targetHost&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ReverseProxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">url&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">targetHost&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewSingleHostReverseProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">originalDirector&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Director&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Director&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">originalDirector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">modifyRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ModifyResponse&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">modifyResponse&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ErrorHandler&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nf">errorHandler&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">modifyRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">req&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">req&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Header&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;X-Proxy&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;Simple-Reverse-Proxy&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">errorHandler&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">req&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Printf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Got error while modifying response: %v \n&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">modifyResponse&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resp&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;response body is invalid&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ProxyRequestHandler handles the http request using proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="nf">ProxyRequestHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">proxy&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">httputil&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ReverseProxy&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ResponseWriter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Request&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ServeHTTP&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">w&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">r&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// initialize a reverse proxy and pass the actual backend server url here
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">NewProxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;http://my-api-server.com&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">panic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// handle all requests to your server using the proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HandleFunc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nf">ProxyRequestHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">proxy&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">http&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ListenAndServe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;:8080&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>反向代理非常强大，如文章之前所说，它有很多应用场景。你可以根据你的情况对其进行自定义。
如果遇到任何问题，我非常乐意为你提供帮助。
如果你觉得这篇文章有趣，请分享一下，让更多 gopher 可以阅读！ 非常感谢你的阅读。&lt;/p>
&lt;hr>
&lt;p>via: &lt;a href="https://blog.joshsoftware.com/2021/05/25/simple-and-powerful-reverseproxy-in-go/">https://blog.joshsoftware.com/2021/05/25/simple-and-powerful-reverseproxy-in-go/&lt;/a>&lt;/p>
&lt;p>作者：&lt;a href="https://blog.joshsoftware.com/author/devanujverma/">Anuj Verma&lt;/a>
译者：&lt;a href="https://www.h1z3y3.me">h1z3y3&lt;/a>
校对：&lt;a href="https://github.com/%E6%A0%A1%E5%AF%B9%E8%80%85ID">校对者ID&lt;/a>&lt;/p>
&lt;p>本文由 &lt;a href="https://github.com/studygolang/GCTT">GCTT&lt;/a> 原创编译，&lt;a href="https://studygolang.com/">Go 中文网&lt;/a> 荣誉推出&lt;/p></description><category domain="https://h1z3y3.me/tags/golang/">Golang</category></item><item><title>Unescape values in html templates using golang</title><link>https://h1z3y3.me/posts/go-html-template-script-unescape/</link><guid isPermaLink="true">https://h1z3y3.me/posts/go-html-template-script-unescape/</guid><pubDate>Tue, 25 May 2021 21:56:44 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>While using values in a html template, my colleague faced a problem that the values in &lt;code>&amp;lt;script&amp;gt;&lt;/code> tags were all escaped.
So the template developer can not use the values in javascript. It just looks like this:&lt;/p>
&lt;pre tabindex="0">&lt;code>// ...
&amp;lt;script type=&amp;#34;text/json&amp;#34; id=&amp;#34;channel&amp;#34; ch=&amp;#34;recommend&amp;#34; tk=&amp;#34;&amp;#34;&amp;gt;
[&amp;amp;#34;news&amp;amp;#34;,&amp;amp;#34;comedy&amp;amp;#34;,&amp;amp;#34;cartoon&amp;amp;#34;,&amp;amp;#34;tech&amp;amp;#34;,&amp;amp;#34;travelling&amp;amp;#34;,&amp;amp;#34;fashion&amp;amp;#34;,&amp;amp;#34;photograph&amp;amp;#34;,&amp;amp;#34;household&amp;amp;#34;,&amp;amp;#34;movies&amp;amp;#34;,&amp;amp;#34;foods&amp;amp;#34;,&amp;amp;#34;military&amp;amp;#34;,&amp;amp;#34;health&amp;amp;#34;,&amp;amp;#34;test&amp;amp;#34;]
&amp;lt;/script&amp;gt;
// ...
&lt;/code>&lt;/pre>&lt;p>I tried to find out the reason. So I copied the template values in other HTML tags, like &lt;code>&amp;lt;div&amp;gt;&lt;/code>, rather than in &lt;code>&amp;lt;script&amp;gt;&lt;/code>.
It rendered correctly, and was not escaped:&lt;/p>
&lt;pre tabindex="0">&lt;code>// ...
&amp;lt;div&amp;gt;
[&amp;#34;news&amp;#34;,&amp;#34;comedy&amp;#34;,&amp;#34;cartoon&amp;#34;,&amp;#34;tech&amp;#34;,&amp;#34;travelling&amp;#34;,&amp;#34;fashion&amp;#34;,&amp;#34;photograph&amp;#34;,&amp;#34;household&amp;#34;,&amp;#34;movies&amp;#34;,&amp;#34;foods&amp;#34;,&amp;#34;military&amp;#34;,&amp;#34;health&amp;#34;,&amp;#34;test&amp;#34;]
&amp;lt;div&amp;gt;
// ...
&lt;/code>&lt;/pre>&lt;p>I start google the reason. At first, I thought it was because the values given template is wrong,
it had already been escaped before the page rendering.
I found some reasons in google, they said using &lt;code>json.NewEncoder()&lt;/code> instead of using &lt;code>json.Marshal()&lt;/code>.
So it can &lt;code>SetEscapeHTML(false)&lt;/code> before encode the data. This is the example code:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">toRawJson&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">v&lt;/span> &lt;span class="kd">interface&lt;/span>&lt;span class="p">{})&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">buf&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">new&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bytes&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Buffer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">enc&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewEncoder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">buf&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">enc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SetEscapeHTML&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">enc&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Encode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">v&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">TrimSuffix&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">buf&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">String&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="s">&amp;#34;\n&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I tried it, but didn&amp;rsquo;t work either.&lt;/p>
&lt;p>Now, let&amp;rsquo;s think about for a moment. Only &lt;code>&amp;lt;script&amp;gt;&lt;/code> has the problem, but other html tags did not.
So if I could say the value is actually correct, but the template rendered in wrong ?&lt;/p>
&lt;p>I started google the problems about template render but not the wrong value.&lt;/p>
&lt;p>Then I found the &lt;code>html/template&lt;/code> official document:&lt;/p>
&lt;p>Let&amp;rsquo;s see its introduction: &lt;code>html/template&lt;/code> &lt;a href="https://golang.org/pkg/html/template/#hdr-Introduction">introduction&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>This package wraps package text/template so you can share its template API
to parse and execute HTML templates safely.&lt;/p>
&lt;pre tabindex="0">&lt;code>tmpl, err := template.New(&amp;#34;name&amp;#34;).Parse(...)
// Error checking elided
err = tmpl.Execute(out, data)
&lt;/code>&lt;/pre>&lt;p>If successful, tmpl will now be injection-safe. Otherwise,
err is an error defined in the docs for ErrorCode.&lt;/p>
&lt;p>HTML templates treat data values as plain text which should be encoded so they
can be safely embedded in an HTML document.
The escaping is contextual, so actions can appear within JavaScript, CSS, and URI contexts.
The security model used by this package assumes that template authors are trusted,
while Execute&amp;rsquo;s data parameter is not. More details are provided below.&lt;/p>
&lt;p>Example&lt;/p>
&lt;pre tabindex="0">&lt;code>import &amp;#34;text/template&amp;#34;
...
t, err := template.New(&amp;#34;foo&amp;#34;).Parse(`{{define &amp;#34;T&amp;#34;}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, &amp;#34;T&amp;#34;, &amp;#34;&amp;lt;script&amp;gt;alert(&amp;#39;you have been pwned&amp;#39;)&amp;lt;/script&amp;gt;&amp;#34;)
&lt;/code>&lt;/pre>&lt;p>produces&lt;/p>
&lt;pre tabindex="0">&lt;code>Hello, &amp;lt;script&amp;gt;alert(&amp;#39;you have been pwned&amp;#39;)&amp;lt;/script&amp;gt;!
&lt;/code>&lt;/pre>&lt;p>but the contextual autoescaping in html/template&lt;/p>
&lt;pre tabindex="0">&lt;code>import &amp;#34;html/template&amp;#34;
...
t, err := template.New(&amp;#34;foo&amp;#34;).Parse(`{{define &amp;#34;T&amp;#34;}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, &amp;#34;T&amp;#34;, &amp;#34;&amp;lt;script&amp;gt;alert(&amp;#39;you have been pwned&amp;#39;)&amp;lt;/script&amp;gt;&amp;#34;)
&lt;/code>&lt;/pre>&lt;p>produces safe, escaped HTML output&lt;/p>
&lt;pre tabindex="0">&lt;code>Hello, &amp;amp;lt;script&amp;amp;gt;alert(&amp;amp;#39;you have been pwned&amp;amp;#39;)&amp;amp;lt;/script&amp;amp;gt;!
&lt;/code>&lt;/pre>&lt;/blockquote>
&lt;p>🥳🥳🥳 Now we find out the reason, but how we solve this problem?&lt;/p>
&lt;p>Maybe I could provide a function to unescape the value in templates.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">r&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Default&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">funcMap&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;unescapeHTML&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">template&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">HTML&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">template&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">HTML&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">r&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SetFuncMap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">funcMap&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Then you can use it in templates as below:&lt;/p>
&lt;pre tabindex="0">&lt;code>// ...
&amp;lt;script type=&amp;#34;text/json&amp;#34; id=&amp;#34;channel&amp;#34; ch=&amp;#34;recommend&amp;#34; tk=&amp;#34;&amp;#34;&amp;gt;
{{ .channel_data | unescapeHTML }}
&amp;lt;/script&amp;gt;
// ...
&lt;/code>&lt;/pre>&lt;p>Finally, it works.
When the template is loaded, the value won&amp;rsquo;t be escaped!&lt;/p>
&lt;pre tabindex="0">&lt;code>// ...
&amp;lt;script type=&amp;#34;text/json&amp;#34; id=&amp;#34;channel&amp;#34; ch=&amp;#34;recommend&amp;#34; tk=&amp;#34;&amp;#34;&amp;gt;
[&amp;#34;news&amp;#34;,&amp;#34;comedy&amp;#34;,&amp;#34;cartoon&amp;#34;,&amp;#34;tech&amp;#34;,&amp;#34;travelling&amp;#34;,&amp;#34;fashion&amp;#34;,&amp;#34;photograph&amp;#34;,&amp;#34;household&amp;#34;,&amp;#34;movies&amp;#34;,&amp;#34;foods&amp;#34;,&amp;#34;military&amp;#34;,&amp;#34;health&amp;#34;,&amp;#34;test&amp;#34;]
&amp;lt;/script&amp;gt;
// ...
&lt;/code>&lt;/pre>&lt;p>&lt;strong>Hope this helps you!! Thanks for reading :)&lt;/strong>&lt;/p></description><category domain="https://h1z3y3.me/tags/golang/">Golang</category></item><item><title>go module 使用 gitlab 私有仓库</title><link>https://h1z3y3.me/posts/go-private-git-repository/</link><guid isPermaLink="true">https://h1z3y3.me/posts/go-private-git-repository/</guid><pubDate>Thu, 20 May 2021 12:33:52 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h1 id="go-module-使用-gitlab-私有仓库">Go Module 使用 gitlab 私有仓库&lt;/h1>
&lt;p>包管理是 Go 一直被诟病做的不好的功能。在 1.11 之前，go get 缺乏对依赖包版本管理和 reproducible build 的支持。
当时在 Go 社区当时诞生了许多好用的工具，比如 glide，dep 等。
在 1.11 版本之后， Go 引入了 Go Module，再也没有 GOPATH 的限制，你可以随意在任何路径写项目，但是此时对私有仓库的支持还不是很好。
而在 1.13 版本之后， Go 对 Go Module 又进行了优化，支持了 &lt;code>GOPRIVATE&lt;/code> 环境变量，可以指定私有仓库的地址，使用十分便捷。
大家在使用过程中，或多或少地会遇到一些问题，下面我针对自己遇到的问题进行总结。&lt;/p>
&lt;h2 id="go-get">go get&lt;/h2>
&lt;p>如果在没有进行任何设置的情况下直接执行 &lt;code>go get your.gitlab.com/pkg/example&lt;/code>，你很可能会遇到以下错误：&lt;/p>
&lt;pre tabindex="0">&lt;code>go get: module your.gitlab.com/pkg/example: git ls-remote -q origin in /go/pkg/mod/cache/vcs/a39fc2dbfb0a9645950d24df5d7e922bb7a6a877aecfe2b20f74b96385a83109: exit status 128:
fatal: could not read Username for &amp;#39;https://your.gitlab.com&amp;#39;: terminal prompts disabled
Confirm the import path was entered correctly.
If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.
&lt;/code>&lt;/pre>&lt;p>其实错误提示已经把解决方案给到我们了，我们只需要点击 &lt;a href="https://golang.org/doc/faq#git_https">https://golang.org/doc/faq#git_https&lt;/a> 查看即可。&lt;/p>
&lt;p>下面是原文：&lt;/p>
&lt;blockquote>
&lt;h4 id="why-does-go-get-use-https-when-cloning-a-repository">Why does &amp;ldquo;go get&amp;rdquo; use HTTPS when cloning a repository?&lt;/h4>
&lt;p>Companies often permit outgoing traffic only on the standard TCP ports 80 (HTTP) and 443 (HTTPS), blocking outgoing traffic on other ports, including TCP port 9418 (git) and TCP port 22 (SSH).
When using HTTPS instead of HTTP, git enforces certificate validation by default, providing protection against man-in-the-middle, eavesdropping and tampering attacks.
The go get command therefore uses HTTPS for safety.&lt;/p>
&lt;p>Git can be configured to authenticate over HTTPS or to use SSH in place of HTTPS. To authenticate over HTTPS, you can add a line to the $HOME/.netrc file that git consults:&lt;/p>
&lt;pre tabindex="0">&lt;code>machine github.com login USERNAME password APIKEY
&lt;/code>&lt;/pre>&lt;p>For GitHub accounts, the password can be a &lt;a href="https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/">personal access token&lt;/a>.
Git can also be configured to use SSH in place of HTTPS for URLs matching a given prefix. For example, to use SSH for all GitHub access, add these lines to your ~/.gitconfig:&lt;/p>
&lt;pre tabindex="0">&lt;code>[url &amp;#34;ssh://git@github.com/&amp;#34;]
insteadOf = https://github.com/
&lt;/code>&lt;/pre>&lt;/blockquote>
&lt;p>大概意思是，HTTPS 更安全，所以 &lt;code>go get&lt;/code> 命令使用 HTTPS。&lt;/p>
&lt;p>如果你要用 HTTPS，那你就需要配置 HTTPS 的用户名和密码：&lt;/p>
&lt;pre tabindex="0">&lt;code>machine github.com login USERNAME password APIKEY
&lt;/code>&lt;/pre>&lt;p>当然也可以使用 ssh，需要修改你的 git 配置，&lt;/p>
&lt;p>修改当前用户的 &lt;code>~/.gitconfig&lt;/code>，添加：&lt;/p>
&lt;pre tabindex="0">&lt;code>[url &amp;#34;ssh://git@your.gitlab.com/&amp;#34;]
insteadOf = https://your.gitlab.com/
&lt;/code>&lt;/pre>&lt;p>另外执行下面的命令也能达到同样的效果：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git config --global url.&lt;span class="s2">&amp;#34;git@your.gitlab.com/&amp;#34;&lt;/span>.insteadof &lt;span class="s2">&amp;#34;https://your.gitlab.com/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>操作完之后，我们就可以使用 &lt;code>go get&lt;/code> 了，使用 &lt;code>go get -v&lt;/code> 可以展示执行日志。&lt;/p>
&lt;h2 id="gonoproxy">GONOPROXY&lt;/h2>
&lt;p>众所周知，国内用户大多设置代理，我们在 Go 1.12 之前如果使用 &lt;code>GOPROXY&lt;/code> 这个环境变量设置代理，并使用私有仓库，很有可能会遇到下面的错误：&lt;/p>
&lt;pre tabindex="0">&lt;code>go get your.gitlab.com/pkg/example: module your.gitlab.com/pkg/example:
reading https://goproxy.cn/your.gitlab.com/pkg/example/@v/list: 404 Not Found
&lt;/code>&lt;/pre>&lt;p>这是因为代理服务不可能访问到我们的私有代码仓库，所以报错 404。而且，就算使用上文提到的 &lt;code>ssh&lt;/code> 鉴权也不行。&lt;/p>
&lt;p>&lt;strong>Go 1.13 后可以设置 &lt;code>GONOPROXY&lt;/code> 这个环境变量来指定不使用代理的域名，支持逗号分隔多个值。&lt;/strong>&lt;/p>
&lt;h2 id="gonosumdb">GONOSUMDB&lt;/h2>
&lt;p>go mod 需要对下载后的依赖包进行 checksum 校验，当你的 git 仓库是开放的话没问题，但是如果是不可访问的私有仓库，甚至在公司内网。
很可能出现校验失败的错误：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">get &lt;span class="s2">&amp;#34;your.gitlab.com/pkg/example&amp;#34;&lt;/span>: found meta tag get.metaImport&lt;span class="o">{&lt;/span>Prefix:&lt;span class="s2">&amp;#34;your.gitlab.com/pkg/example&amp;#34;&lt;/span>, VCS:&lt;span class="s2">&amp;#34;git&amp;#34;&lt;/span>, RepoRoot:&lt;span class="s2">&amp;#34;https://your.gitlab.com/pkg/example.git&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span> at //your.gitlab.com/pkg/example?go-get&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> verifying your.gitlab.com/pkg/example@v0.0.0: your.gitlab.com/pkg/example@v0.0.0: reading https://sum.golang.org/lookup/your.gitlab.com/pkg/example@v0.0.0: &lt;span class="m">410&lt;/span> Gone
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>和代理一样，我们的私有仓库对 sum.golang.org 是不可见的，所以肯定没办法执行安全校验。&lt;/p>
&lt;p>&lt;strong>同样的在 Go 1.13 后可以设置 &lt;code>GONOSUMDB&lt;/code> 环境变量指定跳过校验的的域名，支持逗号分割多个值。&lt;/strong>&lt;/p>
&lt;h2 id="goprivate">GOPRIVATE&lt;/h2>
&lt;p>最后 Go 1.13 还引入的 &lt;code>GOPRIVATE&lt;/code> 环境变量，可以说设置后一劳永逸，能自动跳过 proxy server 和 校验检查，
这个变量值也支持逗号分割，可以填写多个值，如：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">GOPRIVATE&lt;/span>&lt;span class="o">=&lt;/span>*.corp.example.com,your.gitlab.com
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>当然，设置 &lt;code>GOPRIVATE&lt;/code> 之后，还可以在通过 &lt;code>GONOPROXY&lt;/code> 和 &lt;code>GONOSUMDB&lt;/code> 来单独进行控制，&lt;/p>
&lt;p>不过需要注意下 &lt;code>GOPRIVATE&lt;/code> 失效的问题，&lt;/p>
&lt;p>举个例子，如果公司内部有私有仓库：&lt;code>your.corp.com&lt;/code>，如果这样设置：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">GOPRIVATE&lt;/span>&lt;span class="o">=&lt;/span>your.corp.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">GOPROXY&lt;/span>&lt;span class="o">=&lt;/span>https://goproxy.cn
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">GONOPROXY&lt;/span>&lt;span class="o">=&lt;/span>none
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>因为 &lt;code>GONOPROXY&lt;/code> 的值是 &lt;code>none&lt;/code>，那么用户还是会从 &lt;code>GOPROXY&lt;/code> 的地址下载所有私有和共有的仓库，
此时可能还是会报错，&lt;code>GONOSUMDB&lt;/code> 同理，大家注意一下这个问题。&lt;/p></description><category domain="https://h1z3y3.me/tags/golang/">Golang</category></item><item><title>如何在 Go 中组织项目结构</title><link>https://h1z3y3.me/posts/how-i-structure-services-in-go/</link><guid isPermaLink="true">https://h1z3y3.me/posts/how-i-structure-services-in-go/</guid><pubDate>Wed, 12 May 2021 08:04:11 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>译者注：在翻译这篇文章之前，我自己其实对 Bob 大叔的 Clean Architecture 也做过一些研究，在项目中实践之后，
也确确实实体验到了分层的魅力。在层与层之间将依赖进行隔离，各个层只关注自己本身的逻辑，
所以能让开发者只关注本层的业务逻辑， 也更容易进行单元测试，无形中就提高了你代码的质量和可阅读性。
我觉得如果你对自己的代码有追求，就一定要去学习一下 Clean Architecture。
当然另一方面，Clean Architecture 也不是银弹，在复杂的项目中确实能帮助我们解藕，但是如果你的项目非常简单，
那传统的 MVC 就足够了，就像本文作者最后说的，千万不要让简单的事情变复杂。
另外，其实对于 Golang 的项目组织方式，github 上面火许多 star 非常多的项目，大多开箱即用，
比如：
&lt;a href="https://github.com/xinliangnote/go-gin-api">go-gin-api&lt;/a> （国人开源的）、
&lt;a href="https://github.com/bxcodec/go-clean-arch">go-clean-arch&lt;/a> ，这里分享给大家，也是给大家提供更多的选择。&lt;/p>
&lt;p>以下是原文：&lt;/p>
&lt;p>一个 main.go 文件，几个 HTTP handler 就可以构建一个新的 HTTP 服务。然而，当你开始添加更多的路由规则，
开始将不同的功能拆分到不同的文件中，可能会随处创建好多 packages ，但是你不确定长远来看将会它们怎样发展，
同时你也希望它们能够随着服务增长而有意义。&lt;/p>
&lt;p>这几年我经历过几次这样的场景，后来读了一些文章、博客还有 Robert Martin 的
&lt;a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">Clean Architecture&lt;/a> (干净架构)，
我找到了一种适合我的通用的代码结构，所以我想我应该分享出来。需要注意的是它可能并不能刚好适合你的应用场景，特别是一些特别简单的服务，
比如说一个 main.go 文件，几个 packages 就已经足够的服务。&lt;/p>
&lt;p>让我们直接开始！&lt;/p>
&lt;h2 id="概览">概览&lt;/h2>
&lt;pre tabindex="0">&lt;code class="language-goalng" data-lang="goalng">Go-Service
- cmd/
- api/
- pkg/
- api/
- db/
- services/
- serviceA/
- serviceB/
- ...
- utils/
- docker-compose.yml
- Dockerfile
- Makefile
- go.mod
- go.sum
- &amp;lt;environment&amp;gt;.env
- README.md
- ...
&lt;/code>&lt;/pre>&lt;p>整个结构有 3 个重要的部分：root（译者注：根目录）、cmd 和 pkg。我将逐个解释各个文件夹的职责，
然后我们再来仔细看看每个 service (&lt;code>pkg/service/...&lt;/code>) 如何组织。&lt;/p>
&lt;h3 id="root根目录">Root（根目录）&lt;/h3>
&lt;p>我喜欢将一些启动和运行的代码放到根目录，比如：构建工具、配置文件、依赖管理等等。
它也提供给阅读代码的人或开发代码的人一个很好的切入点，他们启动服务所需的所有配置都在项目根目录下。&lt;/p>
&lt;h3 id="cmd">Cmd&lt;/h3>
&lt;p>这里会被分成几个目录，每个目录都是我们整个服务一部分，比如 API 服务，定时脚本任务等等。
实际上这里会有各子服务的 main package，所以我们在这里初始化配置和我们需要的依赖包，最后子服务会被编译成对应的二进制来提供服务。&lt;/p>
&lt;h3 id="pkg">Pkg&lt;/h3>
&lt;p>这里包含了我们项目的主要部分：定义我们服务业务逻辑的一些 package。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>api/&lt;/p>
&lt;p>在这里，我定义了如何通过初始化数据库，服务，HTTP路由器+中间件来连接API，并定义了运行API所需的配置。
我一般会加一个 &lt;code>Start(cfg *Config)&lt;/code> 函数，提供给 &lt;code>cmd/api/main.go&lt;/code> 调用。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>db/&lt;/p>
&lt;p>顾名思义，这里是连接、迁移数据库逻辑，我也倾向于将任何关于迁移的文件夹或文件都放在这里。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>utils/&lt;/p>
&lt;p>我会将任何对请求、日志、自定义中间件等提供辅助功能的 pakcage 放在这里。我虽然不太喜欢这个名字，但是我也没找到更适合它的名字了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>services/&lt;/p>
&lt;p>这个需要详细解释一下，因为我用特定的方式去组织所有的 service。通常来说，每个 package
都定义了各自服务的功能（基于功能而不是函数进行组织结构）。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="services">Services&lt;/h2>
&lt;p>让我们通过一个例子来看看他们是如何组织的。我们要创建一个服务，可以让我们保存并创建文章，他看起来是下面这样：&lt;/p>
&lt;pre tabindex="0">&lt;code>...
- Services/
- Article/
- store/
- repo.go
- transport/
- http.go
- article.go
- errors.go
- models.go
&lt;/code>&lt;/pre>&lt;p>我们将数据的存储和传输逻辑分到了不同的 package，这帮助我们专注于我们的业务逻辑而不需要关心我们应该如何保存数据或者其如何传递给调用方。
此外，当我们想要改变我们的底层存储时，我们只需要定义好存储的 interface，就可以轻松地更换底层存储，
而不需要修改其余的逻辑（ 一个简单的
&lt;a href="https://en.wikipedia.org/wiki/Dependency_inversion_principle">依赖反转原则&lt;/a> 的例子 ）。&lt;/p>
&lt;p>&lt;code>error.go&lt;/code> 和 &lt;code>models.go&lt;/code> 比较简单，就不赘述了，让我们看看 &lt;code>article.go&lt;/code> 都有什么功能：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">articles&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;context&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Repo defines the DB level interaction of articles
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">type&lt;/span> &lt;span class="nx">Repo&lt;/span> &lt;span class="kd">interface&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">Article&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">Create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ar&lt;/span> &lt;span class="nx">ArticleCreateUpdate&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Service defines the service level contract that other services
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// outside this package can use to interact with Article resources
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">type&lt;/span> &lt;span class="nx">Service&lt;/span> &lt;span class="kd">interface&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">Article&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">Create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ar&lt;/span> &lt;span class="nx">ArticleCreateUpdate&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">Article&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">article&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">repo&lt;/span> &lt;span class="nx">Repo&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// New Service instance
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">repo&lt;/span> &lt;span class="nx">Repo&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nx">Service&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">article&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">repo&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Get sends the request straight to the repo
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">article&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">Article&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">repo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Create passes of the created to the repo and retrieves the newly created record
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">article&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ar&lt;/span> &lt;span class="nx">ArticleCreateUpdate&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">Article&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">id&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">repo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ar&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">Article&lt;/span>&lt;span class="p">{},&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">repo&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">id&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这里需要注意的是，在调用 &lt;code>New()&lt;/code> 创建我们 &lt;code>Article&lt;/code> 服务实例的时候，传递了一个 &lt;code>Repo&lt;/code> interface。
这个是我们刚刚说的解耦的好处，而且也能帮助我们更好地去做单元测试。
我们可以通过创建一个实现了 &lt;code>Repo&lt;/code> interface 的 mock 实例，然后作为 &lt;code>New()&lt;/code> 的参数传递给 &lt;code>Article&lt;/code>，
这样我们就可以绕过我们的数据库去对我们的逻辑进行单元测试。&lt;/p>
&lt;h3 id="如何暴露服务">如何暴露服务&lt;/h3>
&lt;p>设置方法不需要知道每个服务的接入点、如何初始化存储层或者其他的一些事项。
只需要将数据库连接和路由实例传递给 &lt;code>Activate()&lt;/code> 方法，
然后 &lt;code>transport&lt;/code> package 中的路由注册程序将其路由进行注册，就可以对外提供服务了：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">transport&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;database/sql&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;net/http&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/gin-gonic/gin&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/kott/go-service-example/pkg/services/articles&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/kott/go-service-example/pkg/services/articles/store&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">handler&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ArticleService&lt;/span> &lt;span class="nx">articles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Service&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Activate sets all the services required for articles and registers all the endpoints with the engine.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="nf">Activate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">router&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Engine&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">db&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">sql&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DB&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">articleService&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">articles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">store&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">db&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">newHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">router&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">articleService&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">newHandler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">router&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Engine&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">as&lt;/span> &lt;span class="nx">articles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Service&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">h&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">handler&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ArticleService&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">as&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GET&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/articles/:id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">h&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Get&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">POST&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/articles/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">h&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Create&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">h&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">handler&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">h&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">handler&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">c&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>还记得我之前说的 &lt;code>Start()&lt;/code> 方法（在 &lt;code>pkg/api&lt;/code> 中）吗？
它是我们启动我们服务和配置的入口：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">api&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;context&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/gin-gonic/gin&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/kott/go-service-example/pkg/db&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">articles&lt;/span> &lt;span class="s">&amp;#34;github.com/kott/go-service-example/pkg/services/articles/transport&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/kott/go-service-example/pkg/utils/log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/kott/go-service-example/pkg/utils/middleware&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Config defines what the API requires to run
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">type&lt;/span> &lt;span class="nx">Config&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DBHost&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DBPort&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DBUser&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DBPassword&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DBName&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">AppHost&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">AppPort&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Start initializes the API server, adding the required middleware and dependent services
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">func&lt;/span> &lt;span class="nf">Start&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">cfg&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Config&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">conn&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GetConnection&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cfg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DBHost&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cfg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DBPort&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cfg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DBUser&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cfg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DBPassword&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">cfg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DBName&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;unable to establish a database connection: %s&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">conn&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">conn&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">router&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">gin&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Use&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="cm">/* some middleware */&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">articles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Activate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">router&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">conn&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">router&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;%s:%d&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cfg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">AppHost&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cfg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">AppPort&lt;/span>&lt;span class="p">));&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>以上就是全部内容，实际上只是试图去找到一种抽象的方法，以便于让你的程序更易读，而不需要增加你项目的复杂度。&lt;/p>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>有许许多多可以组织项目的方式，这个方式是我认为最好的。
根据功能将 service 分开有助于之后进行修改时定义上下文边界和代码导航。
将路由注册、业务逻辑、存储等放到同一个 &lt;code>service&lt;/code> 层中，也让我们更关注业务逻辑本身和更容易地进行测试。
不要让简单的事情变复杂！如果您想节约时间，那就一定要这样做。&lt;/p>
&lt;p>我希望能帮助到你，如果你想阅读所有源码，你可以在 &lt;a href="https://github.com/kott/go-service-example">Github&lt;/a> 下载。&lt;/p>
&lt;hr>
&lt;p>via: &lt;a href="https://medium.com/@ott.kristian/how-i-structure-services-in-go-19147ad0e6bd">https://medium.com/@ott.kristian/how-i-structure-services-in-go-19147ad0e6bd&lt;/a>&lt;/p>
&lt;p>作者：&lt;a href="https://medium.com/@ott.kristian">Kristian Ott&lt;/a>
译者：&lt;a href="https://h1z3y3.me">h1z3y3&lt;/a>&lt;/p>
&lt;p>本文由 &lt;a href="https://github.com/studygolang/GCTT">GCTT&lt;/a> 原创编译，&lt;a href="https://studygolang.com/">Go 中文网&lt;/a> 荣誉推出&lt;/p></description><category domain="https://h1z3y3.me/tags/golang/">Golang</category><category domain="https://h1z3y3.me/tags/clean-arch/">clean-arch</category></item><item><title>使用 Github Actions 自动发布 hugo 站点</title><link>https://h1z3y3.me/posts/hugo-auto-deploy-github-with-actions/</link><guid isPermaLink="true">https://h1z3y3.me/posts/hugo-auto-deploy-github-with-actions/</guid><pubDate>Thu, 22 Apr 2021 22:21:44 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>从 2018 年底到 2021 年初，博客一直搁置，虽然也偶尔写几篇文章，但是都发布在团队的公众号中了， 现在重新开始维护自己的博客，也从
&lt;a href="https://github.com/hexojs/hexo">hexo&lt;/a>
转移到了
&lt;a href="https://github.com/gohugoio/hugo">hugo&lt;/a> ，
这两个都是非常优秀的静态网站生成工具。&lt;/p>
&lt;p>但是不得不说，使用 hugo 之后感觉易用性以及文件的生成速度都优于我之前使用的 hexo 的版本（说的严谨一些，我也不知道现在如何）。&lt;/p>
&lt;p>同时我还给自己重新挑选了主题，使用的是
&lt;a href="https://github.com/h1z3y3/hugo-theme-meme">hugo-theme-meme&lt;/a> ，
并且我还做了一部分自定义， 比如 文章列表页 的样式、字体、还修改了移动端标题过长样式错乱的 bug，
另外还接入了 &lt;a href="">Gitalk&lt;/a>（已经提 pr 并合并）和页面右下角的 &lt;a href="">Webpusher&lt;/a> (没有提交 PR),
不得不说，把自己的博客评论用 &lt;a href="https://github.com/h1z3y3/h1z3y3.github.io/issues">Github Issue&lt;/a> 来维护的想法也太赞了！&lt;/p>
&lt;h1 id="使用-github-action-自动发布-hugo-站点">使用 Github Action 自动发布 hugo 站点&lt;/h1>
&lt;p>如何使用 hugo 以及 Github Pages 的基本知识我就不再赘述，本文主要给大家讲解如何使用 Github Actions 自动编译你的站点实时发布，
本文可以帮你解决的问题是：如何使用你的 &lt;code>*.github.io&lt;/code> 的仓库维护站点的配置 （&lt;code>config.toml&lt;/code>）、
使用的主题、或者自己 markdown 格式的原文，而不需要创建新仓库。&lt;/p>
&lt;p>因为我自己一开始也想创建新仓库维护，但是后来查阅了一些相关文档后，发现完全没必要，下面介绍给大家。&lt;/p>
&lt;h2 id="定义-github-actions">定义 Github Actions&lt;/h2>
&lt;p>下面是我站点的 Hugo Actions 的配置文件，和原始配置有些小改动，
原始配置文件可以在 &lt;a href="https://github.com/peaceiris/actions-gh-pages">这里&lt;/a> 找到&lt;/p>
&lt;p>在 hugo 站点的根目录创建 &lt;code>.github/workflows/gh-pages.yml&lt;/code> ，并将下面的内容拷贝进去。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">github pages&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># on 是 Actions 的触发条件，这里的配置说明当 master 分支有提交的时候，根据这个配置文件执行&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">push&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">branches&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">master&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Set a branch to deploy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># jobs 是要执行的任务，我们看到他要执行 deploy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">deploy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-18.04&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 运行环境&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 执行步骤&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># checkout 分支&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">submodules&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Fetch Hugo themes (true OR recursive)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fetch-depth&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Fetch all history for .GitInfo and .Lastmod&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># 安装 hugo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Setup Hugo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">peaceiris/actions-hugo@v2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hugo-version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;latest&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># extended: true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># 编译站点&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">hugo --minify&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># 创建 CNAME，这个是原始配置中没有的&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;finnp/create-file-action@master&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">FILE_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;./public/CNAME&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">FILE_DATA&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;h1z3y3.me&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># 将站点发布到对应分支&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Deploy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">peaceiris/actions-gh-pages@v3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">github_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.GITHUB_TOKEN }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">publish_dir&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">./public&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Github Actions 更详细的使用可以从
&lt;a href="https://docs.github.com/en/actions/learn-github-actions">这里&lt;/a> 获取。&lt;/p>
&lt;h2 id="设置-hugo-目录的提交仓库为-githubio">设置 hugo 目录的提交仓库为 *.github.io&lt;/h2>
&lt;p>如果你的 hugo 没在 github 维护，执行:&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; git remote add origin git@github.com:your/your.github.io.git
&lt;/code>&lt;/pre>&lt;p>如果已经在 github 维护了，执行：&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; git remote set-url origin git@github.com:your/your.github.io.git
&lt;/code>&lt;/pre>&lt;p>如果仓库有文件，很可能你需要使用 &lt;code>git push -f origin master&lt;/code> 来强制覆盖。&lt;/p>
&lt;p>推送到 Github 之后，现在 Github 的代码就是你 hugo 的根目录，这时候我们的 *.github.io 的站点肯定不工作了，接下来往下看。&lt;/p>
&lt;h2 id="actions-执行">Actions 执行&lt;/h2>
&lt;p>我们切换到 &lt;strong>Actions&lt;/strong> 选项卡，可以看到我们刚才提交的工作流已经执行了，
同时在 &lt;strong>Code&lt;/strong> 选项卡，也帮我们创建好了一个 &lt;code>gh-pages&lt;/code> 的分支，
切换之后发现这个 hugo 已经编译好的静态站点。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/hugo-auto-deploy-github-with-actions/actions-list.png" alt="">&lt;/p>
&lt;h2 id="修改-github-page-站点的默认分支">修改 Github Page 站点的默认分支&lt;/h2>
&lt;p>没错，&lt;code>*.github.io&lt;/code> 的仓库，允许你自定义你要提供静态站点的分支，默认是 master，可以自定义为其他的&lt;/p>
&lt;p>操作方法：&lt;/p>
&lt;p>进入你的 &lt;code>*.github.io&lt;/code> 的仓库，点击项目的 &lt;code>Settings&lt;/code> ，并点击左侧的 &lt;code>Pages&lt;/code>，在这里你可以自定义一些你站点的配置，
在这里你可以自己选择自己站点要使用的&lt;code>分支&lt;/code>以及&lt;code>目录&lt;/code>，我们这里切换为 &lt;code>gh-pages&lt;/code> 这个分支，并点击保存。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/blog_images/master/hugo-auto-deploy-github-with-actions/set-github-pages-default-branch.png" alt="">&lt;/p>
&lt;h2 id="检查">检查&lt;/h2>
&lt;p>全部设置好之后，稍等几分钟生效后，通过浏览器访问 &lt;code>your.github.io&lt;/code>，就可以正常访问了，之后每次我们文章有更新，
从 &lt;code>master&lt;/code> 分支提交之后，Github 就会通过 Actions 帮我们去编译然后发布在 &lt;code>gh-pages&lt;/code> 分支，十分方便。&lt;/p>
&lt;p>如果你设置了 CNAME，那么请参照我 Actions 配置，里面有创建 CNAME 文件的步骤，添加到你自己的配置即可。&lt;/p></description><category domain="https://h1z3y3.me/tags/hugo/">hugo</category></item><item><title>Web 反爬指南（或至少让其更难抓取）</title><link>https://h1z3y3.me/posts/how-to-prevent-scraping/</link><guid isPermaLink="true">https://h1z3y3.me/posts/how-to-prevent-scraping/</guid><pubDate>Thu, 22 Apr 2021 20:02:34 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h1 id="web-反爬指南或至少让其更难抓取">Web 反爬指南（或至少让其更难抓取）&lt;/h1>
&lt;hr>
&lt;p>提示：这篇文章是我 Stack Overflow &lt;a href="http://stackoverflow.com/a/34828465/4428462">这个问题&lt;/a> 回答的扩展，
我把它整理在 Github 因为它实在是太长了，超过了 Stack Overflow 的字数限制（最多 3 万个字，这文章已经超过 4 万字）&lt;/p>
&lt;p>欢迎大家修改、完善还有分享，本文使用 &lt;a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA 3.0&lt;/a> 许可。&lt;/p>
&lt;hr>
&lt;p>本质上说，&lt;strong>防抓的目的在于增加脚本或机器获取你网站内容的难度，而不要影响真实用户的使用或搜索引擎的收录&lt;/strong>&lt;/p>
&lt;p>不幸的是这挺难的，你需要在防抓和降低真实用户以及搜索引擎的可访问性之间做一下权衡。&lt;/p>
&lt;p>为了防爬（也称为网页抓取、屏幕抓取、网站数据挖掘、网站收割或者网站数据获取），
了解他们的工作原理很重要，这能防止他们能高效爬取，这个就是这篇文章的主要内容。&lt;/p>
&lt;p>通常情况下，抓取程序的目的是为了获取你网站特定的信息，比如文章内容、搜索结果、产品详情还有网站上艺术家或者相册信息。
他们爬取这些内容用于维护他们自己的网站（甚至通过你的内容赚钱！），或者制作和你网站不一样的前端界面（比如去做移动 APP），
还有一些可能作为个人研究或分析使用。&lt;/p>
&lt;p>实际上，有特别多的爬虫类型，而且他们的爬取方式都不太相同：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>蜘蛛，比如 &lt;a href="http://googlebot.com/">Google&amp;rsquo;s bot&lt;/a>
或者网站复制工具 &lt;a href="http://www.httrack.com/">HTtrack&lt;/a>，他们访问你的网站，
而且在页面中寻找链接后递归的去爬取以获取页面的数据。有时候他们只用于获取特定数据，
而不会全部爬取，通常结合 HTML 分析器在页面中获取想要的数据。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Shell 脚本，有时候，通用的 Unix 工具也被用来爬取：wget 或者 curl 用于下载页面，
用 Grep （Regex） 去分析获取想要的数据，一般都会写一个 Shell 脚本。
这些都是最简单的爬虫，也是最脆弱的一类爬虫
(&lt;a href="https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454">Don&amp;rsquo;t ever try parse HTML with regex !&lt;/a>），
所以是最早发现和防范的爬虫。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>HTML 爬取器和分析器，会基于 &lt;a href="http://jsoup.org/">Jsoup&lt;/a>、&lt;a href="http://scrapy.org/">Scrapy&lt;/a> 还有其他一些工具。
和 shell 脚本基于正则类似，他们能基于特定模式（pattern）分析你的 HTML 结构从而获取想要的数据。&lt;/p>
&lt;p>举个例子：如果你的网站有搜索功能，那这个爬虫可能会模拟提交一个搜索的 HTTP 请求，然后从搜索结果页中获取所有的链接和标题，有时候会构造有成千上百不同的请求，只是为了获取标题和链接而已。这是最常见的一类爬虫。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>屏幕爬取，比如会基于 &lt;a href="http://www.seleniumhq.org/">Selenium&lt;/a> 或者 &lt;a href="http://phantomjs.org/">PhantomJS&lt;/a>，
他们实际上会通过真实浏览器打开你的网站，因此也会运行你网站的 JavaScript、AJAX 或者其他的，
然后他们从你的页面获取自己想要的文本内容，通常：&lt;/p>
&lt;ul>
&lt;li>等页面加载完毕， JavaScript 也执行之后，从浏览器获取 HTML 结构，然后用 HTML 分析器去获取想要的数据或者文本。这是常见做法，所以有很多方法可以防止这类 HTML 分析器或爬虫。&lt;/li>
&lt;li>获取加载完页面的屏幕截图，然后使用 OCR 分析从截屏中获取想要的数据。这类不常见，而且只有非常想要你网站内容的爬虫才会使用。&lt;/li>
&lt;/ul>
&lt;p>基于浏览器的爬去很难处理，他们运行脚本，渲染 HTML，就像真实用户一样访问你的网站。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Web爬取服务，比如 &lt;a href="http://scrapinghub.com/">ScrapingHub&lt;/a>或者 &lt;a href="https://www.kimonolabs.com/">Kimono&lt;/a>。
实际上，很多人的工作就是找出如何爬取你的页面获取其中内容并给其他人使用。
他们有时会用大量的代理甚至修改 IP 地址来绕过频率限制和封禁，所以他们是反爬的关键对象。&lt;/p>
&lt;p>毫无疑问，防止专业的爬取服务是非常困难的，但是如果你增加爬取的难度，或者增加他们找出爬取方法的时间，那这些人（或者付钱让他们这样做的人）可能会放弃爬取你的网站。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>把你的网站页面通过 &lt;a href="https://en.wikipedia.org/wiki/Framing_(World_Wide_Web)">frames&lt;/a> 嵌入其他站点， 或者把你的页面嵌入移动 APP。&lt;/p>
&lt;p>虽然没有什么技术含量，但是确实也是个问题，比如移动 APP（Android 和 iOS）可以嵌入你的网站，甚至可以注入一些自定义的 CSS 和
JavaScript，所以可以完全改变你网站的外观，然后只展示想要的信息，比如只展示文章内容或者搜索结果，然后隐藏你网站的 headers、footers 还有广告。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>人肉复制粘贴：人肉复制和粘贴你网站的内容到其他地方。很遗憾，这个没有好的方法加以防范。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>以上不同的爬虫类型有很多相同点，很多爬虫的行为也很相似，即使他们使用了不同的技术或者方案去爬取你的内容。&lt;/p>
&lt;p>这些大多数都是我自己的想法，我在写爬虫时也遇到许多困难，还有一些是来源于网络。&lt;/p>
&lt;h2 id="如何反爬">如何反爬&lt;/h2>
&lt;p>一些常见的检测和防止爬虫的方法：&lt;/p>
&lt;h3 id="监控你的日志和请求当发现异常行为时限制访问">监控你的日志和请求；当发现异常行为时限制访问&lt;/h3>
&lt;p>周期性的检查你的日志，如果发现有异常活动表明是自动爬取（爬虫），类似某一个相同的 IP 很多相同的行为， 那你就可以阻止或限制访问。&lt;/p>
&lt;p>一些常用的方法：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>频率限制&lt;/strong>&lt;/p>
&lt;p>只允许用户（或爬虫）在一段时间内访问特定的次数，举个例子，某个 IP 或用户只允许一分钟搜索很少的次数。
这个会减慢爬虫的爬取速度，让他们变得低效。如果次数特别多或者比真实用户多很多，那你也可以显示验证码页面。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>检测异常行为&lt;/strong>&lt;/p>
&lt;p>如果你能看到异常行为，比如同一个 IP 有很多相同的请求，有些会翻很多页或者访问一些异常的页码，你可以拒绝访问或者在后续的请求中展示验证码。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>不要只通过 IP 检测和限制，也要用其他的用户标识&lt;/strong>&lt;/p>
&lt;p>如果你做了访问限制或频率限制，不要只简单的根据单个 IP 地址去做；你可以通过其他的标识和方法去识别一个用户或爬虫。
一些可以帮你识别用户/爬虫的标识：&lt;/p>
&lt;ul>
&lt;li>用户填写表单的速度，还有他们点击按钮的位置&lt;/li>
&lt;li>你可以通过 JavaScript 获取很多信息，比如屏幕的大小 / 分辨率，时区，安装的字体等等，你可以用这些去识别用户&lt;/li>
&lt;li>携带的 HTTP 头，特别是 User-Agent&lt;/li>
&lt;/ul>
&lt;p>举个例子，如果某个 IP 请求了你网站很多次，所有的访问都有相同的 UserAgent 、屏幕尺寸（JavaScript
检测），还有全都使用同样的方式和固定的时间间隔点击同一个按钮，那它大概是一个屏幕爬虫；
你可以临时限制相似的请求（比如 只限制来自那个 IP 特定 User-Agent 和 屏幕尺寸的请求），这样你不会误伤同样使用这个 IP 的真实用户，比如共享网络链接的用户。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>更进一步，当你发现相似的请求，但是来自不同的 IP 地址，表明是分布式爬虫（使用僵尸网络或网络代理的爬虫）。
如果你收到类似大量的请求，但是他们来自不同的 IP 地址，你可以拒绝访问。再强调一下，小心不经意限制了真实用户。&lt;/p>
&lt;p>这种方法对那种运行 JavaScript 的屏幕爬虫比较有效，因为你可以获得他们大量的信息。&lt;/p>
&lt;p>Stack Exchange 上关于 Secruity 的相关问题：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="http://security.stackexchange.com/questions/81302/how-to-uniquely-identify-users-with-the-same-external-ip-address">How to uniquely identify users with the same external IP address? &lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="http://security.stackexchange.com/questions/96377/why-do-people-use-ip-address-bans-when-ip-addresses-often-change">Why do people use IP address bans when IP addresses often change? &lt;/a>
关于仅使用 IP 的其他限制&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>使用验证码，而不是临时限制访问&lt;/strong>&lt;/p>
&lt;p>对于频率限制，最简单的方式就是临时限制访问，然而使用验证码会更好，可以看下面关于验证码的部分。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="要求注册和登录">要求注册和登录&lt;/h3>
&lt;p>如果可行，要求创建用户才可以查看内容。这个可以很好的遏制爬虫，但很容易遏制真实用户：&lt;/p>
&lt;ul>
&lt;li>如果你要求必须创建账户和登录，你可以精准的跟踪用户和爬虫的行为。这样的话，你可以很简单就能检测到有爬取行为的账户，然后封禁它。
像频率限制或检测滥用（比如一段时间有大量搜索）就变得简单，你也可以不仅仅通过 IP 去识别爬虫。&lt;/li>
&lt;/ul>
&lt;p>为了防止爬虫创建大量的用户，你应该：&lt;/p>
&lt;ul>
&lt;li>注册时需要提供 email 地址，发送一个验证链接到邮箱，而且这个链接必须被打开才可以激活这个账户。一个邮箱只允许一个账户使用。&lt;/li>
&lt;li>在注册或创建用户时，必须通过验证码验证，为了防止创建用户的自动脚本。&lt;/li>
&lt;/ul>
&lt;p>要求注册用户对用户和搜索引擎来说不友好；如果你要求必须注册才可以看文章，那用户也可能直接离开。&lt;/p>
&lt;h3 id="阻止来自云服务商和抓取服务的-ip">阻止来自云服务商和抓取服务的 IP&lt;/h3>
&lt;p>有时，爬虫会被运行在云服务商，比如 Amazon Web Services 或 Google App Engine，或者其他 VPS。
限制这些来自云服务商的 IP 地址访问你的网站（或出验证码）。 你可以可以直接对来自爬取服务的 IP 地址限制访问。&lt;/p>
&lt;p>类似的，你也可以限制来自代理或 VPN 的 IP 地址，因为很多爬虫会使用它们来防止被检测到。&lt;/p>
&lt;p>但是要知道限制来自代理服务器或 VPN 的 IP，也很容易影响到真实用户。&lt;/p>
&lt;h3 id="当你封禁时不要展示具体错误信息">当你封禁时不要展示具体错误信息&lt;/h3>
&lt;p>如果你要封禁或限制访问，你应该确保不要把导致封禁的原因告诉爬虫，他们会根据提示修改自己的爬虫程序。所以下面的这些错误最好不要出现：&lt;/p>
&lt;ul>
&lt;li>你的 IP 访问太多次了，请重试&lt;/li>
&lt;li>出错了，没有 User Agent&lt;/li>
&lt;/ul>
&lt;p>相反的，展示一些不包含原因并对用户友好信息会更好，比如下面的信息会更好：&lt;/p>
&lt;ul>
&lt;li>对不起，有些不对劲。如果问题持续出现，你可以联系 &lt;a href="mailto:helpdesk@example.com">helpdesk@example.com&lt;/a> 以获取支持。&lt;/li>
&lt;/ul>
&lt;p>如果真实用户看到这个错误页面，这个对真实用户来说也十分友好。在后续访问中，你也可以考虑展示验证码而不是直接封禁，
如果真实用户看到这个错误信息，对于合法用户也会联系你。&lt;/p>
&lt;h3 id="如果有爬虫访问请使用验证码">如果有爬虫访问，请使用验证码&lt;/h3>
&lt;p>验证码（Captcha，“Completely Automated Test to Tell Computers and Humans Apart”)对于防爬十分有效。不幸的是，它也很容易惹恼用户。&lt;/p>
&lt;p>因此，如果你发现疑似爬虫，而且想要阻止它的爬取行为，而不是封禁它以免它是真实用户。你可以考虑显示验证码在它再次允许访问之前。&lt;/p>
&lt;p>使用验证码的注意事项：&lt;/p>
&lt;ul>
&lt;li>不要造轮子，使用类似 Google 的 &lt;a href="https://www.google.com/recaptcha/intro/index.html">reCaptcha&lt;/a>的一些服务：
比你自己实现一套验证码服务要简单太多，对于用户来说，也比识别模糊或扭曲的文字更友好（用户一般只需要点击一下），
而且相对于你自己提供的简单验证码图片，爬虫也更难去破解&lt;/li>
&lt;li>不要在 HTML 结构中包含验证码答案：我曾经看到过一个网站在它自己页面上有验证码的答案，（虽然隐藏的很好）所以让验证码根本就没有用。
不要做类似的事情。再强调一次，用像 reCaptcha 的服务，你将不会有类似的问题（如果你正确地使用它）&lt;/li>
&lt;/ul>
&lt;h3 id="将你的文字转位图片">将你的文字转位图片&lt;/h3>
&lt;p>你可以在服务端将文字渲染成图片显示，他将防止简单的爬虫去获取文字内容。&lt;/p>
&lt;p>然而，这个对于屏幕阅读器、搜索引擎、性能还有一些其他一些事情都不太好。
这个在某些方面也是不违法的（源于访问性问题，eg. the Americans with Disabilities Act），而且对于一些 OCR 爬虫也能非常简单的规避，所以不要采用这种方式。&lt;/p>
&lt;p>你也可以用 CSS sprites 做类似的事情，但是也有相同的问题。&lt;/p>
&lt;h3 id="不要暴露你完整的数据">不要暴露你完整的数据&lt;/h3>
&lt;p>如果可以的话，不要让脚本/爬虫能获取你所有的数据。举个例子：你有一个新闻站，有大量的个人文章。
你应该确保文章只能通过你自己网站的搜索到，如果你网站不是到处都有你的文章还有链接，那么确保它们只能被搜索功能访问到。
这意味着如果一个脚本想要获取你网站的所有文章，就必须通过你站点文章中所有可能的短语去进行搜索，从而获取所有的文章列表，但是这个会非常耗时，而且低效，从而让爬虫放弃。&lt;/p>
&lt;p>以下操作会让你完全暴露：&lt;/p>
&lt;ul>
&lt;li>爬虫/脚本并不想/需要获取所有的数据&lt;/li>
&lt;li>你站点文章的链接看起来是这种方式 &lt;code>example.com/article.php?articleId=12345&lt;/code>，这样做（或类似的其他做法）会让爬虫很简单的迭代 &lt;code>articleID&lt;/code> 就能获取你所有的文章&lt;/li>
&lt;li>还有一些其他方式获取所有的文章，比如写一个脚本去递归的爬取你文章中其他文章的链接&lt;/li>
&lt;li>搜索一些常用的词比如 &amp;ldquo;一&amp;rdquo; 或者 &amp;ldquo;的&amp;rdquo; 将会暴露几乎所有的内容。所以这点是要注意的（你可以通过只返回 top 10 或 top 20 来避免这个问题）&lt;/li>
&lt;li>你的文章需要被搜索引擎收录&lt;/li>
&lt;/ul>
&lt;h3 id="不要暴露你的-api节点或其他类似的东西">不要暴露你的 API、节点或其他类似的东西&lt;/h3>
&lt;p>确保你不会暴露你的任何 API，很多时候都是无意间暴露。举个例子，如果你正在使用 AJAX 或 Adobe Flash 的网络请求 或 Java Applet（千万不要用！）来加载你的数据，
从这些网络请求中找到要请求的 API 十分简单，比如可以进行反编译然后在爬虫程序中使用这些接口。确保你混淆了你的 API 并且其他人要用它会非常难破解。&lt;/p>
&lt;h2 id="阻止-html-解析器和爬取器">阻止 HTML 解析器和爬取器&lt;/h2>
&lt;p>由于 HTML 解析器是通过分析页面特定结构去获取内容，我们可以故意修改这些结构来阻止这类爬虫，甚至从根本上他们获取内容。
下面大多数做法对其他类型爬虫如搜索引擎蜘蛛、屏幕爬虫也有效。&lt;/p>
&lt;h3 id="经常修改你的-html-结构">经常修改你的 HTML 结构&lt;/h3>
&lt;p>处理 HTML 的爬虫通过分析特定可识别的部分来处理 HTML。举个例子：如果你所有的页面都有 id 为 &lt;code>article-content&lt;/code> 的 &lt;code>div&lt;/code>结构，然后里面有你的文章内容，
那要获取你站点所有文章内容是十分简单的，只要解析那个 &lt;code>article-content&lt;/code> 那个 &lt;code>div&lt;/code> 就可以了，然后有这个结构的爬虫就可以把你的文章随便用在什么地方。&lt;/p>
&lt;p>如果你常常修改 HTML 还有你页面的结构， 那这类爬虫就不能一直使用。&lt;/p>
&lt;ul>
&lt;li>你可以经常修改你 HTML 的 &lt;code>id&lt;/code> 和 &lt;code>class&lt;/code>，甚至让他能自动改变。所以，如果你的 &lt;code>div.article-content&lt;/code> 变成 &lt;code>div.a4c36dda13eaf0&lt;/code>，而且每周都会变化，
那么爬虫一开始可能能正常工作，但是一周之后就不能使用了。同时也确保修改你 id/class的长度，这样也可以避免爬虫使用类似 &lt;code>div.[any-14-characters]&lt;/code> 来找到想要的 &lt;code>div&lt;/code>。&lt;/li>
&lt;li>如果无法从标记中找到所需的内容，则抓取工具将通过HTML的结构方式进行查找。所以，如果你的所有文章都有类似的结构，比如每个 &lt;code>div&lt;/code>，并且里面通过 &lt;code>h1&lt;/code>
放文章的标题，爬虫将基于这个结构获取文章内容。同样的，为了防止这个，你可以在你的 HTML 添加/删除 额外的标记，周期并且随机的做，eg. 添加额外的 &lt;code>div&lt;/code> 或 &lt;code>span&lt;/code>。对于服务端渲染的程序，这应该不会很难。&lt;/li>
&lt;/ul>
&lt;h4 id="注意事项">注意事项&lt;/h4>
&lt;ul>
&lt;li>它的实现、维护和调试都是很复杂困难的&lt;/li>
&lt;li>你要注意缓存。特别是你修改你 HTML 元素的 id 或 class 时，也要去修改相应的 CSS 和 JavaScript
文件，这意味着每次修改都要修改这些，而浏览器每次都要重新下载他们。这将导致页面打开慢也会导致服务端负载升高。不过这也不是一个大问题，如果你只是一个星期改变一次。&lt;/li>
&lt;li>聪明的爬虫仍然能推断出你文章的位置，比如，页面上大块的文本大概率是文章内容。这个让爬虫从页面找到、获取想要的数据。 &lt;a href="https://code.google.com/archive/p/boilerpipe/">Boilerpipe&lt;/a>
就是这样做的。&lt;/li>
&lt;/ul>
&lt;p>本质上来说，就是确保爬虫从相似页面获取想要的内容变得不那么容易。&lt;/p>
&lt;p>可以参考这个 PHP
的实现：&lt;a href="http://stackoverflow.com/questions/30361740/">How to prevent crawlers depending on XPath from getting page contents&lt;/a>&lt;/p>
&lt;h3 id="基于用户地理位置修改-html">基于用户地理位置修改 HTML&lt;/h3>
&lt;p>这个和前一个类似。如果你根据不同用户的位置/国家（根据 IP 获取）来提供不同的 HTML，这个可能会破坏将站点 HTML 给用户的爬虫。比如，如果有人写了一个移动 APP
来抓取你的站点，一开始可以用，但是对于不同地区的用户就不起作用了，因为他们会获取到不同的 HTML 结构，嵌入式的 HTML 将不不能正常使用。&lt;/p>
&lt;h3 id="经常改变-html并与爬虫斗智斗勇">经常改变 HTML，并与爬虫斗智斗勇！&lt;/h3>
&lt;p>举个例子：你有一个包含搜索功能的网站，&lt;code>example.com/search?query=somesearchquery&lt;/code> 的搜索结果是下面的 HTML 结构：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;search-result&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h3&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;search-result-title&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Stack Overflow has become the world&amp;#39;s most popular programming Q &lt;span class="err">&amp;amp;&lt;/span> A website&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h3&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;search-result-excerpt&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>The website Stack Overflow has now become the most popular programming Q &lt;span class="err">&amp;amp;&lt;/span> A
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> website, with 10 million questions and many users, which...&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="na">search-result-link&lt;/span>&lt;span class="err">&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/stories/stack-overflow-has-become-the-most-popular&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Read more&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(And so on, lots more identically structured divs with search results)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>你可以猜到这个非常容易爬取：一个爬虫需要做的就只是访问搜索链接，然后从返回的 HTML 分析想要的数据。除了定期修改上面 HTML 的内容，
你也可以&lt;strong>保留旧结构的 id 和 class，然后使用 CSS 进行隐藏，并使用假数据进行填充，从而给爬虫投毒&lt;/strong>。比如像下面这样：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;the-real-search-result&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h3&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;the-real-search-result-title&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Stack Overflow has become the world&amp;#39;s most popular programming Q &lt;span class="err">&amp;amp;&lt;/span> A
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> website&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h3&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;the-real-search-result-excerpt&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>The website Stack Overflow has now become the most popular programming Q &lt;span class="err">&amp;amp;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> A website, with 10 million questions and many users, which...&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="na">the-real-search-result-link&lt;/span>&lt;span class="err">&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/stories/stack-overflow-has-become-the-most-popular&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Read more&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;search-result&amp;#34;&lt;/span> &lt;span class="na">style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;display:none&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h3&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;search-result-title&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Visit example.com now, for all the latest Stack Overflow related news !&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h3&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;search-result-excerpt&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>EXAMPLE.COM IS SO AWESOME, VISIT NOW! (Real users of your site will never see this,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> only the scrapers will.)&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="na">search-result-link&lt;/span>&lt;span class="err">&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;http://example.com/&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Visit Now !&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(More real search results follow)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>这意味着基于 id 或 class 获取特定数据的爬虫仍然能继续工作，但是他们将会获取假数据甚至广告，而这些数据真实用户是看不到的，因为他们被 CSS 隐藏了。&lt;/p>
&lt;h3 id="与爬虫斗智斗勇在页面插入假的不可见的蜜罐数据">与爬虫斗智斗勇：在页面插入假的、不可见的蜜罐数据&lt;/h3>
&lt;p>对上个例子进行补充，你可以在你的 HTML 里面增加不可见的蜜罐数据来抓住爬虫。下面的例子来补充之前说的搜索结果：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;search-result&amp;#34;&lt;/span> &lt;span class="na">style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;display:none&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">h3&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;search-result-title&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>This search result is here to prevent scraping&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">h3&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;search-result-excerpt&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>If you&amp;#39;re a human and see this, please ignore it. If you&amp;#39;re a scraper, please click
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> the link below :-)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Note that clicking the link below will block access to this site for 24 hours.&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">a&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>&lt;span class="na">search-result-link&lt;/span>&lt;span class="err">&amp;#34;&lt;/span> &lt;span class="na">href&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/scrapertrap/scrapertrap.php&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>I&amp;#39;m a scraper !&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">a&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(The actual, real, search results follow.)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>一个来获取所有内容的爬虫将会被找到，就像获取其他结果一样，访问链接，查找想要的内容。一个真人将不会看到（因为使用 CSS
隐藏），而且更不会访问这个链接。而正规或者期望的蜘蛛比如谷歌的蜘蛛将不会访问这个链接，因为你可以将 &lt;code>/scrapertrap/&lt;/code> 加入你的 robots.txt 中（不要忘记增加）&lt;/p>
&lt;p>你可以让你的 &lt;code>scrapertrap.php&lt;/code> 做一些比如限制这个 IP 访问的事情，或者强制这个 IP 之后的请求出验证码。&lt;/p>
&lt;ul>
&lt;li>不要忘记在你的 robots.txt 中添加禁止访问 &lt;code>/scrapertrap/&lt;/code> 的规则避免所有的搜索引擎不会中招&lt;/li>
&lt;li>你可以/应该和上个经常更换 HTML 结构的例子一起使用&lt;/li>
&lt;li>也要经常修改它，因为爬虫可能会周期性的了解并不去访问它。修改蜜罐 URL 和文本。
同时要考虑使用 id 属性或外部的 CSS 来取代内联的 CSS，要不然爬虫将会学会防止爬取所有包含隐藏属性 &lt;code>style&lt;/code> 的节点。
并且只是偶尔启用它， 这样爬虫一开始正常工作，但是过段时间就不起作用。这个同样也适用于上个例子。&lt;/li>
&lt;li>要注意可能会有恶意的人在论坛发布像 &lt;code>[img]http://yoursite.com/scrapertrap/scrapertrap.php[img]&lt;/code>，
然后当正常用户访问轮然然后点击到你的蜜罐链接。所以上个注意事项中经常更换你的蜜罐链接是非常重要的，当然你也可以检查 Referer。&lt;/li>
&lt;/ul>
&lt;h3 id="当识别为爬虫时提供假的或无用的数据">当识别为爬虫时，提供假的或无用的数据&lt;/h3>
&lt;p>如果你确定某个访问是爬虫，你可以提供假的或者无用的数据；这将破坏爬虫从你网站获取的数据。
你还应该把它和真实数据进行混淆，这样爬虫就不知道他们获取的到底是真的还是假的数据。&lt;/p>
&lt;p>举个例子：如果你有一个新闻站，你检测到了一个爬虫，不要去直接封禁它，
而是提供假的或者 &lt;a href="https://en.wikipedia.org/wiki/Markov_chain#Markov_text_generators">随机生成&lt;/a>
的文章，那爬虫获取的数据将会被破坏。如果你将假数据和真实的数据进行混淆，那爬虫很难获取到他们想要的真实文章。&lt;/p>
&lt;h3 id="不要接受没有-useragent-的请求">不要接受没有 UserAgent 的请求&lt;/h3>
&lt;p>很多懒惰的程序员不会在他们的爬虫发请求时带上 UserAgent，而所有的浏览器包括搜索引擎蜘蛛都会携带。&lt;/p>
&lt;p>如果请求你时没有携带 UserAgent header 头，你可以展示验证码，或者直接封禁或者限制访问（或者像上面说的提供假数据或者其他的）&lt;/p>
&lt;p>这个非常简单去避免，但是作为一种针对书写不当的爬虫，是值得去做的。&lt;/p>
&lt;h3 id="不要接受-useragent-是通用爬虫或在爬虫黑名单的请求">不要接受 UserAgent 是通用爬虫或在爬虫黑名单的请求&lt;/h3>
&lt;p>很多情况下，爬虫将会使用真实浏览器或搜索引擎爬虫绝对不会使用的 UserAgent，比如：&lt;/p>
&lt;ul>
&lt;li>&amp;ldquo;Mozilla&amp;rdquo; （就只有这个，我曾经看到过一些爬虫的问题用这个，但真实浏览器绝对不会用）&lt;/li>
&lt;li>&amp;ldquo;Java 1.7.43_u43&amp;rdquo; （Java 的 HttpUrlConnection 的默认 UserAgent）&lt;/li>
&lt;li>&amp;ldquo;BIZCO EasyScraping Studio 2.0&amp;rdquo;&lt;/li>
&lt;li>&amp;ldquo;wget&amp;rdquo;, &amp;ldquo;curl&amp;rdquo;, &amp;ldquo;libcurl&amp;rdquo;,.. （基础爬虫有时候会用 Wget 和 cURL）&lt;/li>
&lt;/ul>
&lt;p>如果你发现一些爬虫使用真实浏览器或合法蜘蛛绝对不会使用的 UserAgent，你可以将其添加到你的黑名单中。&lt;/p>
&lt;h3 id="检查-referer-header-头">检查 Referer header 头&lt;/h3>
&lt;p>对上一章节的补充，你也可以检查 &lt;a href="https://en.wikipedia.org/wiki/HTTP_referer">Referer&lt;/a>
（是的，它是 Referer，而不是 Referrer）， 一些懒惰的爬虫可能不会携带的这个，或者只是每次都携带一样的（有时候是 “google.com”）。
举个例子，如果用户是从站内搜索结果页点击进入文章详情的，那要检查 Referer 这个 header 头是否存在还要看搜索结果页的打点。&lt;/p>
&lt;p>注意：&lt;/p>
&lt;ul>
&lt;li>真实浏览器并不总是携带 Referer；&lt;/li>
&lt;li>很容易被避免&lt;/li>
&lt;/ul>
&lt;p>还是，作为一种防止简单爬虫的方法，也值得去实现。&lt;/p>
&lt;h3 id="如果不请求资源cssimages它可能不是真实浏览器">如果不请求资源（CSS，images），它可能不是真实浏览器&lt;/h3>
&lt;p>一个真实浏览器（通常）会请求和下载资源比如 CSS 和 图片。HTML 解析器和爬取器可能只关心特定的页面和内容。&lt;/p>
&lt;p>你可以基于访问你资源的日志，如果你看到很多请求只请求 HTML，那它可能就是爬虫。&lt;/p>
&lt;p>注意搜索引擎蜘蛛、旧的移动设备、屏幕阅读器和设置错误的设备可能也不会请求资源。&lt;/p>
&lt;h3 id="要求使用-cookie使用它们来跟踪用户和爬虫行为">要求使用 Cookie；使用它们来跟踪用户和爬虫行为&lt;/h3>
&lt;p>访问你网站时，你可以要求 cookie 必须开启。这个能识别没有经验和爬虫新手，然而爬虫要携带 Cookie 也十分简单。如果你要求开启
Cookie，你能使用它们来追踪用户和爬虫的行为，然后基于此来实现频率限制、封禁、或者显示验证码而不仅仅依赖 IP 地址。&lt;/p>
&lt;p>举个例子： 当用户进行搜索时，设置一个唯一的 Cookie。当搜索结果加载出来之后，验证这个 Cookie。
如果一个用户打开了所有的搜索结果（可以从 Cookie 中得知），那很可能就是爬虫&lt;/p>
&lt;p>使用 Cookie 可能是低效的，因为爬虫也可以携带 Cookie 发送请求，也可以根据需要丢弃。
如果你的网站只能在开启 Cookie 时使用，你也不能给关闭 Cookie 的用户提供服务。&lt;/p>
&lt;p>要注意如果你使用 JavaScript 去设置和检测 Cookie，你能封禁那些没有运行 JavaScript 的爬虫，因为它们没办法获取和发送 Cookie。&lt;/p>
&lt;h3 id="使用-javascript-和-ajax-加载内容">使用 JavaScript 和 AJAX 加载内容&lt;/h3>
&lt;p>你可以在页面加载完成之后，使用 JavaScript + AJAX 来加载你的内容。这个对于那些没有运行 JavaScript 的 HTML 分析器来说将无法取得数据。
这个对于没有经验或者新手程序员写的爬虫非常有效。&lt;/p>
&lt;p>注意：&lt;/p>
&lt;ul>
&lt;li>使用 JavaScript 加载内容将会降低用户体验和性能；&lt;/li>
&lt;li>搜索引擎也不会运行 JavaScript，因此不会对你的内容进行收录。这对于搜索结果来说可能不是问题，但是要注意其他页面，比如文章页面；&lt;/li>
&lt;li>写爬虫的程序员获取到加载内容的 API 后可以直接使用它&lt;/li>
&lt;/ul>
&lt;h3 id="混淆你的数据和网络请求不要让其直接通过脚本就能获取">混淆你的数据和网络请求，不要让其直接通过脚本就能获取&lt;/h3>
&lt;p>如果你用 Ajax 和 JavaScript 加载你的数据，在传输的时候要混淆一下。比如，你可以在服务器端 encode 你的数据（比如简单的使用 base64 或 负载一些的多次混淆、位偏移或者是进行加密），
然后在客户端在 Ajax 获取数据之后再进行 decode。这意味着如果有人要抓包获取你的请求就不能直接看到你页面如何加载数据，而且那些人也不能直接通过 API 获得你的数据，如果想要获取数据，就必须要去解密你的算法。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>如果你用 Ajax 加载数据，那你应该强制在页面加载之后才可以获取，比如要求获取数据必须包含 session 信息，这些你可以在页面加载的时候嵌入到 JavaScript 或 HTML 中&lt;/p>
&lt;/li>
&lt;li>
&lt;p>你也可以直接吧混淆的数据嵌入到 HTML 中，然后用 JavaScript 去解密然后再显示它们，这样的话，你就不需要再使用 Ajax 去做额外的请求。
这样做可以让那些不运行 JavaScript 的 HTML 解析器更难获取你的数据，他们必须要反解你的 JavaScript（没错，JavaScript 也要做混淆）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>你应该经常更换混淆方法以免爬虫找出方法揭秘它&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>下面是一些这个方式的缺点：&lt;/p>
&lt;ul>
&lt;li>实现、维护和调试都非常麻烦&lt;/li>
&lt;li>&lt;strong>虽然让爬虫变得不容易抓取，但是对于截屏类的爬虫来说，它们实际上会运行 JavaScript，所以能获取到数据&lt;/strong>
（不过很多简单的 HTML 解释器不会运行 JavaScript）&lt;/li>
&lt;li>如果真实用户禁用了 JavaScript，那你的网站将不能正常显示&lt;/li>
&lt;li>性能和页面加载速度会受到影响&lt;/li>
&lt;/ul>
&lt;h2 id="其他非技术做法">其他非技术做法&lt;/h2>
&lt;h3 id="你的服务器供应商可能提供搜索引擎蜘蛛或爬虫的防御">你的服务器供应商可能提供搜索引擎蜘蛛或爬虫的防御：&lt;/h3>
&lt;p>比如，CloudFlare 提供反蜘蛛和反爬虫的防御，你只需要直接启用它就可以了，另外 AWS 也提供类似服务。而且 Apache 的 mod_evasive 模块也能让你很轻松地实现频率限制。&lt;/p>
&lt;h3 id="直接告诉别人不要抓取会有人尊重而且停止抓取">直接告诉别人不要抓取，会有人尊重而且停止抓取&lt;/h3>
&lt;p>你应该直接告诉人们不要抓取你的网站，比如，在你的服务条款中表明。有些人确实会尊重它，而且不在你允许的情况下不会再去抓取数据。&lt;/p>
&lt;h3 id="寻求律师的援助">寻求律师的援助&lt;/h3>
&lt;p>律师们知道如何处理侵犯版权的事情，而且他们可以发送律师函。
DMCA（译者注：Digital Millennium Copyright Act，数字千年版权法案，是一个美国版权法律） 也能提供援助。&lt;/p>
&lt;h3 id="直接提供-api-获取你的数据">直接提供 API 获取你的数据&lt;/h3>
&lt;p>这看起来适得其反，但是你可以要求标明来源并包含返回你站点的链接。甚至也可以售卖你的 API 而赚取费用。&lt;/p>
&lt;p>还有就是，Stack Exchange 提供了 API，但是必须要标明来源。&lt;/p>
&lt;h2 id="其他补充">其他补充&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>要在用户体验和反爬之间做权衡：你做的每个举措都有可能在某种程度上影响用户体验，所以你必须要权衡和妥协；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>不要忘你的移动站点和 APP：如果你的站点有移动版，要小心爬虫也可以通过它爬取你的数据。或者说你有移动 APP，他们也可以截屏分析，
或者可以抓取你的网络请求去直接找到你的 RESTful 的 API 直接使用；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果你对一些特定浏览器提供了特定的版本，比如对很老的 IE 版本提供网站的阉割版，不要忘记爬虫也可以直接爬取它；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>选出集中最适合你的策略结合起来使用，而不是只用一种；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>爬虫可以抓取其他爬虫：如果有个网站显示的内容都是从你网站爬取的，那另外一个爬虫也可以直接爬取那个网站。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="有哪些最有效的方法-">有哪些最有效的方法 ？&lt;/h2>
&lt;p>以我自己写和帮忙别人写爬虫的经验，我认为最有效的方法是：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>经常修改 HTML 的结构&lt;/p>
&lt;/li>
&lt;li>
&lt;p>蜜罐和假数据&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用混淆的 JavaScript、Ajax 还有 Cookie&lt;/p>
&lt;/li>
&lt;li>
&lt;p>频率限制、爬虫检测和请求封禁&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>扩展阅读：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://en.wikipedia.org/wiki/Web_scraping">维基百科关于 Web 爬虫的文章&lt;/a> ，
其中提到了很多 Web 爬虫的相关技术和爬虫类型，看完如何进行 web 爬取的一些信息，也不要忘记看一看爬取的合法性。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>最后祝你在保护你网站的内容坎坷路上一路顺风&amp;hellip;&lt;/strong>&lt;/p>
&lt;p>via: &lt;a href="https://github.com/JonasCz/How-To-Prevent-Scraping">https://github.com/JonasCz/How-To-Prevent-Scraping&lt;/a>&lt;/p>
&lt;p>作者：&lt;a href="https://github.com/JonasCz">JonasCz&lt;/a>
译者：&lt;a href="https://h1z3y3.me">h1z3y3&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/%E5%8F%8D%E7%88%AC/">反爬</category></item><item><title>Golang 逃逸分析</title><link>https://h1z3y3.me/posts/golang-escape-analysis/</link><guid isPermaLink="true">https://h1z3y3.me/posts/golang-escape-analysis/</guid><pubDate>Wed, 21 Apr 2021 13:12:00 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h1 id="golang-逃逸分析">Golang 逃逸分析&lt;/h1>
&lt;p>Golang 的垃圾回收机制可以进行自动内存管理让我们的代码更简洁，同时发生内存泄漏的可能性更小。
然而，GC 会定期停止并收集未使用的对象，因此还是会增加程序的开销。
Go 的编译器十分聪明，比如决定变量需要分配在堆上还是栈上，和分配在堆上不同的是在栈上的变量在声明它的函数结束之后就会被回收。
那对于 GC 来说，分配在栈上的变量不会带来额外的开销，在函数 return 之后，函数的整个调用栈都会被销毁。&lt;/p>
&lt;p>那到底变量应该分配在堆上还是栈上，Golang 如何决定呢，这就要说到 Golang 的&lt;strong>逃逸分析&lt;/strong>。&lt;/p>
&lt;p>判断逃逸的基本规则是如果一个函数的返回值是本函数内声明的某个变量的引用，那么就称这个变量从这个函数中逃逸了。
作为本函数的返回值，它还能被函数外的其他程序修改，所以它必须分配在堆上，而不能分配在那个函数的栈上。&lt;/p>
&lt;p>因此，如果在编译过程中，能对变量的逃逸情况作分析，可以提高我们程序的性能。
首先最大的好处就是能够减少垃圾回收的压力，没有逃逸的变量分配在栈上，函数返回就能直接回收资源；
其次逃逸分析完之后，我们能确定哪些变量其实可以分配在栈上，栈的分配比堆快，性能更好；
还有可以进行同步消除，如果定义变量的函数有同步锁，但是运行时却只有一个线程访问，那此时逃逸分析后的机器码，会去掉同步锁运行。&lt;/p>
&lt;h2 id="开启-go-编译时的逃逸分析日志">开启 Go 编译时的逃逸分析日志&lt;/h2>
&lt;p>在编译时，添加 &lt;code>-gcflags '-m'&lt;/code> 参数即可查看go编译过程中详细的逃逸分析日志。
但为了不让 Go 编译时自动内联函数，会加上 &lt;code>-l&lt;/code> 参数，最终为 &lt;code>-gcflags '-m -l'&lt;/code>。&lt;/p>
&lt;p>Example 0:&lt;/p>
&lt;pre tabindex="0">&lt;code>package main
type S struct {}
func main() {
var x S
_ = identity(x)
}
func identity(x S) S {
return x
}
&lt;/code>&lt;/pre>&lt;p>Output:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ go run -gcflags &amp;#39;-m -l&amp;#39; escape.go
$
$
&lt;/code>&lt;/pre>&lt;p>可以看到没有任何输出，我们知道 Go 在调用函数的时候，会采用按值传递，那么在 &lt;code>main&lt;/code> 函数中声明的 &lt;code>x&lt;/code>，
会被拷贝到 &lt;code>identity()&lt;/code> 函数的栈中。&lt;strong>通常，没有引用的代码总是使用栈分配，因此没有逃逸分析日志的输出。&lt;/strong>&lt;/p>
&lt;p>那如果稍微改一下代码：&lt;/p>
&lt;p>Example 1:&lt;/p>
&lt;pre tabindex="0">&lt;code>package main
type S struct {}
func main() {
var x S
y := &amp;amp;x
_ = *identity(y)
}
func identity(z *S) *S {
return z
}
&lt;/code>&lt;/pre>&lt;p>Output:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ go run -gcflags &amp;#39;-m -l&amp;#39; escape.go
# command-line-arguments
./escape.go:11:22: leaking param: z to result ~r1 level=0
./escape.go:7:8: main &amp;amp;x does not escape
&lt;/code>&lt;/pre>&lt;p>第一行是 &lt;code>z&lt;/code> 变量是流经某个函数的意思，仅作为函数的输入，并且直接返回，
在 &lt;code>identity()&lt;/code> 中也没有使用到 &lt;code>z&lt;/code> 的引用，所以变量没有逃逸。&lt;/p>
&lt;p>第二行，&lt;code>x&lt;/code> 在 &lt;code>main()&lt;/code> 函数中声明，所以是在 &lt;code>main()&lt;/code> 函数中的栈中的，也没有逃逸。&lt;/p>
&lt;p>Example 2:&lt;/p>
&lt;pre tabindex="0">&lt;code>package main
type S struct {}
func main() {
var x S
_ = *ref(x)
}
func ref(z S) *S {
return &amp;amp;z
}
&lt;/code>&lt;/pre>&lt;p>Output:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ go run -gcflags &amp;#39;-m -l&amp;#39; escape.go
# command-line-arguments
./escape.go:11:10: &amp;amp;z escapes to heap
./escape.go:10:16: moved to heap: z
&lt;/code>&lt;/pre>&lt;p>可以看到发生了逃逸。
&lt;code>ref()&lt;/code> 的参数 &lt;code>z&lt;/code> 是通过值传递的，所以 &lt;code>z&lt;/code> 是 &lt;code>main()&lt;/code> 函数中 &lt;code>x&lt;/code> 的一个值拷贝，
而 &lt;code>ref()&lt;/code> 返回了 &lt;code>z&lt;/code> 的引用，所以 &lt;code>z&lt;/code> 不能放在&lt;code>ref()&lt;/code>的栈中， 实际上被分配到了堆上。&lt;/p>
&lt;p>实际上，我们发现 &lt;code>main()&lt;/code> 函数中没有直接使用 &lt;code>ref()&lt;/code> 返回的引用，这种情况其实 &lt;code>z&lt;/code> 可以分配到&lt;code>ref()&lt;/code>的栈上，
但是Go的逃逸分析并没有复杂到来识别出这种情况，它只看输入还有返回的变量的流程。
&lt;strong>值得注意的是，如果我们没有加 &lt;code>-l&lt;/code> 参数，其实 &lt;code>ref()&lt;/code> 会被编译器内联到 &lt;code>main()&lt;/code> 使用。&lt;/strong>&lt;/p>
&lt;p>如果引用被赋值到结构体的成员呢？&lt;/p>
&lt;p>Example 3:&lt;/p>
&lt;pre tabindex="0">&lt;code>package main
type S struct {
M *int
}
func main() {
var i int
refStruct(i)
}
func refStruct(y int) (z S) {
z.M = &amp;amp;y
return z
}
&lt;/code>&lt;/pre>&lt;p>Output:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ go run -gcflags &amp;#39;-m -l&amp;#39; escape.go
# command-line-arguments
./escape.go:13:9: &amp;amp;y escapes to heap
./escape.go:12:26: moved to heap: y
&lt;/code>&lt;/pre>&lt;p>可以发现，即使这个引用是结构体的一个成员，Go的逃逸分析可以跟踪引用的。
当结构体 &lt;code>refStruct&lt;/code> 返回时，&lt;code>y&lt;/code> 一定是从 &lt;code>refStruct()&lt;/code> 中逃逸的。&lt;/p>
&lt;p>可以再和下面例子比较一下：&lt;/p>
&lt;p>Example 4:&lt;/p>
&lt;pre tabindex="0">&lt;code>package main
type S struct {
M *int
}
func main() {
var i int
refStruct(&amp;amp;i)
}
func refStruct(y *int) (z S) {
z.M = y
return z
}
&lt;/code>&lt;/pre>&lt;p>Output:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ go run -gcflags &amp;#39;-m -l&amp;#39; escape.go
# command-line-arguments
./escape.go:12:27: leaking param: y to result z level=0
./escape.go:9:13: main &amp;amp;i does not escape
&lt;/code>&lt;/pre>&lt;p>那这个 &lt;code>y&lt;/code> 没有逃逸的原因是，&lt;code>main()&lt;/code> 中带着 &lt;code>i&lt;/code> 的引用调用了 &lt;code>refStruct()&lt;/code> 并直接返回了，
从来没有超过 &lt;code>main()&lt;/code> 函数的调用栈，原因和 Example 1 实际上是一样的。&lt;/p>
&lt;p>另外要说明一点，Example 4 要比 Example 3 更高效：&lt;/p>
&lt;p>在 Example 3 中，&lt;code>i&lt;/code> 必须在&lt;code>main()&lt;/code> 的栈中申请一块栈空间，经过&lt;code>refStruct()&lt;/code>后，&lt;code>y&lt;/code>还要在堆上再申请一块空间；
而在 Example 4 中，实际上只有&lt;code>i&lt;/code>申请了一次空间，然后它的引用经过了 &lt;code>refStruct()&lt;/code> 而已。&lt;/p>
&lt;p>一个更复杂的例子：&lt;/p>
&lt;p>Example 5:&lt;/p>
&lt;pre tabindex="0">&lt;code>package main
type S struct {
M *int
}
func main() {
var x S
var i int
ref(&amp;amp;i, &amp;amp;x)
}
func ref(y *int, z *S) {
z.M = y
}
&lt;/code>&lt;/pre>&lt;p>Output:&lt;/p>
&lt;pre tabindex="0">&lt;code>$ go run -gcflags &amp;#39;-m -l&amp;#39; escape.go
# command-line-arguments
./escape.go:13:21: leaking param: y
./escape.go:13:21: ref z does not escape
./escape.go:10:7: &amp;amp;i escapes to heap
./escape.go:9:7: moved to heap: i
./escape.go:10:11: main &amp;amp;x does not escape
&lt;/code>&lt;/pre>&lt;p>&lt;code>y&lt;/code> 和 &lt;code>z&lt;/code> 没有逃逸很好理解，但问题在于 &lt;code>y&lt;/code> 还被赋值到函数 &lt;code>ref()&lt;/code> 的输入 &lt;code>z&lt;/code> 的成员了，
而Go的逃逸分析不能跟踪变量之间的关系，不知道 &lt;code>i&lt;/code> 变成了 &lt;code>x&lt;/code> 的一个成员，
分析结果说 &lt;code>i&lt;/code> 是逃逸的，但本质上 &lt;code>i&lt;/code>是没逃逸的， 这个时候Go的逃逸分析实际上是有问题的。&lt;/p>
&lt;p>这里还有好多因为Go逃逸分析的不足而导致被分配到堆的变态例子：
&lt;a href="https://docs.google.com/document/d/1CxgUBPlx9iJzkz9JWkb6tIpTe5q32QDmz8l0BouG0Cw/preview">https://docs.google.com/document/d/1CxgUBPlx9iJzkz9JWkb6tIpTe5q32QDmz8l0BouG0Cw/preview&lt;/a>&lt;/p>
&lt;p>其实说这么多其实就是为了说明如果想要减少垃圾回收的时间，提高程序性能，
那就要尽量避免在堆上分配空间，之后在写程序的时候可以多考虑一下这方面的问题。;)&lt;/p>
&lt;h3 id="参考">参考&lt;/h3>
&lt;p>&lt;a href="http://www.agardner.me/golang/garbage/collection/gc/escape/analysis/2015/10/18/go-escape-analysis.html">《Golang escape analysis》&lt;/a>&lt;/p>
&lt;p>&lt;a href="https://docs.google.com/document/d/1CxgUBPlx9iJzkz9JWkb6tIpTe5q32QDmz8l0BouG0Cw/preview">《Go Escape Analysis Flaws》&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/golang/">Golang</category></item><item><title>深入剖析 Golang Pprof 标签</title><link>https://h1z3y3.me/posts/demysitifying-pprof-labels-with-go/</link><guid isPermaLink="true">https://h1z3y3.me/posts/demysitifying-pprof-labels-with-go/</guid><pubDate>Sun, 18 Apr 2021 21:22:44 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h1 id="深入剖析-golang-pprof-标签">深入剖析 Golang Pprof 标签&lt;/h1>
&lt;p>Polar Signal 提供的持续分析工具可以和任何格式的 pprof 配置适配，Go 深度集成了 pprof 甚至支持了它的&lt;code>标签&lt;/code>特性。然而，自从我们发布了我们持续分析的产品之后，我们收到了很多工程师的反馈，发现许多工程师不知道如何去分析，或者不知道分析能给他们带来什么好处。这篇文章主要剖析 pprof 标签，并会结合一些 Go 的示例代码去分析。&lt;/p>
&lt;h2 id="基础">基础&lt;/h2>
&lt;p>pprof 标签只支持 Go 的 CPU 分析器。Go 的分析器是抽样分析，这意味着它只会根据特定的频率（默认是 1 秒钟 100 次）去获取执行中函数的调用栈并记录。简单来说，开发者如果使用标签，在分析器取样时就可以将函数的调用栈进行区分，然后只聚合有相同标签的函数调用栈。&lt;/p>
&lt;p>Go 在 &lt;code>runtime/pprof&lt;/code> 包中已经支持了标签检测，可以使用 &lt;code>pprof.Do&lt;/code> 函数非常方便的使用。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">pprof&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Do&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">pprof&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Labels&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;label-key&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;label-value&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// execute labeled code
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="进阶">进阶&lt;/h2>
&lt;p>为了进行演示如何使用 pprof 标签，我们创建了一个包含许多示例的仓库，示例仓库会作为这篇文章内容的说明。仓库地址：&lt;a href="https://github.com/polarsignals/pprof-labels-example">https://github.com/polarsignals/pprof-labels-example&lt;/a>&lt;/p>
&lt;p>示例代码的 &lt;code>main.go&lt;/code> 通过将 &lt;code>tanant&lt;/code> 传递给 &lt;code>iterate&lt;/code> 函数实现了大量的 for 循环，其中&lt;code>tanant1&lt;/code> 做了 10 亿次循环，而 &lt;code>tanant2&lt;/code> 做了 1 亿次循环，同时会记录 CPU 的分析日志并将其写入 &lt;code>./cpuprofile.pb.gz&lt;/code>。&lt;/p>
&lt;p>为了演示如何在 pprof 的分析日志中展示 pprof 标签，用 &lt;code>printprofile.go&lt;/code> 来打印每次抽样函数调用栈以及样本值，还有收集到样本的标签。&lt;/p>
&lt;p>如果我们注释掉 &lt;code>pprof.Do&lt;/code> 的这部分，我们将无法进行标签检测，运行 &lt;code>printprofile.go&lt;/code> 代码，让我们看看没有标签的抽样分析日志：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">runtime.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iteratePerTenant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iterate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">2540000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">runtime.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iteratePerTenant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iterate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">250000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Total: 2.79s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>CPU 分析数据的单位是 纳秒，所以这些抽样总共花费时间是 2.79 秒（2540000000ns + 250000000ns = 2790000000ns = 2.79s）。&lt;/p>
&lt;p>同样的，现在当每次调用 &lt;code>iterate&lt;/code> 时添加标签，用 pprof 分析，这些数据看起来就不太一样，打印出带有标签的抽样分析日志：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">runtime.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iteratePerTenant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">runtime/pprof.Do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iteratePerTenant.func1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iterate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">10000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">runtime.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iteratePerTenant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">runtime/pprof.Do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iteratePerTenant.func1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iterate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">2540000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">map&lt;span class="o">[&lt;/span>tenant:&lt;span class="o">[&lt;/span>tenant1&lt;span class="o">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">runtime.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iteratePerTenant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">runtime/pprof.Do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iteratePerTenant.func1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iterate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">10000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">map&lt;span class="o">[&lt;/span>tenant:&lt;span class="o">[&lt;/span>tenant1&lt;span class="o">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">runtime.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iteratePerTenant
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">runtime/pprof.Do
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iteratePerTenant.func1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">main.iterate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">260000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">map&lt;span class="o">[&lt;/span>tenant:&lt;span class="o">[&lt;/span>tenant2&lt;span class="o">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Total: 2.82s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>将所有抽样加起来总共花费了 2.82 秒，然而，因为调用 &lt;code>iterate&lt;/code> 时，我们添加了标签，所以我们能在结果中区分哪个 &lt;code>tanant&lt;/code> 导致了更多的 CPU 占用。现在我们可以说 &lt;code>tenant1&lt;/code> 花费了总时间 2.82 秒中的 2.55 秒（2540000000ns + 10000000ns = 2550000000ns = 2.55s）。&lt;/p>
&lt;p>让我们看看抽样的原始日志（还有它们的元数据），去更深入理解一下它们的格式：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ protoc --decode perftools.profiles.Profile --proto_path ~/src/github.com/google/pprof/proto profile.proto &amp;lt; cpuprofile.pb &lt;span class="p">|&lt;/span> grep -A12 &lt;span class="s2">&amp;#34;sample {&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sample &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: &lt;span class="m">10000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sample &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: &lt;span class="m">254&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: &lt;span class="m">2540000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> label &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> key: &lt;span class="m">14&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> str: &lt;span class="m">15&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sample &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">6&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: &lt;span class="m">10000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> label &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> key: &lt;span class="m">14&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> str: &lt;span class="m">15&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sample &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> location_id: &lt;span class="m">5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: &lt;span class="m">26&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> value: &lt;span class="m">260000000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> label &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> key: &lt;span class="m">14&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> str: &lt;span class="m">16&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们可以看到每个抽样都由许多 ID 组成，这些 ID 指向它们在分析日志 &lt;code>location&lt;/code> 数组中的位置，除了这些 ID 还有几个值。仔细看下 &lt;code>printprofile.go&lt;/code> 程序，你会发现它使用了每个抽样的最后一个抽样 value。 实际上，Go 的 CPU 分析器会记录两个 value，第一个代表这个调用栈在一次分析区间被记录样本的数量，第二个代表它花费了多少纳秒。pprof 的定义描述当没设置 &lt;code>default_sample_type&lt;/code> 时（在 Go 的 CPU 配置中设置），就使用所有 value 中的最后一个，因此我们使用的是代表纳秒的 value 而不是样本数的 value。最后，我们可以打印出标签，它是 pprof 定义的一个以字符串组成的字典。&lt;/p>
&lt;p>最后，因为用标签去区分数据，我们可以让可视化界面更直观。&lt;/p>
&lt;p>你可以在 Polar Signals 网站这里去更详细的了解上面的这次分析：&lt;a href="https://share.polarsignals.com/2063c5c/">https://share.polarsignals.com/2063c5c/&lt;/a>&lt;/p>
&lt;h2 id="结论">结论&lt;/h2>
&lt;p>pprof 标签是帮助我们理解不同执行路径非常有用的方法，许多人喜欢在多租户系统中使用它们，目的就是为了能够定位在他们系统中出现的由某一个租户导致的性能问题。就像开头说的，只需要调用 &lt;code>pprof.Do&lt;/code> 就可以了。&lt;/p>
&lt;p>Polar Signals 提供的持续分析工具也支持了 pprof 标签的可视化界面和报告，如果你想参与个人体验版请点击：&lt;a href="https://www.polarsignals.com/#request-access">申请资格&lt;/a>&lt;/p>
&lt;hr>
&lt;p>via: &lt;a href="https://www.polarsignals.com/blog/posts/2021/04/13/demystifying-pprof-labels-with-go/">https://www.polarsignals.com/blog/posts/2021/04/13/demystifying-pprof-labels-with-go/&lt;/a>&lt;/p>
&lt;p>作者：&lt;a href="https://twitter.com/fredbrancz">Frederic Branczyk&lt;/a>
译者：&lt;a href="https://h1z3y3.me">h1z3y3&lt;/a>
校对：&lt;a href="https://github.com/%E6%A0%A1%E5%AF%B9%E8%80%85ID">校对者ID&lt;/a>&lt;/p>
&lt;p>本文由 &lt;a href="https://github.com/studygolang/GCTT">GCTT&lt;/a> 原创编译，&lt;a href="https://studygolang.com/">Go 中文网&lt;/a> 荣誉推出&lt;/p></description><category domain="https://h1z3y3.me/tags/golang/">Golang</category><category domain="https://h1z3y3.me/tags/gctt/">GCTT</category></item><item><title>用 kqueue 实现一个简单的 TCP Server</title><link>https://h1z3y3.me/posts/writing-a-tcp-server-using-kqueue/</link><guid isPermaLink="true">https://h1z3y3.me/posts/writing-a-tcp-server-using-kqueue/</guid><pubDate>Fri, 16 Apr 2021 21:36:44 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h2 id="介绍">介绍&lt;/h2>
&lt;p>在 &lt;a href="https://dev.to/frosnerd/explain-non-blocking-i-o-like-i-m-five-2a5f">非阻塞 I/O 超简明介绍&lt;/a> 中，
我们已经讨论过现代 Web 服务器可以处理大量并发请求，这得益于现代操作系统内核内置的事件通知机制。
受 Linux epoll [ &lt;a href="https://man7.org/linux/man-pages/man7/epoll.7.html">文档&lt;/a> ] 启发，
FreeBSD 发明了 kqueue [ &lt;a href="https://people.freebsd.org/~jlemon/papers/kqueue.pdf">论文&lt;/a> ,
&lt;a href="https://www.freebsd.org/cgi/man.cgi?query=kqueue&amp;amp;sektion=2">文档&lt;/a> ]&lt;/p>
&lt;p>这篇文章我们将仔细研究下 kqueue，我们会用 Go 实现一个基于 kqueue event loop 的 TCP server，
你可以在 Github 上找到 &lt;a href="https://github.com/FRosner/FrSrv">源代码&lt;/a> 。
要运行代码必须使用和 FreeBSD 兼容的操作系统，比如 macOS。&lt;/p>
&lt;p>注意 kqueue 不仅能处理 socket event，而且还能处理文件描述符 event、信号、异步 I/O event、子进程状态改变 event、
定时器以及用户自定义 event。它确实通用和强大。&lt;/p>
&lt;p>我们这篇文章主要分为一下几部分讲解。
首先，我们会在先从理论出发设计我们的 TCP Server。
然后，我们会去实现它的必要的模块。
最后我们会对整个过程进行总结以及思考。&lt;/p>
&lt;h2 id="设计">设计&lt;/h2>
&lt;p>我们 TCP Server 大概有以下几部分：
一个监听 TCP 的 socket、
接收客户端连接的 socket、
一个内核事件队列（kqueue），
还有一个事件循环机制来轮询这个队列。
下面这个图描述了接收连接的场景。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/gctt-images2/master/20210219-Writing-A-Simple-TCP-Server-Using-Kqueue/accepting-incoming-connections.png" alt="">&lt;/p>
&lt;p>当客户端想要连接服务端，一个连接请求将会被放到 TCP 连接队列中，
而内核会将一个新的事件放到 kqueue 中。
这个事件将会在事件循环时被处理，事件循环会接受请求，并创建一个新的客户端连接。
下面这个图描述了新创建的 socket 如何从客户端读取请求。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/gctt-images2/master/20210219-Writing-A-Simple-TCP-Server-Using-Kqueue/read-data-from-the-client.png" alt="">&lt;/p>
&lt;p>客户端写数据到新创建的 socket，内核会将一个新 event 放到 kqueue 中，表示在这个 socket 中有等待读取的数据。
事件循环将轮询到这个事件，并从 socket 读取数据。
注意只有一个 socket 监听连接，而我们将为每一个客户端连接创建新的 socket。&lt;/p>
&lt;p>下文要讨论实现细节，可以大概按照下面的步骤实现我们的设计。&lt;/p>
&lt;p>1 创建，绑定以及监听新的 socket
2 创建 kqueue
3 订阅 socket event
4 循环队列获取 event 并处理它们&lt;/p>
&lt;h2 id="实现">实现&lt;/h2>
&lt;p>为了避免单个文件有大量系统调用，我们拆分成几个不同模块：&lt;/p>
&lt;ul>
&lt;li>一个 &lt;code>socket&lt;/code> 模块来处理所有管理 socket 的相关功能，&lt;/li>
&lt;li>一个 &lt;code>kqueue&lt;/code> 模块来处理事件循环，&lt;/li>
&lt;li>最后 &lt;code>main&lt;/code> 模块用来整合所有模块并启动我们的 TCP server。&lt;/li>
&lt;/ul>
&lt;p>我们下面从 &lt;code>socket&lt;/code> 模块开始。&lt;/p>
&lt;h3 id="定义-socket">定义 Socket&lt;/h3>
&lt;p>首先，让我们创建一个 socket 结构体。类 Unix 操作系统，比如 FreeBSD，会把 socket 作为文件。
为了用 Go 实现 socket，我们需要了解 &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=fd&amp;amp;sektion=4&amp;amp;manpath=freebsd-release-ports">文件描述符&lt;/a>。
所以我们可以创建一个类似下面带有文件描述符的结构体。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Socket&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">FileDescriptor&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>我们期望我们的 socket 可以应对不同的场景，比如：读、写 socket 数据，以及关闭 socket。
在 Go 中，要支持这些操作，需要实现通用的 interface，比如 &lt;code>io.Reader&lt;/code>，&lt;code>io.Writer&lt;/code>，还有 &lt;code>io.Closer&lt;/code>&lt;/p>
&lt;p>首先，实现 &lt;code>io.Reader&lt;/code> 这个接口，他会调用 &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=read&amp;amp;sektion=2">read()&lt;/a> 系统函数。
这个函数会返回读到字节的数量，以及进行读操作时可能发生的错误。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">socket&lt;/span> &lt;span class="nx">Socket&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Read&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bytes&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bytes&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">numBytesRead&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Read&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FileDescriptor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bytes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">numBytesRead&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">numBytesRead&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>类似的，我们通过调用 &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=write&amp;amp;sektion=2">write()&lt;/a> 来实现 &lt;code>io.Writer&lt;/code> 接口。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">socket&lt;/span> &lt;span class="nx">Socket&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">bytes&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="kt">byte&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">numBytesWritten&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FileDescriptor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bytes&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">numBytesWritten&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">numBytesWritten&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>最后关闭 socket 可以调用 &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=close&amp;amp;apropos=0&amp;amp;sektion=2">close()&lt;/a> ，并传入 socket 对应的文件描述符。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">socket&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Socket&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FileDescriptor&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>为了稍后能打印一些有用的错误和日志，我们也需要实现 &lt;code>fmt.Stringer&lt;/code> 接口。
我们通过不同的文件描述符来区分不同的 socket。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">socket&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Socket&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">String&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="kt">string&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">strconv&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Itoa&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FileDescriptor&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="监听一个-socket">监听一个 Socket&lt;/h3>
&lt;p>定义好 Socket 之后，我们需要初始化它，并让它一个监听特定 IP 和 端口的。
监听一个 socket 也可以通过一些系统函数来实现。
现在先整体看一下我们实现的 &lt;code>Listen()&lt;/code> 方法，然后再一步步进行分析。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">Listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ip&lt;/span> &lt;span class="kt">string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">port&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">Socket&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">socket&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">Socket&lt;/span>&lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">socketFileDescriptor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Socket&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">AF_INET&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SOCK_STREAM&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to create socket (%v)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FileDescriptor&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">socketFileDescriptor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">socketAddress&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SockaddrInet4&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Port&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">port&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">copy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">socketAddress&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Addr&lt;/span>&lt;span class="p">[:],&lt;/span> &lt;span class="nx">net&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ParseIP&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ip&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Bind&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FileDescriptor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">socketAddress&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to bind socket (%v)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FileDescriptor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SOMAXCONN&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to listen on socket (%v)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">socket&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>首先调用了 &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=socket&amp;amp;apropos=0&amp;amp;sektion=2">socket()&lt;/a> 函数，
这将会创建通信的接入点，并返回描述符编号。
它需要 3 个参数：&lt;/p>
&lt;ul>
&lt;li>地址类型：我们用的是 &lt;code>AF_INET&lt;/code> (IPv4)&lt;/li>
&lt;li>socket 类型：我们用 &lt;code>SOCKET_STREAM&lt;/code>，代表基于字节流连续、可靠的双向连接。&lt;/li>
&lt;li>协议类型：&lt;code>0&lt;/code> 在 &lt;code>SOCKET_STREAM&lt;/code> 类型下代表的是 TCP。&lt;/li>
&lt;/ul>
&lt;p>然后，我们调用了 &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=bind&amp;amp;apropos=0&amp;amp;sektion=2">bind()&lt;/a> 方法来指定新创建 socket 的协议地址。
&lt;code>bind()&lt;/code> 方法的第一个参数是文件描述符，第二个参数是包含地址信息的结构体指针。
我们在这里使用了 Go 预定义的 &lt;code>SockaddrInet4&lt;/code> 结构体，并指定要绑定的 IP 地址和端口。&lt;/p>
&lt;p>最后，我们调用了 &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=listen&amp;amp;apropos=0&amp;amp;sektion=2">listen()&lt;/a> 方法，这样我们就能等待接收连接了。
它的第二个参数是连接请求队列的最大长度。
我们使用了内核参数 &lt;code>SOMAXCONN&lt;/code> ，在我的 Mac 上默认是 128。
你可以通过执行 &lt;code>sysctl kern.ipc.somaxconn&lt;/code> 来获取这个值。&lt;/p>
&lt;h3 id="定义事件循环">定义事件循环&lt;/h3>
&lt;p>同样的，我们将定义一个结构体来表示 kqueue 的事件循环。
我们必须要保存 kqueue 的文件描述符以及 socket 的文件描述符,
我们当然也能将我们前面定义的 socket 对象作为指针来替代 &lt;code>SocketFileDescriptor&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">EventLoop&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">KqueueFileDescriptor&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">SocketFileDescriptor&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>接下来，我们需要一个函数根据我们提供的 socket 创建一个事件循环。
和之前一样，我们需要用一系列系统函数去创建 Kqueue。
我们还是先看下整个函数，然后再一步步拆解来看。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">NewEventLoop&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Socket&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">EventLoop&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">kQueue&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Kqueue&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to create kqueue file descriptor (%v)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">changeEvent&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Kevent_t&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Ident&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">uint64&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FileDescriptor&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Filter&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">EVFILT_READ&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Flags&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">EV_ADD&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">EV_ENABLE&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Fflags&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Udata&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">changeEventRegistered&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Kevent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">kQueue&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[]&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Kevent_t&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">changeEvent&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">changeEventRegistered&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Errorf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;failed to register change event (%v)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">EventLoop&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">KqueueFileDescriptor&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">kQueue&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">SocketFileDescriptor&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FileDescriptor&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>第一个系统函数 &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=kqueue&amp;amp;apropos=0&amp;amp;sektion=0&amp;amp;format=html">kqueue()&lt;/a> 创建了一个新的内核事件队列，并且返回了它的文件描述符。
我们等会调用 &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=kqueue&amp;amp;apropos=0&amp;amp;sektion=0&amp;amp;format=html">&lt;code>kevent()&lt;/code>&lt;/a> 的时候会用到这个队列。
&lt;code>kevent()&lt;/code> 有两个功能，订阅新事件和轮询队列。&lt;/p>
&lt;p>我们的例子是要订阅传入连接的事件，
可以通过传递 &lt;code>kevent&lt;/code> 结构体（在 Go 中，用 &lt;code>Kevent_t&lt;/code> 表示）给 &lt;code>kevent()&lt;/code> 这个系统函数来实现订阅。
&lt;code>Kevent_t&lt;/code> 需要包含以下信息：&lt;/p>
&lt;ul>
&lt;li>&lt;code>Ident&lt;/code> 的文件描述符：值是我们 socket 的文件描述符&lt;/li>
&lt;li>处理事件的 &lt;code>Filter&lt;/code>：设置为 &lt;code>EVFILT_READ&lt;/code>，当和监听 socket 一起用时，它代表我们只关心传入连接的事件。&lt;/li>
&lt;li>代表对这个事件要执行操作的 &lt;code>Flag&lt;/code>：在我们例子中，我们想要添加（EV_ADD）事件到 &lt;code>kqueue&lt;/code>，比如说订阅事件，同时要启用（EV_ENABLE）它。Flag 可以使用 &lt;code>或&lt;/code> 这个位操作进行结合。&lt;/li>
&lt;/ul>
&lt;p>其他的几个参数我们就不需要了，创建好这个事件之后，要把它用一个数组包裹，并传递给 &lt;code>kevent()&lt;/code> 这个系统函数。
最后，我们返回这个等待被轮询的事件循环。接下来让我们实现轮询的函数。&lt;/p>
&lt;h3 id="事件循环轮询">事件循环轮询&lt;/h3>
&lt;p>事件循环是一个简单的 for 循环，可以轮询新的内核事件并进行处理。
之前使用系统函数 &lt;code>kevent()&lt;/code> 时，订阅轮询就已经完成了，但是现在我们又传递一个空的事件数组给它，
目的是当有新的事件时，新的事件会填充到这个数组。&lt;/p>
&lt;p>然后我们就可以一个个循环这些事件并处理它们了。
新的客户端连接会被转换成客户端 socket，所以我们可以从客户端读取或写入数据。
现在让我们看下代码如何循环不同的事件类型。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">eventLoop&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">EventLoop&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">Handle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">handler&lt;/span> &lt;span class="nx">Handler&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newEvents&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Kevent_t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">numNewEvents&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Kevent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">eventLoop&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">KqueueFileDescriptor&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newEvents&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="nx">numNewEvents&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">currentEvent&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">newEvents&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">i&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">eventFileDescriptor&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">currentEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Ident&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">currentEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Flags&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">EV_EOF&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// client closing connection
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">eventFileDescriptor&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="nx">eventFileDescriptor&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">eventLoop&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SocketFileDescriptor&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// new incoming connection
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">socketConnection&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Accept&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">eventFileDescriptor&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">socketEvent&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Kevent_t&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Ident&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">uint64&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">socketConnection&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Filter&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">EVFILT_READ&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Flags&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">EV_ADD&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Fflags&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Udata&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">socketEventRegistered&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Kevent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">eventLoop&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">KqueueFileDescriptor&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[]&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Kevent_t&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">socketEvent&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kc">nil&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kc">nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">socketEventRegistered&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="nx">currentEvent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Filter&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">EVFILT_READ&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// data available -&amp;gt; forward to handler
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nf">handler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Socket&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">FileDescriptor&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">eventFileDescriptor&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ignore all other events
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>第一种情况，我们要处理 &lt;code>EV_EOF&lt;/code> 事件，代表客户端想要关闭它的连接的事件。这种情况我们简单的关闭了对应 socket 的文件描述符。&lt;/p>
&lt;p>第二种情况代表我们的监听 socket 有连接请求。
我们可以使用系统函数 &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=accept">accept()&lt;/a> 从 TCP 连接请求队列中获取连接请求，
它会为监听 socket 创建一个新的客户端 socket 和新的文件描述符。
我们为这个新创建的 socket 订阅一个新的 &lt;code>EVFILT_READ&lt;/code> 事件。
在新创建的客户端 socket 中，无论什么时候有可以读取的数据，就会有 &lt;code>EVFILT_READ&lt;/code> 事件发生。&lt;/p>
&lt;p>第三种情况就是处理刚提到的 &lt;code>EVFILT_READ&lt;/code> 事件，这些事件有客户端 socket 的文件描述符，
我们将其封装在 &lt;code>Socket&lt;/code> 对象中并传递给要处理它的方法。&lt;/p>
&lt;p>要注意我们省略一些错误然后使用了简单的 continue 继续执行循环。现在事件循环函数也写好了，让我们将所有的逻辑封装在 main 函数中并执行。&lt;/p>
&lt;h3 id="main-函数">main 函数&lt;/h3>
&lt;p>因为之前已经定义好了 &lt;code>socket&lt;/code> 和 &lt;code>kqueue&lt;/code> 模块，我们现在可以非常容易地实现服务器。
我们首先创建一个监听特定 IP 地址和端口的 socket，然后基于它创建一个新的事件循环，
最后我们定义处理输出的函数，来开启我们的事件循环。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-golang" data-lang="golang">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Listen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;127.0.0.1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">8080&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Failed to create Socket:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">eventLoop&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">kqueue&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewEventLoop&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Failed to create event loop:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Exit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Server started. Waiting for incoming connections. ^C to exit.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">eventLoop&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Handle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Socket&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">reader&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">bufio&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewReader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">s&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">line&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">reader&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ReadString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sc">&amp;#39;\n&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">TrimSpace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">line&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Write&lt;/span>&lt;span class="p">([]&lt;/span>&lt;span class="nb">byte&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">line&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Close&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>处理函数会根据换行符逐行读取数据内容，直到它读取到空行，然后会关闭连接。
我们可以通过 &lt;code>curl&lt;/code> 来测试，&lt;code>curl&lt;/code> 将会发送一个 GET 请求，并输出响应的内容，响应内容其实就是它发送的 GET 请求体的内容。&lt;/p>
&lt;p>&lt;img src="https://raw.githubusercontent.com/h1z3y3/gctt-images2/master/20210219-Writing-A-Simple-TCP-Server-Using-Kqueue/demo.gif" alt="">&lt;/p>
&lt;h2 id="思考">思考&lt;/h2>
&lt;p>我们成功用 &lt;code>kqueue&lt;/code> 实现了一个简单的 TCP Server，当然，这个代码想用于生产环境还需要做很多工作。
我们使用单进程和阻塞 socket 运行，另外，也没有去处理错误。
其实大多数情况下，使用已经存在的库而不是自己调用操作系统内核函数会更好。&lt;/p>
&lt;p>没想到用内核操作事件这么难，API 非常复杂，而且必须要读好多文档去找需要怎么做。
然而，这是一个惊人的学习体验。&lt;/p>
&lt;hr>
&lt;p>via: &lt;a href="https://dev.to/frosnerd/writing-a-simple-tcp-server-using-kqueue-cah">https://dev.to/frosnerd/writing-a-simple-tcp-server-using-kqueue-cah&lt;/a>&lt;/p>
&lt;p>作者：&lt;a href="https://dev.to/frosnerd">Frank Rosner&lt;/a>
译者：&lt;a href="https://github.com/h1z3y3">h1z3y3&lt;/a>
校对：&lt;a href="https://github.com/polarisxu">polarisxu&lt;/a>&lt;/p>
&lt;p>本文由 &lt;a href="https://github.com/studygolang/GCTT">GCTT&lt;/a> 原创编译，&lt;a href="https://studygolang.com/">Go 中文网&lt;/a> 荣誉推出&lt;/p></description><category domain="https://h1z3y3.me/tags/golang/">Golang</category><category domain="https://h1z3y3.me/tags/gctt/">GCTT</category></item><item><title>译：HTTP/3 来了</title><link>https://h1z3y3.me/posts/http3/</link><guid isPermaLink="true">https://h1z3y3.me/posts/http3/</guid><pubDate>Thu, 06 Dec 2018 14:43:12 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>英文原文链接：&lt;a href="https://www.zdnet.com/article/http-over-quic-to-be-renamed-http3/">https://www.zdnet.com/article/http-over-quic-to-be-renamed-http3/&lt;/a>&lt;/p>
&lt;p>据 IETF 官方人员透露，HTTP-over-QUIC 实验方案将会被命名为 HTTP/3，并将成为 HTTP 协议的第三个官方版本。 &lt;br>
在谷歌将 SPDY 技术发展成为 HTTP/2 协议之后，这是谷歌第二次将实验技术发展成为 HTTP 的官方协议版本。 &lt;br>
HTTP-over-QUIC 协议是 HTTP 协议的升级，谷歌使用 QUIC 取代 TCP (Transmission Control Protocal) 作为 HTTP 的基础技术。 &lt;br>
QUIC 全称 Quick UDP Internet Connections，是谷歌将 TCP 协议重写为一种结合了HTTP/2、TCP、UDP 和 TLS 的改进技术。&lt;/p>
&lt;p>谷歌希望 QUIC 能逐渐取代 TCP 和 UDP 成为在因特网传输二进制数据协议的新选择，而使用它的更好的理由，是 QUIC 的加密方案实现已经被测试证明更快而且更安全 (目前 HTTP-over-QUIC 协议草案使用的是 TLS1.3 协议)。&lt;/p>
&lt;p>&lt;img src="https://p4.ssl.qhimg.com/t01f147b97634e732d0.png" alt="Google">&lt;/p>
&lt;p>QUIC 被提议作为2015年 IETF 的标准草案，而 HTTP-over-QUIC 这个基于 QUIC 而不是 TCP 重写 HTTP 的协议则在2016年7月被提议。 &lt;br>
在那之后，HTTP-over-QUIC 在 Chrome 29、Opera 16 被支持，当然还有一些低性能的浏览器。最初，只有谷歌的服务器支持 HTTP-over-QUIC，今年，Facebook也开始采用这项技术。 &lt;br>
在2018年10月份的邮件讨论里，IETF HTTP 和 QUIC 工作组的主席 Mark Nottingham 提出了将 HTTP-over-QUIC 重命名为 HTTP/3 的官方申请，并希望将 QUIC 工作组的开发工作转递给 HTTP 工作组。 &lt;br>
经过几天的讨论，Nottingham 的提议被 IETF 的成员接受并给出了官方认可，将 HTTP-over-QUIC 作为 HTTP 协议的下一个版本 —— HTTP/3，用于完善优化当今的网络。 &lt;br>
根据 W3Techs 的统计，截止2018年11月，全球访问量最高的1千万个网站中，已经有31.2%的网站支持了 HTTP/2，只有1.2%的网站支持了 QUIC。&lt;/p>
&lt;p>&lt;em>以下是自己的一点思考：&lt;/em>&lt;br>
可以看到，HTTP/3 协议带来的最大改变是协议底层将采用 UDP 协议，而不再是 TCP 协议，那这样的好处可以说是更低时延和更好的拥塞控制，还有更高效率的多路复用，可以说谷歌真的很厉害了，要知道 HTTP/2 也是谷歌的 SPDY 标准化之后的协议。而且这次 QUIC 发音同 quick ，上次 SPDY 发音同 speedy，是巧合还是有意为之呢。：）&lt;/p>
&lt;p>真是强者制定规则：&lt;/p>
&lt;blockquote>
&lt;p>They are in control of future web protocol development.&lt;/p>
&lt;/blockquote></description><category domain="https://h1z3y3.me/tags/http/">HTTP</category></item><item><title>设计模式：工厂方法模式</title><link>https://h1z3y3.me/posts/design-pattern-factory-method/</link><guid isPermaLink="true">https://h1z3y3.me/posts/design-pattern-factory-method/</guid><pubDate>Sun, 25 Nov 2018 18:01:29 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>工厂方法模式是简单工厂的升级。他创建一个用于实例化类的接口，并由工厂的子类决定实例化哪个类。工厂方法模式使得一个类的实例化延迟到子类。&lt;/p>
&lt;p>下面仍然以“两个数字的运算”作为例子&lt;/p>
&lt;!-- more -->
&lt;p>operations.go // 运算类&lt;/p>
&lt;pre tabindex="0">&lt;code>package factory_method
// 运算
type Operation interface {
SetA(float64)
SetB(float64)
GetResult() (float64, error)
}
// 运算基类，实现公共的方法
type OperationBase struct {
a float64
b float64
}
func (oper *OperationBase) SetA(a float64) {
oper.a = a
}
func (oper *OperationBase) SetB(b float64) {
oper.b = b
}
// 加法运算
type AddOperation struct {
*OperationBase
}
func (oper *AddOperation) GetResult() (float64, error) {
return oper.a + oper.b, nil
}
// 减法运算
type SubOperation struct {
*OperationBase
}
func (oper *SubOperation) GetResult() (float64, error) {
return oper.a - oper.b, nil
}
// 乘法元算
type MulOperation struct {
*OperationBase
}
func (oper *MulOperation) GetResult() (float64, error) {
return oper.a * oper.b, nil
}
&lt;/code>&lt;/pre>&lt;p>factory_method.go // 工厂类&lt;/p>
&lt;pre tabindex="0">&lt;code>package factory_method
// 工厂类
type OperationFactory interface {
CreateOperation() Operation
}
// 加法工厂
type AddFactory struct {
}
func (f *AddFactory) CreateOperation() Operation {
return &amp;amp;AddOperation{
OperationBase: &amp;amp;OperationBase{},
}
}
// 减法工厂
type SubFactory struct {
}
func (f *SubFactory) CreateOperation() Operation {
return &amp;amp;SubOperation{
OperationBase: &amp;amp;OperationBase{},
}
}
// 乘法工厂
type MulFactory struct {
}
func (f *MulFactory) CreateOperation() Operation {
return &amp;amp;MulOperation{
OperationBase: &amp;amp;OperationBase{},
}
}
&lt;/code>&lt;/pre>&lt;p>源码以及测试源码下载地址：&lt;a href="https://github.com/h1z3y3/big-talk-go-design-patterns/tree/master/04_factory_method">https://github.com/h1z3y3/big-talk-go-design-patterns/tree/master/04_factory_method&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/">设计模式</category><category domain="https://h1z3y3.me/tags/golang/">Golang</category></item><item><title>设计模式：代理模式</title><link>https://h1z3y3.me/posts/design-pattern-proxy/</link><guid isPermaLink="true">https://h1z3y3.me/posts/design-pattern-proxy/</guid><pubDate>Sun, 25 Nov 2018 16:50:58 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>代理模式即在真实类的基础上封装一层代理类，由代理类完成对真实类的调用。 &lt;br>
以便可以在代理类中做一些额外的工作，如进行访问权限校验、保存Cache缓存等操作。&lt;/p>
&lt;p>下面以&amp;quot;读取图片资源&amp;quot;为例说明代理模式：&lt;/p>
&lt;!-- more -->
&lt;pre tabindex="0">&lt;code>package proxy
// 接口，代理类和真实类都要实现
type Image interface {
Get() string
}
// 真实的图片类
type RealImage struct {}
func (r *RealImage) Get() string {
return &amp;#34;real_image_url&amp;#34;
}
// 代理类
type ImageProxy struct {
realImage RealImage
}
// 由代理类进行原类的调用，从而能在原类基础上做一些操作
func (r *ImageProxy) Get() string {
var res string
// pre: 权限检查、查看是否有cache等
res += &amp;#34;pre:&amp;#34;
res += r.realImage.Get()
// after: 保存cache、格式化结果等
res += &amp;#34;:after&amp;#34;
return res
}
&lt;/code>&lt;/pre>&lt;p>源码以及测试源码下载地址：&lt;a href="https://github.com/h1z3y3/big-talk-go-design-patterns/tree/master/03_proxy">https://github.com/h1z3y3/big-talk-go-design-patterns/tree/master/03_proxy&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/">设计模式</category><category domain="https://h1z3y3.me/tags/golang/">Golang</category></item><item><title>Linux:平均负载</title><link>https://h1z3y3.me/posts/linux-avarage-load/</link><guid isPermaLink="true">https://h1z3y3.me/posts/linux-avarage-load/</guid><pubDate>Sun, 25 Nov 2018 16:08:22 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;ol>
&lt;li>&lt;strong>平均负载&lt;/strong>是单位时间内“正在使用CPU”、“等待CPU”、“等待IO”的进程数量&lt;/li>
&lt;li>&lt;strong>平均负载&lt;/strong>和&lt;em>CPU使用率&lt;/em>不一样，但是CPU密集型任务可能导致平均负载升高&lt;/li>
&lt;li>由1，2可知，&lt;strong>平均负载&lt;/strong>升高，不一定是由于CPU使用率高导致，也可能是IO繁忙&lt;/li>
&lt;li>除了&lt;code>top&lt;/code>命令，还有&lt;code>mpstat&lt;/code>、&lt;code>pidstat&lt;/code>等命令&lt;/li>
&lt;li>&lt;code>watch&lt;/code>命令，可以检测一个命令的执行结果，而不必一次次手动输入运行&lt;/li>
&lt;li>&lt;code>uptime&lt;/code>命令&lt;/li>
&lt;/ol></description><category domain="https://h1z3y3.me/tags/linux/">Linux</category></item><item><title>设计模式：装饰器模式</title><link>https://h1z3y3.me/posts/design-pattern-decorator/</link><guid isPermaLink="true">https://h1z3y3.me/posts/design-pattern-decorator/</guid><pubDate>Tue, 06 Nov 2018 21:33:52 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>装饰器模式主要解决要动态的给一个类添加一些新功能，而又不想让这个类变得庞大。
这种模式需要创建一个装饰类来包装扩展原有的类，并且在保证原有的类保持结构一致的前提下，提供额外的功能。&lt;/p>
&lt;p>下面是给一个人装饰衣服的实例：&lt;/p>
&lt;!-- more -->
&lt;pre tabindex="0">&lt;code>package decorator
import &amp;#34;fmt&amp;#34;
type Person interface {
Show()
}
// 具体实现
type ConcreteComponent struct {
}
func (c *ConcreteComponent) Show() {
fmt.Print(&amp;#34;A Person wears sunglasses; &amp;#34;)
}
// 男人
type Man struct{}
func (m *Man) Show() {
fmt.Print(&amp;#34;A man wear a hat!&amp;#34;)
}
// 女人
type Woman struct{}
func (w *Woman) Show() {
fmt.Print(&amp;#34;A woman wear a skirt!&amp;#34;)
}
// TShirt
type TShirtDecorator struct {
Person
Color string
}
func (t *TShirtDecorator) Show() {
t.Person.Show() // 调用父类的 Show() 方法
// &amp;#34;装饰&amp;#34;: 增加自己特有的属性
fmt.Print(fmt.Sprintf(&amp;#34;Color: %s, TShirt; &amp;#34;, t.Color))
}
func WearTShirt(p Person, c string) Person {
return &amp;amp;TShirtDecorator{
Person: p,
Color: c,
}
}
// Pants
type PantsDecorator struct {
Person
Length int64
}
func (p *PantsDecorator) Show() {
p.Person.Show()
fmt.Print(fmt.Sprintf(&amp;#34;Lenght: %dcm, Pants.; &amp;#34;, p.Length))
}
func WearPants(p Person, l int64) Person {
return &amp;amp;PantsDecorator{
Person: p,
Length: l,
}
}
// Shoes
type ShoesDecorator struct {
Person
Size int64
}
func (s *ShoesDecorator) Show() {
s.Person.Show()
fmt.Print(fmt.Sprintf(&amp;#34;Size: %d, Shoes; &amp;#34;, s.Size))
}
func WearShoes(p Person, s int64) Person {
return &amp;amp;ShoesDecorator{
Person: p,
Size: s,
}
}
// Examples
func ExampleConcrete_Wear() {
var p Person = &amp;amp;ConcreteComponent{}
p = WearTShirt(p, &amp;#34;Blue&amp;#34;)
p = WearPants(p, 100)
p = WearShoes(p, 42)
p.Show()
// Output: A Person wears sunglasses; Color: Blue, TShirt; Lenght: 100cm, Pants.; Size: 42, Shoes;
}
func ExampleMan_Show() {
var xiaoming Person = &amp;amp;Man{}
xiaoming = WearShoes(xiaoming, 43)
xiaoming = WearTShirt(xiaoming, &amp;#34;White&amp;#34;)
xiaoming.Show()
// Output:A man wear a hat!Size: 43, Shoes; Color: White, TShirt;
}
func ExampleWoman_Show() {
var xiaohong Person = &amp;amp;Woman{}
xiaohong = WearTShirt(xiaohong, &amp;#34;Red&amp;#34;)
xiaohong = WearShoes(xiaohong, 38)
xiaohong.Show()
// Output: A woman wear a skirt!Color: Red, TShirt; Size: 38, Shoes;
}
&lt;/code>&lt;/pre>&lt;p>源码以及测试源码下载地址：&lt;a href="https://github.com/h1z3y3/big-talk-go-design-patterns/tree/master/02_decorator">https://github.com/h1z3y3/big-talk-go-design-patterns/tree/master/02_decorator&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/">设计模式</category><category domain="https://h1z3y3.me/tags/golang/">Golang</category></item><item><title>设计模式：策略模式</title><link>https://h1z3y3.me/posts/design-pattern-strategy/</link><guid isPermaLink="true">https://h1z3y3.me/posts/design-pattern-strategy/</guid><pubDate>Wed, 24 Oct 2018 23:33:52 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>在策略模式中，我们需要创建一系列策略对象和一个能随策略对象改变而改变的Context对象，策略对象改变Context的执行方法。&lt;/p>
&lt;p>仍以两个数字的加减乘除操作作为示例&lt;/p>
&lt;!-- more -->
&lt;pre tabindex="0">&lt;code>package strategy
import &amp;#34;fmt&amp;#34;
// Context 类
type Context struct {
strategy Strategy
}
func NewContext(strategy Strategy) *Context {
return &amp;amp;Context{
strategy: strategy,
}
}
func (c *Context) GetResult(a float64, b float64) (float64, error) {
return c.strategy.GetResult(a, b)
}
// 策略接口
type Strategy interface {
GetResult(a float64, b float64) (float64, error)
}
// 以下类实现策略接口
// 加法
type Add struct{}
func (o *Add) GetResult(a float64, b float64) (float64, error) {
return a + b, nil
}
// 减法
type Sub struct{}
func (o *Sub) GetResult(a float64, b float64) (float64, error) {
return a - b, nil
}
// 乘法
type Mul struct{}
func (o *Mul) GetResult(a float64, b float64) (float64, error) {
return a * b, nil
}
// 除法
type Div struct{}
func (o *Div) GetResult(a float64, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf(&amp;#34;除数不能为0&amp;#34;)
}
return a / b, nil
}
&lt;/code>&lt;/pre>&lt;p>源码以及测试源码下载地址：&lt;a href="https://github.com/h1z3y3/big-talk-go-design-patterns/tree/master/01_strategy">https://github.com/h1z3y3/big-talk-go-design-patterns/tree/master/01_strategy&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/">设计模式</category><category domain="https://h1z3y3.me/tags/golang/">Golang</category></item><item><title>设计模式：简单工厂模式</title><link>https://h1z3y3.me/posts/design-pattern-simple-factory/</link><guid isPermaLink="true">https://h1z3y3.me/posts/design-pattern-simple-factory/</guid><pubDate>Sun, 21 Oct 2018 20:58:52 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>这个系列是《大话设计模式》的读后感，将书中的设计模式用golang实现。&lt;/p>
&lt;p>第一个设计模式是简单工厂模式，主要用到的知识点是类的&lt;strong>多态&lt;/strong>。 &lt;br>
&lt;strong>多态&lt;/strong>表示不同的类可以执行相同的方法，但要通过它们自己的实现代码来执行。 &lt;br>
而在golang中没有类的概念，我们可以借助接口(interface)类型实现类的多态。&lt;br>
如果一个类型实现了接口的所有方法，那么就可以说这个类型实现了这个接口。&lt;/p>
&lt;p>我们要实现两个数字的加减乘除操作作为示例&lt;/p>
&lt;!-- more -->
&lt;pre tabindex="0">&lt;code>package simplefactory
import &amp;#34;fmt&amp;#34;
// 1. 定义一个接口类型，子类必须实现GoResult方法来实现该接口
type Operation interface {
GetResult(a float64, b float64) (float64, error)
}
// 2. 初始化工厂类方法，传入操作符，返回对应的类
func NewOperation(oper string) Operation {
switch oper {
case &amp;#34;+&amp;#34;:
return &amp;amp;operationAdd{}
case &amp;#34;-&amp;#34;:
return &amp;amp;operationSub{}
case &amp;#34;*&amp;#34;:
return &amp;amp;operationMul{}
case &amp;#34;/&amp;#34;:
return &amp;amp;operationDiv{}
default:
return nil
}
}
// 加法
type operationAdd struct{}
func (o *operationAdd) GetResult(a float64, b float64) (float64, error) {
return a + b, nil
}
// 减法
type operationSub struct{}
func (o *operationSub) GetResult(a float64, b float64) (float64, error) {
return a - b, nil
}
// 乘法
type operationMul struct{}
func (o *operationMul) GetResult(a float64, b float64) (float64, error) {
return a * b, nil
}
// 除法
type operationDiv struct{}
func (o *operationDiv) GetResult(a float64, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf(&amp;#34;除数不能为0&amp;#34;)
}
return a / b, nil
}
&lt;/code>&lt;/pre>&lt;p>源码以及测试源码下载地址：&lt;a href="https://github.com/h1z3y3/big-talk-go-design-patterns/tree/master/00_simple_factory">https://github.com/h1z3y3/big-talk-go-design-patterns/tree/master/00_simple_factory&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/">设计模式</category><category domain="https://h1z3y3.me/tags/golang/">Golang</category></item><item><title>计算密集型 vs. IO密集型</title><link>https://h1z3y3.me/posts/cpu-io/</link><guid isPermaLink="true">https://h1z3y3.me/posts/cpu-io/</guid><pubDate>Thu, 16 Nov 2017 21:56:44 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;blockquote>
&lt;p>计算密集型任务&lt;/p>
&lt;/blockquote>
&lt;p>计算密集型任务主要消耗CPU资源，需要进行大量的计算，如计算圆周率或对视频进行高清的解码等，全靠CPU的运算能力。
这种任务虽然可以利用多任务去完成，但是任务越多，花在任务切换上的时间就会越多，那么CPU的执行效率就会越低，
所以，要高效的利用CPU，计算密集型任务同时进行的数量应当等于CPU的核心数，尽量减少CPU在任务间的切换时间。&lt;/p>
&lt;blockquote>
&lt;p>IO密集型任务&lt;/p>
&lt;/blockquote>
&lt;p>一般涉及到网络、磁盘IO的任务都是IO密集型任务，这类任务的特点就是CPU消耗很少，任务的大部分时间都花在等待IO操作的完成。
由于IO的速度远远低于CPU和内存的速度，所以IO密集型任务的数量越多，CPU的效率就会越高，但是也会有一定的限度。
常见的大部分任务都是IO密集型任务，比如Web应用。&lt;/p>
&lt;p>IO密集型任务执行期间，99%的时间都花在IO上，花在CPU上的时间很少，因此，用运行速度极快的C语言替换Python这样运行速度极低的脚本语言，
也完全无法提升运行效率。对于IO密集型任务，最合适的语言就是开发效率最高，代码量最少的语言，脚本语言是首选，C语言最差。&lt;/p>
&lt;blockquote>
&lt;p>总结&lt;/p>
&lt;/blockquote>
&lt;p>计算密集型程序适合C语言多线程
IO密集型程序适合脚本语言开发的多线程&lt;/p></description><category domain="https://h1z3y3.me/tags/%E5%9F%BA%E7%A1%80/">基础</category></item><item><title>去除CKFinder版权信息以及启用文件移动功能</title><link>https://h1z3y3.me/posts/remove-ckfinder-copyright/</link><guid isPermaLink="true">https://h1z3y3.me/posts/remove-ckfinder-copyright/</guid><pubDate>Sun, 14 May 2017 20:12:44 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>最近在做毕业设计，使用到了CkEditor和CkFinder来做富文本编辑框以及文件管理。
因为CkFinder只能下载使用Demo版本，会有一些版权信息。
我不是用做商业目的，仅仅为了学习，所以想把版权信息去掉。
看了几篇博客，都是教你把alert框加上&lt;code>display:none&lt;/code>，我考虑到这样并不好，因为如果有其他正常的信息也不能显示了，所以花了些时间改了下ckfinder.js，既去掉了版权信息，又不会影响其他信息的提示。&lt;/p>
&lt;p>&lt;strong>下文提供了两种方法，建议使用第二种。&lt;/strong>&lt;/p>
&lt;p>&lt;strong>如果你是用于商业目的，请到&lt;a href="https://cksource.com/ckfinder/buy">官网&lt;/a>购买使用的License使用。&lt;/strong>&lt;/p>
&lt;p>&lt;em>&lt;strong>CKFinder版本: Version: 2.6.2.1 • Released 11 Oct 2016&lt;/strong>&lt;/em>&lt;/p>
&lt;p>不想自己操作这么麻烦的，去CSDN下载我上传好的，2积分 哈哈哈&lt;br>
链接：&lt;a href="http://download.csdn.net/detail/qq1255685485/9845062">http://download.csdn.net/detail/qq1255685485/9845062&lt;/a>&lt;/p>
&lt;h1 id="第一种">第一种&lt;/h1>
&lt;p>第一种麻烦而且需要逻辑改动较大。&lt;/p>
&lt;h2 id="左下角信息">左下角信息&lt;/h2>
&lt;p>在ckfinder.js中搜索:&lt;/p>
&lt;pre>&lt;code>t=&amp;quot;\x54\150\151\163\040\x69\163\x20\164\x68\145\040\x44\105\x4d\x4f\040\166\x65\x72\x73\x69\157\156\x20\157\146\040\103\113\x46\151\x6e\144\145\x72\056\040\120\154\x65\141\163\145\040\166\x69\x73\x69\x74\x20\164\150\145\040\x3c\x61\x20\150\162\x65\x66\x3d\x27\150\164\x74\x70\x3a\x2f\x2f\x63\x6b\163\157\x75\x72\x63\x65\056\143\x6f\x6d\057\143\153\146\151\x6e\144\x65\162\x27\x20\164\141\162\x67\x65\x74\x3d\047\x5f\x62\154\141\x6e\x6b\x27\x3e\x43\x4b\106\151\156\144\x65\x72\040\x77\145\x62\040\163\151\x74\x65\x3c\x2f\x61\x3e\x20\164\157\040\x6f\x62\x74\141\x69\x6e\x20\141\040\166\x61\154\151\144\040\x6c\151\x63\x65\156\163\x65\x2e&amp;quot;
&lt;/code>&lt;/pre>
&lt;p>位置在文件比较靠后的位置，替换为:&lt;/p>
&lt;pre>&lt;code>t=&amp;quot;&amp;quot;
&lt;/code>&lt;/pre>
&lt;p>这一句的解码后为:&lt;/p>
&lt;pre>&lt;code>This is the DEMO version of CKFinder.Please visit the &amp;lt;a href='http://cksource.com/ckfinder' target='_blank'&amp;gt;CKFinder web site&amp;lt;/a&amp;gt; to obtain a valid license.
&lt;/code>&lt;/pre>
&lt;p>可以在 &lt;a href="http://ddecode.com/hexdecoder">http://ddecode.com/hexdecoder&lt;/a> 解码&lt;/p>
&lt;h2 id="顶端信息">顶端信息&lt;/h2>
&lt;p>在ckfinder.js中搜索:&lt;/p>
&lt;pre>&lt;code>P=&amp;quot;\x50\x6c\x65\141\x73\x65\040\166\151\x73\x69\x74\x20\164\x68\x65\040\074\141\040\x68\162\x65\146\075\x27\x68\164\x74\160\072\057\057\x63\x6b\x73\x6f\x75\x72\x63\x65\056\x63\157\155\057\143\x6b\x66\151\x6e\x64\x65\x72\x27\x20\x74\141\162\147\145\x74\x3d\x27\x5f\x62\154\141\x6e\153\x27\x3e\x43\x4b\106\x69\x6e\x64\x65\162\x20\x77\x65\142\x20\x73\x69\x74\145\x3c\x2f\x61\x3e\040\x74\x6f\x20\157\142\x74\x61\151\156\x20\141\040\166\141\154\151\x64\040\154\151\143\145\x6e\163\145\x2e&amp;quot;,Q=&amp;quot;\x54\150\x69\163\x20\151\x73\040\x74\x68\x65\040\x44\x45\x4d\117\040\x76\x65\x72\163\x69\157\156\x20\157\x66\040\x43\x4b\106\x69\156\144\145\x72\x2e\x20&amp;quot;+P,R=&amp;quot;\x50\162\157\144\x75\x63\164\040\154\151\143\x65\156\163\145\x20\150\141\x73\x20\x65\x78\160\x69\162\145\144\x2e\040&amp;quot;+P;
&lt;/code>&lt;/pre>
&lt;p>这里定义了三个变量P, Q, R, 分别代表的意思如下:&lt;/p>
&lt;pre>&lt;code>P=&amp;quot;Please visit the &amp;lt;a href='http://cksource.com/ckfinder' target='_blank'&amp;gt;CKFinder web site&amp;lt;/a&amp;gt; to obtain a valid license.&amp;quot;;
Q=&amp;quot;This is the DEMO version of CKFinder.&amp;quot; + P;
R=&amp;quot;Product license has expired.&amp;quot; + P;
&lt;/code>&lt;/pre>
&lt;p>将搜索到的内容替换为&lt;/p>
&lt;pre>&lt;code>P=&amp;quot;&amp;quot;; Q=&amp;quot;&amp;quot;; R=&amp;quot;&amp;quot;;
&lt;/code>&lt;/pre>
&lt;p>替换完成后发现文件库为空白，更具体可以说，当将&lt;code>Q&lt;/code>设置为空白字符串之后才会显示空白，而替换&lt;code>P&lt;/code>和&lt;code>R&lt;/code>都不会出现这种情况，因为代码后面对&lt;code>Q&lt;/code>的值做了判断.&lt;/p>
&lt;p>所以我们继续向下看，继续搜索：&lt;/p>
&lt;pre>&lt;code>if(!Q)
&lt;/code>&lt;/pre>
&lt;p>可以看到这个判断&lt;code>if(!Q) return;&lt;/code>,删除这一个判断体.&lt;/p>
&lt;p>还没有结束，继续搜索:&lt;/p>
&lt;pre>&lt;code>Q.charAt
&lt;/code>&lt;/pre>
&lt;p>可以看到另外一个判断&lt;code>if(pw||Q.charAt(2&amp;lt;&amp;lt;2)!='t')return;&lt;/code>，为了保证正常使用，将这个判断体替换为:&lt;/p>
&lt;pre>&lt;code>if(pw) return;
&lt;/code>&lt;/pre>
&lt;p>另外多说一句，&lt;code>Q&lt;/code>变量删除到&lt;code>Q=&amp;quot;\x54\150\x69\163\x20\151\x73\040\x74&amp;quot;&lt;/code>的时候还能正常使用，也就是&lt;code>Q=&amp;quot;This is t&amp;quot;&lt;/code>，可以做些尝试。&lt;/p>
&lt;p>当以上工作全部完成之后，刷新页面即可以看到版权信息全部去掉了，而且并没有影响到其他信息的提醒。&lt;/p>
&lt;p>哈哈哈😂。看到这的都是真爱。
那么给你们说一个更简单的方法，只需三步，更完美的解决问题。&lt;/p>
&lt;h1 id="第二种">第二种&lt;/h1>
&lt;p>简单，只需三步&lt;/p>
&lt;h2 id="左下角信息-1">左下角信息&lt;/h2>
&lt;p>搜索: &lt;code>this.dV().getChild(0).appendHtml&lt;/code>，很显然是为左下角添加信息，看这一句上面有个if判断:&lt;/p>
&lt;pre>&lt;code> if (this.app.gd == 'bootstrap') {
var H = &amp;quot;\x3c\x64\151\x76\x20\143\x6c\x61\x73\x73\x3d\x27\166\x69\145\167\x20\164\157\157\154\137\160\141\x6e\145\x6c\047\040\x73\x74\x79\154\145\075\047\x64\151\x73\x70\154\141\x79\x3a\x62\x6c\157\143\153\x20\x21\x69\x6d\160\157\x72\164\x61\x6e\164\x3b\160\x6f\163\x69\x74\151\x6f\x6e\072\x73\x74\x61\164\151\143\040\x21\x69\x6d\160\157\x72\164\x61\x6e\x74\x3b\x63\x6f\x6c\157\x72\x3a\043\x33\x31\x37\x30\x38\x66\x20\041\151\155\x70\157\x72\x74\x61\156\x74\073\x62\x61\x63\x6b\147\162\157\165\x6e\144\055\143\157\x6c\x6f\x72\x3a\x23\144\x39\145\144\146\x37\x20\x21\x69\x6d\x70\157\x72\x74\141\156\x74\073\146\x6f\156\164\x2d\x73\151\172\x65\x3a\x20\061\063\x70\170\073\040\x70\141\x64\144\x69\156\147\072\040\x35\x70\170\073\x20\155\141\162\147\x69\x6e\072\065\160\170\x3b\x62\157\162\144\x65\162\055\x72\141\144\x69\x75\x73\x3a\x34\160\x78\x3b\047\x3e&amp;quot;;
v = H + t + s;
w = H + u + s;
}
&lt;/code>&lt;/pre>
&lt;p>进行解码和字符串组合，可以看到变量&lt;code>v&lt;/code>就是版权信息, &lt;code>w&lt;/code>是已经购买的信息。在搜索到的&lt;code>this.dV().getChild(0).appendHtml&lt;/code>括号里面，改为:&lt;/p>
&lt;pre>&lt;code>this.dV().getChild(0).appendHtml(C||D||A!=4?&amp;quot;&amp;quot;:&amp;quot;&amp;quot;);
&lt;/code>&lt;/pre>
&lt;p>简言之就是不管买没买，都不显示这个提示，设置为空字符串就好了。&lt;/p>
&lt;h2 id="顶端信息-1">顶端信息&lt;/h2>
&lt;p>顶端的去处版权信息的方法和左下角类似。&lt;/p>
&lt;p>搜索: &lt;code>aT.mj=Q&lt;/code> 替换为 &lt;code>aT.mj=Q.toLowerCase().indexOf(&amp;quot;demo&amp;quot;) ? &amp;quot;&amp;quot; : Q;&lt;/code>
搜索: &lt;code>aT.mj=R&lt;/code> 替换为 &lt;code>aT.mj=R.toLowerCase().indexOf(&amp;quot;demo&amp;quot;) ? &amp;quot;&amp;quot; : R;&lt;/code>&lt;/p>
&lt;p>大功告成， 更完美的解决了问题～ 😎&lt;/p>
&lt;h1 id="2017-5-17-更新启用剪切功能">2017-5-17 更新：启用剪切功能&lt;/h1>
&lt;p>今天发现虽然版权信息没有了，但是“临时文件夹”功能“从临时文件夹剪切至此”在Demo版本中不能使用，提示:&lt;/p>
&lt;pre>&lt;code>This function is disabled in the demo version of CKFinder.
Please visit the CKFinder web site to obtain a valid license.
&lt;/code>&lt;/pre>
&lt;p>启用方法：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>搜索&lt;code>z.app.msgDialog&lt;/code>, 可以看到包含它的是一个判断。&lt;/p>
&lt;pre>&lt;code> if('balabalabalabalabala...') //这个判断很长
显示这是Demo版本，不能用这个功能;
else
执行指令;
&lt;/code>&lt;/pre>
&lt;p>那我们要做的就是只留下需要执行的指令就可以了。
这一步完成，就可以从临时文件中移动文件了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>和第1步类似，搜索&lt;code>y.msgDialog&lt;/code>,同样包含它的是一个判断，不同的是这个判断体的&lt;code>else&lt;/code>部分执行了多条指令。删除判断只留下指令就可以了。&lt;/p>
&lt;p>这一步完成，就可以在各个文件夹中移动文件了。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>这样文件的移动功能就可以正常使用了。我好像太坏了。&lt;/p></description><category domain="https://h1z3y3.me/tags/j2ee/">J2EE</category><category domain="https://h1z3y3.me/tags/ckfinder/">CKFinder</category><category domain="https://h1z3y3.me/tags/ckeditor/">CKEditor</category></item><item><title>Shiro清除更新缓存的用户权限</title><link>https://h1z3y3.me/posts/shiro-clear-update-authorization/</link><guid isPermaLink="true">https://h1z3y3.me/posts/shiro-clear-update-authorization/</guid><pubDate>Tue, 04 Apr 2017 18:57:44 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>Apache Shiro用于权限管理十分方便，但存在一个问题，就是当用户的权限发生变化的时候，就需要用户重新登录，重新缓存用户的权限信息。&lt;br>
现在想要在改变用户的权限的时候，清理用户的权限。 &lt;br>
在写的过程中查找了一些资料，但是并没有成功实现权限的清理，所以我进行了一些修改，并实现了Helper类。&lt;/p>
&lt;p>我写的帮助类：&lt;/p>
&lt;pre>&lt;code>package com.zhaoyang.core.feature.security;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author zhaoyang
*
* @since 2017-04-04 2:12 PM
*/
public class ShiroAuthorizationHelper {
//shiro 配置的cacheManager， 需要使用Spring bean进行注入
private static CacheManager cacheManager;
private static Logger logger = LoggerFactory.getLogger(ShiroAuthorizationHelper.class);
/**
* 清除用户的权限
*
* 这里需要注意的是，
* 网上很多实现都是这里只传递一个String类型的username过来，根据这个String当key去清除Cache
* 但是Shiro在缓存用户权限的时候使用的key并不是String类型，所以调用remove的时候并不能清除缓存的权限
*
* shiro缓存时使用的key，是登录时使用的SimplePrincipalCollection对象，所以remove的时候需要的不是一个String值，
* 具体可以参考下面方法中打印cache的key的过程, 可以看到打印出key的类是 `class org.apache.shiro.subject.SimplePrincipalCollection`
* 所以你cache.remove(String username)肯定清除不了
*
* @param principal
*/
public static void clearAuthorizationInfo(SimplePrincipalCollection principal) {
logger.info(&amp;quot;clear the user: &amp;quot; + principal.toString() + &amp;quot;'s authorizationInfo&amp;quot;);
Cache&amp;lt;Object, Object&amp;gt; cache = cacheManager.getCache(&amp;quot;myShiroCache&amp;quot;);//myShiroCache是我配置用于缓存的cache的Name，在spring配置文件中配置，可以看文章最后
// for (Object k : cache.keys()) {
// System.out.println(k.getClass());
// }
cache.remove(principal);
}
/**
* 清除当前用户的权限
*/
public static void clearAuthorizationInfo() {
if (SecurityUtils.getSubject().isAuthenticated()) {
Subject subject = SecurityUtils.getSubject();
String username = subject.getPrincipal().toString();
String realmName = subject.getPrincipals().getRealmNames().iterator().next();
SimplePrincipalCollection principalCollection = new SimplePrincipalCollection(username, realmName);
// 调用清理用户权限
clearAuthorizationInfo(principalCollection);
}
}
/**
* 由Spring bean将对象注入
* @param cacheManager
*/
public static void setCacheManager(CacheManager cacheManager) {
ShiroAuthorizationHelper.cacheManager = cacheManager;
}
}
&lt;/code>&lt;/pre>
&lt;p>将cacheManager注入到帮助类：&lt;/p>
&lt;pre>&lt;code> &amp;lt;!-- 注入Shiro帮助类的cacheManager --&amp;gt;
&amp;lt;bean class=&amp;quot;org.springframework.beans.factory.config.MethodInvokingFactoryBean&amp;quot;&amp;gt;
&amp;lt;property name=&amp;quot;staticMethod&amp;quot; value=&amp;quot;com.damaiya.DMYSite.core.feature.security.ShiroAuthorizationHelper.setCacheManager&amp;quot;/&amp;gt;
&amp;lt;property name=&amp;quot;arguments&amp;quot; ref=&amp;quot;shiroEhcacheManager&amp;quot;/&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>当然ref=“shiroEhcacheManager”需要你自己去实现, 我这里贴下我的：&lt;/p>
&lt;pre>&lt;code> &amp;lt;!-- 缓存管理器 使用Ehcache实现 --&amp;gt;
&amp;lt;bean id=&amp;quot;shiroEhcacheManager&amp;quot; class=&amp;quot;org.apache.shiro.cache.ehcache.EhCacheManager&amp;quot;&amp;gt;
&amp;lt;property name=&amp;quot;cacheManagerConfigFile&amp;quot; value=&amp;quot;classpath:ehcache-shiro.xml&amp;quot;/&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>下面是ehcache-shiro.xml配置, 具体的参数作用我就不说了:&lt;/p>
&lt;pre>&lt;code>&amp;lt;ehcache updateCheck=&amp;quot;false&amp;quot; name=&amp;quot;shiroCache&amp;quot;&amp;gt;
&amp;lt;defaultCache
maxElementsInMemory=&amp;quot;10000&amp;quot;
eternal=&amp;quot;false&amp;quot;
timeToIdleSeconds=&amp;quot;120&amp;quot;
timeToLiveSeconds=&amp;quot;120&amp;quot;
overflowToDisk=&amp;quot;false&amp;quot;
diskPersistent=&amp;quot;false&amp;quot;
diskExpiryThreadIntervalSeconds=&amp;quot;120&amp;quot;
/&amp;gt;
&amp;lt;cache name=&amp;quot;myShiroCache&amp;quot;
maxElementsInMemory=&amp;quot;10000&amp;quot;
eternal=&amp;quot;false&amp;quot;
timeToIdleSeconds=&amp;quot;30&amp;quot;
timeToLiveSeconds=&amp;quot;0&amp;quot;
overflowToDisk=&amp;quot;false&amp;quot;
diskPersistent=&amp;quot;false&amp;quot;
diskExpiryThreadIntervalSeconds=&amp;quot;120&amp;quot;/&amp;gt;
&amp;lt;/ehcache&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>使用指定Name的Cache进行权限缓存配置, securityRealm是我自己的Realm：&lt;/p>
&lt;pre>&lt;code>&amp;lt;bean id=&amp;quot;securityRealm&amp;quot; class=&amp;quot;com.damaiya.DMYSite.web.security.SecurityRealm&amp;quot;&amp;gt;
&amp;lt;property name=&amp;quot;authorizationCacheName&amp;quot; value=&amp;quot;myShiroCache&amp;quot;/&amp;gt;
&amp;lt;/bean&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>这样整个配置就完成了，而且调用&lt;code>clearAuthorizationInfo()&lt;/code>时就可以清除当前登录用户的权限信息了。&lt;/p></description><category domain="https://h1z3y3.me/tags/j2ee/">J2EE</category><category domain="https://h1z3y3.me/tags/shiro/">Shiro</category></item><item><title>最简单方法解决使用Shiro后URL中JSESSIONID的问题</title><link>https://h1z3y3.me/posts/tomcat-shiro-disable-jsessionid-in-url/</link><guid isPermaLink="true">https://h1z3y3.me/posts/tomcat-shiro-disable-jsessionid-in-url/</guid><pubDate>Thu, 30 Mar 2017 22:02:19 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>&lt;strong>在J2EE项目中使用Shiro进行权限验证后，每次部署跳转到登录界面总会在链接后面多出&lt;code>;JSESSION=xxxx&lt;/code>，查了很多，大概有下面几种方法：&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>
&lt;p>在web.xml中添加以下代码：&lt;/p>
&lt;pre>&lt;code> &amp;lt;session-config&amp;gt;
&amp;lt;tracking-mode&amp;gt;COOKIE&amp;lt;/tracking-mode&amp;gt;
&amp;lt;/session-config&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>具体请参考：&lt;br>
&lt;a href="">http://stackoverflow.com/questions/11327631/remove-jsessionid-from-url&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;ol start="2">
&lt;li>
&lt;p>使用Filter对URL进行rewrite&lt;br>
具体请参考：&lt;br>
&lt;a href="">http://dr-yanglong.github.io/2015/07/07/del-jeesessionid/&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>有些同学重写了shiro重定向时需要调用的方法&lt;code>encodeRedirectURL()&lt;/code>和&lt;code>toEncoded()&lt;/code>&lt;/p>
&lt;p>具体请参考：&lt;br>
&lt;a href="">http://dwangel.iteye.com/blog/2275899&lt;/a>&lt;br>
&lt;a href="">http://alex233.blog.51cto.com/8904951/1856155&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>后来去看了源码，发现了一个最最简单的方法，如下：&lt;/p>
&lt;p>shiro源码：&lt;br>
&lt;a href="">https://github.com/apache/shiro/pull/31/commits/7f37394c6048d8c8a214eabd312721ddb51adc9b&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>阅读源码之后，可以发现&lt;code>DefaultWebSessionManager.java&lt;/code>文件中添加了新属性&lt;code>private boolean sessionIdUrlRewritingEnabled;&lt;/code>, 顾名思义, 是用来控制是否重写URL添加SESSIONID的，只要修改shiro的sessionManager配置如下即可：&lt;/p>
&lt;pre>&lt;code> &amp;lt;bean id=&amp;quot;sessionManager&amp;quot; class=&amp;quot;org.apache.shiro.web.session.mgt.DefaultWebSessionManager&amp;quot;&amp;gt;
...
&amp;lt;property name=&amp;quot;sessionIdUrlRewritingEnabled&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;
...
&amp;lt;/bean&amp;gt;
&lt;/code>&lt;/pre>
&lt;h4 id="more-source">More Source：&lt;/h4>
&lt;p>&lt;a href="">https://fralef.me/tomcat-disable-jsessionid-in-url.html&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/j2ee/">J2EE</category><category domain="https://h1z3y3.me/tags/shiro/">Shiro</category></item><item><title>Grep, Sed, Awk 日常使用</title><link>https://h1z3y3.me/posts/grepsedawk/</link><guid isPermaLink="true">https://h1z3y3.me/posts/grepsedawk/</guid><pubDate>Fri, 19 Aug 2016 22:03:44 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h2 id="grep">grep&lt;/h2>
&lt;h3 id="概述">概述&lt;/h3>
&lt;p>在给出文件列表或标准输入后, grep会对匹配一个或多个正则表达式的文本进行搜索, 并输出匹配（或者不匹配）的行或文本.&lt;/p>
&lt;h3 id="使用格式">使用格式&lt;/h3>
&lt;pre>&lt;code>grep [options] PATTERN [FILE...]
&lt;/code>&lt;/pre>
&lt;h3 id="常用选项">常用选项&lt;/h3>
&lt;ul>
&lt;li>-i 忽略字符大小写&lt;/li>
&lt;li>-v 显示未被模式匹配到的行或串&lt;/li>
&lt;li>-o 只显示匹配到的串而不是整行&lt;/li>
&lt;li>-n 显示匹配的行及行号&lt;/li>
&lt;li>-E 使用扩展的正则表达式&lt;/li>
&lt;li>-A n 显示出匹配到的行和后n行&lt;/li>
&lt;li>-B n 显示出匹配到的行和前n行&lt;/li>
&lt;li>-C n 显示出匹配到的行和前后各n行&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>扩展的正则表达式&lt;/strong>&lt;/p>
&lt;p>扩展正则表达式与基础正则表达式的唯一区别在于: &lt;code>() {} ? +&lt;/code> 这几个字符.&lt;/p>
&lt;p>基础正则表达式中, &lt;code>() {} ? +&lt;/code> 表示特殊含义，使用时需要将他们转义&lt;/p>
&lt;p>而扩展正则表达式中, &lt;code>() {} ? +&lt;/code> 不表示特殊含义, 你需要将他们转义.&lt;/p>
&lt;p>转义符号, 都是一样的: 反斜线 &lt;code>\&lt;/code> .&lt;/p>
&lt;p>所谓特殊含义, 就是正则表达式中的含义. 非特殊含义, 就是这个符号本身.&lt;/p>
&lt;h3 id="正则表达式">正则表达式&lt;/h3>
&lt;h4 id="元字符">元字符&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">.&lt;/td>
&lt;td style="text-align:left">匹配任意单个字符&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">[]&lt;/td>
&lt;td style="text-align:left">匹配[]指定范围内的任意单个字符&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">[^]&lt;/td>
&lt;td style="text-align:left">匹配[]指定范围外的任意单个字符&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h4 id="字符集">字符集&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>[:digit:]&lt;/td>
&lt;td>数字&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>[:lower:]&lt;/td>
&lt;td>小写字母&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>[:upper:]&lt;/td>
&lt;td>大写字母&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>[:punct:]&lt;/td>
&lt;td>标点符号&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>[:space:]&lt;/td>
&lt;td>空白字符&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>[:alpha:]&lt;/td>
&lt;td>所有字母&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>[:alnum:]&lt;/td>
&lt;td>所有字母和数字, 非标点&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>例1.1&lt;/strong>&lt;/p>
&lt;pre>&lt;code>$ grep '[[:punct:]]' grep_example # 所有有标点的行
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="http://p2.qhimg.com/t01e24acc78b267c1db.png" alt="">&lt;/p>
&lt;h4 id="匹配次数">匹配次数&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>.*&lt;/td>
&lt;td>匹配任意字符串&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>*&lt;/td>
&lt;td>前面的字符重复任意次数, 0次1次或多次&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>+&lt;/td>
&lt;td>前面的字符重复1次或多次&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>?&lt;/td>
&lt;td>前面的字符重复0次或者多次&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>{m, n}&lt;/td>
&lt;td>匹配前面的字符至少m次, 至多n次&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>{m, }&lt;/td>
&lt;td>匹配前面的字符至少m次&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h4 id="位置锚定">位置锚定&lt;/h4>
&lt;p>|||
| ^ | ^后面的字符必须出现在行首 |
| $ | $前面的字符必须出现在行尾 |
| ^$ | 空白行 |
| &amp;lt; | &amp;lt;后面的字符必须出现在词首 |
| &amp;gt; | &amp;gt;前面的字符必须出现在词尾 |
| () | ()中的当做一个整体匹配 |&lt;/p>
&lt;p>&lt;strong>例1.2&lt;/strong>&lt;/p>
&lt;pre>&lt;code>$ grep -E '.*(d+)' grep_example
$ grep -E '.*(d+)$' grep_example
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="http://p7.qhimg.com/t01376509bbf9bd0068.png" alt="">&lt;/p>
&lt;h2 id="sed">sed&lt;/h2>
&lt;h3 id="概述-1">概述&lt;/h3>
&lt;p>sed (意为流编辑器, 源自英语&amp;quot;Stream Editor&amp;quot;的缩写)&lt;/p>
&lt;p>sed用来把文档或字符串里面的文字经过一系列编辑命令转换为另一种格式输出.&lt;/p>
&lt;p>sed通常用来匹配一个或多个正则表达式的文本进行处理.&lt;/p>
&lt;p>&lt;strong>模式空间&lt;/strong>&lt;/p>
&lt;p>模式空间: 把当前处理的行存储在临时的缓冲区, 称为&amp;quot;模式空间&amp;quot;(Pattern Space)&lt;/p>
&lt;p>模式空间就是读入行所在的缓存，sed对文本行进行的处理都是在这个缓存中进行的。&lt;/p>
&lt;h3 id="使用格式-1">使用格式&lt;/h3>
&lt;pre>&lt;code>sed 'Address Command' [FILE...]
&lt;/code>&lt;/pre>
&lt;h3 id="常用选项-1">常用选项&lt;/h3>
&lt;ul>
&lt;li>-n 不显示模式空间中的内容&lt;/li>
&lt;li>-i 直接修改原文件&lt;/li>
&lt;li>-r 使用扩展的正则表达式&lt;/li>
&lt;/ul>
&lt;h3 id="address">Address&lt;/h3>
&lt;pre>&lt;code>1. StartLine, EndLine:
# 开始行, 结束行
# 如: 1, 100 表示 1 到 100 行
2. /RegExp/
# 正则表达式
3. /pattern1/, /pattern2/
# 从匹配到/pattern1/开始, 一直到匹配到/pattern2/结束中间所有的行
4. LineNumber
# 指定的某一行
5. StartLine, +N
# 从StartLine开始后面N行
&lt;/code>&lt;/pre>
&lt;h3 id="command">Command&lt;/h3>
&lt;ol>
&lt;li>d 删除满足模式的行&lt;/li>
&lt;li>p 显示满足模式的行&lt;/li>
&lt;li>a \string 在满足模式的行后添加新行, 内容为string串的内容&lt;/li>
&lt;li>i \string 在满足模式的行前添加新行, 内容为string串的内容&lt;/li>
&lt;li>r file：将指定的文件的内容添加至符合条件的行后&lt;/li>
&lt;li>w file：将地址指定范围内的行另存至指定的文件中&lt;/li>
&lt;li>s \pattern\string\修饰符 将满足pattern的串替换为string
修饰符:
\g 全局替换, 即将该行中所有匹配项替换, 默认只替换第一个匹配项
\i 忽略大小写
&amp;amp; 引用模式匹配的整个串
\1 表示匹配到模式中的第一个 ( 开始的串
\2 表示匹配到模式中的第二个 ( 开始的串&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>例2.1&lt;/strong>&lt;/p>
&lt;pre>&lt;code>#从以hezhaoyang开头的行开始匹配, 一直到以I开头的行, 显示中间所有行
$ sed -n '/^hezhaoyang/,/^I/p' sed_example
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="http://p1.qhimg.com/t016c7b865c3597bb7c.png" alt="">&lt;/p>
&lt;p>&lt;strong>例2.2&lt;/strong>&lt;/p>
&lt;pre>&lt;code>#将sed_insert中的内容插入到以hezhaoyang开头的行后
$ sed '/hezhaoyang/r sed_insert' sed_example
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="http://p2.qhimg.com/t01c35938e797df3318.png" alt="">&lt;/p>
&lt;h2 id="awk">awk&lt;/h2>
&lt;h3 id="概述-2">概述&lt;/h3>
&lt;p>AWK是一种处理文本文件的语言. 它将文件作为记录序列处理.
在一般情况下，文件内容的每行都是一个记录. 每行内容都会被分割成一系列的域,
因此, 我们可以认为一行的第一个词为第一个域,第二个词为第二个, 以此类推.
AWK程序是由一些处理特定模式的语句块构成的. AWK一次可以读取一个输入行.
对每个输入行, AWK解释器会判断它是否符合程序中出现的各个模式, 并执行符合的模式所对应的动作。&lt;/p>
&lt;p>——阿尔佛雷德·艾侯，&lt;a href="http://www.computerworld.com.au/index.php/id;1726534212;pp;2">The A-Z of Programming Languages: AWK&lt;/a>&lt;/p>
&lt;h3 id="使用格式-2">使用格式&lt;/h3>
&lt;pre>&lt;code>awk [options] 'BEGIN{ } pattern{ } END{ }'
&lt;/code>&lt;/pre>
&lt;h3 id="常用选项-2">常用选项&lt;/h3>
&lt;ul>
&lt;li>-F fs : 指定分隔符为fs&lt;/li>
&lt;li>-v var=value : 定义可传递给awk的变量&lt;/li>
&lt;li>-f scriptfile : 从脚本文件中读取awk命令&lt;/li>
&lt;/ul>
&lt;h3 id="基本结构">基本结构&lt;/h3>
&lt;p>一个awk脚本通常由: BEGIN语句块、能够使用模式匹配的通用语句块、END语句块3部分组成, 这三个部分是可选的.
任意一个部分都可以不出现在脚本中, 脚本通常是被单引号或双引号中.&lt;/p>
&lt;h3 id="awk-的工作过程">awk 的工作过程&lt;/h3>
&lt;p>第一步: 执行BEGIN{ commands }语句块中的语句;
第二步: 从文件或标准输入(stdin)读取一行, 然后执行pattern{ commands }语句块, 它逐行扫描文件, 从第一行到最后一行重复这个过程, 直到文件全部被读取完毕.
第三步: 当读至输入流末尾时, 执行END{ commands }语句块.&lt;/p>
&lt;p>BEGIN语句块在awk开始从输入流中读取行之前被执行, 这是一个可选的语句块, 比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中.
END语句块在awk从输入流中读取完所有的行之后即被执行, 比如打印所有行的分析结果这类信息汇总都是在END语句块中完成, 它也是一个可选语句块.
pattern语句块中的通用命令是最重要的部分, 它也是可选的. {} 相当于一个循环体, 它会对文件中的每一行进行迭代.&lt;/p>
&lt;h3 id="输出-print--printf">输出 print / printf&lt;/h3>
&lt;h4 id="print">print&lt;/h4>
&lt;ol>
&lt;li>
&lt;p>当要输出多个变量时, 应该用逗号&amp;rsquo;,&amp;lsquo;分隔&lt;/p>
&lt;pre>&lt;code> $ echo | awk '{ var1=&amp;quot;v1&amp;quot;; var2=&amp;quot;v2&amp;quot;; var3=&amp;quot;v3&amp;quot;; print var1,var2,var3; }'
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>
&lt;p>只写 &lt;code>print&lt;/code> 相当于 &lt;code>print $0&lt;/code>, 如果想要输出空行, 应该写做: &lt;code>print ''&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>$1&lt;/code>, &lt;code>$2&lt;/code>, &lt;code>$3&lt;/code> &amp;hellip; 分别对应该行的第1个, 第2个, 第3个字段, 以此类对&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h4 id="printf">printf&lt;/h4>
&lt;p>与 print 的区别在于 printf 需要格式化输出.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>%c&lt;/td>
&lt;td>字符类型&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>%d, %i&lt;/td>
&lt;td>十进制&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>%e, %E&lt;/td>
&lt;td>科学计数法输出数值&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>%f&lt;/td>
&lt;td>浮点数类型&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>%g, %G&lt;/td>
&lt;td>科学计数法或浮点数格式显示&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>%s&lt;/td>
&lt;td>字符串&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>%u&lt;/td>
&lt;td>无符号整数&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>%%&lt;/td>
&lt;td>%本身&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h5 id="修饰符">修饰符&lt;/h5>
&lt;ul>
&lt;li>N: 宽度&lt;/li>
&lt;li>-: 左对齐&lt;/li>
&lt;li>+: 显示数值符号&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>注意&lt;/strong>:
printf 默认不换行, 需要使用&lt;code>&amp;quot;\n&amp;quot;&lt;/code>&lt;/p>
&lt;p>&lt;strong>例3.1:&lt;/strong>&lt;/p>
&lt;pre>&lt;code>#输出用户名和使用的shell类型
$ head -10 /etc/passwd | awk -F: '{printf &amp;quot;%-15s%s\n&amp;quot;, $1, $7}'
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="http://p3.qhimg.com/t0171ee814b38a60d04.png" alt="">&lt;/p>
&lt;h3 id="awk内置变量">awk内置变量&lt;/h3>
&lt;h4 id="记录变量">记录变量&lt;/h4>
&lt;ul>
&lt;li>FS: field separator 字段分隔符, 默认为空白字符&lt;/li>
&lt;li>RS: record separator 记录分隔符, 默认为换行符&lt;/li>
&lt;li>OFS: output field separator 输出字段分隔符&lt;/li>
&lt;li>ORS: output record separator 输出记录分隔符&lt;/li>
&lt;/ul>
&lt;h4 id="数据变量">数据变量&lt;/h4>
&lt;ul>
&lt;li>
&lt;p>NR: number of input rows 处理的行数&lt;/p>
&lt;/li>
&lt;li>
&lt;p>NF: number of fields 该行字段的个数&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ARGV: 数组, 保存awk命令行.
如: $ awk &amp;lsquo;{ print $0 }&amp;rsquo; a.txt b.txt中ARGV[0]=awk, ARGV[1]=a.txt&lt;/p>
&lt;/li>
&lt;li>
&lt;p>ARGC: awk命令参数的个数&lt;/p>
&lt;/li>
&lt;li>
&lt;p>FILENAME: 所处理文件的名称&lt;/p>
&lt;/li>
&lt;li>
&lt;p>IGNORECASE: IGNORECASE=1时忽略大小写&lt;/p>
&lt;pre>&lt;code> $ echo &amp;quot;TEST HHHH&amp;quot; | awk '{IGNORECASE=1; if($1 == &amp;quot;test&amp;quot;){print &amp;quot;ok&amp;quot;;}}'
&lt;/code>&lt;/pre>
&lt;/li>
&lt;/ul>
&lt;h3 id="赋值">赋值&lt;/h3>
&lt;pre>&lt;code>$ awk -v var=&amp;quot;value&amp;quot; '{print var}'
$ awk 'BEGIN{ var=&amp;quot;value&amp;quot;; print var; }'
&lt;/code>&lt;/pre>
&lt;h3 id="操作符">操作符&lt;/h3>
&lt;pre>&lt;code>+ - * / ^ ** %
&lt;/code>&lt;/pre>
&lt;h3 id="比较运算符">比较运算符&lt;/h3>
&lt;pre>&lt;code> &amp;lt; &amp;lt;= &amp;gt;= &amp;gt; &amp;gt;= == !=
x~y
# True if the string x matches the regexp denoted by y
# 如果字符串x被正则表达式y匹配, 则为真
x!~y
&lt;/code>&lt;/pre>
&lt;h3 id="数组">数组&lt;/h3>
&lt;h4 id="定义">定义&lt;/h4>
&lt;pre>&lt;code>arr[1] = 'first'
arr[2] = 'second'
arr['year'] = 2016
arr['month'] = 8
&lt;/code>&lt;/pre>
&lt;h4 id="长度">长度&lt;/h4>
&lt;pre>&lt;code>length() 获取字符串，数组的长度
split() 分割数组， 返回长度
asort() 排序数组， 返回长度
&lt;/code>&lt;/pre>
&lt;p>&lt;strong>需要注意&lt;/strong>：&lt;/p>
&lt;p>判断数组是否包含某键值&lt;/p>
&lt;pre>&lt;code>#错误写法
$ echo | awk 'BEGIN{a[&amp;quot;a&amp;quot;]=1;a[&amp;quot;b&amp;quot;]=2; if(a[&amp;quot;c&amp;quot;]!=1){print &amp;quot;ok&amp;quot;}; for(i in a) {print i, a[i];} }'
#正确判断
$ echo | awk 'BEGIN{a[&amp;quot;a&amp;quot;]=1;a[&amp;quot;b&amp;quot;]=2; if(&amp;quot;c&amp;quot; in a){print &amp;quot;ok&amp;quot;}; for(i in a) {print i, a[i];} }'
&lt;/code>&lt;/pre>
&lt;p>awk 中的数组是关联数组, 只要是通过他的数组引用过key, 就会自动创建该序列.&lt;/p>
&lt;h4 id="多维数组">多维数组&lt;/h4>
&lt;p>awk的多维数组本质上是一维数组, 更确切地说, awk是不支持以为多维数组的.
但是awk提供了逻辑上模拟多维数组的方式. 在awk中, &lt;code>array[1, 2]=1&lt;/code> , 这样的写法是允许的.
他是使用了一个特殊字符串&lt;code>SUBSEP(\034)&lt;/code>分隔键, &lt;strong>[2, 4]&lt;strong>实际上是&lt;/strong>[2SUBSEP4]&lt;/strong>.&lt;/p>
&lt;p>访问可以使用&lt;code>for( (i, j) in array ){}&lt;/code>但是必须用圆括号.
但是单独访问时, 必须使用split()将数组键值分开.
即:&lt;/p>
&lt;pre>&lt;code>arr[1,1] = 11;
arr[1,2] = 12;
arr[2,1] = 21;
arr[2,2] = 22;
for (a in arr) {
split(a, tmp, SUBSEP);
print tmp[1], tmp[2];z
}
&lt;/code>&lt;/pre>
&lt;h3 id="控制语句">控制语句&lt;/h3>
&lt;pre>&lt;code>1. if-else
if (condition) {} else {}
2. while
while (condition) {}
3. do-while
do {} while (condition)
4. for
for ( ; ; ) {}
for (item in array) {}
5. switch
switch (expression) { case x: ; case y: ;}
6. break, continue
7. next # 提前结束本行
&lt;/code>&lt;/pre>
&lt;h3 id="内置函数">内置函数&lt;/h3>
&lt;pre>&lt;code>split(string, array [,fieldsep])
# 使用fieldsep分隔string, 并存储在名为array的数组中
#实例:
$ awk '{ split( &amp;quot;20:18:00&amp;quot;, time, &amp;quot;:&amp;quot; ); print time[2] }'
# 上例把时间按冒号分割到time数组内，并显示第二个数组元素18。
# 返回值为分割数组的长度
length(string)
substr(string, start [, length])
system(command) : 执行命令并返回给awk
systime() : 返回当前时间时间戳
tolower(string)
toupper(string)
match(string, regular expression)
# match函数返回在字符串中正则表达式位置的索引,
# 如果找不到指定的正则表达式则返回0, 找到返回1。
# match函数会设置内建变量RSTART为字符串中子字符串的开始位置, RLENGTH为到子字符串末尾的字符个数.
# substr可利于这些变量来截取字符串.
#实例：
$ awk '{start=match(&amp;quot;this is a test&amp;quot;,/[a-z]+$/); print start}'
$ awk '{start=match(&amp;quot;this is a test&amp;quot;,/[a-z]+$/); print start, RSTART, RLENGTH }'
# 第一个实例打印以连续小写字符结尾的开始位置, 这里是11.
# 第二个实例还打印RSTART和RLENGTH变量, 这里是11(start), 11(RSTART), 4(RLENGTH).
&lt;/code>&lt;/pre>
&lt;h3 id="内建数学函数">内建数学函数&lt;/h3>
&lt;pre>&lt;code>1. cos(x) 余弦函数
2. exp(x) 求幂
3. int(x) 取整
4. log(x) 自然对数, 过程没有舍入
5. rand() 产生一个大于等于0而小于1的随机数
6. sin(x) 正弦
7. sqrt(x) 平方根
8. srand(x) x是rand()函数的种子
&lt;/code>&lt;/pre>
&lt;h3 id="自定义函数">自定义函数&lt;/h3>
&lt;pre>&lt;code>function F_NAME([variable])
{
statements
}
# 函数还可以使用return语句返回值，格式为“return value”
&lt;/code>&lt;/pre></description><category domain="https://h1z3y3.me/tags/linux/">Linux</category></item><item><title>Vim 初级入门</title><link>https://h1z3y3.me/posts/how-to-use-vim/</link><guid isPermaLink="true">https://h1z3y3.me/posts/how-to-use-vim/</guid><pubDate>Sat, 23 Jul 2016 19:04:11 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>没有接触过Vim的同学, 一定会觉得平时使用的IDE很好用, 但是当你开始使用Vim, 并且渐渐熟悉, 我觉得你会爱上这一款编辑器.
千万不要因为一开始Vim有太多命令需要记忆而放弃它.&lt;/p>
&lt;p>这篇博客是翻译自《&lt;a href="http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/">Learn Vim Progressively&lt;/a>》, 并在我觉得有必要添加说明的地方添加了自己的说明. 我认为是最适合入门Vim的教程, 没有之一.&lt;/p>
&lt;hr>
&lt;h2 id="正文开始">正文开始&lt;/h2>
&lt;p>你想以最快的方式自学Vim吗? 在人类最优秀的编辑器面前, 你必须至少先学会如何幸存下来, 然后再去一点点整合使用它的技巧.&lt;/p>
&lt;p>&lt;a href="http://www.vim.org/">Vim&lt;/a> the Six Billion Dollar editor&lt;/p>
&lt;blockquote>
&lt;p>Better, Stronger, Faster&lt;/p>
&lt;/blockquote>
&lt;p>当你学会使用Vim, 它将成为你最后一个编辑器. 虽然学习它的过程很不容易, 但是最后它会难以置信的好用.&lt;/p>
&lt;p>下面4个步骤是你自学的感受:&lt;/p>
&lt;ol>
&lt;li>存活&lt;/li>
&lt;li>使用顺手&lt;/li>
&lt;li>更好, 更强, 更快&lt;/li>
&lt;li>使用Vim的超能力&lt;/li>
&lt;/ol>
&lt;p>当你读完这篇文章, 你将会爱上vim.&lt;/p>
&lt;p>但是我们开始之前, 要先说明一点:&lt;/p>
&lt;ul>
&lt;li>学习vim的过程将是十分痛苦的&lt;/li>
&lt;li>将花费一些时间, 千万不要觉得浪费时间儿放弃&lt;/li>
&lt;li>学习他的过程就像是学习乐器, 一旦入门, 就会变得很轻松&lt;/li>
&lt;li>不要奢望像其他编辑器一样三天之内就能熟练掌握, 学习vim至少需要两个星期而不是三天&lt;/li>
&lt;/ul>
&lt;h3 id="level-1---存活">Level 1 - 存活&lt;/h3>
&lt;ol start="0">
&lt;li>安装vim&lt;/li>
&lt;li>运行vim&lt;/li>
&lt;li>&lt;strong>不要做任何事情!&lt;/strong> 请先阅读&lt;/li>
&lt;/ol>
&lt;p>在普通编辑器中, 当你敲击键盘就能在屏幕上看到想要的字符. 但是对于Vim来讲, 不是这样的. 当你启动vim的时候, vim当前正在 &lt;em>&lt;code>Normal&lt;/code>&lt;/em> 模式下, 你需要先将模式切换为 &lt;em>&lt;code>Insert&lt;/code>&lt;/em> 模式, 才能够输入字符. 从 &lt;em>&lt;code>Normal&lt;/code>&lt;/em> 模式到 &lt;em>&lt;code>Insert&lt;/code>&lt;/em> 模式, 需要敲击键盘字符 &lt;code>i&lt;/code>.&lt;/p>
&lt;p>当你进入*&lt;code>Insert&lt;/code>&lt;em>模式, 你可能会感觉舒服一点, 因为你可以像普通编辑器一样输入字符了. 如果你想从&lt;/em>&lt;code>Insert&lt;/code>&lt;em>模式切换回&lt;/em>&lt;code>Normal&lt;/code>*模式, 只需要敲击键盘最左上角的键&lt;code>ESC&lt;/code>键.&lt;/p>
&lt;p>现在你知道如何在*&lt;code>Insert&lt;/code>&lt;em>模式和&lt;/em>&lt;code>Normal&lt;/code>&lt;em>模式之间切换了. 现在, 你必须要掌握下面几个命令以保证你能在&lt;/em>&lt;code>Normal&lt;/code>*模式下存活下去:&lt;/p>
&lt;blockquote>
&lt;ul>
&lt;li>&lt;code>i&lt;/code> : 切换到*&lt;code>Insert&lt;/code>&lt;em>模式. 按&lt;code>ESC&lt;/code>返回&lt;/em>&lt;code>Normal&lt;/code>*模式&lt;/li>
&lt;li>&lt;code>x&lt;/code> : 删除当前光标所在的一个字符&lt;/li>
&lt;li>&lt;code>:wq&lt;/code> : 保存并退出 (&lt;code>:w&lt;/code> 保存编辑的文件, &lt;code>:q&lt;/code> 退出vim)&lt;/li>
&lt;li>&lt;code>dd&lt;/code> : 删除光标所在的一行&lt;/li>
&lt;li>&lt;code>p&lt;/code> : 粘贴&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;blockquote>
&lt;p>#####推荐&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;ul>
&lt;li>&lt;code>hjkl&lt;/code> (强烈推荐但不强制使用): 你可以使用键盘的 ↑ ↓ ← → 进行光标的移动. &lt;strong>提示:&lt;/strong> 在*&lt;code>Normal&lt;/code>*模式下, &lt;code>j&lt;/code>代表方向 ↓&lt;/li>
&lt;li>&lt;code>:help &amp;lt;command&amp;gt;&lt;/code> : 显示 &lt;code>&amp;lt;command&amp;gt;&lt;/code> 的帮助文档. 你可以直接输入 &lt;code>:help&lt;/code> 而不输入 &lt;code>&amp;lt;command&amp;gt;&lt;/code> 获取一般性帮助.&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;p>最开始, 你只需要上面5个命令. 当你很自然地使用以上命令, 抛弃之前普通编辑器的习惯, 那么你就可以到&lt;strong>Level 2&lt;/strong>了.&lt;/p>
&lt;p>但是, 在*&lt;code>Normal&lt;/code>&lt;em>模式下需要注意的是: 在普通编辑器下, &lt;code>Ctrl-C&lt;/code>是复制命令, 这是个组合按键来完成一项复制的功能, 但是在vim下, 不可以轻易地使用&lt;code>Ctrl&lt;/code>. 事实上, 在Vim的 &lt;em>&lt;code>Normal&lt;/code>&lt;/em> 模式下, vim已经赋予每一个按键功能, 也就是说, 在&lt;/em>&lt;code>Normal&lt;/code>*模式下, 每一个键都是功能键, 而不需要你按下&lt;code>Ctrl&lt;/code>去组合其他按键实现某项功能.&lt;/p>
&lt;p>&lt;strong>提示&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>为了便于书写和阅读, 下面的文章中, &lt;code>&amp;lt;C-λ&amp;gt;&lt;/code> 将代表键入 &lt;code>Ctrl-λ&lt;/code>&lt;/li>
&lt;li>命令都是以&lt;code>:&lt;/code>开头, 并且最后要键入&lt;code>&amp;lt;enter&amp;gt;&lt;/code>. 如: 当我写&lt;code>:q&lt;/code>, 将代表&lt;code>:q&amp;lt;enter&amp;gt;&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="level-2---感觉顺手">Level 2 - 感觉顺手&lt;/h3>
&lt;p>你现在已经可以在vim中存活了. 现在是时候学点其他的命令了. 下面是我的建议(以下命令都是在*&lt;code>Normal&lt;/code>*模式下键入):&lt;/p>
&lt;ol>
&lt;li>各种插入模式&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;ul>
&lt;li>&lt;code>a&lt;/code> : 在当前光标后开始插入, 并且进入*&lt;code>Insert&lt;/code>*模式&lt;/li>
&lt;li>&lt;code>o&lt;/code> : 从当前行下面插入新行, 并且进入*&lt;code>Insert&lt;/code>*模式&lt;/li>
&lt;li>&lt;code>O&lt;/code> : 从当前行上面插入新行, 并且进入*&lt;code>Insert&lt;/code>*模式&lt;/li>
&lt;li>&lt;code>cw&lt;/code> : 删除光标开始的单词, 并且进入*&lt;code>Insert&lt;/code>*模式&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;ol start="2">
&lt;li>基本的移动&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;ul>
&lt;li>&lt;code>0&lt;/code> : 到光标所在行的最开始&lt;/li>
&lt;li>&lt;code>^&lt;/code> : 到光标所在行第一个非空字符&lt;/li>
&lt;li>&lt;code>$&lt;/code> : 到光标所在行的末尾&lt;/li>
&lt;li>&lt;code>g_&lt;/code> : 到光标所在行最后一个非空字符&lt;/li>
&lt;li>&lt;code>/pattern&lt;/code> : 向下搜索字符串&lt;code>pattern&lt;/code>&lt;/li>
&lt;li>&lt;code>?pattern&lt;/code> : 向上搜索字符串&lt;code>pattern&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>注&lt;/strong>: 搜索时, 键入&lt;code>n&lt;/code>键可以查看下一项, 键入&lt;code>N&lt;/code>键可以查看上一项&lt;/p>
&lt;/blockquote>
&lt;ol start="3">
&lt;li>复制/粘贴&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;ul>
&lt;li>&lt;code>p&lt;/code> : 在当前光标后粘贴&lt;/li>
&lt;li>&lt;code>P&lt;/code> : 在当前光标前粘贴&lt;/li>
&lt;li>&lt;code>yy&lt;/code>: 复制光标所在整行&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;ol start="4">
&lt;li>撤销/恢复 (Undo/Redo)&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;ul>
&lt;li>&lt;code>u&lt;/code> : 撤销&lt;/li>
&lt;li>&lt;code>&amp;lt;C-r&amp;gt;&lt;/code> : 恢复&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;ol start="5">
&lt;li>载入/保存/退出/修改文件(Buffer)&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>&lt;code>:e &amp;lt;path/to/file&amp;gt;&lt;/code> : 打开文件
&lt;code>:w&lt;/code> : 保存文件修改
&lt;code>:saveas &amp;lt;path/to/file&amp;gt;&lt;/code> : 另存为&lt;code>&amp;lt;path/to/file&amp;gt;&lt;/code>
&lt;code>:x&lt;/code>, &lt;code>ZZ&lt;/code> or &lt;code>:wq&lt;/code> : 保存并退出. &lt;code>:wq&lt;/code>相当于先&lt;code>:w&lt;/code>再&lt;code>:q&lt;/code>; &lt;code>:x&lt;/code>只在需要时保存; &lt;code>ZZ&lt;/code>不需要输入&lt;code>:&lt;/code>和回车&lt;code>&amp;lt;enter&amp;gt;&lt;/code>
&lt;code>:q!&lt;/code> : 强制退出而不保存修改. &lt;code>:qa!&lt;/code>, 强制退出所有正在编辑的文件, 即使其他文件有做过修改而没有保存
&lt;code>:bn&lt;/code>和&lt;code>:bp&lt;/code> : 切换到下/上一个文件&lt;/p>
&lt;/blockquote>
&lt;p>花一点时间去熟悉上面的命令. 当你熟练掌握之后, 你可以完成你在普通编辑器几乎所有功能. 你可能会觉得用vim还是有点别扭. 但是跟着我进入下一Level, 或许你可以明白为什么要花费这么多力气去学习它.&lt;/p>
&lt;h3 id="level-3---更好-更强-更快">Level 3 - 更好. 更强. 更快.&lt;/h3>
&lt;p>恭喜你已经到达了Level 3! 我们现在开始学习一些有趣的东西. 在Level 3, 我们只讨论和vi编辑器兼容的那些命令.&lt;/p>
&lt;h4 id="31-更好">3.1 更好&lt;/h4>
&lt;p>让我们看看vim是怎么样帮助你做重复的命令的:&lt;/p>
&lt;ol>
&lt;li>&lt;code>.&lt;/code>(点) : 重复上一次的命令&lt;/li>
&lt;li>&lt;code>N&amp;lt;command&amp;gt;&lt;/code>: 将会运行&lt;code>&amp;lt;command&amp;gt;&lt;/code>N次, 这里的N是一个整数&lt;/li>
&lt;/ol>
&lt;p>下面是一个例子. 打开一个文件然后输入下面的命令:&lt;/p>
&lt;blockquote>
&lt;ul>
&lt;li>&lt;code>2dd&lt;/code> : 将会删除2行&lt;/li>
&lt;li>&lt;code>3p&lt;/code> : 将会执行粘贴命令3次&lt;/li>
&lt;li>&lt;code>100idesu [ESC]&lt;/code> : 将会输入 &amp;ldquo;desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu&amp;rdquo;&lt;/li>
&lt;li>&lt;code>.&lt;/code> : 跟在上一个命令后面代表再次输入100次&amp;quot;desu&amp;quot;&lt;/li>
&lt;li>&lt;code>3.&lt;/code> : 将会输入3次&amp;quot;desu&amp;quot;(而不是300次, 你看vim多么聪明)&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;h4 id="32-更强">3.2 更强&lt;/h4>
&lt;p>知道如何高效的在vim中移动光标十分重要. 不要跳过这一部分.&lt;/p>
&lt;ol>
&lt;li>N&lt;code>G&lt;/code> : 移动光标到第N行&lt;/li>
&lt;li>&lt;code>gg&lt;/code> : 相当于 &lt;code>1G&lt;/code>, 到文件的最开始一行&lt;/li>
&lt;li>&lt;code>G&lt;/code> : 到文件的最后一行&lt;/li>
&lt;li>按照单词移动:&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;ol>
&lt;li>&lt;code>w&lt;/code> : 到下一个单词的开始&lt;/li>
&lt;li>&lt;code>e&lt;/code> : 到下一个单词的结尾
默认情况下, 我们认为单词是由一系列字符和下划线组成. 如果你认为由空格分隔开的字符串即为单词, 那么你只需要使用&lt;code>W&lt;/code>和&lt;code>E&lt;/code>.&lt;/li>
&lt;/ol>
&lt;/blockquote>
&lt;blockquote>
&lt;p>&lt;img src="http://p3.qhimg.com/t01ba88a3a55faa8746.jpg" alt="单词移动">&lt;/p>
&lt;/blockquote>
&lt;p>现在介绍更高效的移动方式:&lt;/p>
&lt;blockquote>
&lt;ul>
&lt;li>&lt;code>%&lt;/code> : 匹配括号移动光标, 如&lt;code>()&lt;/code>,&lt;code>[]&lt;/code>,&lt;code>{}&lt;/code>&lt;/li>
&lt;li>&lt;code>*&lt;/code>和&lt;code>#&lt;/code> : 匹配光标当前所在单词, 并且向下/上查找匹配的单词, 并把光标移到查找到的单词&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;p>相信我, 最后的三个命令是十分高效的.&lt;/p>
&lt;h4 id="33-更快">3.3 更快&lt;/h4>
&lt;p>记住如何移动光标了吗, 因为很多命令都是可以组合使用的. 并且大多是以以下格式:&lt;/p>
&lt;p>&lt;code>&amp;lt;start position&amp;gt;&amp;lt;command&amp;gt;&amp;lt;end position&amp;gt;&lt;/code>&lt;/p>
&lt;p>例如: &lt;code>0y$&lt;/code> 意思是&lt;/p>
&lt;ul>
&lt;li>&lt;code>0&lt;/code> : 将光标移动到行首&lt;/li>
&lt;li>&lt;code>y&lt;/code> : 开始复制&lt;/li>
&lt;li>&lt;code>$&lt;/code> : 复制到行尾&lt;/li>
&lt;/ul>
&lt;p>我们也可以使用&lt;code>ye&lt;/code>, 从光标所在位置复制到单词结束.
也可以使用&lt;code>y2/foo&lt;/code>, 从光标所在位置复制到匹配的第二个&lt;code>foo&lt;/code>&lt;/p>
&lt;p>对&lt;code>y&lt;/code>(复制)来说是这样的, 那么对&lt;code>d&lt;/code>(删除), &lt;code>v&lt;/code>(可视化选择), &lt;code>gU&lt;/code>(转大写), &lt;code>gu&lt;/code>(转小写)等等命令来说也是同样适用的&lt;/p>
&lt;h3 id="level-4---适用vim的超能力">Level 4 - 适用Vim的超能力&lt;/h3>
&lt;p>&amp;laquo;未完待续&amp;raquo;&lt;/p></description><category domain="https://h1z3y3.me/tags/vim/">vim</category></item><item><title>NSPredicate(谓词) 的使用</title><link>https://h1z3y3.me/posts/predicateuse/</link><guid isPermaLink="true">https://h1z3y3.me/posts/predicateuse/</guid><pubDate>Tue, 22 Mar 2016 17:41:22 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;blockquote>
&lt;p>谓词（NSPredicate）提供在Cocoa中指定查询的普通解决方案。NSPredicate类用于定义逻辑条件以限制或筛选获取结果。&lt;/p>
&lt;/blockquote>
&lt;h3 id="nspredicate-的基本使用">NSPredicate 的基本使用&lt;/h3>
&lt;h3 id="定义">定义：&lt;/h3>
&lt;pre>&lt;code>NSPredicate *predict = [NSPredicate predicateWithFormat: @&amp;quot;SELF CONTAINS[cd] %@&amp;quot;, SOMESTRING];
&lt;/code>&lt;/pre>
&lt;h3 id="常用方法">常用方法：&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">关键字&lt;/th>
&lt;th style="text-align:left">效果&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>比较运算符&lt;/strong>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&amp;gt;,&amp;lt;,==,&amp;gt;=,&amp;lt;=,!=&lt;/td>
&lt;td style="text-align:left">左侧满足比较运算符&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>字符相关&lt;/strong>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">BEGINSWITH&lt;/td>
&lt;td style="text-align:left">左侧表达式 以 右侧表达式 开始&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">ENDSWITH&lt;/td>
&lt;td style="text-align:left">左侧表达式 以 右侧表达式 结束&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">CONTAINS&lt;/td>
&lt;td style="text-align:left">左侧表达式包含右侧表达式&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>范围相关&lt;/strong>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">BETWEEN&lt;/td>
&lt;td style="text-align:left">左侧表达式在右侧表达式范围内&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">IN&lt;/td>
&lt;td style="text-align:left">左侧表达式在右侧表达式内&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>正则表达式&lt;/strong>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">MATCHES&lt;/td>
&lt;td style="text-align:left">左侧表达式满足右侧表达式，右侧为正则表达式&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>通配符&lt;/strong>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">LIKE&lt;/td>
&lt;td style="text-align:left">左侧表达式等于右侧表达式: 允许将 &lt;strong>?&lt;/strong> 和 &lt;strong>*&lt;/strong> 用作通配符,其中 &lt;strong>?&lt;/strong> 匹配一个字符,而 &lt;strong>*&lt;/strong> 匹配零个或多个字符&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="字符串">字符串&lt;/h3>
&lt;pre>&lt;code>NSString *originStr = @&amp;quot;I like shangha!&amp;quot;;
NSString *str = @&amp;quot;shanghai&amp;quot;;
NSPredicate *predict = [NSPredicate predicateWithFormat: @&amp;quot;SELF CONTAINS %@&amp;quot;, str];
if ([predict evaluateWithObject:originStr]) {
NSLog(@&amp;quot;含有字符串%@&amp;quot;, str);
}
&lt;/code>&lt;/pre>
&lt;p>其中 &lt;code>CONTAINS&lt;/code> 也可以替换为 &lt;code>BEGINSWITH&lt;/code>,&lt;code>ENDSWITH&lt;/code>。
在它们后面，可以在方括号中添加&lt;code>c&lt;/code>、&lt;code>d&lt;/code> 或 &lt;code>cd&lt;/code>, 如&lt;code>CONTAINS[cd]&lt;/code>,其中&lt;code>c&lt;/code>代表不区分大小写, &lt;code>d&lt;/code>代表不区分音调符号。&lt;/p>
&lt;h3 id="数组和字典">数组和字典&lt;/h3>
&lt;p>谓词常用于数组的筛选:&lt;/p>
&lt;pre>&lt;code> NSArray *mArray = [NSArray arrayWithObjects:@&amp;quot;beijing&amp;quot;, @&amp;quot;shanghai&amp;quot;, @&amp;quot;shenzhen&amp;quot;, @&amp;quot;guangzhou&amp;quot;, nil];
//IN 用法
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:@&amp;quot;SELF IN {'shanghai', 'guangzhou'}&amp;quot;];
NSArray *filteredArray1 = [mArray filteredArrayUsingPredicate: predicate1];
NSLog(@&amp;quot;筛选后数组为:%@&amp;quot;, filteredArray1);
//LIKE 用法
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@&amp;quot;SELF LIKE '*h*'&amp;quot;];
NSArray *filteredArray2 = [mArray filteredArrayUsingPredicate: predicate2];
NSLog(@&amp;quot;筛选后数组为:%@&amp;quot;, filteredArray2);
&lt;/code>&lt;/pre>
&lt;p>谓词的条件也可以通过字典的占位符实现:&lt;/p>
&lt;pre>&lt;code>NSPredicate *p1 = [NSPredicate predicateWithFormat:@&amp;quot;name==$NAME AND price==$PRICE&amp;quot;];
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@&amp;quot;name5&amp;quot;, @&amp;quot;NAME&amp;quot;, @&amp;quot;5000&amp;quot;, @&amp;quot;PRICE&amp;quot;, nil];
NSPredicate *p2 = [p1 predicateWithSubstitutionVariables:dic];
//表示从cars数组中筛选满足 name='name5' AND price='5000' 条件的元素
NSArray *filteredCars = [cars filteredArrayUsingPredicate:p2];
&lt;/code>&lt;/pre></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>HTML5 WebWorker 简单使用</title><link>https://h1z3y3.me/posts/html5webworker/</link><guid isPermaLink="true">https://h1z3y3.me/posts/html5webworker/</guid><pubDate>Sun, 20 Mar 2016 15:18:33 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;ol>
&lt;li>
&lt;p>什么是WebWorker&lt;/p>
&lt;p>web worker 是运行在后台的 JavaScript，独立于其他脚本，不会影响页面的性能。我们知道页面的展示放在主线程，如果让主线程进行一系列复杂的操作，那么页面就会变得非常卡，用户体验会很差。这是我们可以使用web worker进行复杂操作的实现，然后将处理结果返回给页面，页面进行更新即可，这样就不会影响用户主页面展示的执行。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>方法：
postMessage() : 用于向HTML页面返回消息
terminate() : 终止web worker， 并且释放资源&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h4 id="实现方法">实现方法：&lt;/h4>
&lt;p>Demo: 数字从0开始累加&lt;/p>
&lt;h4 id="indexhtml">index.html&lt;/h4>
&lt;pre>&lt;code>&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
&amp;lt;title&amp;gt;&amp;lt;/title&amp;gt;
&amp;lt;script src=&amp;quot;index.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;div id=&amp;quot;numDiv&amp;quot;&amp;gt;0&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code>&lt;/pre>
&lt;h4 id="indexjs">index.js&lt;/h4>
&lt;pre>&lt;code>var numDiv;
window.onload = function() {
numDiv = document.getElementById(&amp;quot;numDiv&amp;quot;);
var worker = new Worker(&amp;quot;webWorker.js&amp;quot;);//创建Worker对象
worker.onmessage = function (e) {
numDiv.innerHTML = e.data;
}
}
&lt;/code>&lt;/pre>
&lt;h4 id="webworkerjs">webWorker.js&lt;/h4>
&lt;pre>&lt;code>var countNum = 0;
function count () {
postMessage(countNum);//给html页面返回数据
countNum ++;//数字累加
setTimeout(count, 1000);//一秒执行一次
}
count();//调用函数执行
&lt;/code>&lt;/pre></description><category domain="https://h1z3y3.me/tags/html5/">HTML5</category><category domain="https://h1z3y3.me/tags/webworker/">WebWorker</category></item><item><title>HTML5应用缓存简单使用</title><link>https://h1z3y3.me/posts/html5webcache/</link><guid isPermaLink="true">https://h1z3y3.me/posts/html5webcache/</guid><pubDate>Sun, 20 Mar 2016 14:53:00 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;ol>
&lt;li>
&lt;p>什么是应用缓存？&lt;/p>
&lt;p>HTML5引入了应用缓存概念，意味着在没有因特网连接时也可以进行访问。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用应用缓存好处：&lt;/p>
&lt;ul>
&lt;li>离线浏览，没有因特网的情况下依然可以进行访问&lt;/li>
&lt;li>访问速度提升，已经缓存的资源加载更快&lt;/li>
&lt;li>减少服务器负载，浏览器只需要下载更新过的页面资源&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>实现方法：&lt;/p>
&lt;p>如果需要使用应用缓存，需要在页面&lt;code>&amp;lt;html&amp;gt;&lt;/code>标签中包含 &lt;code>manifest&lt;/code> 属性，而manifest文件建议使用文件扩展名&lt;code>.appcache&lt;/code>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Manifest文件功能：&lt;/p>
&lt;ul>
&lt;li>CACHE: 在此标题下列出的文件会在首次访问加载之后进行缓存；&lt;/li>
&lt;li>NETWORK: 在此标题下列出的文件需要与服务器连接、且不会被缓存；&lt;/li>
&lt;li>FALLBACK: 在此标题下列出的文件规定当页面无法访问时的退回页面（如404页面）&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="功能实现">功能实现&lt;/h3>
&lt;h4 id="indexhtml--请注意html标签">index.html 请注意&lt;code>&amp;lt;html&amp;gt;&lt;/code>标签&lt;/h4>
&lt;pre>&lt;code>&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot; manifest=&amp;quot;index.appcache&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
&amp;lt;title&amp;gt;&amp;lt;/title&amp;gt;
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;style.css&amp;quot;&amp;gt;
&amp;lt;script src=&amp;quot;index.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1 class=&amp;quot;h1&amp;quot;&amp;gt;HELLO HMTL5&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code>&lt;/pre>
&lt;h4 id="stylecss">style.css&lt;/h4>
&lt;pre>&lt;code>.h1 {
color: red;
background: blue;
}
&lt;/code>&lt;/pre>
&lt;h4 id="indexjs">index.js&lt;/h4>
&lt;pre>&lt;code>/*空文件*/
&lt;/code>&lt;/pre>
&lt;h4 id="indexappcache">index.appcache&lt;/h4>
&lt;pre>&lt;code>CACHE MANIFEST
CACHE:
index.html
style.css
index.js
NETWORK:
FALLBACK:
&lt;/code>&lt;/pre>
&lt;h4 id="测试">测试&lt;/h4>
&lt;ul>
&lt;li>
&lt;p>开启本地服务器，在Chrome输入&lt;code>localhost/webCache&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>使用Chrome审查元素功能，切换到Resources功能标签，点击左侧&lt;code>Application Cache&lt;/code>功能标签，可以观察到我们设置缓存的三个文件已经缓存成功&lt;/p>
&lt;p>&lt;img src="https://p1.ssl.qhimg.com/t01cc408c49a3c3052e.jpg" alt="缓存列表">&lt;/p>
&lt;/li>
&lt;li>
&lt;p>断开本地服务器，重新刷新页面，会发现页面样式仍然保持，说明缓存起作用了。&lt;/p>
&lt;/li>
&lt;/ul></description><category domain="https://h1z3y3.me/tags/html5/">HTML5</category><category domain="https://h1z3y3.me/tags/cache/">Cache</category></item><item><title>UITableView使用简单进阶(二):索引条</title><link>https://h1z3y3.me/posts/uitableviewadvance02/</link><guid isPermaLink="true">https://h1z3y3.me/posts/uitableviewadvance02/</guid><pubDate>Sat, 19 Mar 2016 21:32:22 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>前文已经介绍了如何给UITableView添加搜索栏，这次要给UITableView进一步添加索引条。
基本思路：&lt;/p>
&lt;ol>
&lt;li>获取总统名字的首字母组成一个索引字母表储存在数组中；&lt;/li>
&lt;li>修改TableView的代理方法实现section的显示，section的数量应为索引字母表的元素的个数；&lt;/li>
&lt;li>把索引条添加到TableView 中，用TableView的代理方法即可实现。&lt;/li>
&lt;/ol>
&lt;p>由于接着上文，所以本文中代码将不会全部展示，代码的重复部分将用省略号代替。&lt;/p>
&lt;h3 id="step1-添加属性">Step1. 添加属性&lt;/h3>
&lt;p>在ViewController.h中添加属性：NSMutableArray *alphabetArray;&lt;/p>
&lt;pre>&lt;code>#import &amp;lt;UIKit/UIKit.h&amp;gt;
@interface ViewController : UIViewController
...
@property (nonatomic, strong) NSMutableArray *alphabetArray;
...
@end
&lt;/code>&lt;/pre>
&lt;h3 id="step2-修改viewcontrollerm">Step2. 修改ViewController.m&lt;/h3>
&lt;pre>&lt;code>//
// ViewController.m
// UITableViewAdvanced01
//
// Created by mungo on 19/03/16.
// Copyright © 2016 mungo. All rights reserved.
//
#import &amp;quot;ViewController.h&amp;quot;
#import &amp;quot;President.h&amp;quot;
@interface ViewController () &amp;lt;UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchResultsUpdating, UISearchDisplayDelegate&amp;gt;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化数据
...
//创建tableview
...
//创建searchController
...
//设置tableview的搜索栏
...
//设置字母表
self.alphabetArray = [self getAlphetSortedArray];
}
/**
* 新添加方法：
* 获取字母表
* @return MSMutableArray* 已经排序的字母表数组
*/
- (NSMutableArray *) getAlphetSortedArray {
self.alphabetArray = [[NSMutableArray alloc] init];
for (int i = 0; i &amp;lt; [self.presidents count]; i ++) {
//获取名字的第一个字母
President *president = [self.presidents objectAtIndex:i];
char letter = [president.firstName characterAtIndex:0];
NSString *uniqueChar = [NSString stringWithFormat:@&amp;quot;%c&amp;quot;, letter];
//将字母加入到字母表中
if (![self.alphabetArray containsObject:uniqueChar]) {
[self.alphabetArray addObject:uniqueChar];
}
}
//对字母表进行排序
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@&amp;quot;self&amp;quot; ascending:YES];
NSArray *sortDescirptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray;
sortedArray = [self.alphabetArray sortedArrayUsingDescriptors:sortDescirptors];
NSMutableArray *alphabetArray = [[NSMutableArray alloc] initWithArray:sortedArray copyItems:YES];
return alphabetArray;
}
#pragma mark - tableView Delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.searchController.active) {
...
} else {
//根据section筛选以当前字母开头的总统数组
NSArray *tmpArray = [self getAlphabetArrayWithIndex:section];
return [tmpArray count];
}
}
#pragma mark - Indexing UITableView
//索引条的字母表
- (NSArray&amp;lt;NSString *&amp;gt; *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return self.alphabetArray;
}
- (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return index;
}
#pragma mark - tableView Datasource
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
if (self.searchController.active) {
self.alphabetArray = nil;//搜索时不显示section
return 1;
} else {
self.alphabetArray = [self getAlphetSortedArray];//停止搜索恢复section显示
return [self.alphabetArray count];
}
}
//tableView section的标题
- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [self.alphabetArray objectAtIndex: section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
President *president;
if (self.searchController.active) {
...
} else {
//根据section筛选以当前字母开头的总统数组
NSArray *tmpArray = [self getAlphabetArrayWithIndex:indexPath.section];
if ([tmpArray count]) {
president = [tmpArray objectAtIndex:indexPath.row];
}
}
if (president) {
cell.textLabel.text = [NSString stringWithFormat:@&amp;quot;%@ %@&amp;quot;, president.firstName, president.lastName];
}
return cell;
}
/*
* 新添加方法：
* 根据首字母对所有总统进行筛选
* @return NSArray* 于当前首字母相同的总统数组
*/
- (NSArray *) getAlphabetArrayWithIndex:(NSInteger)index{
NSString *alphabet = [self.alphabetArray objectAtIndex:index];
NSPredicate *president = [NSPredicate predicateWithFormat:@&amp;quot;firstName BEGINSWITH [cd] %@&amp;quot;, alphabet];
NSArray *tmpArray = [self.presidents filteredArrayUsingPredicate:president];
return tmpArray;
}
#pragma mark - SearchController delegate
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
...
}
@end
&lt;/code>&lt;/pre>
&lt;h4 id="注意">注意：&lt;/h4>
&lt;p>numberOfSectionsInTableView中， 开始搜索的时候要将TableView的section数量设置为1，并且要把字母表设置为空；不搜索时要恢复section的数量。&lt;/p>
&lt;h3 id="实现效果">实现效果&lt;/h3>
&lt;p>&lt;img src="https://p4.ssl.qhimg.com/t0162f8e32897367e8f.gif" alt="实现效果">&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category><category domain="https://h1z3y3.me/tags/uitableview/">UITableView</category></item><item><title>UITableView使用简单进阶(一):搜索栏</title><link>https://h1z3y3.me/posts/uitableviewadvanced01/</link><guid isPermaLink="true">https://h1z3y3.me/posts/uitableviewadvanced01/</guid><pubDate>Sat, 19 Mar 2016 18:39:48 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>UITableView 是开发中使用十分频繁的控件，本系列记录UITableView的进阶使用：UITableView的搜索栏和字母索引表。&lt;/p>
&lt;p>不想看我废话的可以直接到gitHub仓库下载源码😏。
&lt;a href="https://github.com/Gitzhaoyang/iOSWithOC/tree/master/UITableViewAdvanced">UITableView使用进阶gitHub源码&lt;/a>&lt;/p>
&lt;p>搜索栏有两种实现方式，第一种是通过UISearchBar和UISearchDisplayController实现，第二种是通过UISearchController实现。而在iOS8.0之后，苹果官方推荐使用第二种方式。&lt;/p>
&lt;p>&lt;img src="https://p1.ssl.qhimg.com/t014836c37890cb46c7.jpg" alt="使用UISearchController">&lt;/p>
&lt;p>本文使用的是第二种方式（UISearchController），不过源码中也实现了第一种(UISearchBar+UISearchDisplayController)。关于UISearchBar和UISearchDisplayController的使用，可以参考我的另一篇文章：&lt;a href="http://mungo.space/2016/03/19/UISearchBar-UISearchDisplayController/">UISearchBar和UISearchDisplayController实现搜索栏&lt;/a>&lt;/p>
&lt;p>本文先介绍UITableView的搜索栏（UISearchController），是一个显示美国总统的TableView。&lt;/p>
&lt;h3 id="step1-创建president类">Step1. 创建President类&lt;/h3>
&lt;p>创建一个名为President的Objective-C类，它继承于NSObject类，用于保存总统的姓氏和名字。定义一个静态方法，用来创建President对象，并且对firstName和lastName赋值。&lt;/p>
&lt;h4 id="presidenth">President.h&lt;/h4>
&lt;pre>&lt;code>#import &amp;lt;Foundation/Foundation.h&amp;gt;
@interface President : NSObject
@property (nonatomic, retain) NSString *firstName;
@property (nonatomic, retain) NSString *lastName;
+ (President *) presidentWithFirstName: (NSString *)firstname lastName: (NSString *)lastname;
@end
&lt;/code>&lt;/pre>
&lt;h4 id="presidentm">President.m&lt;/h4>
&lt;pre>&lt;code>#import &amp;quot;President.h&amp;quot;
@implementation President
+ (President *) presidentWithFirstName:(NSString *)firstname lastName:(NSString *)lastname {
President *president = [[President alloc] init];
president.firstName = firstname;
president.lastName = lastname;
return president;
}
@end
&lt;/code>&lt;/pre>
&lt;h3 id="step2-编写viewcontroller">Step2. 编写ViewController&lt;/h3>
&lt;h4 id="viewcontrollerh">viewController.h&lt;/h4>
&lt;pre>&lt;code>#import &amp;lt;UIKit/UIKit.h&amp;gt;
@interface ViewController : UIViewController
@property (nonatomic, strong) UITableView *mTableView;
@property (nonatomic, strong) NSArray *presidents;//所有总统
@property (nonatomic, strong) NSArray *filteredPresidents;//保存搜索结果
@property (nonatomic, retain) UISearchController *searchController;
@end
&lt;/code>&lt;/pre>
&lt;h4 id="viewcontrollerm">viewController.m&lt;/h4>
&lt;pre>&lt;code>#import &amp;quot;ViewController.h&amp;quot;
#import &amp;quot;President.h&amp;quot;
@interface ViewController () &amp;lt;UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchResultsUpdating, UISearchDisplayDelegate&amp;gt;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化数据
self.presidents = [[NSArray alloc] initWithObjects:
[President presidentWithFirstName:@&amp;quot;George&amp;quot; lastName:@&amp;quot;Washington&amp;quot;],
[President presidentWithFirstName:@&amp;quot;John&amp;quot; lastName:@&amp;quot;Adams&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Thomas&amp;quot; lastName:@&amp;quot;Jeffeson&amp;quot;],
[President presidentWithFirstName:@&amp;quot;James&amp;quot; lastName:@&amp;quot;Madison&amp;quot;],
[President presidentWithFirstName:@&amp;quot;James&amp;quot; lastName:@&amp;quot;Monroe&amp;quot;],
[President presidentWithFirstName:@&amp;quot;John Quincy&amp;quot; lastName:@&amp;quot;Adams&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Andrew&amp;quot; lastName:@&amp;quot;Jackson&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Martin&amp;quot; lastName:@&amp;quot;van Buren&amp;quot;],
[President presidentWithFirstName:@&amp;quot;William Henry&amp;quot; lastName:@&amp;quot;Harrison&amp;quot;],
[President presidentWithFirstName:@&amp;quot;John&amp;quot; lastName:@&amp;quot;Tyler&amp;quot;],
[President presidentWithFirstName:@&amp;quot;James K&amp;quot; lastName:@&amp;quot;Polk&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Zachary&amp;quot; lastName:@&amp;quot;Taylor&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Millard&amp;quot; lastName:@&amp;quot;Fillmore&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Franklin&amp;quot; lastName:@&amp;quot;Pierce&amp;quot;],
[President presidentWithFirstName:@&amp;quot;James&amp;quot; lastName:@&amp;quot;Buchanan&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Abraham&amp;quot; lastName:@&amp;quot;Lincoln&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Andrew&amp;quot; lastName:@&amp;quot;Johnson&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Ulysses S&amp;quot; lastName:@&amp;quot;Grant&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Rutherford B&amp;quot; lastName:@&amp;quot;Hayes&amp;quot;],
[President presidentWithFirstName:@&amp;quot;James A&amp;quot; lastName:@&amp;quot;Garfield&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Chester A&amp;quot; lastName:@&amp;quot;Arthur&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Grover&amp;quot; lastName:@&amp;quot;Cleveland&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Bejamin&amp;quot; lastName:@&amp;quot;Harrison&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Grover&amp;quot; lastName:@&amp;quot;Cleveland&amp;quot;],
[President presidentWithFirstName:@&amp;quot;William&amp;quot; lastName:@&amp;quot;McKinley&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Theodore&amp;quot; lastName:@&amp;quot;Roosevelt&amp;quot;],
[President presidentWithFirstName:@&amp;quot;William Howard&amp;quot; lastName:@&amp;quot;Taft&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Woodrow&amp;quot; lastName:@&amp;quot;Wilson&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Warren G&amp;quot; lastName:@&amp;quot;Harding&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Calvin&amp;quot; lastName:@&amp;quot;Coolidge&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Herbert&amp;quot; lastName:@&amp;quot;Hoover&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Franklin D&amp;quot; lastName:@&amp;quot;Roosevelt&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Harry S&amp;quot; lastName:@&amp;quot;Truman&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Dwight D&amp;quot; lastName:@&amp;quot;Eisenhower&amp;quot;],
[President presidentWithFirstName:@&amp;quot;John F&amp;quot; lastName:@&amp;quot;Kennedy&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Lyndon B&amp;quot; lastName:@&amp;quot;Johnson&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Richard&amp;quot; lastName:@&amp;quot;Nixon&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Gerald&amp;quot; lastName:@&amp;quot;Ford&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Jimmy&amp;quot; lastName:@&amp;quot;Carter&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Ronald&amp;quot; lastName:@&amp;quot;Reagan&amp;quot;],
[President presidentWithFirstName:@&amp;quot;George H W&amp;quot; lastName:@&amp;quot;Bush&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Bill&amp;quot; lastName:@&amp;quot;Clinton&amp;quot;],
[President presidentWithFirstName:@&amp;quot;George W&amp;quot; lastName:@&amp;quot;Bush&amp;quot;],
[President presidentWithFirstName:@&amp;quot;Barack&amp;quot; lastName:@&amp;quot;Obama&amp;quot;],
nil];
CGRect appFrame = [[UIScreen mainScreen] bounds];
//创建tableview
self.mTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 20, appFrame.size.width, appFrame.size.height - 20) style:UITableViewStylePlain];
self.mTableView.delegate = self;
self.mTableView.dataSource = self;
[self.view addSubview:self.mTableView];
//创建searchController
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.hidesNavigationBarDuringPresentation = NO;
//设置tableview的搜索栏
self.mTableView.tableHeaderView = self.searchController.searchBar;
}
#pragma mark - tableView Delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.searchController.active) {
return [self.filteredPresidents count];
} else {
return [self.presidents count];
}
}
#pragma mark - tableView Datasource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = @&amp;quot;cellId&amp;quot;;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
President *president;
if (self.searchController.active) {
president = [self.filteredPresidents objectAtIndex:indexPath.row];
} else {
president = [self.presidents objectAtIndex:indexPath.row];
}
cell.textLabel.text = [NSString stringWithFormat:@&amp;quot;%@ %@&amp;quot;, president.firstName, president.lastName];
return cell;
}
#pragma mark - SearchController delegate
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
NSString *searchString = [self.searchController.searchBar text];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@&amp;quot;firstName CONTAINS[cd] %@ OR lastName CONTAINS[cd] %@&amp;quot;, searchString, searchString];
self.filteredPresidents = [self.presidents filteredArrayUsingPredicate:predicate];
//刷新表格
[self.mTableView reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
&lt;/code>&lt;/pre>
&lt;h3 id="实现效果">实现效果&lt;/h3>
&lt;p>&lt;img src="https://p5.ssl.qhimg.com/t011ce6e41ca1d6164b.gif" alt="实现效果">&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category><category domain="https://h1z3y3.me/tags/uitableview/">UITableView</category><category domain="https://h1z3y3.me/tags/%E6%90%9C%E7%B4%A2%E6%A0%8F/">搜索栏</category></item><item><title>UISearchBar和UISearchDisplayController实现搜索栏</title><link>https://h1z3y3.me/posts/uisearchbar-uisearchdisplaycontroller/</link><guid isPermaLink="true">https://h1z3y3.me/posts/uisearchbar-uisearchdisplaycontroller/</guid><pubDate>Sat, 19 Mar 2016 17:36:43 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>要实现tableview的搜索栏，实现方法有两种：第一种是UISearchBar和UIDisplayController结合起来实现，另一种是通过UISearchController实现。这里只介绍第一种：&lt;/p>
&lt;p>注意: UISearchBar和UISearchDisplay只推荐iOS8.0之前使用。
&lt;img src="https://p1.ssl.qhimg.com/t014836c37890cb46c7.jpg" alt="">&lt;/p>
&lt;p>关于UISearchController的使用请跳转至：
&lt;a href="http://mungo.space/2016/03/19/UITableViewAdvanced01/">UITableView使用进阶(一):搜索栏&lt;/a>&lt;/p>
&lt;h3 id="使用的协议">使用的协议&lt;/h3>
&lt;p>需要使用四个协议，分别是&lt;/p>
&lt;p>&amp;lt;UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate&amp;gt;&lt;/p>
&lt;h3 id="头文件-viewcontrollerh">头文件 ViewController.h&lt;/h3>
&lt;pre>&lt;code>#import &amp;lt;UIKit/UIKit.h&amp;gt;
@interface ViewController : UIViewController
@property (nonatomic, strong) UITableView *mTableView;
//搜索结果
@property (nonatomic, strong) NSArray *filterData;
//全部数据
@property (nonatomic, strong) NSMutableArray *allData;
@property (nonatomic, retain) UISearchDisplayController *searchDisplayController;
@end
&lt;/code>&lt;/pre>
&lt;h3 id="实现文件-viewcontrollerm">实现文件 ViewController.m&lt;/h3>
&lt;pre>&lt;code>#import &amp;quot;ViewController.h&amp;quot;
@interface ViewController () &amp;lt;UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchDisplayDelegate&amp;gt;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化数据
int count = 100;
self.allData = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i &amp;lt; count; i ++) {
[self.allData addObject:[NSString stringWithFormat:@&amp;quot;第%d行&amp;quot;, i]];
}
//定义tableview
CGRect appFrame = [[UIScreen mainScreen] bounds];
self.mTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 20, appFrame.size.width, appFrame.size.height - 20) style:UITableViewStylePlain];
self.mTableView.delegate = self;
self.mTableView.dataSource = self;
[self.view addSubview:self.mTableView];
//定义UISearchBar
UISearchBar *mySearchBar = [[UISearchBar alloc] init];
mySearchBar.delegate = self;
[mySearchBar setAutocapitalizationType:UITextAutocapitalizationTypeNone];
[mySearchBar sizeToFit];
self.mTableView.tableHeaderView = mySearchBar;
//定义UISearchDisplayController
self.searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:mySearchBar contentsController:self];
self.searchDisplayController.delegate = self;
[self.searchDisplayController setSearchResultsDataSource:self];
[self setSearchDisplayController:self.searchDisplayController];
}
#pragma mark - UITableView Delegate
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == self.searchDisplayController.searchResultsTableView) {
return [self.filterData count];
} else {
return [self.allData count];
}
}
#pragma mark - UITableView DataSource
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellid = @&amp;quot;cellid&amp;quot;;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellid];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellid];
}
if (tableView == self.searchDisplayController.searchResultsTableView) {
cell.textLabel.text = [self.filterData objectAtIndex:indexPath.row];
} else {
cell.textLabel.text = [self.allData objectAtIndex:indexPath.row];
}
return cell;
}
#pragma mark - UISearchDisplayController
- (BOOL) searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@&amp;quot;SELF CONTAINS[cd] %@&amp;quot;, searchString];
self.filterData = [self.allData filteredArrayUsingPredicate:predicate];
return YES;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
&lt;/code>&lt;/pre>
&lt;h3 id="实现效果">实现效果&lt;/h3>
&lt;p>&lt;img src="https://p1.ssl.qhimg.com/t01136391b0c01f4d40.gif" alt="实现效果">&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category><category domain="https://h1z3y3.me/tags/uitableview/">UITableView</category><category domain="https://h1z3y3.me/tags/%E6%90%9C%E7%B4%A2%E6%A0%8F/">搜索栏</category></item><item><title>stretchableImageWithLeftCapWith:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight</title><link>https://h1z3y3.me/posts/stretchableimagewithleftcapwidth/</link><guid isPermaLink="true">https://h1z3y3.me/posts/stretchableimagewithleftcapwidth/</guid><pubDate>Sat, 19 Mar 2016 14:35:39 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;pre>&lt;code>- (UIImage *) stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight
&lt;/code>&lt;/pre>
&lt;p>这个函数是UIImage的一个类函数，它的功能是创建一个内容可拉伸，而边角不拉伸的图片。两个参数的含义分别为：不拉伸区域的宽度、不拉伸区域的高度。&lt;/p>
&lt;p>根据设置的宽度和高度，在像素点&lt;code>((leftCapWidth+1), (topCapWidth+1))&lt;/code>开始左右扩展和上下拉伸。&lt;/p>
&lt;h3 id="注意">注意：&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>可拉伸范围是距离leftCapWidth+1的那一列像素和topCapHeight+1的那一横排像素。
如果设置参数为10和5，那么，图片左边10个像素和上边5个像素区域内不会被拉伸。而从(11, 5)开始扩展和拉伸。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>只是对一个像素进行复制到一定宽度，而图像后面的剩余部分也不会被拉伸。&lt;/p>
&lt;pre>&lt;code> //原始图片
UIImage *image = [UIImage imageNamed:@&amp;quot;yoububble.png&amp;quot;] ;
UIImageView *imageView1 = [[UIImageView alloc] initWithImage:image];
[imageView1 setFrame:CGRectMake(10, 60, 40, 40)];
[self.view addSubview:imageView1];
//拉伸后图片
UIImage *strechImage = [image stretchableImageWithLeftCapWidth:10 topCapHeight:10];
UIImageView *imageView2 = [[UIImageView alloc] initWithImage:strechImage];
[imageView2 setFrame:CGRectMake(10, 120, 300, 100)];
[self.view addSubview:imageView2];
&lt;/code>&lt;/pre>
&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://p3.ssl.qhimg.com/t012520479c8ccb0d67.png" alt="图片效果">&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>根据字数自适应高度的UILabel</title><link>https://h1z3y3.me/posts/uilabel-with-height-fixed/</link><guid isPermaLink="true">https://h1z3y3.me/posts/uilabel-with-height-fixed/</guid><pubDate>Thu, 17 Mar 2016 15:36:59 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;pre>&lt;code>- (UILabel *) getLabelHeightFixedWithText: (NSString *) text {
UIFont *font = [UIFont boldSystemFontOfSize: 12.0f];
int width = 225, height = 10000;
NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
[attrs setObject: font forKey: NSFontAttributeName];
CGRect size = [text boundingRectWithSize:CGSizeMake(width, height) options: NSStringDrawingUsesLineFragmentOrigin attributes: attrs context: nil];
UILabel *label = [[UILabel alloc] initWithFrame: CGRectMake(0, 0, size.size.width, size.size.height)];
label.numberOfLines = 0;//一定要设置行数为0
label.font = font;
label.lineBreakMode = NSLineBreakByWordWrapping;
label.text = (text ? text : @&amp;quot;&amp;quot;);
label.backgroundColor = [UIColor clearColor];
label.textColor = [UIColor blackColor];
return label;
}
&lt;/code>&lt;/pre></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category><category domain="https://h1z3y3.me/tags/uilabel/">UILabel</category></item><item><title>JS实现密码加密(base64, md5, sha1)</title><link>https://h1z3y3.me/posts/javascript-md5-base64-sha1/</link><guid isPermaLink="true">https://h1z3y3.me/posts/javascript-md5-base64-sha1/</guid><pubDate>Thu, 17 Mar 2016 12:32:54 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h3 id="简介">简介&lt;/h3>
&lt;p>在编写Web程序时，表单的提交若密码使用明文提交会十分不安全，因此在浏览器端也要对密码进行加密处理。但是若只是在浏览器端处理了，而服务器没有再一次加密，也是不妥当的，因为&amp;quot;中间人&amp;quot;只要获取了浏览器端加密的密码，不需要进行处理也能进行登录。所以我一般的做法是在前端加密一次，在服务器再加密一次。密码加盐（salt）的问题等我先研究下再写一下。而浏览器端加密一般我都用javascript进行加密后再提交。下面是用javascript编写的base64加密，md5加密和sha1加密。使用方法也极其简单，只要在页面内引入相应js文件即可。&lt;/p>
&lt;h3 id="base64加解密">base64加解密&lt;/h3>
&lt;pre>&lt;code>&amp;lt;!DOCTYPE HTML&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&amp;quot;utf-8&amp;quot;&amp;gt;
&amp;lt;title&amp;gt;base64加解密&amp;lt;/title&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;base64.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
var base64 = new Base64();
//加密
var base64encodeStg = base64.encode(&amp;quot;hello world!&amp;quot;);
alert(&amp;quot;base64encode:&amp;quot; + base64encodeStg);
//解密
var base64decodeStg = base64.decode(base64encodeStg);
alert(&amp;quot;base64decode:&amp;quot; + base64decodeStg);
&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code>&lt;/pre>
&lt;h3 id="md5加密">md5加密&lt;/h3>
&lt;pre>&lt;code>&amp;lt;!DOCTYPE HTML&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&amp;quot;utf-8&amp;quot;&amp;gt;
&amp;lt;title&amp;gt;md5加密&amp;lt;/title&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;md5.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
var hash = hex_md5(&amp;quot;hello world!&amp;quot;);
alert(hash);
&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code>&lt;/pre>
&lt;h3 id="sha1加密">sha1加密&lt;/h3>
&lt;p>相对于前两个，sha1加密可能更安全&lt;/p>
&lt;pre>&lt;code>&amp;lt;!DOCTYPE HTML&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&amp;quot;utf-8&amp;quot;&amp;gt;
&amp;lt;title&amp;gt;sha1加密&amp;lt;/title&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;sha1.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
var sha1 = hex_sha1(&amp;quot;hello world!&amp;quot;);
alert(sha1);
&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code>&lt;/pre>
&lt;h3 id="相关文件下载">相关文件下载&lt;/h3>
&lt;p>&lt;a href="http://pan.baidu.com/s/1i3TPnCx">base64.js | md5.js | sha1.js 百度网盘分享&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/md5/">MD5</category><category domain="https://h1z3y3.me/tags/base64/">base64</category><category domain="https://h1z3y3.me/tags/sha1/">sha1</category><category domain="https://h1z3y3.me/tags/javascript/">javascript</category><category domain="https://h1z3y3.me/tags/%E5%8A%A0%E5%AF%86/">加密</category></item><item><title>贴图库被网信办关闭 官方：解决需要时间</title><link>https://h1z3y3.me/posts/tietuku-clienthold/</link><guid isPermaLink="true">https://h1z3y3.me/posts/tietuku-clienthold/</guid><pubDate>Sun, 13 Mar 2016 20:58:52 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>&lt;strong>&lt;a href="CHinaZ.com">站长之家&lt;/a>（ChinaZ.com）&lt;/strong> 3月4日消息，昨日开始，国内知名图床服务网站贴图库首页和服务器部分地区出现了无法访问的情况。据站长之家了解，贴图库因被检测到非法内容，遭到网信办下令要求域名服务商易名中国将域名**clientHold（锁定）**处理。&lt;/p>
&lt;p>我博客上面的图片也全都放在贴图库上面的，今天刚回归博客就发现图片不能访问了。查过之后才贴图库被关闭了，不过我觉得官方应该能解决好这个事情，毕竟是国内知名图床服务网站，用户数量应该很多。我觉得会给用户一个满意的答复的。要不然我博客里面的图片怎么办啊。摔！&lt;/p></description><category domain="https://h1z3y3.me/tags/%E8%B4%B4%E5%9B%BE%E5%BA%93/">贴图库</category><category domain="https://h1z3y3.me/tags/%E7%BD%91%E4%BF%A1%E5%8A%9E/">网信办</category><category domain="https://h1z3y3.me/tags/%E8%AE%B0%E5%BD%95/">记录</category></item><item><title>iOS NSString字符串MD5加密</title><link>https://h1z3y3.me/posts/ios-md5/</link><guid isPermaLink="true">https://h1z3y3.me/posts/ios-md5/</guid><pubDate>Sun, 13 Mar 2016 18:59:02 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>为了使保存的密码更安全， 我们应该实现一个NSString的分类，为密码创建一个MD5的哈希值，而且并把它保存在keychain中；keychain是在设备中保存关键数据的唯一安全的地方。&lt;/p>
&lt;h3 id="step1--新建文件">step1. 新建文件&lt;/h3>
&lt;p>新建文件，在模板列表中选择Objective-C Category项。单击Next按钮，输入“MD5”作为这个创建的分类的名字，然后在CategoryOn下拉菜单中选择NSString，表明是NSString的Category。&lt;/p>
&lt;h3 id="step2--头文件">step2. 头文件&lt;/h3>
&lt;pre>&lt;code>#import &amp;lt;Foundation/Foundation.h&amp;gt;
@interface NSString (MD5)
- (NSString *) MD5;
@end
&lt;/code>&lt;/pre>
&lt;h3 id="step3--源文件">step3. 源文件&lt;/h3>
&lt;pre>&lt;code>#import &amp;quot;NSString+MD5.h&amp;quot;
#import &amp;lt;CommonCrypto/CommonDigest.h&amp;gt;
@implementation NSString (MD5)
- (NSString *) MD5{
//转化为UTF8格式字符串
const char *ptr = [self UTF8String];
//开辟一个16字节数组
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
//调用官方封装的加密方法, 将ptr开始的字符串存储到md5Buffer[]中
CC_MD5(ptr, strlen(ptr), md5Buffer);
//转换位NSString并返回
NSMutableString *output = [NSMutableString stringWithCapacity: CC_MD5_DIGEST_LENGTH * 2];
for (int i = 0; i &amp;lt; CC_MD5_DIGEST_LENGTH; i ++) {
[output appendFormat: @&amp;quot;%02x&amp;quot;, md5Buffer[i]];
}
/*
* `x`表示十六进制，%02 意思是不足两位用0补齐，如果多于2位则不影响
* NSLog(&amp;quot;%02x&amp;quot;, 0x666); //输出 `666`
* NSLog(&amp;quot;%02x&amp;quot;, 0x3); //输出 `03`
*/
return output;
}
@end&lt;/code>&lt;/pre></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category><category domain="https://h1z3y3.me/tags/%E5%8A%A0%E5%AF%86/">加密</category><category domain="https://h1z3y3.me/tags/md5/">MD5</category></item><item><title>NSDictionary含有null导致写文件(writeToFile)失败(豆瓣电影Api返回Json数据中含有null)</title><link>https://h1z3y3.me/posts/nsdictionarywritetofilefailedbecauseofcontainsnull/</link><guid isPermaLink="true">https://h1z3y3.me/posts/nsdictionarywritetofilefailedbecauseofcontainsnull/</guid><pubDate>Tue, 10 Nov 2015 22:43:40 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>之前从接口获取数据后转换为NSDictionary然后执行writeToFile就可以写入文件成功，昨天在使用&lt;a href="http://developers.douban.com/wiki/?title=api_v2">豆瓣电影Api&lt;/a>时，出现了前几条请求的数据写入缓存成功，从某一次请求开始就总是写入不成功。因为我是用gcd方式请求数据的，我还以为是因为线程竞争的问题，但是写文件的原子操作我设置的YES啊。然后写进程锁试一下，然而仍然写入失败，后来我不用gcd以为能好，结果还是写入失败。后来翻资料，说好像如果NSDictionary中有自定义的object类型是不能写入文件的，比如像&lt;strong>null&lt;/strong>，呵呵。但是我找了半天也没找到错误所在，然后也不太影响正常使用，就暂时放在一边了。&lt;/p>
&lt;p>结果今天出问题了，之前获取的是电影的列表信息，今天要写电影的详情页信息。写好后不断测试，结果有一条电影，一点进入详情就会崩溃。好了，设置好断点，一步步排查，最后NSLog导演头像的urlString时，找到了问题所在，导演的头像图片的url为&lt;strong>null&lt;/strong>！终于找到了问题所在。那么可以解决了&amp;hellip;&lt;/p>
&lt;p>我获取了该条目电影的id号，然后在浏览器中获取了他的json数据，果不其然，就是这个电影!《火云端》!&lt;/p>
&lt;p>&lt;img src="https://p3.ssl.qhimg.com/t01e3e13585b1cbbbe5.jpg" alt="含有null">&lt;/p>
&lt;p>然而我并没有想到有这么多&lt;strong>null&lt;/strong>。&lt;/p>
&lt;p>经过百度，好像要写文件，NSDictionary里面的object必须是NSString，NSData，NSNumber，NSDate，NSArray，NSDictionary中的数据类型。不过我知道，含有&lt;strong>null&lt;/strong>是万万不能的，混蛋。&lt;/p>
&lt;h2 id="解决方案nsdictionary---nsdate">解决方案：NSDictionary -&amp;gt; NSDate&lt;/h2>
&lt;p>NSDictionary写文件之前，可以把它转换成NSData类型的数据，再执行写文件操作。
从文件读取时把读取出的NSData转换为NSDictionary就可以了。&lt;/p>
&lt;h3 id="用到的方法">用到的方法&lt;/h3>
&lt;p>NSDictionary -&amp;gt; NSData:&lt;/p>
&lt;pre>&lt;code>NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary];
&lt;/code>&lt;/pre>
&lt;p>NSData -&amp;gt; NSDictionary:&lt;/p>
&lt;pre>&lt;code>NSDictionary *dictionary = (NSDictionary *)[NSKeyedUnarchiver unarchiveObjectWithData:data];
&lt;/code>&lt;/pre>
&lt;p>昨天的问题解决了，这样写时候，每一次都能写入缓存文件成功。&lt;/p>
&lt;p>今天的涉及到NSDictionary值的获取的部分，我都加了一个判断 &lt;code>if([theValue isKindOfClass:[NSNull class]])&lt;/code>，然后进行相应处理就可以了。&lt;/p>
&lt;p>这个时iOS的一个归档方法，不仅仅能归档&lt;code>null&lt;/code>，&lt;code>自定义的类型&lt;/code>也是可以的。具体可以参见&lt;a href="http://www.cnblogs.com/xiaobaizhu/p/4011332.html">小白猪jianjian的博客-使用NSKeyedArchiver归档&lt;/a>&lt;/p>
&lt;h2 id="感想">感想&lt;/h2>
&lt;p>生活中或者程序开发中，遇到了问题不一定非要立马解决，如果死钻牛角尖儿可能一直都解决不了，那时只能浪费时间。如果暂时放放，休息一会，再回来想解决办法可能就能想到解决方案。就像今天我解决我的问题一样，完全是机缘巧合。同时我们也应该发现，如果想做出好的软件，前期开发的投入很重要，但是后期的测试也很重要。功能做完之后我也是点击测试了不说几百次，几十次也是有了才发现了这个问题。&lt;/p>
&lt;p>最后给大家看下我做的&lt;strong>电影影讯&lt;/strong>，嘿嘿，使用了&lt;a href="http://developers.douban.com/wiki/?title=api_v2">豆瓣电影Api&lt;/a>。&lt;/p>
&lt;p>&lt;img src="https://p3.ssl.qhimg.com/t010921a0625be5aab8.gif" alt="威海影讯列表">
&lt;img src="https://p4.ssl.qhimg.com/t018dd1c22d5ce572a7.gif" alt="电影详情页">&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>纯代码高仿网易新闻客户端两个scrollView联动（二）：实现界面逻辑变换</title><link>https://h1z3y3.me/posts/twoscrollviewlinkage-2-businesslogic/</link><guid isPermaLink="true">https://h1z3y3.me/posts/twoscrollviewlinkage-2-businesslogic/</guid><pubDate>Sun, 08 Nov 2015 11:37:09 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>上一篇已经实现了页面的布局，这一篇我们来实现界面的逻辑变换。主要用到的是scollView的两个代理方法。我们先看一下效果。&lt;/p>
&lt;p>&lt;img src="https://p2.ssl.qhimg.com/t015616f03de7b014b5.gif" alt="效果预览图">&lt;/p>
&lt;h2 id="1实现点击顶部按钮切换底部详情页面">1.实现点击顶部按钮切换底部详情页面&lt;/h2>
&lt;h3 id="给顶部items的按钮添加target">给顶部items的按钮添加target&lt;/h3>
&lt;p>在&lt;code>- (void) addFirstScrollViewOnView &lt;/code>方法中的&lt;code>for循环&lt;/code>中，为每个button添加一个target，用于监听点击按钮的事件。&lt;/p>
&lt;pre>&lt;code> [itemButton addTarget:self action:@selector(itemButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
&lt;/code>&lt;/pre>
&lt;h3 id="添加监听方法">添加监听方法&lt;/h3>
&lt;p>我们应该知道当点击顶部item按钮时，底部详情页应该做相应的切换，同时按钮样式也改变。
大家可能注意到了需要用到按钮的tag值，所以我们同样在&lt;code>上一步的方法&lt;/code>的&lt;code>for循环&lt;/code>中为每个按钮设置tag值。&lt;/p>
&lt;pre>&lt;code>itemButton.tag = 100 + i;//设置button的tag值
&lt;/code>&lt;/pre>
&lt;p>同时，需要添加一个实例变量：&lt;/p>
&lt;pre>&lt;code> //记录当前被点击的按钮tag
@property (nonatomic, assign) NSInteger currentButtonTag;
&lt;/code>&lt;/pre>
&lt;p>**(重要)**并在&lt;code>上一步方法的最后&lt;/code>为它设置初始值为&lt;code>100&lt;/code>，也就是当前点击的按钮为第一个。&lt;/p>
&lt;h4 id="tips不从1开始设置tag值的原因是因为前100可能有系统的控件占用">tips：不从1开始设置tag值的原因是因为前100可能有系统的控件占用&lt;/h4>
&lt;p>scrollView详情页切换：获取当前按钮的tag值，计算偏移量&lt;/p>
&lt;pre>&lt;code>- (void) itemButtonClicked:(UIButton *)button {
//**1.偏移底部详情scrollView
NSInteger buttonTag = button.tag - 100;//获取点击按钮的tag
self.secondScrollView.contentOffset = CGPointMake(buttonTag * KSCREEN_WIDTH, 0);//设置底部scrollview的内容偏移量
//**2.恢复前一个被点击的按钮的样式
UIButton *preClickedButton = (UIButton *)[self.view viewWithTag:self.currentButtonTag];
preClickedButton.titleLabel.font = [UIFont systemFontOfSize:14.0f];
[preClickedButton setTitleColor:[UIColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1.0] forState:UIControlStateNormal];//标题颜色
//**3.设置当前点击按钮样式
button.titleLabel.font = [UIFont systemFontOfSize:18.0f];
[button setTitleColor:[UIColor colorWithRed:1.0 green:0.3 blue:0.3 alpha:1.0f] forState:UIControlStateNormal];
//**4.改变当前点击按钮的tag值
self.currentButtonTag = buttonTag + 100;
}
&lt;/code>&lt;/pre>
&lt;p>完成这一步，当你点击顶部按钮的时候，你就可以看到顶部按钮样式的改变和底部详情页面的切换。&lt;/p>
&lt;h2 id="2实现滚动底部详情页顶部按钮字体切换">2.实现滚动底部详情页顶部按钮字体切换&lt;/h2>
&lt;h3 id="添加scrollview的代理">添加scrollView的代理&lt;/h3>
&lt;p>添加代理&lt;/p>
&lt;pre>&lt;code>@interface ViewController () &amp;lt;UIScrollViewDelegate&amp;gt;
&lt;/code>&lt;/pre>
&lt;p>在&lt;code>- (void) addSecondScrollViewOnView &lt;/code>方法中设置&lt;code>secondScrollView&lt;/code>的代理为self。这步不要忘记了。&lt;/p>
&lt;pre>&lt;code>secondScrollView.delegate = self;
&lt;/code>&lt;/pre>
&lt;h3 id="实现scrollview-的代理方法---void-scrollviewdidscrolluiscrollview-scrollview">实现scrollView 的代理方法 - (void) scrollViewDidScroll:(UIScrollView *)scrollView；&lt;/h3>
&lt;p>在这一步，我们要实现滚动时，顶部items的按钮字体大小和颜色的改变。&lt;/p>
&lt;pre>&lt;code> //正在滑动调用的代理方法
- (void) scrollViewDidScroll:(UIScrollView *)scrollView {
//获取当前第二个scrollView的偏移量
CGFloat secondScrollViewContentOffsetX = scrollView.contentOffset.x;
//获取选中按钮的序号
int buttonTag = (secondScrollViewContentOffsetX) / KSCREEN_WIDTH;
//计算手指滑动了多少距离
CGFloat fingerDistance = secondScrollViewContentOffsetX - KSCREEN_WIDTH * buttonTag;
//获取到下一个按钮，并改变其字体大小和颜色，逐渐放大（根据手指滑动的距离动态改变）
UIButton *buttonNext = (UIButton *)[self.view viewWithTag:(buttonTag + 100 + 1)];
buttonNext.titleLabel.font = [UIFont systemFontOfSize:(14.0 + fingerDistance * 4 / (KSCREEN_WIDTH))];
[buttonNext setTitleColor:[UIColor colorWithRed:(0.4f + 3 * fingerDistance / (KSCREEN_WIDTH * 5)) green:0.3 blue:0.3 alpha:1.0] forState:UIControlStateNormal];
//同样方法获取到当前按钮，并改变其字体大小和颜色恢复回原来样式，逐渐缩小（根据手指滑动的距离动态改变）
UIButton *buttonCurr = (UIButton *)[self.view viewWithTag:(buttonTag + 100)];
buttonCurr.titleLabel.font = [UIFont systemFontOfSize:(18.0 - fingerDistance * 4 / (KSCREEN_WIDTH))];
[buttonCurr setTitleColor:[UIColor colorWithRed:(1.0f - 3 * fingerDistance / (KSCREEN_WIDTH * 5)) green:0.3 blue:0.3 alpha:1.0] forState:UIControlStateNormal];
}
&lt;/code>&lt;/pre>
&lt;p>这里可以看到下一个按钮的字体动态改变，计算方法其实非常简单。
设手指滑动距离为x，字体大小为y。那么我们有两个值 &lt;em>(0,14)&lt;/em>, &lt;em>(KSCREEN_WIDTH, 18)&lt;/em>。
然后列二元一次方程组，就可以得到 &lt;em>a&lt;/em> 和 &lt;em>b&lt;/em>。
我这里解得 &lt;em>a = 4/KSCREEN_WIDTD&lt;/em> , &lt;em>b = 14&lt;/em>&lt;/p>
&lt;p>当前按钮的改变亦然。只是两个点变为 &lt;em>(KSCREEN_WIDTH, 14)&lt;/em>, &lt;em>(0, 18)&lt;/em>
计算得到 &lt;em>a = －(4/KSCREEN_WIDTH)&lt;/em>, &lt;em>b = 18&lt;/em>&lt;/p>
&lt;p>颜色大家计算方式相似，不再赘述。&lt;/p>
&lt;h2 id="3使顶部scrollview随底部scrollview滑动而滚动">3.使顶部scrollView随底部scrollView滑动而滚动&lt;/h2>
&lt;p>我们注意看网易新闻客户端，它底部scrollView滚动之后，顶部的scrollView也会随之滚动，并且除了开头或者末尾的几个按钮，它当前所在的新闻类型始终在屏幕中间。并且顶部的滚动总是在底部滑动结束之后，所以我们实现scrollView的代理方法 &lt;code>- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView &lt;/code>&lt;/p>
&lt;pre>&lt;code>//滑动结束调用的代理方法
- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
//**1.获取选中详情页对应的顶部button
//移动顶部选中的按钮
CGFloat secondScrollViewContentOffsetX = scrollView.contentOffset.x;
//获取选中按钮的序号
int buttonTag = (secondScrollViewContentOffsetX) / KSCREEN_WIDTH;
//根据按钮号码获取到顶部的按钮
UIButton *buttonCurr = (UIButton *)[self.view viewWithTag:(buttonTag + 100)];
//**2.(重要)设置当前选中的按钮号。如若不写，将导致滑动后再点击顶部按钮，上一个按钮颜色，字体不会改变
self.currentButtonTag = buttonTag + 100;
//**3.始终保持顶部选中按钮在中间位置
//注意一：开始的几个按钮，和末尾的几个按钮并不需要一直保持中间。
//注意二：对于已经放置在firstScrollView中的按钮，它的center是相对于scrollView的content而言的，注意并不是相对于self.view的bounds而言的。也就是说，放置好按钮，它的center就不会再改变
//如果是顶部scrollView即将到末尾的几个按钮，设置偏移量，直接return
if (buttonCurr.center.x + KSCREEN_WIDTH * 0.5 &amp;gt; self.firstScrollView.contentSize.width) {
[UIView animateWithDuration:0.3 animations:^{
self.firstScrollView.contentOffset = CGPointMake(self.firstScrollView.contentSize.width - KSCREEN_WIDTH, 0);
}];
return;
}
//如果是顶部scrollView开头的几个按钮，设置偏移量，直接return
if (buttonCurr.center.x &amp;lt; KSCREEN_WIDTH * 0.5) {
[UIView animateWithDuration:0.3 animations:^{
self.firstScrollView.contentOffset = CGPointMake(0, 0);
}];
return;
}
//如果是中间几个按钮的情况
if (buttonCurr.center.x &amp;gt; (KSCREEN_WIDTH * 0.5)) {
[UIView animateWithDuration:0.3 animations:^{
self.firstScrollView.contentOffset = CGPointMake(buttonCurr.center.x - self.view.center.x, 0);
}];
}
}
&lt;/code>&lt;/pre>
&lt;p>这里需要注意的是我注释里面的&lt;strong>第2步&lt;/strong>和&lt;strong>第3步&lt;/strong>。
第2步一定要写，否则导致先滑动底部scrollView，再点击顶部scrollView的button，出现之前的那个button样式不恢复的情况。
第3步，注意前几个按钮和后几个按钮位置的判断，如果一味保持按钮在中间，就会出现顶部的offset过多，而后面出现空白，大家可以简单尝试一下。&lt;/p>
&lt;p>整个效果就已经完成了～☺️&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>纯代码高仿网易新闻客户端两个scrollView联动（一）：设置基本的界面布局</title><link>https://h1z3y3.me/posts/twoscrollviewlinkage-1-baseview/</link><guid isPermaLink="true">https://h1z3y3.me/posts/twoscrollviewlinkage-1-baseview/</guid><pubDate>Sat, 07 Nov 2015 23:31:46 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>自己再开发app过程中遇到了这样那样的需求，其中有一项是新闻页面，需要两个scrollView联动，上面的scrollView是新闻类型，下面scrollView是tableView新闻标题。开发过程中我全部都是用代码布局的，因为自己是新手，不知道到底是用代码写比较方便还是用storyBoard更方便，但是感觉手写代码适应屏幕上更容易上手吧。需求实现之后现在拿出来简单整理一下，方便自己日后查看，也给后来者以参考。因为刚刚开始学，用到的都是些简单的方法，也可能会出错，如果有什么不足，请留言给我指出。谢谢～&lt;/p>
&lt;p>现在开始正题：
本文是第一篇，先是简单的页面布局，首先我们看一下布局之后的效果：&lt;/p>
&lt;p>&lt;img src="https://p4.ssl.qhimg.com/t010500cb2fa4fe823f.gif" alt="twoScrollViewLinkage_baseViewLayout">&lt;/p>
&lt;h3 id="1宏定义">1.宏定义&lt;/h3>
&lt;pre>&lt;code>//屏幕的宽和高
#define KSCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
#define KSCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
//最顶部状态栏的高度
#define KSTATUS_HEIGHT 20
//顶部items的scrollView的高度
#define KFIRST_SCROLLVIEW_HEIGHT 30
//根据计算，得到底部详情scrollView的高度
#define KSECOND_SCROLLVIEW_HEIGHT (KSCREEN_HEIGHT - KSTATUS_HEIGHT - KFIRST_SCROLLVIEW_HEIGHT)
//顶部scrollView每个item按钮的宽度
#define KFIRST_SCROLLVIEW_ITEM_WIDTH 55
&lt;/code>&lt;/pre>
&lt;h3 id="2首先定义需要用到的变量">2.首先定义需要用到的变量&lt;/h3>
&lt;pre>&lt;code>@interface ViewController ()
//顶部items的scrollView
@property (nonatomic, weak) UIScrollView *firstScrollView;
//底部详情scrollView
@property (nonatomic, weak) UIScrollView *secondScrollView;
//item类型的数组
@property (nonatomic, strong) NSArray *itemsTitlesArray;
//为了方便，定义一个包含颜色的NSArray（自行取舍）
@property (nonatomic, strong) NSArray *colorArray;
@end
&lt;/code>&lt;/pre>
&lt;h3 id="3懒加载">3.懒加载&lt;/h3>
&lt;pre>&lt;code>#pragma mark - lazy load
//定义items的标题
- (NSArray *) itemsTitlesArray {
if (!_itemsTitlesArray) {
_itemsTitlesArray = [NSArray arrayWithObjects:@&amp;quot;热点新闻&amp;quot;, @&amp;quot;新闻快讯&amp;quot;, @&amp;quot;通知公告&amp;quot;, @&amp;quot;院系通知&amp;quot;, @&amp;quot;人物风采&amp;quot;, nil];
}
return _itemsTitlesArray;
}
//颜色数组
- (NSArray *) colorArray {
if (!_colorArray) {
_colorArray = [NSArray arrayWithObjects:
[UIColor colorWithRed:240.0/255.0 green:89.0/255.0 blue:136.0/255.0 alpha:1.0],
[UIColor colorWithRed:0.0/255.0 green:179.0/255.0 blue:155.0/255.0 alpha:1.0],
[UIColor colorWithRed:244.0/255.0 green:131.0/255.0 blue:69.0/255.0 alpha:1.0],
[UIColor colorWithRed:241.0/255.0 green:90.0/255.0 blue:102.0/255.0 alpha:1.0],
[UIColor colorWithRed:0.0/255.0 green:179.0/255.0 blue:155.0/255.0 alpha:1.0],
[UIColor colorWithRed:255.0/255.0 green:223.0/255.0 blue:104.0/255.0 alpha:1.0],
nil];
}
return _colorArray;
}
&lt;/code>&lt;/pre>
&lt;h3 id="4添加顶部items的scrollview">4.添加顶部items的scrollView&lt;/h3>
&lt;pre>&lt;code>//添加第一个scrollview
- (void) addFirstScrollViewOnView {
//**1.设置顶部类型scrollView
UIScrollView *firstScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, KSTATUS_HEIGHT, KSCREEN_WIDTH, KFIRST_SCROLLVIEW_HEIGHT)];
firstScrollView.bounces = NO;//禁止反弹
firstScrollView.showsHorizontalScrollIndicator = NO;//禁止显示水平滚动条
//设置scrollView的内容大小，宽度为 宏定义顶部按钮的宽度 ＊ items数组的数量
firstScrollView.contentSize = CGSizeMake(self.itemsTitlesArray.count * KFIRST_SCROLLVIEW_ITEM_WIDTH, KFIRST_SCROLLVIEW_HEIGHT);
self.firstScrollView = firstScrollView;
[self.view addSubview:firstScrollView];//添加到self.view
//**2.为第一个scrollView添加buttons
for (int i = 0; i &amp;lt; self.itemsTitlesArray.count; i ++) {
UIButton *itemButton = [[UIButton alloc] initWithFrame:CGRectMake(i * KFIRST_SCROLLVIEW_ITEM_WIDTH, 0, KFIRST_SCROLLVIEW_ITEM_WIDTH, KFIRST_SCROLLVIEW_HEIGHT)];//注意左边距的写法
[itemButton setTitle:[self.itemsTitlesArray objectAtIndex:i] forState:UIControlStateNormal];//标题
itemButton.backgroundColor = [UIColor colorWithRed:0.97 green:0.97 blue:0.97 alpha:1.0];//背景颜色
[itemButton setTitleColor:[UIColor colorWithRed:0.4 green:0.4 blue:0.4 alpha:1.0] forState:UIControlStateNormal];//标题颜色
itemButton.titleLabel.font = [UIFont systemFontOfSize:14.0];
[firstScrollView addSubview:itemButton];//添加到第一个scrollView
//定义第一个顶部item的按钮样式
if (i == 0) {
itemButton.titleLabel.font = [UIFont systemFontOfSize:18.0f];
[itemButton setTitleColor:[UIColor colorWithRed:1.0 green:0.3 blue:0.3 alpha:1.0f] forState:UIControlStateNormal];
}
}
}
&lt;/code>&lt;/pre>
&lt;h3 id="5添加底部详情scrollview">5.添加底部详情scrollView&lt;/h3>
&lt;pre>&lt;code>//添加第二个scrollview
- (void) addSecondScrollViewOnView {
//**1.设置底部详情scrollView
UIScrollView *secondScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, KSTATUS_HEIGHT + KFIRST_SCROLLVIEW_HEIGHT, KSCREEN_WIDTH, KSECOND_SCROLLVIEW_HEIGHT)];
secondScrollView.pagingEnabled = YES;//分页
secondScrollView.bounces = NO;//禁止反弹
secondScrollView.delegate = self;
//设置内容大小，宽度为 屏幕的宽度 * items数组的数量
secondScrollView.contentSize = CGSizeMake(self.itemsTitlesArray.count * KSCREEN_WIDTH, KSECOND_SCROLLVIEW_HEIGHT);
self.secondScrollView = secondScrollView;
[self.view addSubview:secondScrollView];
//**2.添加Views
for (int i = 0; i &amp;lt; self.itemsTitlesArray.count; i ++) {
UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(i * KSCREEN_WIDTH, 0, KSCREEN_WIDTH, KSECOND_SCROLLVIEW_HEIGHT)];
bottomView.backgroundColor = [self.colorArray objectAtIndex:(i % 6)];//颜色数组取余
//为了方便观察，放置一个label，如果需要其它控件自行替换（如若新闻，用tableView）
UILabel *flagLabel = [[UILabel alloc] initWithFrame:bottomView.bounds];
flagLabel.font = [UIFont systemFontOfSize:50.0f];
flagLabel.textColor = [UIColor whiteColor];
flagLabel.textAlignment = NSTextAlignmentCenter;
flagLabel.text = [NSString stringWithFormat:@&amp;quot;%@&amp;quot;, [self.itemsTitlesArray objectAtIndex:i]];
[bottomView addSubview:flagLabel];
[secondScrollView addSubview:bottomView];
}
}
&lt;/code>&lt;/pre>
&lt;h3 id="6在-voidviewdidload方法中调用两个添加scrollview的方法">6.在-(void)viewDidLoad方法中调用两个添加scrollView的方法&lt;/h3>
&lt;pre>&lt;code>- (void)viewDidLoad {
[super viewDidLoad];
[self addFirstScrollViewOnView];//添加顶部items的scrollView
[self addSecondScrollViewOnView];//添加底部详情的scrollView
}
&lt;/code>&lt;/pre>
&lt;h3 id="7声明">7.声明&lt;/h3>
&lt;p>本文涉及的一切代码，都将上传到我的github仓库:&lt;a href="https://github.com/Gitzhaoyang/iOSWithOC/tree/master/twoScrollViewLinkage">https://github.com/Gitzhaoyang/iOSWithOC/tree/master/twoScrollViewLinkage&lt;/a>，请自行查看。&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>UIImageView更换图片时渐隐渐现</title><link>https://h1z3y3.me/posts/uiimageview-image-fade-out/</link><guid isPermaLink="true">https://h1z3y3.me/posts/uiimageview-image-fade-out/</guid><pubDate>Mon, 02 Nov 2015 11:47:44 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>实现原理十分简单，使用UIImageView的透明度即可。然后在动画中完成。
alpha = 1 为全透明。
运行图例：&lt;/p>
&lt;p>&lt;img src="https://p3.ssl.qhimg.com/t0150836f31829840e4.gif" alt="UIImageView-image-fade-out">&lt;/p>
&lt;p>实现代码:&lt;/p>
&lt;pre>&lt;code>//图片渐隐渐现
self.backgroundView.alpha = 0.7;
[UIView animateWithDuration:0.5 animations:^{
self.backgroundView.alpha = 1;
self.backgroundView.image = [UIImage imageNamed:@&amp;quot;weather_bg_02.jpg&amp;quot;];
}];
&lt;/code>&lt;/pre>
&lt;hr>
&lt;p>后来我知道了可以用更好的方法实现， 后续会有更新&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>UITabBarController嵌套UINavigationController后，关于tabBar的问题以及解决方法</title><link>https://h1z3y3.me/posts/how-to-use-sethidebottombarwhenpushed/</link><guid isPermaLink="true">https://h1z3y3.me/posts/how-to-use-sethidebottombarwhenpushed/</guid><pubDate>Sat, 24 Oct 2015 20:03:13 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>一开始自己将tabBar全部自定义，将系统tabbar设置为&lt;code>self.tabBar.hidden=YES&lt;/code>，隐藏系统的tabbar。但是自定义的tabbar在push出新页面的时候，tabbar并不会自动隐藏。之后在新页面&lt;code>viewWillAppear&lt;/code>中设置自动隐藏，但是pop回之前页面（在&lt;code>viewDidAppear&lt;/code>中设置tabbar显示）又出现不能及时出现，会有时间延迟，看上去特别不舒服。而且，边缘返回旧页面的时候不能及时显示tabbar也，所以打算用系统默认的tabbar。记录下&lt;strong>系统tabBar&lt;/strong>样式的简单定义。&lt;/p>
&lt;h2 id="简单自定义系统的tabbar">简单自定义系统的tabBar&lt;/h2>
&lt;pre>&lt;code>- (void) initTabBarView {
NSArray *titles = @[@&amp;quot;工大威海&amp;quot;, @&amp;quot;校园应用&amp;quot;, @&amp;quot;校园生活&amp;quot;, @&amp;quot;更多功能&amp;quot;];
//tabBarItem选中图片简单颜色变化时，自定义选中后的颜色
[self.tabBar setTintColor:[UIColor redColor]];
int i = 0;
for (UITabBarItem *item in self.tabBar.items) {
//自定义tabBarItem的图片
item.image = [[UIImage imageNamed:
[NSString stringWithFormat:@&amp;quot;home_tab_icon_%d&amp;quot;, i + 1]] imageWithRenderingMode:UIImageRenderingModeAutomatic];
//定义选中图片,上面imageWithRederingMode设置为Automatic，只是简单颜色变换，不需要设置,不再赘述
//item.selectedImage = ....
//设置tabBarItem的标题
item.title = titles[i];
i ++;
}
}
&lt;/code>&lt;/pre>
&lt;p>这是运行后的样式：&lt;/p>
&lt;p>&lt;img src="https://p0.ssl.qhimg.com/t01dc370efcf696663c.jpg" alt="样式">&lt;/p>
&lt;h2 id="push新页面tabbar自动隐藏pop回显示tabbar">push新页面tabBar自动隐藏，pop回显示tabBar&lt;/h2>
&lt;p>上面说如果用完全自定义的tabbar，我只能设置到等pop回的页面全部出现之后再让tabBar显示出来，这样十分不友好，我们如果实现手机微信类似这样的效果我们应该如何设置呢？注意红框部分&lt;/p>
&lt;p>&lt;img src="https://p5.ssl.qhimg.com/t01b76fc70985541e13.jpg" alt="微信边缘拖动返回时页面演示">&lt;/p>
&lt;p>我们应该在&lt;strong>要push出新页面的那个页面&lt;/strong>设置&lt;code>viewDidAppear&lt;/code>和&lt;code>viewWillDisappear&lt;/code>方法：&lt;/p>
&lt;pre>&lt;code>- (void) viewDidAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self setHidesBottomBarWhenPushed:YES];
}
- (void) viewWillDisappear:(BOOL)animated {
[self setHidesBottomBarWhenPushed:NO];
[super viewDidDisappear:animated];
}
&lt;/code>&lt;/pre>
&lt;p>边缘拖动返回的时候，即可实现如下效果：&lt;/p>
&lt;p>&lt;img src="https://p1.ssl.qhimg.com/t01485a1ce59a7eb0e7.jpg" alt="我想要的效果">&lt;/p>
&lt;p>完成~ 😏😏😏&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>UIImageView 中图片的几种常用适应方式</title><link>https://h1z3y3.me/posts/some-uiimageview-fit-way/</link><guid isPermaLink="true">https://h1z3y3.me/posts/some-uiimageview-fit-way/</guid><pubDate>Thu, 22 Oct 2015 22:17:46 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>UIImageView三种contentMode区别：UIViewContentModeScaleToFill、UIViewContentModeScaleAspectFit、UIViewContentModeScaleAspectFill&lt;/p>
&lt;h4 id="原图片">原图片：&lt;/h4>
&lt;p>&lt;img src="https://p2.ssl.qhimg.com/t01c8ac21fc4f18715d.jpg" alt="原图片">&lt;/p>
&lt;h4 id="uiviewcontentmodescaletofill">UIViewContentModeScaleToFill：&lt;/h4>
&lt;p>&lt;img src="https://p2.ssl.qhimg.com/t0157f3fc5b7fd29c70.jpg" alt="UIViewContentModeScaleToFill">&lt;/p>
&lt;h4 id="uiviewcontentmodescaleaspectfit">UIViewContentModeScaleAspectFit&lt;/h4>
&lt;p>&lt;img src="https://p5.ssl.qhimg.com/t012f3e198e9b2c975b.jpg" alt="UIViewContentModeScaleAspectFit">&lt;/p>
&lt;h4 id="uiviewcontentmodescaleaspectfill">UIViewContentModeScaleAspectFill&lt;/h4>
&lt;p>&lt;img src="https://p3.ssl.qhimg.com/t01aa70bb092dd21b14.jpg" alt="UIViewContentModeScaleAspectFill">&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>tableView 两种重用cell的方法区别</title><link>https://h1z3y3.me/posts/two-methods-to-reuse-cell/</link><guid isPermaLink="true">https://h1z3y3.me/posts/two-methods-to-reuse-cell/</guid><pubDate>Sun, 18 Oct 2015 16:21:04 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>今天在学习iOS开发的时候，偶然发现tableView有两种重用cell的方法。先整理一下方便日后查阅。&lt;/p>
&lt;p>第一种：&lt;code>[tableView dequeueReusableCellWithIdentifier:identifier]&lt;/code> （SDK 6.0之前）&lt;br>
第二种：&lt;code>[tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath]]&lt;/code> （SDK 6.0之后）&lt;/p>
&lt;p>区别：
第一种：必须判断cell是否定义，未定义则手动创建，代码如下：&lt;/p>
&lt;pre>&lt;code>const NSString *identifier = @&amp;quot;cell&amp;quot;;
UITableView *cell = [tableView dequeueReusableCellWithIndetifier:identifier];
if(!cell) {
cell = [[UITableView alloc] initWithStyle:UITableViewCellStyleDefault];
}
/*
设置cell
cell.textLable.text = @&amp;quot;第...行&amp;quot;;
*/
return cell;
&lt;/code>&lt;/pre>
&lt;p>第二种是SDK 6.0开始新添加的方法。用于你&lt;strong>已经用Nib定义了一个Cell&lt;/strong>，然后就可以省下上面那些代码，只用一行就可以解决：&lt;/p>
&lt;pre>&lt;code> UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@&amp;quot;cell&amp;quot; forIndexPath:indexPath];
/*
设置cell
cell.textLable.text = @&amp;quot;第...行&amp;quot;;
*/
&lt;/code>&lt;/pre>
&lt;p>所以如果有这个错误：&lt;/p>
&lt;pre>&lt;code>reason: 'unable to dequeue a cell with identifier friendCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
&lt;/code>&lt;/pre>
&lt;p>应该想一下自己&lt;strong>是不是为cell创建的Nib&lt;/strong>。&lt;/p></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>MarkDown基本使用方法</title><link>https://h1z3y3.me/posts/markdown-base-use/</link><guid isPermaLink="true">https://h1z3y3.me/posts/markdown-base-use/</guid><pubDate>Sat, 17 Oct 2015 18:31:15 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h2 id="markdown-介绍">Markdown 介绍&lt;/h2>
&lt;blockquote>
&lt;p>Markdown是一种可以使用普通文本编辑器编写的&lt;strong>标记语言&lt;/strong>，通过简单的标记语法，它可以使普通文本内容具有一定的格式。Markdown的使用十分简单，常用的&lt;strong>标记&lt;/strong>其实就十几个，所以相对于HTML这种复杂的标记语言来说，Markdown是十分轻量的标记语言。&lt;/p>
&lt;/blockquote>
&lt;h2 id="markdown的基本使用">Markdown的基本使用&lt;/h2>
&lt;ol>
&lt;li>
&lt;h3 id="加粗和斜体">加粗和斜体&lt;/h3>
&lt;p>&lt;code>*斜体* _斜体_&lt;/code>&lt;/p>
&lt;p>只要用一个&lt;code>_&lt;/code>或者一个&lt;code>#&lt;/code>包裹文字，即可实现文字斜体。&lt;/p>
&lt;p>&lt;code>**加粗文字** __加粗文字__&lt;/code>&lt;/p>
&lt;p>只要用两个&lt;code>_&lt;/code>或者两个&lt;code>#&lt;/code>包裹文字，即可实现文字粗体。&lt;/p>
&lt;p>&lt;img src="https://p5.ssl.qhimg.com/t014be129baf06de9da.jpg" alt="粗体和斜体">&lt;/p>
&lt;/li>
&lt;li>
&lt;h3 id="链接与图片">链接与图片&lt;/h3>
&lt;p>插入链接和图片的格式很相似，只有一个&lt;code>!&lt;/code>的区别&lt;/p>
&lt;p>链接格式为：&lt;code>[链接文字](链接地址)&lt;/code>&lt;/p>
&lt;p>图片格式为：&lt;code>![图片名字](图片地址)&lt;/code>&lt;/p>
&lt;p>在Markdown中插入图片地址要用到图床，我用到的是&lt;a href="http://weibotuchuang.sinaapp.com">围脖图床修复计划&lt;/a>和&lt;a href="http://www.pic100.net">云图图床&lt;/a>，上传图片就可以生成URL&lt;/p>
&lt;p>&lt;img src="https://p0.ssl.qhimg.com/t01846fd89958a14640.jpg" alt="链接和图片">&lt;/p>
&lt;/li>
&lt;li>
&lt;h3 id="标题">标题&lt;/h3>
&lt;p>标题是文章中最常用的文本格式，在Markdown中只要在一行文字前添加&lt;code>#&lt;/code>，即可标记为标题格式。&lt;/p>
&lt;p>&lt;code>＃ 一级标题&lt;/code>&lt;/p>
&lt;p>&lt;code>## 二级标题&lt;/code>&lt;/p>
&lt;p>&lt;code>### 三级标题&lt;/code>&lt;/p>
&lt;p>以此类推，Markdown一共有六级标题，六级标题只需要加上六个&lt;code>######&lt;/code>即可。&lt;/p>
&lt;p>&lt;img src="https://p0.ssl.qhimg.com/t013492ed250532d583.jpg" alt="标题">&lt;/p>
&lt;/li>
&lt;li>
&lt;h3 id="列表">列表&lt;/h3>
&lt;p>列表有&lt;strong>有序列表&lt;/strong>和&lt;strong>无序列表&lt;/strong>&lt;/p>
&lt;p>有序列表格式：&lt;/p>
&lt;pre>&lt;code> 1. 红色
2. 蓝色
3. 黑色
&lt;/code>&lt;/pre>
&lt;p>无序列表格式：&lt;/p>
&lt;pre>&lt;code> * 红色
* 蓝色
* 黑色
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="https://p4.ssl.qhimg.com/t012e847b97fdfde44f.jpg" alt="列表">&lt;/p>
&lt;/li>
&lt;li>
&lt;h3 id="引用">引用&lt;/h3>
&lt;p>引用只需要在文字前面加上 &lt;code>&amp;gt;&lt;/code> 就可以了。你可以联合其它的标记符一起使用。&lt;/p>
&lt;pre>&lt;code> &amp;gt; * 引用中列表
&amp;gt; * 列表
&amp;gt; ### 引用中三级标题
&amp;gt;&amp;gt; 二级引用
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="https://p3.ssl.qhimg.com/t0163d79fe042bb581a.jpg" alt="引用">&lt;/p>
&lt;/li>
&lt;li>
&lt;h3 id="行内代码和代码块">行内代码和代码块&lt;/h3>
&lt;p>行内代码格式： &lt;code>｀这里写代码｀&lt;/code>&lt;br>
代码块格式： 只要比上一行进行右缩进即可。按键盘&lt;code>tab&lt;/code>键可以实现缩进&lt;/p>
&lt;p>&lt;img src="https://p1.ssl.qhimg.com/t013a7cbc965a07325f.jpg" alt="行内代码和代码块">&lt;/p>
&lt;/li>
&lt;li>
&lt;h3 id="表格">表格&lt;/h3>
&lt;p>表格应该是Markdown中最难的标记了&lt;/p>
&lt;p>最简单的表格：&lt;/p>
&lt;pre>&lt;code> | First Header | Second Header | Third Header |
| ------------ | ------------- | ------------ |
| Content Cell | Content Cell | Content Cell |
| Content Cell | Content Cell | Content Cell |
&lt;/code>&lt;/pre>
&lt;p>你也可以设置文字的对齐方式&lt;/p>
&lt;pre>&lt;code> First Header | Second Header | Third Header
:----------- | :-----------: | -----------:
Left | Center | Right
Left | Center | Right
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="https://p1.ssl.qhimg.com/t017070396e44e5c1ef.jpg" alt="表格">&lt;/p>
&lt;/li>
&lt;li>
&lt;h3 id="水平分隔线">水平分隔线&lt;/h3>
&lt;p>使用 &lt;code>---&lt;/code> 或者 &lt;code>***&lt;/code>即可。&lt;/p>
&lt;p>&lt;img src="https://p2.ssl.qhimg.com/t0192d850b3c0576e4e.jpg" alt="水平线">&lt;/p>
&lt;/li>
&lt;li>
&lt;h3 id="删除线">删除线&lt;/h3>
&lt;p>删除线格式：&lt;/p>
&lt;pre>&lt;code> ~~删除线~~
&lt;/code>&lt;/pre>
&lt;p>&lt;img src="https://p0.ssl.qhimg.com/t012a8f4294c4d33c0b.jpg" alt="删除线">&lt;/p>
&lt;/li>
&lt;li>
&lt;h3 id="换行">换行&lt;/h3>
&lt;p>只要在文字每行文字后面加上两个或两个以上的&lt;code>空格&lt;/code>即可实现换行&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="注意">注意&lt;/h2>
&lt;ol>
&lt;li>标记符号后面一定加上&lt;strong>空格&lt;/strong>&lt;/li>
&lt;li>标记语言必须使用英文符号&lt;/li>
&lt;li>如果使用正确而不起作用，换一行再试一次&lt;/li>
&lt;/ol>
&lt;h2 id="markdown编辑工具">Markdown编辑工具&lt;/h2>
&lt;p>本人使用的工具是Mac OS下的&lt;strong>Mou&lt;/strong>,支持&lt;strong>实时预览&lt;/strong>，文章中的截图即为Mou的截图。你也可以在Github上搜索其它主题样式进行安装。&lt;br>
Mac下Markdown编辑器：&lt;a href="http://25.io/mou/">Mou ¥0&lt;/a>、&lt;a href="http://itunes.apple.com/cn/app/byword/id482063361?mt=8">Byword ¥40&lt;/a>、&lt;a href="https://itunes.apple.com/cn/app/ia-writer-pro/id775737590?mt=12">iA Writer ¥128&lt;/a>、&lt;a href="https://itunes.apple.com/cn/app/ulysses-iii/id623795237?mt=12">Ulysses ¥283&lt;/a> etc.&lt;br>
Windows下Markdown编辑器：&lt;a href="http://markdownpad.com/">MarkdownPad&lt;/a>、&lt;a href="http://markpad.fluid.impa.br/">MarkPad&lt;/a> etc.&lt;/p>
&lt;h2 id="官方文档">官方文档&lt;/h2>
&lt;p>&lt;a href="http://daringfireball.net/projects/markdown/syntax">英文Markdown文档&lt;/a>&lt;br>
&lt;a href="http://wowubuntu.com/markdown/">中文Markdown文档&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/markdown/">Markdown</category><category domain="https://h1z3y3.me/tags/hexo/">Hexo</category></item><item><title>UIScrollView的基本属性设置和常用代理方法</title><link>https://h1z3y3.me/posts/how-to-use-uiscrollviewdelegate/</link><guid isPermaLink="true">https://h1z3y3.me/posts/how-to-use-uiscrollviewdelegate/</guid><pubDate>Sat, 17 Oct 2015 17:54:31 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>罗列了UIscrollView的一些基本设置和常用方法&lt;/p>
&lt;ul>
&lt;li>
&lt;p>(void)viewDidLoad {
[super viewDidLoad];
// [self initScrollView];
// [self initPageCtrl];
// [self addTimer];
self.scrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(10, 20, 320, 460)];
self.scrollView.backgroundColor = [UIColor blueColor];
//是否支持滑动到最顶端
self.scrollView.scrollsToTop = NO;
//设置UIScrollView的代理
self.scrollView.delegate = self;
//设置内容大小
self.scrollView.contentSize = CGSizeMake(320 * 10, 460);
//是否反弹
self.scrollView.bounces = NO;
//是否滚动
self.scrollView.scrollEnabled = YES;
//是否分页
self.scrollView.pagingEnabled = NO;
//设置indecator风格
self.scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
//设置内容边缘和Indicator边缘
self.scrollView.contentInset = UIEdgeInsetsMake(0, 50, 50, 0);
self.scrollView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 50, 0, 0);
//是否同时运动
self.scrollView.directionalLockEnabled = NO;&lt;/p>
&lt;pre>&lt;code> [self.view addSubview:self.scrollView];
&lt;/code>&lt;/pre>
&lt;p>}&lt;/p>
&lt;p>//是否支持滑动到顶部&lt;/p>
&lt;ul>
&lt;li>(BOOL) scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
return YES;
}&lt;/li>
&lt;/ul>
&lt;p>//滑动到顶部时调用该方法&lt;/p>
&lt;ul>
&lt;li>(void) scrollViewDidScrollToTop:(UIScrollView *)scrollView {
NSLog(@&amp;ldquo;滑动到顶部了&amp;rdquo;);
}&lt;/li>
&lt;/ul>
&lt;p>//已经滑动(正在滑动也会调用该方法)&lt;/p>
&lt;ul>
&lt;li>(void) scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(@&amp;ldquo;已经开始滑动&amp;rdquo;);
}&lt;/li>
&lt;/ul>
&lt;p>//开始拖动&lt;/p>
&lt;ul>
&lt;li>(void) scrollViewWillBeginDragging:(UIScrollView *)scrollView {
NSLog(@&amp;ldquo;开始拖动&amp;rdquo;);
}&lt;/li>
&lt;/ul>
&lt;p>//结束拖动&lt;/p>
&lt;ul>
&lt;li>(void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
NSLog(@&amp;ldquo;结束拖动&amp;rdquo;);
}&lt;/li>
&lt;/ul>
&lt;p>//开始减速&lt;/p>
&lt;ul>
&lt;li>(void) scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
NSLog(@&amp;ldquo;开始减速&amp;rdquo;);
}&lt;/li>
&lt;/ul>
&lt;p>//减速停止&lt;/p>
&lt;ul>
&lt;li>(void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSLog(@&amp;ldquo;减速停止&amp;rdquo;);
}&lt;/li>
&lt;/ul>
&lt;p>//结束滚动动画&lt;/p>
&lt;ul>
&lt;li>
&lt;p>(void) scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
NSLog(@&amp;ldquo;结束滚动动画&amp;rdquo;);
}&lt;/p>
&lt;/li>
&lt;li>
&lt;p>(void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>函数respondsToSelector的使用</title><link>https://h1z3y3.me/posts/how-to-use-founction-respondstoselector/</link><guid isPermaLink="true">https://h1z3y3.me/posts/how-to-use-founction-respondstoselector/</guid><pubDate>Thu, 15 Oct 2015 00:44:48 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>-(BOOL)respondsToSelector: selector
//用来判断是否有以某个名字命名的方法（被封装在一个selector的对象里传递）&lt;/p>
&lt;hr>
&lt;pre>&lt;code>@protocol appViewDelegate &amp;lt;NSObject&amp;gt;
@optional
- (void) theButtonOnClicked:(UIButton *)button;
@end
&lt;/code>&lt;/pre>
&lt;hr>
&lt;pre>&lt;code>if([self.delegate respondsToSelector:@selector(theButtonOnClicked:)]) {
self.delegate theButtonOnClicked:sender;
}
&lt;/code>&lt;/pre>
&lt;hr>
&lt;pre>&lt;code>#pragma mark - appViewDelegate
- (void) theButtonOnClicked:(UIButton *)button {
button.enabled = NO;
[]button setTitle:@&amp;quot;已下载&amp;quot; ForState:UIControlStateDisabled];
}
&lt;/code>&lt;/pre>
&lt;hr></description><category domain="https://h1z3y3.me/tags/ios/">IOS</category></item><item><title>在Github上面搭建Hexo博客（四）:使用不同电脑维护</title><link>https://h1z3y3.me/posts/create-hexo-on-github-4/</link><guid isPermaLink="true">https://h1z3y3.me/posts/create-hexo-on-github-4/</guid><pubDate>Wed, 14 Oct 2015 12:13:01 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>这一篇是本系列的最后一篇，到目前为止，我们搭建的Hexo博客已经能满足我们日常的需求。可是有没有想过我们其实只能在这一台电脑上更新我们的博客？如果有一天我换了一台电脑，或者公司和家里不同的电脑都想更新博客，应该怎么办呢？&lt;/p>
&lt;p>这里只给类似我这样的git新人做参考，git的很多用法我也不太熟练。如果有错误还请各位指正。&lt;/p>
&lt;p>Note: &lt;strong>这里需要注意的是，当我们执行&lt;code>$ hexo deploy&lt;/code>，部署到&lt;a href="https://github.com">Github&lt;/a>
上面的是hexo给我们生成的静态页面，并不是整个hexo博客工程文件，所以并不能简单的在不同PC的更新博客。&lt;/strong>&lt;/p>
&lt;p>其实有两种办法，第一就是**&lt;em>把整个目录备份到云盘&lt;/em>&lt;strong>，然后开启云盘同步该文件夹，虽然操作简单，但是同步很麻烦，需要手动将文件夹进行覆盖。第二种就是&lt;/strong>&lt;em>使用Git的第三方服务&lt;/em>**
，只要配置完成，不管到哪里，用哪台电脑都能简单实现更新博客（当然需要Node.js和Git运行环境，在我们&lt;a href="https://h1z3y3.me/2015/10/12/create-hexo-on-github-1/">本系列的第一篇&lt;/a>有讲解）。&lt;/p>
&lt;p>在本文中详细讲解如何使用第三方git服务进行博客的备份。可以&lt;a href="https://github.com">Github&lt;/a>放在共有仓库，如果你担心泄密，可以用&lt;a href="https://github.com">Github&lt;/a>
的私有仓库（收费），国内除了&lt;a href="https://github.com">Github&lt;/a>
还有许多知名的git服务商，如：gitcafe，bitbucket，oschina，coding等，据我了解，oschina的私有仓库是免费的，而且可以和&lt;a href="https://github.com">Github&lt;/a>
进行同步。因为我们的博客放在了&lt;a href="https://github.com">Github&lt;/a>，所以我们不妨就把我们博客程序也同步到&lt;a href="https://github.com">Github&lt;/a>的仓库中。&lt;/p>
&lt;p>下面我们就来讲解如何实现不同电脑同时更新博客。&lt;/p>
&lt;!-- more -->
&lt;h1 id="将我们的博客目录备份到githubhttpsgithubcom实现多pc维护">将我们的博客目录备份到&lt;a href="https://github.com">Github&lt;/a>，实现多PC维护&lt;/h1>
&lt;h2 id="在githubhttpsgithubcom网站创建一个新的repository">在&lt;a href="https://github.com">Github&lt;/a>网站创建一个新的repository&lt;/h2>
&lt;p>我们在这里给新创建的repository命名为blog；&lt;/p>
&lt;p>不会创建方法的朋友可以参考&lt;a href="https://h1z3y3.me/2015/10/12/create-hexo-on-github-1/">在Github上面搭建Hexo博客（一）&lt;/a>中的创建方法。&lt;/p>
&lt;h2 id="在a电脑中从本地上传hexo到githubhttpsgithubcom仓库">在A电脑中从本地上传Hexo到&lt;a href="https://github.com">Github&lt;/a>仓库&lt;/h2>
&lt;p>Note: &lt;strong>A电脑指的是建立Hexo博客的电脑。&lt;/strong>&lt;/p>
&lt;h2 id="初始化仓库">初始化仓库&lt;/h2>
&lt;p>在Hexo博客的根目录运行Git Bash并输入以下命令：&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; git init
&amp;gt; git remote add origin &amp;lt;server address&amp;gt;
&lt;/code>&lt;/pre>&lt;p>这里&lt;code>&amp;lt;server&amp;gt;&lt;/code>指的是在线仓库的地址，比如在这里我的就应该是&lt;code>https://github.com/Gitzhaoyang/blog.git&lt;/code>，如果你用其它git仓库服务，填写对应仓库地址即可。
&lt;code>origin&lt;/code>是本地分支,&lt;code>remote add&lt;/code>会将本地仓库映射到&lt;a href="https://github.com">Github&lt;/a>仓库&lt;/p>
&lt;h2 id="把本地文件同步到githubhttpsgithubcom上面">把本地文件同步到&lt;a href="https://github.com">Github&lt;/a>上面&lt;/h2>
&lt;p>分别输入执行以下命令：&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; git add . #添加所有目录，注意add后面有个点`.`
&amp;gt; git commit -m &amp;#34;add to Github&amp;#34; #添加提交说明，每次提交都需要
&amp;gt; git push -u origin master #把更新推送到云端
&lt;/code>&lt;/pre>&lt;p>这时可以登录&lt;a href="https://github.com">Github&lt;/a>账户查看刚创建的&lt;code>blog仓库&lt;/code>中是否上传成功&lt;/p>
&lt;p>windows平台可能push过程中会提示输入&lt;a href="https://github.com">Github&lt;/a>的用户名和&lt;a href="https://github.com">Github&lt;/a>的密码，输入正确便是。&lt;/p>
&lt;h3 id="注意">注意&lt;/h3>
&lt;p>为了在另一台电脑上配置更加方便，严重建议把Hexo博客目录下&lt;code>_config.yml&lt;/code>文件复制粘贴一份，并重命名为&lt;code>hexo_config.yml&lt;/code>；把themes目录下你用到主题目录下的&lt;code>_config.yml&lt;/code>
文件也复制一份，并粘贴到&lt;strong>博客根目录，注意，是&amp;rsquo;博客根目录&amp;rsquo;&lt;/strong>，并命名为&lt;code>theme_config.yml&lt;/code>。原因是我们上传的时候，我们自己安装的&lt;code>themes&lt;/code>
如：&lt;code>[NexT](http://theme-next.iissnan.com)&lt;/code>，它的&lt;code>'next'&lt;/code>目录并&lt;strong>不能上传&lt;/strong>，所以我们需要把这两个配置文件都保存下来在进行同步工作。&lt;/p>
&lt;h2 id="在b电脑中从githubhttpsgithubcom仓库取回hexo到本地">在B电脑中从&lt;a href="https://github.com">Github&lt;/a>仓库取回Hexo到本地&lt;/h2>
&lt;p>Note: &lt;strong>B电脑指的是另一台电脑，如果没有另一台电脑也可以找地方新建一个文件夹尝试。&lt;/strong>&lt;/p>
&lt;h2 id="安装git和nodejs">安装Git和Node.js&lt;/h2>
&lt;p>值得注意的是新电脑也需要安装Git和Node.js环境，参考&lt;a href="https://h1z3y3.me/2015/10/12/create-hexo-on-github-1/">本系列的第一篇&lt;/a>中安装方法。&lt;/p>
&lt;h2 id="把文件取回本地">把文件取回本地&lt;/h2>
&lt;p>安装环境完成后，在新文件夹下运行Git Bash并分别执行以下几条命令：&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; git init $ git remote add origin &amp;lt;server&amp;gt;
&amp;gt; git fetch --all $ git reset --hard origin/master
&lt;/code>&lt;/pre>&lt;p>这里&lt;code>&amp;lt;server&amp;gt;&lt;/code>仍然是你的Giuhub地址。&lt;code>fetch&lt;/code>是将仓库中的内容取出来。&lt;code>reset&lt;/code>则是不做任何合并（merge）处理，直接把取出的内容保存。&lt;/p>
&lt;p>运行完&lt;code>reset&lt;/code>后你会发现文件夹中就会出现刚刚上传的内容。但是配置并没有完成，请继续往下看。&lt;/p>
&lt;h2 id="配置新的hexo">配置新的Hexo&lt;/h2>
&lt;p>Note: &lt;strong>如果是新PC，不要忘记我们本机并没有安装Hexo博客&lt;/strong>&lt;/p>
&lt;p>首先，在刚才的目录下执行以下命令以在新机器中安装Hexo&lt;/p>
&lt;pre tabindex="0">&lt;code> $ npm install hexo --save
&lt;/code>&lt;/pre>&lt;p>初始化Hexo并安装相应依赖包&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; hexo init
&amp;gt; npm intall
&lt;/code>&lt;/pre>&lt;p>记得在第一篇中讲过，新安装的Hexo是没有&lt;code>hexo-deployer-git&lt;/code>依赖包的，需要手动安装&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; npm install hexo-deployer-git --save
&lt;/code>&lt;/pre>&lt;p>如果你在A机器上设置了&lt;code>订阅（feed）&lt;/code>，那么你需要重新烧制feed，需要重新安装依赖包，没有设置feed的可以略过&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; npm install hexo-generation-feed --save
&lt;/code>&lt;/pre>&lt;p>安装主题，我在上文中提到新安装的主题并不能被上传，所以也需要重新手动安装(以&lt;a href="http://theme-next.iissnan.com">NexT&lt;/a>主题为例)&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; git clone https://github.com/iissnan/hexo-theme-next themes/next
&lt;/code>&lt;/pre>&lt;p>这里要注意的是：&lt;code>themes/next&lt;/code>是&lt;strong>主题保存目录&lt;/strong>。&lt;/p>
&lt;p>我们之前备份的两个配置文件&lt;code>hexo_config.yml&lt;/code>和&lt;code>theme_config.yml&lt;/code>有用了,&lt;code>hexo_config.yml&lt;/code>重命名为&lt;code>_config.yml&lt;/code>
覆盖根目录下的同名文件，而&lt;code>theme_config.yml&lt;/code>也重命名为&lt;code>_config.yml&lt;/code>覆盖主题目录下的&lt;code>config.yml&lt;/code>文件。注意文件名前面的下划线&amp;rsquo;_&amp;rsquo;。&lt;/p>
&lt;p>输入命令 &lt;code>$ hexo generate&lt;/code> 和命令 &lt;code>$ hexo server&lt;/code> ，
然后在浏览器输入 &lt;code>localhost:4000&lt;/code> 中进行预览。
如果没有问题那么我们在B电脑上就配置成功了。&lt;/p>
&lt;h2 id="在b电脑上更新博客">在B电脑上更新博客&lt;/h2>
&lt;p>现在在B电脑上也可以像在A电脑上一样更新博客了，同样是&lt;code>$ hexo new post &amp;quot;my_new_post&amp;quot;&lt;/code>
编辑完文章，然后执行&lt;code>$ hexo generate&lt;/code>和&lt;code>$ hexo deploy&lt;/code>就可以成功发表了。
这里&lt;code>$ hexo deploy&lt;/code>命令是将我们的博客文章发表到我们的&lt;a href="https://github.com">Github&lt;/a>
上的Hexo博客，并不是前文新建的&lt;code>blog仓库&lt;/code>，新建的&lt;code>blog仓库&lt;/code>用来保存我们的Hexo程序。&lt;/p>
&lt;h2 id="把b电脑上的hexo从本地同步到githubhttpsgithubcom仓库">把B电脑上的Hexo从本地同步到&lt;a href="https://github.com">Github&lt;/a>仓库&lt;/h2>
&lt;p>当发表完文章，我们还需要把Hexo程序同步到我们&lt;a href="https://github.com">Github&lt;/a>的&lt;code>blog仓库&lt;/code>。执行下面指令：&lt;/p>
&lt;p>$ git add .&lt;/p>
&lt;p>这是可以输入命令&lt;code>$ git status&lt;/code>查看状态，回显示刚才编辑过的文件的信息。&lt;/p>
&lt;p>之后分别输入下面指令完成上传：&lt;/p>
&lt;p>$ git commit -m &amp;ldquo;commit from PC_B&amp;rdquo;
$ git push -u origin master&lt;/p>
&lt;p>成功后，我们再次把程序同步更新到了我们的&lt;a href="https://github.com">Github&lt;/a>仓库&lt;code>blog&lt;/code>。 如果再想用A电脑更新我们的博客，只需要在执行添加文章之前先把程序从&lt;code>blog仓库&lt;/code>拉取下来便可。输入命令：&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; git pull https://github.com/Gitzhaoyang/blog.git
&lt;/code>&lt;/pre>&lt;p>即可完成。&lt;/p>
&lt;h2 id="注意事项">注意事项&lt;/h2>
&lt;p>我们每次更新博客时，为了保持我们每次用到的程序都是最新的。&lt;/p>
&lt;p>每次更新博客之前都需要执行&lt;code>$ git pull https://github.com/xxxx/xxx.git&lt;/code>保持本地最新；&lt;/p>
&lt;p>每次更新博客之后都需要执行
&lt;code>$ git add .&lt;/code> , &lt;code>$ git commit -m &amp;quot;message&amp;quot;&lt;/code> , &lt;code>$ git push -u origin master&lt;/code>
以保持 &lt;a href="https://github.com">Github&lt;/a> 仓库程序最新。&lt;/p>
&lt;p>好了，现在我们就能实现在不同电脑都能对我们的Hexo博客进行维护了。😎😘&lt;/p></description><category domain="https://h1z3y3.me/tags/hexo/">Hexo</category><category domain="https://h1z3y3.me/tags/github/">Github</category></item><item><title>在Github上面搭建Hexo博客（三）：更换Hexo主题</title><link>https://h1z3y3.me/posts/create-hexo-on-github-3/</link><guid isPermaLink="true">https://h1z3y3.me/posts/create-hexo-on-github-3/</guid><pubDate>Wed, 14 Oct 2015 11:18:22 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;p>更换主题很简单，首先感谢&lt;a href="https://github.com/iissnan">@iissnan&lt;/a>，因为博主用的是&lt;a href="https://github.com/iissnan">@iissnan&lt;/a>的&lt;a href="http://theme-next.iissnan.com">NexT&lt;/a>主题，所以本文就以&lt;a href="http://theme-next.iissnan.com">NexT&lt;/a>主题为例讲解如何更换博客主题。在文末我会给出更多优秀主题的Github地址。&lt;/p>
&lt;!-- more -->
&lt;h1 id="为自己的hexo博客更换主题">为自己的Hexo博客更换主题&lt;/h1>
&lt;p>我这里讲到的也是参考了&lt;a href="http://theme-next.iissnan.com">NexT&lt;/a>主题作者的&lt;a href="http://theme-next.iissnan.com">使用文档&lt;/a>，更详细的设置读者们可以直接去参考该&lt;a href="http://theme-next.iissnan.com">使用文档&lt;/a>。&lt;/p>
&lt;h2 id="下载nexthttptheme-nextiissnancom主题包">下载&lt;a href="http://theme-next.iissnan.com">NexT&lt;/a>主题包&lt;/h2>
&lt;p>仍然在博客根目录下运行Git bash命令行工具，输入下面一条指令用以克隆最新版本：&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; git clone https://github.com/iissnan/hexo-theme-next theme/next
&lt;/code>&lt;/pre>&lt;h2 id="启用主题">启用主题&lt;/h2>
&lt;p>下载完成后，打开博客配置文件&lt;code>_config.theme&lt;/code>，修改里面的&lt;code>theme&lt;/code>字段，将其值设置为&lt;code>next&lt;/code>&lt;/p>
&lt;h2 id="验证主题是否启用成功">验证主题是否启用成功&lt;/h2>
&lt;p>在Git Bash中输入&lt;code>$ npm server&lt;/code>启用本地服务，然后在浏览器中输入&lt;code>localhost:4000&lt;/code>进行预览即可&lt;/p>
&lt;p>更多详细的设置，不再赘述，可以参考&lt;a href="http://theme-next.iissnan.com">NexT&lt;/a>的&lt;a href="http://theme-next.iissnan.com">使用文档&lt;/a>&lt;/p>
&lt;h1 id="其他优秀的主题推荐">其他优秀的主题推荐：&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://github.com/iissnan/hexo-theme-next.git">iissnan/hexo-theme-next&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/TryGhost">TryGhost/Casper&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/daleanthony">daleanthony/uno&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/orderedlist">orderedlist/modernist&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/litten/hexo-theme-yilia">litten/hexo-theme-yilia&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/A-limon">A-limon/pacman&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/AlxMedia">AlxMedia/hueman&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/kathyqian">kathyqian/crisp-ghost-theme&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>更多优秀的主题可以参考知乎回答：&lt;a href="http://www.zhihu.com/question/24422335">有哪些好看的hexo主题?－家顺张的回答&lt;/a>&lt;/p></description><category domain="https://h1z3y3.me/tags/hexo/">Hexo</category><category domain="https://h1z3y3.me/tags/github/">Github</category></item><item><title>在Github上面搭建Hexo博客（二）：配置和发表文章</title><link>https://h1z3y3.me/posts/create-hexo-on-github-2/</link><guid isPermaLink="true">https://h1z3y3.me/posts/create-hexo-on-github-2/</guid><pubDate>Tue, 13 Oct 2015 23:42:28 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h1 id="如何配置hexo">如何配置Hexo&lt;/h1>
&lt;p>上一节中我们已经在本地和Github上搭建起了自己的博客，但是博客的配置都是默认值，如果我们想个性化自己的博客，我们应该做什么呢。这一节中我们一起来配置自己的博客的基本信息和介绍如何写博客和发表博客。下一节我们将一起为自己的博客安装新的主题。&lt;/p>
&lt;p>博客的主要配置用到根目录下的&lt;code>_config.yml&lt;/code>文件，我在下面给出文件和解释，你只需要根据自己需求作出简单更改即可：&lt;/p>
&lt;pre tabindex="0">&lt;code># Hexo Configuration
## Docs: http://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/
# Site
title: Mungo&amp;#39;s Note##站点的名字
subtitle:##站点的副标题
description: 日常技术分享 ##站点介绍，对站点进行描述
author: Mungo##站点文章的作者
email: gmzhaoyang@gmail.com##你的邮箱地址
language:##语言，默认中文，不填写即可
timezone:##时区，默认即可
# URL
## If your site is put in a subdirectory,
## set url as &amp;#39;http://yoursite.com/child&amp;#39; and root as &amp;#39;/child/&amp;#39;
url: http://mungo.space##站点的域名，如果奇怪我为什么可以用自己的域名，可以看后续更新
root: /
permalink: :year/:month/:day/:title/
permalink_defaults:
## 对资源文件夹的配置，如资源文件夹名称，标签云名称，分类页面名称
# Directory
source_dir: source##资源文件夹,当执行`$ hexo deloy`命令，上传的即是该文件夹里面的内容
public_dir: public##公共文件夹，当执行`$ hexo generate`命令，生成的文件都在里面
tag_dir: tags##标签云文件夹，需要自己生成，详情见下一节如何配置主题
archive_dir: archives##归档文件夹，需要自己生成
category_dir: categories##分类文件夹，需要自己生成
code_dir: downloads/code##代码存放区
i18n_dir: :lang
skip_render:
##此处时配置博客文章内容格式的，可以保持默认，不做修改
# Writing
new_post_name: :title.md # File name of new posts
default_layout: post
titlecase: false # Transform title into titlecase
external_link: true # Open external links in new tab
filename_case: 0
render_drafts: false
post_asset_folder: false
relative_link: false
future: true
highlight:
enable: true
line_number: true
auto_detect: true
tab_replace:
#分类和标签云的配置，可以不做修改，默认即可
# Category &amp;amp; Tag
default_category: uncategorized
category_map:
tag_map:
#日期和时间格式的配置，可以不做修改，默认即可
# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## http://momentjs.com/docs/#/displaying/format/
date_format: YYYY-MM-DD
time_format: HH:mm:ss
#用来配置每页显示的文章数目，可以根据自己需求自行修改
# Pagination
## Set per_page to 0 to disable pagination
per_page: 10
pagination_dir: page
#插件和主题配置，在这里可以修改自己的主题
# Extensions
## Plugins: http://hexo.io/plugins/
## Themes: http://hexo.io/themes/
theme: next
#上传配置，上一节中我们已经配置完成了，在这里不需要再次修改
# Deployment
## Docs: http://hexo.io/docs/deployment.html
deploy:
type: git
repository: https://github.com/Gitzhaoyang/gitzhaoyang.github.io.git
branch: master
&lt;/code>&lt;/pre>&lt;p>当配置完成并保存后，就可以执行&lt;code>$ hexo generate&lt;/code>生成静态文件，然后执行&lt;code>$ hexo server&lt;/code>后就可以打开浏览器输入&lt;code>localhost:4000&lt;/code>进行本地预览了&lt;/p>
&lt;h2 id="tips">tips&lt;/h2>
&lt;p>如果&lt;code>_config.yml&lt;/code>文件打开有乱码应该是用到的编辑器的原因，我用的是Sublime2，所以一般不会出现乱码。如果乱码，那么需要把文件格式转换为UTF-8，转化方法我在这里就不再赘述了。&lt;/p>
&lt;h2 id="如何添加和发表文章">如何添加和发表文章&lt;/h2>
&lt;h3 id="新建文章">新建文章&lt;/h3>
&lt;p>在Git Bash中输入&lt;/p>
&lt;p>$ hexo new post &amp;ldquo;my_first_post&amp;rdquo;&lt;/p>
&lt;h3 id="编辑文章">编辑文章&lt;/h3>
&lt;p>第一步的命令会在&lt;code>\Hexo\source\_posts&lt;/code>文件夹下创建一个后缀&lt;code>.md&lt;/code>文件，你可以在里面添加任何字符串。&lt;/p>
&lt;p>这其实是一个&lt;code>markdown&lt;/code>类型的文件，使用&lt;code>markdown&lt;/code>语言编写，我这篇博文就是用&lt;code>markdown&lt;/code>编写的，如果不了解的，可以看我的后续更新，我会把&lt;code>markdown&lt;/code>的基本使用方法进行整理。&lt;/p>
&lt;h3 id="生成和上传">生成和上传&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>在GitBash中输入&lt;code>$ hexo generate&lt;/code>对文件进行生成；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>生成完成后，可以输入&lt;code>$ hexo server&lt;/code>，然后在浏览器输入&lt;code>localhost:4000&lt;/code>进行预览；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>预览没有问题后，接着输入&lt;code>$ hexo deploy&lt;/code>，windows平台下会提示输入Github的用户名，然后提示输入Github的登录密码。如果输入正确，等待几秒便能上传成功；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>现在可以在浏览器中输入&lt;code>xxxx.github.io&lt;/code>进行访问了。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h3 id="注意">注意&lt;/h3>
&lt;p>可能不会立即生效，只要等待几分钟或者清空一下浏览器缓存基本就能解决。如果仍然看不到，说明前面步骤操作有错误，重新生成和上传就可以了。&lt;/p>
&lt;p>如果实在不行，可以在Git Bash中输入&lt;code>$ hexo clean&lt;/code>或者手动删除&lt;code>.deploy_git&lt;/code>文件夹和&lt;code>db.json&lt;/code>文件再重新生成和上传。&lt;/p>
&lt;p>到目前为止，我们已经搭建起自己的博客，可以进行基本的配置，也可以发表文章，后面会有更高阶的设置，如：如何配置主题，如何在不同电脑上都可以更新自己的博客 etc.感兴趣的人可以关注。😘&lt;/p></description><category domain="https://h1z3y3.me/tags/hexo/">Hexo</category><category domain="https://h1z3y3.me/tags/github/">Github</category></item><item><title>如何配置Github的SSH key</title><link>https://h1z3y3.me/posts/how-to-config-ssh-on-github/</link><guid isPermaLink="true">https://h1z3y3.me/posts/how-to-config-ssh-on-github/</guid><pubDate>Tue, 13 Oct 2015 16:02:34 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h2 id="什么是ssh">什么是SSH&lt;/h2>
&lt;p>SSH为Secure Shell的缩写，是建立在应用层和传输层基础上的协议。SSH是目前较可靠，专为远程登录会话和其他网络服务提供安全性的协议。利用SSH协议可以有效防止远程管理中额信息泄漏问题。我理解的就是给数据进行加密，然后防止中间人进行盗取，能使你的数据安全可靠的传输到目的方。在这里就是为了保证你电脑和&lt;a href="https://github.com">Github&lt;/a>仓库之间通信的安全。&lt;/p>
&lt;h2 id="如何配置githubhttpsgithubcom的ssh">如何配置&lt;a href="https://github.com">Github&lt;/a>的SSH&lt;/h2>
&lt;!-- more -->
&lt;p>题主这里用的依然是windows平台。&lt;/p>
&lt;h3 id="step-1-检查ssh-keys">Step 1: 检查SSH keys&lt;/h3>
&lt;p>首先，我们要查看在你电脑上已经存在的SSH keys。运行Git Bash 然后输入：&lt;/p>
&lt;pre>&lt;code>$ ls -al ~/.ssh
&lt;/code>&lt;/pre>
&lt;p>如果你已经有SSH公钥了。那么你将会看到下面格式的文件名字：&lt;/p>
&lt;pre>&lt;code>id_dsa.pub
id_esdsa.pub
id_ed25519.pub
id_rsa.pub
&lt;/code>&lt;/pre>
&lt;p>如果你已经存在公钥了，那么可以跳过&lt;strong>Step 2&lt;/strong>直接去&lt;strong>Step 3&lt;/strong>了。如果没有也不要担心，我们将在&lt;strong>Step 2&lt;/strong> 会创建公钥。&lt;/p>
&lt;h3 id="step-2-生成ssh-key">Step 2: 生成SSH key&lt;/h3>
&lt;ol>
&lt;li>
&lt;h4 id="在git-bash中输入下面命令引号内一定是你的githubhttpsgithubcom注册邮箱地址">在Git Bash中输入下面命令，引号内一定是你的&lt;a href="https://github.com">Github&lt;/a>注册邮箱地址&lt;/h4>
&lt;pre>&lt;code> $ ssh-keygen -t rsa -b 4096 -C &amp;quot;your_github_email@example.com&amp;quot;
#这句作用是生成一个新的SSH key
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>
&lt;h4 id="等待几秒当提示让你输入保存地址时官方特别推荐放在默认位置就可以了所以这里直接输入回车提示如下">等待几秒，当提示让你输入保存地址时，官方特别推荐放在默认位置就可以了。所以这里直接输入&lt;strong>回车&lt;/strong>，提示如下：&lt;/h4>
&lt;pre>&lt;code> Enter file in which to save the key (/Users/you/.ssh/id_rsa):[直接输入回车]
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>
&lt;h4 id="将会提示你输入一个密码串这里输入密码时不会显示在屏幕上的只要输入正确按回车就好">将会提示你输入一个密码串**(这里输入密码时不会显示在屏幕上的，只要输入正确按回车就好)**:&lt;/h4>
&lt;pre>&lt;code> Enter passphrase （empty for no passphrase）: [输入你想设置的密码]
Enter same passphrase again：[在输入一遍密码]
＃虽然说这里可以设置为空，但是推荐用一个更加安全的密码
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>
&lt;h4 id="输完密码之后你将会得到你的ssh的指纹fingerprint或者id他看起来如下图">输完密码之后，你将会得到你的SSH的指纹(fingerprint)或者id。他看起来如下图：&lt;/h4>
&lt;p>&lt;img src="https://p3.ssl.qhimg.com/t0161534b42efa8b260.jpg" alt="fingerprint">&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="step-3-把你的ssh-key添加到ssh-agent">Step 3: 把你的SSH key添加到ssh-agent&lt;/h3>
&lt;ol>
&lt;li>
&lt;h4 id="输入如下命令">输入如下命令&lt;/h4>
&lt;pre>&lt;code> $ ssh-agent -s
&lt;/code>&lt;/pre>
&lt;p>会响应：&lt;/p>
&lt;pre>&lt;code> echo Agent pid [端口号]
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>
&lt;h4 id="加下来输入如下命令把你的ssh-key添加到ssh-agent">加下来输入如下命令，把你的SSH key添加到ssh-agent&lt;/h4>
&lt;pre>&lt;code> $ ssh-add ~/.ssh/id_rsa
&lt;/code>&lt;/pre>
&lt;p>如果他提示如下，说明不能打开您身份验证的代理&lt;/p>
&lt;pre>&lt;code> Could not open a connection to your authentication agent.
&lt;/code>&lt;/pre>
&lt;p>只需要输入如下命令即可解决：&lt;/p>
&lt;pre>&lt;code> ssh-agnet bash
&lt;/code>&lt;/pre>
&lt;p>更多关于ssh-agent的细节，可以用man ssh-agent 来查看&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="step-4-把你的ssh-key添加到你的githubhttpsgithubcom账户">Step 4: 把你的SSH key添加到你的&lt;a href="https://github.com">Github&lt;/a>账户&lt;/h3>
&lt;p>首先你应该把你的 SSH key 复制到你的剪贴板，输入命令即可完成把 SSH key 复制到你的剪贴板：&lt;/p>
&lt;pre>&lt;code>$ clip &amp;lt; ~/.ssh/id_ras.pub
&lt;/code>&lt;/pre>
&lt;p>添加到你的&lt;a href="https://github.com">Github&lt;/a>账户：&lt;/p>
&lt;ol>
&lt;li>
&lt;h4 id="浏览器登陆你的githubhttpsgithubcom账户点击右上角你的头像然后点击settings">浏览器登陆你的&lt;a href="https://github.com">Github&lt;/a>账户，点击右上角你的头像，然后点击&lt;strong>Settings&lt;/strong>&lt;/h4>
&lt;p>&lt;img src="https://p4.ssl.qhimg.com/t012de386332fd4275a.jpg" alt="点击Settings">&lt;/p>
&lt;/li>
&lt;li>
&lt;h4 id="进入settings点击侧栏选项ssh-key">进入&lt;strong>Settings&lt;/strong>，点击侧栏选项&lt;strong>SSH key&lt;/strong>&lt;/h4>
&lt;p>&lt;img src="https://p2.ssl.qhimg.com/t0139f709bd2e05334f.jpg" alt="点击SSH key">&lt;/p>
&lt;/li>
&lt;li>
&lt;h4 id="单击右边-add-ssh-key-按钮">单击右边 Add SSH key 按钮&lt;/h4>
&lt;p>&lt;img src="https://p3.ssl.qhimg.com/t01b1d4cf5d05ee2de6.jpg" alt="点击Add key">&lt;/p>
&lt;/li>
&lt;li>
&lt;h4 id="在下面输入标题title这个可以自定义和shh-key直接-ctrlv-粘贴就可以">在下面输入标题（Title，这个可以自定义）和SHH Key（直接 Ctrl＋V 粘贴就可以）&lt;/h4>
&lt;/li>
&lt;li>
&lt;h4 id="点击下面的add-key按钮便可以添加成功了">点击下面的&lt;code>Add key&lt;/code>按钮便可以添加成功了&lt;/h4>
&lt;/li>
&lt;/ol>
&lt;h3 id="step-5-测试是否连接成功">Step 5: 测试是否连接成功&lt;/h3>
&lt;ol>
&lt;li>
&lt;h4 id="在git-bash中输入">在Git Bash中输入：&lt;/h4>
&lt;pre>&lt;code> $ ssh -T git@github.com
# ssh尝试连接到GitHub
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>
&lt;h4 id="你可能看到下面的警告">你可能看到下面的警告：&lt;/h4>
&lt;pre>&lt;code> The authenticity of host 'github.com(207.97.227.239)' can't be established.
RSA key fingerprint is SHA256:nJKJFKDnDLFJDndndnfkdfldjfldldfjld.
Are you sure you want to continue connecting (yes/no)?
&lt;/code>&lt;/pre>
&lt;p>确定提示信息里的指纹（fingerprint）是否匹配，如果匹配就键入｀yes｀，将得到：&lt;/p>
&lt;pre>&lt;code> Hi [你的用户名]! You've successfully authenticated, but GitHub does not provide shell access.
&lt;/code>&lt;/pre>
&lt;/li>
&lt;li>
&lt;h4 id="如果提示信息中你的用户名是你的那么你就成功建立了ssh-key">如果提示信息中你的用户名是你的，那么你就成功建立了SSH key！😎😎&lt;/h4>
&lt;/li>
&lt;/ol>
&lt;h3 id="tips如果遇到其他问题可以参考官方文档httpshelpgithubcomarticlesgenerating-ssh-keys也可以给我留言">TIPS：如果遇到其他问题，可以参考&lt;a href="https://help.github.com/articles/generating-ssh-keys/">官方文档&lt;/a>，也可以给我留言&lt;/h3></description><category domain="https://h1z3y3.me/tags/github/">Github</category><category domain="https://h1z3y3.me/tags/ssh/">SSH</category></item><item><title>在Github上面搭建Hexo博客（一）：部署到Github</title><link>https://h1z3y3.me/posts/create-hexo-on-github-1/</link><guid isPermaLink="true">https://h1z3y3.me/posts/create-hexo-on-github-1/</guid><pubDate>Mon, 12 Oct 2015 18:57:44 +0000</pubDate><author>gmzhaoyang@gmail.com (h1z3y3)</author><copyright>[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)</copyright><description>&lt;h1 id="什么是hexohttpshexoio">什么是&lt;a href="https://hexo.io/">Hexo&lt;/a>&lt;/h1>
&lt;p>&lt;a href="https://hexo.io/">Hexo&lt;/a>是一个基于Node.js的静态博客程序，可以方便的生成静态网页托管在&lt;a href="https://github.com/">Github&lt;/a>和Heroku上。并且有很多人为其制作了很多优秀的主题（theme），你可以根据自己的喜好进行设置。主题的设置将在后面的章节中介绍。&lt;/p>
&lt;p>这个是&lt;a href="https://hexo.io/">Hexo&lt;/a>官方网站介绍：&lt;/p>
&lt;blockquote>
&lt;p>Hexo is a fast, simple and powerful blog framework.
You write posts in &lt;a href="http://daringfireball.net/projects/markdown/">Markdown&lt;/a>
(or other languages) and Hexo generates
static files with a beautiful theme in seconds.&lt;/p>
&lt;/blockquote>
&lt;p>翻译过来就是：&lt;/p>
&lt;blockquote>
&lt;p>Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown（或其他渲染引擎）解析文章，
在几秒内，即可利用靓丽的主题生成静态网页。&lt;/p>
&lt;/blockquote>
&lt;h1 id="怎么在githubhttpsgithubcom上搭建一个hexo博客">怎么在&lt;a href="https://github.com/">Github&lt;/a>上搭建一个hexo博客&lt;/h1>
&lt;!-- more -->
&lt;p>我用了一天时间研究和搭建了一个&lt;a href="https://github.com/">Github&lt;/a>博客(GitHub Pages site)，过程中遇到一些小问题，现在写一篇教程，方便和我一样爱折腾但是是新手的人。&lt;/p>
&lt;h2 id="注意">注意&lt;/h2>
&lt;p>因为题主在搭建时在Windows平台，所以讲解为Windows版本，但是各个平台大同小异，所以实践起来并没有很大的差别。&lt;/p>
&lt;p>以下为教程正文：&lt;/p>
&lt;h2 id="安装git">安装Git&lt;/h2>
&lt;p>前往&lt;a href="http://git-scm.com/">Git官网&lt;/a>下载Windows版本压缩包，下载完成后解压安装。&lt;/p>
&lt;h2 id="安装nodejs">安装Node.js&lt;/h2>
&lt;p>前往&lt;a href="https://nodejs.org/en/">Node.js&lt;/a>官方下载网站，下载Node.js官方安装包，下载完成后同样解压安装。&lt;/p>
&lt;h2 id="安装hexohttpshexoio">安装&lt;a href="https://hexo.io/">Hexo&lt;/a>&lt;/h2>
&lt;p>到目前为止，安装&lt;a href="https://hexo.io/">Hexo&lt;/a>所需要的环境已将安装完成，下一步只需要安装&lt;a href="https://hexo.io/">Hexo&lt;/a>便可以了。
点击鼠标右键，看是否有&lt;strong>Git bash Here&lt;/strong>选项。如果没有可以前往Git安装根目录，启动&lt;strong>git-base.exe&lt;/strong>也可以。 在命令行中输入：&lt;/p>
&lt;p>$ npm install -g hexo-cli&lt;/p>
&lt;p>&lt;a href="https://hexo.io/">Hexo&lt;/a> 便安装完成了&lt;/p>
&lt;h2 id="创建hexohttpshexoio文件夹">创建&lt;a href="https://hexo.io/">Hexo&lt;/a>文件夹&lt;/h2>
&lt;p>找到想要放置博客的文件夹，比如（&lt;code>F:\Hexo&lt;/code>），在该目录下鼠标右击打开Gitbash工具，（右键菜单中没有该选项的可以用cmd命令&lt;code>cd&lt;/code>等进入该文件夹）。执行下面的语句，会在&lt;code>F:\Hexo&lt;/code>文件夹下创建&lt;code>node_modules&lt;/code>
文件夹：&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; hexo init
&lt;/code>&lt;/pre>&lt;p>这里 &lt;strong>init&lt;/strong> 后面可以跟文件目录，比如我想在&lt;code>F:\Hexo&lt;/code>下创建博客文件夹，那么可以用下面的命令：&lt;/p>
&lt;p>$ hexo init F:\Hexo&lt;/p>
&lt;h2 id="安装依赖包">安装依赖包&lt;/h2>
&lt;p>在&lt;a href="https://hexo.io/">Hexo&lt;/a>目录下，执行以下命令，你会发现&lt;code>F:\Hexo\node_modules&lt;/code>目录下多了好多文件夹&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; npm install
&lt;/code>&lt;/pre>&lt;h2 id="本地调试">本地调试&lt;/h2>
&lt;p>目前为止，已经搭建好自己的&lt;a href="https://hexo.io/">Hexo&lt;/a>博客了，但是只能在本机上查看。
执行以下两个命令（在&lt;code>F:\Hexo&lt;/code>目录下），然后在浏览器中输入 &lt;code>localhost:4000&lt;/code> 就可以看到自己的博客了&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; hexo generate
&amp;gt; hexo serrver
&lt;/code>&lt;/pre>&lt;p>但是只能在本地查看，如果想让别人也能访问，那么就需要部署到&lt;a href="https://github.com/">Github&lt;/a> 上面，下面，我们就部署上去。&lt;/p>
&lt;h2 id="注册githubhttpsgithubcom账户">注册&lt;a href="https://github.com/">Github&lt;/a>账户&lt;/h2>
&lt;p>前往&lt;a href="https://github.com/">Github&lt;/a>网站，注册一个新用户。用邮箱注册的一定前往邮箱去验证邮件。要不然之后可能会有小问题。&lt;/p>
&lt;h2 id="创建一个新的repository">创建一个新的repository&lt;/h2>
&lt;p>在自己的&lt;a href="https://github.com/">Github&lt;/a>主页右下角,创建一个新的&lt;code>repository&lt;/code>。
比如我的Github用户名为&lt;code>Gitzhaoyang&lt;/code>，那么我创建的repository的名字应该是 &lt;code>gitzhaoyang.github.io&lt;/code> 。&lt;/p>
&lt;p>&lt;img src="https://p3.ssl.qhimg.com/t01eb3bf66c3bc3ad0e.jpg" alt="添加reponsitories">&lt;/p>
&lt;h2 id="这里严重注意">这里严重注意&lt;/h2>
&lt;p>一定要以&lt;code>你的Github用户名.github.io&lt;/code>创建。假如我没有用&lt;code>gitzhaoyang.github.io&lt;/code>而是用了&lt;code>mungo.github.io&lt;/code>
，那么当我浏览器访问博客的时候会出现404错误。这里并不是没有部署成功，而是把它部署在了这里:&lt;code>http://gitzhaoyang.github.io/mungo.github.io&lt;/code>。所以，如果想直接&lt;code>gitzhaoyang.github.io&lt;/code>访问，那么就需要和用户名保持一致。题主在这里吃了不小的苦头，最后给Github客服发邮件才知道原因。&lt;/p>
&lt;p>创建好如下图：&lt;/p>
&lt;p>&lt;img src="https://p2.ssl.qhimg.com/t010ca5a2c0a78b950e.jpg" alt="一定要保持一致">&lt;/p>
&lt;h2 id="将本地的文件部署上传到githubhttpsgithubcom账户中">将本地的文件部署（上传）到&lt;a href="https://github.com/">Github&lt;/a>账户中&lt;/h2>
&lt;p>编辑本地&lt;a href="https://hexo.io/">Hexo&lt;/a>目录下文件&lt;code>_comfig.yml&lt;/code>，在最后添加如下代码（在你修改时，把 &lt;code>gitzhaoyang&lt;/code> 要替换成你自己的用户名）&lt;/p>
&lt;pre tabindex="0">&lt;code>deploy:
type: git
repository: http://github.com/Gitzhaoyang/gitzhaoyang.github.io.git
branch: master
&lt;/code>&lt;/pre>&lt;p>.yml文件对格式规范要求很严格，&lt;code>type:&lt;/code> &lt;code>repository:&lt;/code> &lt;code>branch:&lt;/code> 前面有两个空格，冒号后面都有一个空格。&lt;/p>
&lt;p>执行以下指令即可完成部署（如果提示错误，可以看下面&lt;strong>注意&lt;/strong>）：&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; hexo generate
&amp;gt; hexo deploy
&lt;/code>&lt;/pre>&lt;h2 id="注意事项">注意事项&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>有些用户没有设置Github的SSH，会导致上面两句失败。SSH的介绍和设置方法可以查看
&lt;a href="https://help.github.com/articles/generating-ssh-keys">官方教程&lt;/a>,配置起来很简单。
如果英文看不明白或者过程中出现小问题， 可以查看我写的
&lt;a href="https://h1z3y3.me/2015/10/13/how-to-config-ssh-on-github/index.html">SSH设置教程&lt;/a> ，是对官方教程的解释和扩展，
针对配置过程中的小问题都有解决办法。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>每次修改本地文件，都需要命令&lt;code>$ hexo generate&lt;/code>才能保存。而且每次使用命令都必须在 &lt;a href="https://hexo.io/">Hexo&lt;/a> 根目录下使用。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果你在执行&lt;code>$ hexo deloy&lt;/code>,如果提示 &lt;code>ERROR Deployer not found: git&lt;/code>，那说明你没有安装&lt;code>hexo-deployer-git&lt;/code>依赖包，进入&lt;code>F:\Hexo\node_modules&lt;/code>
发现真的没有&lt;code>hexo-deployer-git&lt;/code>，不用担心，只需要输入下面命令创建&lt;code>hexo-deployer-git&lt;/code>依赖包，然后再执行&lt;code>hexo deploy&lt;/code>就能上传成功了&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;gt; npm install hexo-deployer-git --save
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>如果你是windows用户，那么当你执行&lt;code>$ hexo deploy&lt;/code>命令的时候，
可能会先后出现提示框让你输入你的&lt;strong>Github用户名&lt;/strong>和&lt;strong>Github密码&lt;/strong>，只要输入正确，上传就没有问题。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>好了，现在我们的博客已经在Github上面部署成功了，可以在浏览器访问&lt;code>gitzhaoyang.github.io&lt;/code>试试了。&lt;/p>
&lt;h2 id="提示">提示&lt;/h2>
&lt;p>现在Hexo支持更加简单的命令格式了，比如：&lt;/p>
&lt;pre tabindex="0">&lt;code>hexo s == hexo server
hexo g == hexo generate
hexo d == hexo deploy
hexo n == hexo new
&lt;/code>&lt;/pre>&lt;p>后续我会把如何配置博客信息，发表文章，设置博客主题，不同电脑间进行同时更新自己的Blog的方法等更新上来,感兴趣的人可以关注&lt;/p></description><category domain="https://h1z3y3.me/tags/hexo/">Hexo</category><category domain="https://h1z3y3.me/tags/github/">Github</category></item></channel></rss>