<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Go / Golang 专题：从入门到云原生生产的极致指南 on PlumePHP</title><link>https://plumephp.com/posts/golang/</link><description>Recent content in Go / Golang 专题：从入门到云原生生产的极致指南 on PlumePHP</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Mon, 29 Dec 2025 12:30:00 +0800</lastBuildDate><atom:link href="https://plumephp.com/posts/golang/index.xml" rel="self" type="application/rss+xml"/><item><title>Plumego Best Practices · 一个真实业务（用户中心）的完整示例</title><link>https://plumephp.com/plumego-user-center-example/</link><pubDate>Mon, 29 Dec 2025 12:30:00 +0800</pubDate><guid>https://plumephp.com/plumego-user-center-example/</guid><description>&lt;h2 id="0-这份示例解决什么问题"&gt;0. 这份示例解决什么问题&lt;/h2&gt;
&lt;p&gt;你已经有了 Plumego，但真正把它用进“用户中心”这种业务时，通常会卡在三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;模块边界&lt;/strong&gt;：User / Auth / Project 怎么拆，拆完怎么组装，才不会越写越乱。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;请求与错误模型&lt;/strong&gt;：统一 JSON 输出、统一错误码、统一 trace/request id，把“可观察性”内建进骨架。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;鉴权&lt;/strong&gt;：最小可用、可扩展、可替换（未来可接 JWT / OAuth / SSO），但现在就能跑通。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Plumego 的定位是“标准库内的 Go HTTP 工具箱”，用 &lt;code&gt;core.App&lt;/code&gt; 组合能力，而不是把你锁死在某种框架范式里。&lt;br&gt;
你会看到本文的写法更接近 Birdor 的工程风格：&lt;strong&gt;少魔法、强边界、可演进&lt;/strong&gt;。&lt;/p&gt;</description></item><item><title>一个真实业务（用户中心）的完整 Plumego 示例</title><link>https://plumephp.com/plumego-user-center-complete-example/</link><pubDate>Mon, 29 Dec 2025 12:00:00 +0800</pubDate><guid>https://plumephp.com/plumego-user-center-complete-example/</guid><description>&lt;h2 id="目标与边界"&gt;目标与边界&lt;/h2&gt;
&lt;p&gt;你将得到一个&lt;strong&gt;可运行&lt;/strong&gt;的用户中心（User Center）最小实现，覆盖：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;注册：&lt;code&gt;POST /api/auth/register&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;登录：&lt;code&gt;POST /api/auth/login&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;刷新：&lt;code&gt;POST /api/auth/refresh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;当前用户：&lt;code&gt;GET /api/me&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;更新资料：&lt;code&gt;PUT /api/me&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;管理端用户：&lt;code&gt;GET /api/admin/users&lt;/code&gt;（RBAC：&lt;code&gt;admin&lt;/code&gt; 角色）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实现策略（Birdor 风格）：&lt;/p&gt;</description></item><item><title>Plumego Roadmap</title><link>https://plumephp.com/plumego-roadmap/</link><pubDate>Mon, 29 Dec 2025 11:30:00 +0800</pubDate><guid>https://plumephp.com/plumego-roadmap/</guid><description>&lt;h2 id="状态说明"&gt;状态说明&lt;/h2&gt;
&lt;p&gt;Plumego 的定位：&lt;strong&gt;完全基于 Go 标准库、零外部依赖、强调简单优雅与可组合&lt;/strong&gt;。&lt;br&gt;
本文为一份“可执行”的路线图提案，面向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心贡献者：明确优先级、版本节奏、退出/回滚策略&lt;/li&gt;
&lt;li&gt;使用者：明确当前适用边界、未来能力方向、升级成本预期&lt;/li&gt;
&lt;li&gt;生态建设：示例工程、文档、规范与工具链的逐步完善&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="路线图原则birdor-风格"&gt;路线图原则（Birdor 风格）&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;稳定优先&lt;/strong&gt;：先把现有 API 固定，再扩展能力；避免频繁破坏性变更。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最小抽象&lt;/strong&gt;：只抽象“不可避免的变化点”（store、auth、ws、codec、logger）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;显式组合&lt;/strong&gt;：中间件、组件、模块装配应尽量显式，便于审计与调试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能与观测内建&lt;/strong&gt;：默认提供 request-id/trace、结构化日志、recover。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例驱动&lt;/strong&gt;：每个核心能力必须配套“真实业务示例”（例如 user center）。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="版本策略"&gt;版本策略&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;v0.x&lt;/code&gt;：快速迭代期，允许小范围破坏性调整（但必须提供迁移说明）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v1.0&lt;/code&gt;：API 稳定承诺（SemVer），破坏性变更进入 &lt;code&gt;v2.0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;发布节奏：&lt;strong&gt;每 6–10 周一个 minor&lt;/strong&gt;，每 2–4 周一个 patch（视缺陷密度）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="roadmap-总览2026"&gt;Roadmap 总览（2026）&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;下面按季度给出里程碑；后期将映射到 GitHub Milestones / Projects。&lt;/p&gt;</description></item><item><title>Plumego Docs 信息架构（IA）总览</title><link>https://plumephp.com/plumego-doc-arch/</link><pubDate>Mon, 29 Dec 2025 11:00:00 +0800</pubDate><guid>https://plumephp.com/plumego-doc-arch/</guid><description>&lt;blockquote&gt;
&lt;p&gt;定位：工程级框架文档，而不是教程集合&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docs/
├── _index.md
├── intro/
├── getting-started/
├── concepts/
├── architecture/
├── core/
├── guides/
├── patterns/
├── advanced/
├── reference/
├── faq/
├── roadmap/
└── contributing/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;下面逐层解释&lt;strong&gt;每一部分为什么存在、解决什么问题&lt;/strong&gt;。&lt;/p&gt;</description></item><item><title>Plumego Best Practices</title><link>https://plumephp.com/plumego-best-practices/</link><pubDate>Mon, 29 Dec 2025 10:30:00 +0800</pubDate><guid>https://plumephp.com/plumego-best-practices/</guid><description>&lt;h2 id="写在前面这不是技巧合集"&gt;写在前面：这不是“技巧合集”&lt;/h2&gt;
&lt;p&gt;这份文档&lt;strong&gt;不是&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API 参数的穷举说明&lt;/li&gt;
&lt;li&gt;所有功能的使用手册&lt;/li&gt;
&lt;li&gt;为了“写得高级”而复杂化的套路&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它是一份 &lt;strong&gt;工程级 Best Practices&lt;/strong&gt;，核心目标只有一个：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;让一个 Plumego 项目在 1～3 年后依然清晰、可控、可演进。&lt;/p&gt;</description></item><item><title>Plumego 入门：一个克制而清晰的 Go 服务框架</title><link>https://plumephp.com/getting-started-with-plumego/</link><pubDate>Mon, 29 Dec 2025 10:00:01 +0800</pubDate><guid>https://plumephp.com/getting-started-with-plumego/</guid><description>&lt;h2 id="引言为什么需要-plumego"&gt;引言：为什么需要 Plumego？&lt;/h2&gt;
&lt;p&gt;在 Birdor 的工程实践中，我们反复遇到同一个问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当项目从“能跑”进入“要长期维护”阶段，Go Web 框架往往开始成为负担，而不是助力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Plumego 的目标并不是“更快写完第一个接口”，而是帮助你在 &lt;strong&gt;6 个月、12 个月甚至 3 年后&lt;/strong&gt;，依然能够清晰地理解、演进和重构你的服务。&lt;/p&gt;</description></item><item><title>什么时候不该使用 Plumego</title><link>https://plumephp.com/when-not-to-use-plumego/</link><pubDate>Sun, 28 Dec 2025 07:30:00 +0800</pubDate><guid>https://plumephp.com/when-not-to-use-plumego/</guid><description>&lt;h2 id="引言一个负责任的框架必须明确不适用场景"&gt;引言：一个负责任的框架，必须明确“不适用场景”&lt;/h2&gt;
&lt;p&gt;任何一个认真对待工程质量的框架，都必须敢于回答一个问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在什么情况下，你不应该使用我？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果一个框架声称：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;适合所有团队&lt;/li&gt;
&lt;li&gt;适合所有阶段&lt;/li&gt;
&lt;li&gt;适合所有业务&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么它几乎可以确定是不可信的。&lt;/p&gt;</description></item><item><title>Plumego vs 常见 Go 框架：设计取舍说明</title><link>https://plumephp.com/plumego-vs-go-frameworks/</link><pubDate>Sun, 28 Dec 2025 00:36:00 +0800</pubDate><guid>https://plumephp.com/plumego-vs-go-frameworks/</guid><description>&lt;h2 id="引言框架对比本质是在对比价值观"&gt;引言：框架对比，本质是在对比价值观&lt;/h2&gt;
&lt;p&gt;在技术社区中，“A 框架 vs B 框架”的讨论几乎从未停止。&lt;/p&gt;
&lt;p&gt;但真正成熟的工程师往往知道：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;框架之争，很少是技术对错，更多是价值取舍。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Plumego 并不是在一个“空白市场”中诞生的。&lt;br&gt;
相反，它是在 &lt;strong&gt;Go 生态已经非常繁荣&lt;/strong&gt; 的前提下，刻意选择了一条看起来“不讨巧”的道路。&lt;/p&gt;</description></item><item><title>Plumego：一个回归 Go 本质的极简服务框架</title><link>https://plumephp.com/plumego-introduction/</link><pubDate>Sun, 28 Dec 2025 00:28:00 +0800</pubDate><guid>https://plumephp.com/plumego-introduction/</guid><description>&lt;h2 id="前言为什么还要再做一个-go-框架"&gt;前言：为什么还要再做一个 Go 框架&lt;/h2&gt;
&lt;p&gt;在 Go 生态已经高度成熟的今天，&lt;strong&gt;“再做一个 Go Web 框架”&lt;/strong&gt; 看起来既多余，又无趣。&lt;/p&gt;
&lt;p&gt;我们已经有了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;功能极其完备、社区庞大的全栈式框架&lt;/li&gt;
&lt;li&gt;极致性能导向、偏底层的 HTTP / RPC 框架&lt;/li&gt;
&lt;li&gt;面向云原生、与 Kubernetes 深度绑定的应用运行时&lt;/li&gt;
&lt;li&gt;为微服务量身定制的一整套工程体系&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Plumego 并不是要和它们竞争。&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Go 学习路线图：从入门到专家</title><link>https://plumephp.com/100-go-learning-roadmap/</link><pubDate>Sat, 20 Dec 2025 10:00:00 +0800</pubDate><guid>https://plumephp.com/100-go-learning-roadmap/</guid><description>&lt;h2 id="go-学习路线图从入门到专家"&gt;Go 学习路线图：从入门到专家&lt;/h2&gt;
&lt;p&gt;朋友，恭喜你走到了这里。&lt;/p&gt;
&lt;p&gt;这是本系列的第 100 篇文章，也是最后一篇。从 2020 年的第一篇《Go 语言开发环境搭建》开始，我们一起走过了五年多的旅程——从 &lt;code&gt;Hello World&lt;/code&gt; 到 Kubernetes Operator，从变量声明到领域驱动设计，从 goroutine 初体验到 OpenTelemetry 全链路追踪。这 100 篇文章覆盖了 Go 语言的方方面面，但&amp;quot;学完&amp;quot;和&amp;quot;学会&amp;quot;之间，还隔着一条叫做&amp;quot;体系化&amp;quot;的鸿沟。&lt;/p&gt;</description></item><item><title>Go 2025 入门专题目录：从能写小程序到能维护小服务</title><link>https://plumephp.com/go-2025-beginner-learning-path/</link><pubDate>Thu, 18 Dec 2025 10:16:00 +0800</pubDate><guid>https://plumephp.com/go-2025-beginner-learning-path/</guid><description>&lt;p&gt;2025 年这一组 Go 入门文章已经从零散技巧慢慢长成了一个专题库。单篇文章适合解决一个具体问题：怎么解析表单、怎么写 &lt;code&gt;WaitGroup&lt;/code&gt;、怎么生成安全 Token、怎么做优雅关闭。但读者第一次打开时，未必知道应该先看哪一篇，也不容易判断哪些内容属于同一条学习线。&lt;/p&gt;</description></item><item><title>Go 入门：用 text/template 生成一份朴素报表</title><link>https://plumephp.com/go-text-template-reports-2025/</link><pubDate>Fri, 05 Dec 2025 17:21:00 +0800</pubDate><guid>https://plumephp.com/go-text-template-reports-2025/</guid><description>&lt;p&gt;不是所有输出都需要 HTML。很多内部工具会生成纯文本日报、邮件正文、Markdown 摘要、配置片段或工单说明。直接用 &lt;code&gt;fmt.Fprintf&lt;/code&gt; 拼字符串可以起步，但字段一多，换行和缩进就会变得难维护。Go 标准库的 &lt;code&gt;text/template&lt;/code&gt; 很适合这类场景。&lt;/p&gt;</description></item><item><title>Go 入门：WaitGroup 不只等待，还要收好结果</title><link>https://plumephp.com/go-waitgroup-result-collection-2025/</link><pubDate>Thu, 13 Nov 2025 09:49:00 +0800</pubDate><guid>https://plumephp.com/go-waitgroup-result-collection-2025/</guid><description>&lt;p&gt;&lt;code&gt;sync.WaitGroup&lt;/code&gt; 是 Go 并发入门必学工具。它的职责很简单：等一组 goroutine 做完。但真实代码里，等待只是第一步。你还要拿到每个任务的结果、处理错误、控制并发数量，并保证 channel 正确关闭。很多初学者会写出“能等完，但结果偶尔丢失或死锁”的代码。&lt;/p&gt;</description></item><item><title>Go 并发 map 入门：mutex、sync.Map 和单 goroutine 管理</title><link>https://plumephp.com/go-concurrent-map-patterns-2025/</link><pubDate>Tue, 28 Oct 2025 17:33:00 +0800</pubDate><guid>https://plumephp.com/go-concurrent-map-patterns-2025/</guid><description>&lt;p&gt;Go 的 map 很好用，但普通 map 不是并发安全的。多个 goroutine 同时读写同一个 map，可能触发 &lt;code&gt;fatal error: concurrent map writes&lt;/code&gt;，也可能产生数据竞争。初学者常常在 HTTP handler、缓存、在线用户列表里踩到这个问题。&lt;/p&gt;</description></item><item><title>Go 表单解析入门：FormValue、ParseForm 和 PostForm 怎么选</title><link>https://plumephp.com/go-http-form-parsing-2025/</link><pubDate>Thu, 16 Oct 2025 19:31:00 +0800</pubDate><guid>https://plumephp.com/go-http-form-parsing-2025/</guid><description>&lt;p&gt;不是所有 Web 请求都是 JSON。登录页、搜索框、后台表单、文件上传仍然会用表单。Go 标准库对表单解析支持很好，但几个 API 容易混：&lt;code&gt;FormValue&lt;/code&gt;、&lt;code&gt;PostFormValue&lt;/code&gt;、&lt;code&gt;ParseForm&lt;/code&gt;、&lt;code&gt;ParseMultipartForm&lt;/code&gt;。本文用登录表单讲清楚基本用法。&lt;/p&gt;</description></item><item><title>Go 代码审查指南：从检查清单到团队协作</title><link>https://plumephp.com/99-code-review-guide/</link><pubDate>Sun, 12 Oct 2025 12:00:00 +0800</pubDate><guid>https://plumephp.com/99-code-review-guide/</guid><description>&lt;h2 id="go-代码审查指南从检查清单到团队协作"&gt;Go 代码审查指南：从检查清单到团队协作&lt;/h2&gt;
&lt;p&gt;代码审查是软件开发流程中至关重要的环节，它不仅能发现潜在的 bug 和设计问题，还能促进团队知识共享、提升代码质量、统一编码规范。对于 Go 语言项目而言，良好的代码审查流程更是确保代码简洁、高效、可维护的关键。&lt;/p&gt;</description></item><item><title>Go 领域错误入门：errors.Is 和 errors.As 如何服务业务判断</title><link>https://plumephp.com/go-domain-errors-is-as-2025/</link><pubDate>Tue, 07 Oct 2025 11:47:00 +0800</pubDate><guid>https://plumephp.com/go-domain-errors-is-as-2025/</guid><description>&lt;p&gt;Go 的 &lt;code&gt;error&lt;/code&gt; 很简单，但业务系统里的错误并不简单：订单不存在、库存不足、余额不够、状态不允许、外部支付超时。这些错误需要被上层判断，映射成 HTTP 状态码、用户提示、重试策略。只靠字符串比较会非常脆弱。&lt;/p&gt;</description></item><item><title>Go TLS 客户端证书入门：什么时候需要 mTLS</title><link>https://plumephp.com/go-tls-client-cert-basics-2025/</link><pubDate>Thu, 25 Sep 2025 10:26:00 +0800</pubDate><guid>https://plumephp.com/go-tls-client-cert-basics-2025/</guid><description>&lt;p&gt;普通 HTTPS 里，客户端验证服务器证书，确认自己连的是正确服务。mTLS，也就是双向 TLS，还要求客户端提供证书，服务器也验证客户端身份。内部服务调用、企业网关、支付接口、某些高安全 API 都可能要求客户端证书。&lt;/p&gt;</description></item><item><title>Go 入门：用 net/netip 处理 IP 地址更省心</title><link>https://plumephp.com/go-netip-address-basics-2025/</link><pubDate>Wed, 24 Sep 2025 15:33:00 +0800</pubDate><guid>https://plumephp.com/go-netip-address-basics-2025/</guid><description>&lt;p&gt;处理 IP 地址时，很多旧代码会使用 &lt;code&gt;net.IP&lt;/code&gt;。它能用，但类型是 &lt;code&gt;[]byte&lt;/code&gt;，可变、比较不方便，也容易在 IPv4 和 IPv6 之间绕晕。Go 1.18 引入了 &lt;code&gt;net/netip&lt;/code&gt;，提供了更现代的 &lt;code&gt;Addr&lt;/code&gt; 和 &lt;code&gt;Prefix&lt;/code&gt; 类型。对新代码来说，优先学 &lt;code&gt;netip&lt;/code&gt; 会更省心。&lt;/p&gt;</description></item><item><title>Go build tags 入门：为不同环境编译不同文件</title><link>https://plumephp.com/go-build-tags-environments-2025/</link><pubDate>Fri, 12 Sep 2025 10:09:00 +0800</pubDate><guid>https://plumephp.com/go-build-tags-environments-2025/</guid><description>&lt;p&gt;Go 的 build tags 可以让某些文件只在指定构建条件下参与编译。它常用于平台差异、可选功能、集成测试和少量环境差异。初学者可能会把它当成配置系统使用，这是要谨慎的。build tags 是编译期选择，不是运行时配置。&lt;/p&gt;</description></item><item><title>Go embed 模板布局入门：base、partial 和页面模板怎么组织</title><link>https://plumephp.com/go-embed-template-layouts-2025/</link><pubDate>Tue, 09 Sep 2025 15:18:00 +0800</pubDate><guid>https://plumephp.com/go-embed-template-layouts-2025/</guid><description>&lt;p&gt;Go 的 &lt;code&gt;html/template&lt;/code&gt; 可以渲染页面，&lt;code&gt;embed&lt;/code&gt; 可以把模板打进二进制。小型后台、内部工具、邮件预览页都很适合这种组合。问题是模板一多，如何组织 base layout、partial 和具体页面？如果随便 &lt;code&gt;ParseGlob&lt;/code&gt;，很快会乱。&lt;/p&gt;</description></item><item><title>Go 反模式：常见陷阱与最佳实践</title><link>https://plumephp.com/98-go-anti-patterns/</link><pubDate>Sat, 30 Aug 2025 15:40:00 +0800</pubDate><guid>https://plumephp.com/98-go-anti-patterns/</guid><description>&lt;h2 id="go-反模式常见陷阱与最佳实践"&gt;Go 反模式：常见陷阱与最佳实践&lt;/h2&gt;
&lt;p&gt;你是否遇到过这样的代码：一个函数返回 &lt;code&gt;(T, error)&lt;/code&gt;，调用方却写了一堆 &lt;code&gt;if err != nil { return err }&lt;/code&gt; 把错误直接吞掉；一个 &lt;code&gt;Service&lt;/code&gt; 接口塞了 20 个方法，谁都不敢改；满屏 &lt;code&gt;go func()&lt;/code&gt; 却没有等待机制，主进程退出时一堆 goroutine 还在后台游荡；为了&amp;quot;性能&amp;quot;手动拼字符串，结果 benchmark 一跑比 &lt;code&gt;strings.Builder&lt;/code&gt; 慢 10 倍。&lt;/p&gt;</description></item><item><title>Go 内存限制入门：GOMEMLIMIT、GOGC 和容器里的服务</title><link>https://plumephp.com/go-memory-limit-gogc-2025/</link><pubDate>Thu, 21 Aug 2025 10:36:00 +0800</pubDate><guid>https://plumephp.com/go-memory-limit-gogc-2025/</guid><description>&lt;p&gt;Go 有自动垃圾回收，但这不表示内存可以不管。容器里部署服务时，如果内存持续上涨，最终可能被 OOM kill。初学者常见误解是：只要没有内存泄漏，Go 会自动处理。现实是，GC 有策略，程序有峰值，容器有上限，你需要知道基本参数。&lt;/p&gt;</description></item><item><title>Go expvar 入门：给小服务加几个轻量指标</title><link>https://plumephp.com/go-expvar-metrics-basics-2025/</link><pubDate>Wed, 20 Aug 2025 15:27:00 +0800</pubDate><guid>https://plumephp.com/go-expvar-metrics-basics-2025/</guid><description>&lt;p&gt;很多小服务一开始没有完整监控系统，但也需要知道一些基本状态：请求数、错误数、队列长度、缓存命中次数。Go 标准库里的 &lt;code&gt;expvar&lt;/code&gt; 可以用很低成本暴露 JSON 指标。它不替代 Prometheus 这类监控体系，但很适合入门理解“服务应该给外界看哪些状态”。&lt;/p&gt;</description></item><item><title>Go 工作目录入门：为什么本地能读到文件，部署后却找不到</title><link>https://plumephp.com/go-working-directory-paths-2025/</link><pubDate>Thu, 07 Aug 2025 17:03:00 +0800</pubDate><guid>https://plumephp.com/go-working-directory-paths-2025/</guid><description>&lt;p&gt;“本地能跑，部署后找不到文件”是 Go 初学者常见问题。代码里写了 &lt;code&gt;templates/index.html&lt;/code&gt;，本地从项目根目录运行没事；部署成 systemd 服务后，工作目录变了，程序就报 &lt;code&gt;open templates/index.html: no such file or directory&lt;/code&gt;。问题不在 Go 编译，而在当前工作目录。&lt;/p&gt;</description></item><item><title>Go 入门：随机数别乱用，Token 要用 crypto/rand</title><link>https://plumephp.com/go-random-token-crypto-rand-2025/</link><pubDate>Wed, 06 Aug 2025 13:12:00 +0800</pubDate><guid>https://plumephp.com/go-random-token-crypto-rand-2025/</guid><description>&lt;p&gt;随机数是入门时很容易被低估的主题。抽一个测试样本、打乱列表、模拟掷骰子，用普通伪随机就可以；生成登录 Session、重置密码链接、邮箱验证 Token，就必须使用安全随机。两类需求看起来都叫“随机”，背后的要求完全不同。&lt;/p&gt;</description></item><item><title>Go 入门：用 slog 写可检索的结构化日志</title><link>https://plumephp.com/go-slog-structured-logging-2025/</link><pubDate>Mon, 28 Jul 2025 08:54:00 +0800</pubDate><guid>https://plumephp.com/go-slog-structured-logging-2025/</guid><description>&lt;p&gt;日志不是程序最后随手加的一行 &lt;code&gt;fmt.Println&lt;/code&gt;。线上排查问题时，日志经常是第一证据：哪个用户触发了请求、参数是什么、调用下游花了多久、错误发生在哪一层。纯文本日志能看，但不方便检索。结构化日志把关键信息放到字段里，日志平台可以按字段过滤、聚合和统计。&lt;/p&gt;</description></item><item><title>Go 数据库迁移：goose、golang-migrate 与最佳实践</title><link>https://plumephp.com/97-database-migration/</link><pubDate>Fri, 25 Jul 2025 08:30:00 +0800</pubDate><guid>https://plumephp.com/97-database-migration/</guid><description>&lt;h2 id="go-数据库迁移goosegolang-migrate-与最佳实践"&gt;Go 数据库迁移：goose、golang-migrate 与最佳实践&lt;/h2&gt;
&lt;p&gt;你有没有经历过这样的噩梦：凌晨两点上线新版本，代码部署成功了，结果打开页面一片空白——因为数据库少了一个字段。或者更惨，回滚代码后发现数据库结构已经被改得面目全非，数据也丢了。又或者团队协作时，同事 A 加了一个索引，同事 B 删了一张表，合并代码的时候数据库结构已经乱成一锅粥。&lt;/p&gt;</description></item><item><title>Go fs.FS 入门：让文件读取逻辑更容易测试</title><link>https://plumephp.com/go-fs-fs-testing-2025/</link><pubDate>Wed, 23 Jul 2025 08:39:00 +0800</pubDate><guid>https://plumephp.com/go-fs-fs-testing-2025/</guid><description>&lt;p&gt;很多代码会直接 &lt;code&gt;os.ReadFile(&amp;quot;config.json&amp;quot;)&lt;/code&gt;。这很简单，但测试时会依赖真实文件路径。Go 的 &lt;code&gt;io/fs&lt;/code&gt; 提供了 &lt;code&gt;fs.FS&lt;/code&gt; 接口，可以让函数从“文件系统”读取，而不关心这个文件系统来自磁盘、内存还是 embed。这样文件读取逻辑更容易测试。&lt;/p&gt;</description></item><item><title>Go 文件变化轮询入门：不用依赖也能做简单热加载</title><link>https://plumephp.com/go-file-watcher-polling-2025/</link><pubDate>Tue, 15 Jul 2025 16:29:00 +0800</pubDate><guid>https://plumephp.com/go-file-watcher-polling-2025/</guid><description>&lt;p&gt;监听文件变化常见做法是使用 fsnotify 这类库。但有些小工具不想引入依赖，只需要每隔几秒检查配置文件是否变了。这时轮询修改时间是一个简单可控的方案。它不够实时，但容易理解，也适合入门。&lt;/p&gt;</description></item><item><title>Go 数据库迁移入门：SQL 文件、版本表和上线顺序</title><link>https://plumephp.com/go-database-migrations-basics-2025/</link><pubDate>Tue, 08 Jul 2025 08:58:00 +0800</pubDate><guid>https://plumephp.com/go-database-migrations-basics-2025/</guid><description>&lt;p&gt;数据库表结构不会一开始就设计完。你会新增字段、加索引、拆表、修约束。初学项目常见做法是手工连数据库执行 SQL，短期快，长期危险：谁执行过、执行到哪一步、线上和本地是否一致，都说不清。数据库迁移的目标就是让结构变更有版本、有记录、可重复。&lt;/p&gt;</description></item><item><title>Go 与 Kubernetes：构建自定义 Operator</title><link>https://plumephp.com/95-kubernetes-operator/</link><pubDate>Fri, 20 Jun 2025 10:45:00 +0800</pubDate><guid>https://plumephp.com/95-kubernetes-operator/</guid><description>&lt;h2 id="go-与-kubernetes构建自定义-operator"&gt;Go 与 Kubernetes：构建自定义 Operator&lt;/h2&gt;
&lt;p&gt;你有没有遇到过这样的场景：公司里有一套复杂的中间件，每次部署都需要手动配置一大堆 YAML 文件，稍有不慎就会配错。你心里想，如果 Kubernetes 能&amp;quot;认识&amp;quot;这个中间件，自动帮我管理该多好？&lt;/p&gt;</description></item><item><title>Go JSON 请求校验入门：不用框架也能写清楚规则</title><link>https://plumephp.com/go-json-schema-lite-validation-2025/</link><pubDate>Thu, 19 Jun 2025 19:40:00 +0800</pubDate><guid>https://plumephp.com/go-json-schema-lite-validation-2025/</guid><description>&lt;p&gt;写 JSON API 时，校验是绕不开的。字段缺失、字符串太长、枚举值不合法、嵌套数组为空，这些都应该在进入业务逻辑前拦住。很多项目会使用校验库，这没问题；但初学者最好先理解不用框架时规则应该放在哪里、错误怎么组织。&lt;/p&gt;</description></item><item><title>Go 入门：用 time.Ticker 做一个朴素限速器</title><link>https://plumephp.com/go-rate-limiter-ticker-2025/</link><pubDate>Wed, 18 Jun 2025 11:27:00 +0800</pubDate><guid>https://plumephp.com/go-rate-limiter-ticker-2025/</guid><description>&lt;p&gt;限速听起来像网关和高并发系统才需要的能力，其实入门项目也经常遇到。比如你要批量同步 5000 个客户资料到第三方 CRM，对方文档写着“每秒最多 5 次请求”。如果你开 50 个 goroutine 一口气打过去，很快就会收到 429，严重时还会被封禁。&lt;/p&gt;</description></item><item><title>Go os/exec 入门：调用外部命令时如何处理超时和输出</title><link>https://plumephp.com/go-os-exec-command-2025/</link><pubDate>Wed, 11 Jun 2025 13:48:00 +0800</pubDate><guid>https://plumephp.com/go-os-exec-command-2025/</guid><description>&lt;p&gt;Go 程序有时需要调用外部命令：图片转换、压缩文件、调用已有脚本、执行系统工具。标准库的 &lt;code&gt;os/exec&lt;/code&gt; 可以做到，但边界要清楚。最重要的是：不要把用户输入拼成 shell 命令；要设置超时；要处理 stdout 和 stderr。&lt;/p&gt;</description></item><item><title>Fyne的UI定制</title><link>https://plumephp.com/fyne%E7%9A%84ui%E5%AE%9A%E5%88%B6/</link><pubDate>Tue, 03 Jun 2025 10:44:07 +0800</pubDate><guid>https://plumephp.com/fyne%E7%9A%84ui%E5%AE%9A%E5%88%B6/</guid><description>&lt;p&gt;Fyne 虽然默认的 UI 风格较为“简洁保守”，但它提供了完整的自定义接口（例如 &lt;code&gt;fyne.Theme&lt;/code&gt;、自定义 &lt;code&gt;CanvasObject&lt;/code&gt;、绘图 API 等），可以实现高度定制的 UI。如果你希望构建更现代、美观、甚至炫酷的界面，以下是 &lt;strong&gt;全面提升 Fyne UI 定制能力的技巧与实践&lt;/strong&gt;：&lt;/p&gt;</description></item><item><title>Go TestMain 入门：为一组测试准备共享环境</title><link>https://plumephp.com/go-testmain-fixtures-2025/</link><pubDate>Tue, 03 Jun 2025 10:44:00 +0800</pubDate><guid>https://plumephp.com/go-testmain-fixtures-2025/</guid><description>&lt;p&gt;大多数 Go 测试只需要普通 &lt;code&gt;TestXxx&lt;/code&gt;。但有时一个包里的多组测试需要共享准备工作，比如创建临时目录、启动测试服务器、准备测试数据库连接。&lt;code&gt;TestMain&lt;/code&gt; 可以在一个测试包运行前后执行统一逻辑。&lt;/p&gt;</description></item><item><title>Fyne跨平台的最佳实践</title><link>https://plumephp.com/fyne%E8%B7%A8%E5%B9%B3%E5%8F%B0%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/</link><pubDate>Tue, 03 Jun 2025 10:33:15 +0800</pubDate><guid>https://plumephp.com/fyne%E8%B7%A8%E5%B9%B3%E5%8F%B0%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/</guid><description>&lt;p&gt;Fyne 是一个主打“纯 Go + 原生跨平台 GUI”的框架，因此特别适合开发需要快速部署、小巧轻便、无需额外依赖的跨平台桌面应用。为了最大程度发挥 Fyne 的跨平台优势，以下是 &lt;strong&gt;Fyne 跨平台开发的最佳实践&lt;/strong&gt;，涵盖项目结构、UI设计、打包发布等方面。&lt;/p&gt;</description></item><item><title>Fyne和wails的详细对比</title><link>https://plumephp.com/fyne%E5%92%8Cwails%E7%9A%84%E8%AF%A6%E7%BB%86%E5%AF%B9%E6%AF%94/</link><pubDate>Tue, 03 Jun 2025 10:29:07 +0800</pubDate><guid>https://plumephp.com/fyne%E5%92%8Cwails%E7%9A%84%E8%AF%A6%E7%BB%86%E5%AF%B9%E6%AF%94/</guid><description>&lt;p&gt;Fyne 和 Wails 是两个用于构建桌面应用的 Go 框架，它们在架构、使用方式、成熟度、生态和社区等方面都有显著差异。以下是对二者的详细对比分析：&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="架构对比"&gt;架构对比&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;特性&lt;/th&gt;
					&lt;th&gt;Fyne&lt;/th&gt;
					&lt;th&gt;Wails&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;架构模式&lt;/td&gt;
					&lt;td&gt;纯 Go 原生 GUI&lt;/td&gt;
					&lt;td&gt;前端用 Web 技术（HTML/CSS/JS），后端 Go 提供服务&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;渲染机制&lt;/td&gt;
					&lt;td&gt;内置渲染引擎（基于 OpenGL）&lt;/td&gt;
					&lt;td&gt;内嵌浏览器（WebView2, WebKit, etc.）显示网页&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;平台支持&lt;/td&gt;
					&lt;td&gt;Windows, macOS, Linux, Android, iOS（实验性）&lt;/td&gt;
					&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;构建模式&lt;/td&gt;
					&lt;td&gt;单可执行文件，无需浏览器&lt;/td&gt;
					&lt;td&gt;前端需构建 Web 项目，最终打包为桌面应用&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="-成熟度和生态对比"&gt;🌱 成熟度和生态对比&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;特性&lt;/th&gt;
					&lt;th&gt;Fyne&lt;/th&gt;
					&lt;th&gt;Wails&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;项目启动时间&lt;/td&gt;
					&lt;td&gt;2018 年&lt;/td&gt;
					&lt;td&gt;2019 年&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;最新稳定版本（2025 年）&lt;/td&gt;
					&lt;td&gt;v2.x，活跃更新中&lt;/td&gt;
					&lt;td&gt;v2.7+，活跃更新中&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;成熟度&lt;/td&gt;
					&lt;td&gt;较成熟，UI 库逐步完善&lt;/td&gt;
					&lt;td&gt;成熟，适合已有 Web 技术栈&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;社区活跃度&lt;/td&gt;
					&lt;td&gt;中等偏上，有官方 Slack 和 GitHub 活跃&lt;/td&gt;
					&lt;td&gt;高，社区、文档、插件生态活跃&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;文档完善度&lt;/td&gt;
					&lt;td&gt;完善，官方有完整 API 文档和教程&lt;/td&gt;
					&lt;td&gt;非常完善，示例多、教程丰富&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;插件/生态扩展&lt;/td&gt;
					&lt;td&gt;比较有限，主要靠官方和社区贡献的组件&lt;/td&gt;
					&lt;td&gt;丰富，可用所有 JS/HTML/CSS 前端生态&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;开发体验&lt;/td&gt;
					&lt;td&gt;类似 Flutter（声明式 GUI）&lt;/td&gt;
					&lt;td&gt;类似 Electron，但后端是 Go&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;UI 灵活性&lt;/td&gt;
					&lt;td&gt;中等，定制成本较高&lt;/td&gt;
					&lt;td&gt;极高，取决于 Web 前端能力&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="-ui-开发体验"&gt;🖥️ UI 开发体验&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;特性&lt;/th&gt;
					&lt;th&gt;Fyne&lt;/th&gt;
					&lt;th&gt;Wails&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;UI 构建方式&lt;/td&gt;
					&lt;td&gt;纯 Go 编写 UI，类似 Flutter&lt;/td&gt;
					&lt;td&gt;用 Vue, React, Svelte, 甚至原生 HTML/CSS&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;热重载支持&lt;/td&gt;
					&lt;td&gt;有限（实验性）&lt;/td&gt;
					&lt;td&gt;支持前端热重载（例如 Vite）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;响应式设计支持&lt;/td&gt;
					&lt;td&gt;有，基于容器布局&lt;/td&gt;
					&lt;td&gt;前端框架原生支持响应式&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;开发效率&lt;/td&gt;
					&lt;td&gt;中等，UI 设计需理解 Fyne 组件体系&lt;/td&gt;
					&lt;td&gt;高（前端开发者）、中（Go-only 开发者）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多语言支持&lt;/td&gt;
					&lt;td&gt;内建 i18n 模块&lt;/td&gt;
					&lt;td&gt;需前端自己处理（如 Vue-i18n）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="性能和资源占用"&gt;性能和资源占用&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;特性&lt;/th&gt;
					&lt;th&gt;Fyne&lt;/th&gt;
					&lt;th&gt;Wails&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;启动速度&lt;/td&gt;
					&lt;td&gt;快，轻量运行&lt;/td&gt;
					&lt;td&gt;略慢（取决于嵌入 Web 引擎）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;可执行文件大小&lt;/td&gt;
					&lt;td&gt;较小（10~20MB）&lt;/td&gt;
					&lt;td&gt;较大（50MB+，含 Web 引擎）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;系统资源占用&lt;/td&gt;
					&lt;td&gt;少&lt;/td&gt;
					&lt;td&gt;多（嵌入浏览器内核）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;GPU 加速&lt;/td&gt;
					&lt;td&gt;有 OpenGL 支持&lt;/td&gt;
					&lt;td&gt;由浏览器内核负责加速&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="-安全性"&gt;🔒 安全性&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;特性&lt;/th&gt;
					&lt;th&gt;Fyne&lt;/th&gt;
					&lt;th&gt;Wails&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;攻击面&lt;/td&gt;
					&lt;td&gt;小，纯 Go 无浏览器漏洞&lt;/td&gt;
					&lt;td&gt;稍大，需注意前端代码安全、XSS 等&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;前端资源&lt;/td&gt;
					&lt;td&gt;编译进可执行文件&lt;/td&gt;
					&lt;td&gt;前端可嵌入也可从文件系统加载&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="-使用场景推荐"&gt;🛠️ 使用场景推荐&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;场景&lt;/th&gt;
					&lt;th&gt;推荐框架&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;全 Go 项目、对前端技术不熟悉&lt;/td&gt;
					&lt;td&gt;✅ Fyne&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;需要跨平台快速构建简单 GUI 工具&lt;/td&gt;
					&lt;td&gt;✅ Fyne&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;有 Web 前端团队或项目已有 Web UI&lt;/td&gt;
					&lt;td&gt;✅ Wails&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;追求高度定制化 UI、美观交互体验&lt;/td&gt;
					&lt;td&gt;✅ Wails&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;构建现代桌面应用（类似 Electron）&lt;/td&gt;
					&lt;td&gt;✅ Wails&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;移动端开发需求&lt;/td&gt;
					&lt;td&gt;🚧 Fyne（实验性支持 Android/iOS）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="-实际项目对比建议"&gt;🧪 实际项目对比建议&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;项目类型&lt;/th&gt;
					&lt;th&gt;推荐&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;内网企业小工具&lt;/td&gt;
					&lt;td&gt;Fyne（部署方便）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;管理后台客户端&lt;/td&gt;
					&lt;td&gt;Wails（复用已有前端）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;面向公众的桌面应用&lt;/td&gt;
					&lt;td&gt;Wails（现代 UI）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Go 教学/CLI GUI 包装工具&lt;/td&gt;
					&lt;td&gt;Fyne&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;高度交互的桌面应用&lt;/td&gt;
					&lt;td&gt;Wails&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="-示例项目与资源"&gt;📦 示例项目与资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fyne 示例：&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Go net/url 入门：安全地拼 URL 和查询参数</title><link>https://plumephp.com/go-net-url-query-2025/</link><pubDate>Tue, 27 May 2025 11:16:00 +0800</pubDate><guid>https://plumephp.com/go-net-url-query-2025/</guid><description>&lt;p&gt;很多 HTTP 客户端 bug 都来自手写 URL。用户输入里有空格、斜杠、中文、&lt;code&gt;&amp;amp;&lt;/code&gt;、&lt;code&gt;?&lt;/code&gt;，一旦直接字符串拼接，就可能生成错误 URL，甚至改变参数含义。Go 标准库的 &lt;code&gt;net/url&lt;/code&gt; 可以安全地处理路径和查询参数。&lt;/p&gt;</description></item><item><title>Go 与 WebAssembly：从浏览器到边缘计算</title><link>https://plumephp.com/93-webassembly/</link><pubDate>Thu, 22 May 2025 11:00:00 +0800</pubDate><guid>https://plumephp.com/93-webassembly/</guid><description>&lt;h2 id="引言"&gt;引言&lt;/h2&gt;
&lt;p&gt;如果说 Go 语言的&amp;quot;一次编译，到处运行&amp;quot;已经让人印象深刻，那么 WebAssembly（简称 WASM）把这个理念推向了极致——一次编译，&lt;strong&gt;任何地方&lt;/strong&gt;都能运行。浏览器里、Node.js 中、服务器端、IoT 设备、CDN 边缘节点……只要有 WASM 运行时的地方，就有你的代码。&lt;/p&gt;</description></item><item><title>Go 后台任务重试入门：失败后怎么重试才不添乱</title><link>https://plumephp.com/go-background-job-retry-2025/</link><pubDate>Wed, 14 May 2025 11:22:00 +0800</pubDate><guid>https://plumephp.com/go-background-job-retry-2025/</guid><description>&lt;p&gt;后台任务失败后要不要重试？答案通常是“要，但不能乱重试”。发送通知、同步数据、生成报表、调用第三方接口都可能遇到临时失败。如果完全不重试，偶发抖动会变成用户可见失败；如果无限重试，可能把一个小问题放大成队列堵塞或外部服务雪崩。&lt;/p&gt;</description></item><item><title>Go worker 取消入门：后台循环如何听懂 context</title><link>https://plumephp.com/go-worker-context-cancel-2025/</link><pubDate>Fri, 09 May 2025 13:37:00 +0800</pubDate><guid>https://plumephp.com/go-worker-context-cancel-2025/</guid><description>&lt;p&gt;后台 worker 是 Go 服务里很常见的结构：消费队列、定时清理、同步外部数据、处理导入任务。很多 worker 写起来很简单，但停不下来。服务收到关闭信号后，HTTP server 已经退出，worker 还卡在 sleep、网络请求或 channel receive 上，进程迟迟不结束。&lt;/p&gt;</description></item><item><title>Go 入门：regexp 能做什么，不能替你做什么</title><link>https://plumephp.com/go-regexp-input-cleaning-2025/</link><pubDate>Tue, 29 Apr 2025 16:05:00 +0800</pubDate><guid>https://plumephp.com/go-regexp-input-cleaning-2025/</guid><description>&lt;p&gt;正则表达式很容易让人又爱又怕。它写得好，可以快速从文本里找出订单号、清理多余空白、判断一个字段是否像手机号；写得太贪，就会变成没人敢改的一串符号。Go 的 &lt;code&gt;regexp&lt;/code&gt; 包使用 RE2 语法，避免了某些语言里灾难性回溯的问题，但这不代表正则可以随便写。&lt;/p&gt;</description></item><item><title>Go HTTP 客户端上传文件入门：multipart 请求怎么构造</title><link>https://plumephp.com/go-http-client-multipart-upload-2025/</link><pubDate>Tue, 22 Apr 2025 14:05:00 +0800</pubDate><guid>https://plumephp.com/go-http-client-multipart-upload-2025/</guid><description>&lt;p&gt;前面很多教程会讲服务端如何接收文件上传，但客户端怎么上传也很常见：你的 Go 程序要把图片传给素材服务，把 CSV 传给导入接口，或者把日志包传给诊断系统。这类请求通常使用 &lt;code&gt;multipart/form-data&lt;/code&gt;。标准库可以完成，但写法比普通 JSON 请求稍微长一点。&lt;/p&gt;</description></item><item><title>Go 与领域驱动设计：构建复杂业务系统</title><link>https://plumephp.com/96-domain-driven-design/</link><pubDate>Fri, 18 Apr 2025 13:30:00 +0800</pubDate><guid>https://plumephp.com/96-domain-driven-design/</guid><description>&lt;h2 id="go-与领域驱动设计构建复杂业务系统"&gt;Go 与领域驱动设计：构建复杂业务系统&lt;/h2&gt;
&lt;p&gt;你有没有遇到过这样的代码：一个 &lt;code&gt;UserService&lt;/code&gt; 里有 3000 行代码，混杂着数据库查询、业务规则校验、发邮件、记录日志……每次改一个小需求都要小心翼翼地测试整个链路。新同事入职看到这种代码，第一反应是&amp;quot;这谁写的？&amp;ldquo;第二反应是&amp;quot;我也不敢动&amp;rdquo;。&lt;/p&gt;</description></item><item><title>Go HTTP 服务优雅关闭入门：停止接新请求，等旧请求收尾</title><link>https://plumephp.com/go-http-server-shutdown-drain-2025/</link><pubDate>Fri, 18 Apr 2025 08:42:00 +0800</pubDate><guid>https://plumephp.com/go-http-server-shutdown-drain-2025/</guid><description>&lt;p&gt;HTTP 服务发布或重启时，如果直接杀进程，正在处理的请求可能被中断，用户看到连接错误，后台写入也可能只完成一半。Go 的 &lt;code&gt;http.Server.Shutdown&lt;/code&gt; 可以帮助服务优雅关闭：停止接收新连接，等待已有请求完成，直到超时。&lt;/p&gt;</description></item><item><title>Go atomic 计数入门：比 mutex 更适合简单计数的场景</title><link>https://plumephp.com/go-atomic-counters-2025/</link><pubDate>Wed, 09 Apr 2025 15:44:00 +0800</pubDate><guid>https://plumephp.com/go-atomic-counters-2025/</guid><description>&lt;p&gt;并发计数是 Go 服务里很常见的小需求：请求数、错误数、当前连接数、后台任务数量。用 mutex 可以实现，但对单个整数计数来说，&lt;code&gt;sync/atomic&lt;/code&gt; 更轻便。较新的 Go 版本提供了类型化原子值，比如 &lt;code&gt;atomic.Int64&lt;/code&gt;，比老式函数更好读。&lt;/p&gt;</description></item><item><title>Go SQL 预编译入门：PrepareContext 什么时候值得用</title><link>https://plumephp.com/go-sql-prepared-statements-2025/</link><pubDate>Thu, 27 Mar 2025 14:26:00 +0800</pubDate><guid>https://plumephp.com/go-sql-prepared-statements-2025/</guid><description>&lt;p&gt;写 Go 数据库代码时，我们经常用 &lt;code&gt;ExecContext&lt;/code&gt; 和 &lt;code&gt;QueryContext&lt;/code&gt; 传 SQL 和参数。那 &lt;code&gt;PrepareContext&lt;/code&gt; 是做什么的？简单说，它会创建一个预编译语句，后续可以多次执行，适合同一条 SQL 重复执行的场景，比如批量插入、批量更新。&lt;/p&gt;</description></item><item><title>Go sync.Pool 入门：复用临时对象前先看清边界</title><link>https://plumephp.com/go-sync-pool-buffers-2025/</link><pubDate>Wed, 19 Mar 2025 09:22:00 +0800</pubDate><guid>https://plumephp.com/go-sync-pool-buffers-2025/</guid><description>&lt;p&gt;&lt;code&gt;sync.Pool&lt;/code&gt; 经常出现在性能优化文章里。它可以复用临时对象，减少分配压力。比如高频请求里不断创建 &lt;code&gt;bytes.Buffer&lt;/code&gt;，可以考虑用 pool 复用。但 &lt;code&gt;sync.Pool&lt;/code&gt; 也很容易被滥用：代码复杂了，性能却没明显提升，甚至因为对象没清理干净引入 bug。&lt;/p&gt;</description></item><item><title>Go 入门：CSV 导入导出别只会 strings.Split</title><link>https://plumephp.com/go-csv-reading-writing-2025/</link><pubDate>Wed, 12 Mar 2025 10:42:00 +0800</pubDate><guid>https://plumephp.com/go-csv-reading-writing-2025/</guid><description>&lt;p&gt;CSV 是最不起眼、也最容易出事故的数据格式。运营同事从后台导出一份表，财务系统给你一份结算明细，供应商发来一批商品编码，很多时候都是 CSV。初学者常犯的错误是用 &lt;code&gt;strings.Split(line, &amp;quot;,&amp;quot;)&lt;/code&gt; 处理。只要字段里出现逗号、换行或双引号，这个办法马上失效。&lt;/p&gt;</description></item><item><title>Go 与 AI：构建智能应用</title><link>https://plumephp.com/92-go-with-ai/</link><pubDate>Mon, 10 Mar 2025 14:20:00 +0800</pubDate><guid>https://plumephp.com/92-go-with-ai/</guid><description>&lt;h2 id="引言"&gt;引言&lt;/h2&gt;
&lt;p&gt;2025 年，如果你还觉得&amp;quot;AI 是 Python 的事&amp;quot;，那可就大错特错了。诚然，Python 在模型训练和数据科学领域占据统治地位，但当你需要把 AI 能力&lt;strong&gt;部署到生产环境&lt;/strong&gt;、构建高性能的推理服务、或者开发面向用户的 AI 应用时，Go 的优势就凸显出来了：并发能力强、内存占用小、编译成单一二进制、部署简单。&lt;/p&gt;</description></item><item><title>Go context 值入门：请求范围数据可以放，业务参数不要乱塞</title><link>https://plumephp.com/go-context-values-boundary-2025/</link><pubDate>Thu, 06 Mar 2025 10:47:00 +0800</pubDate><guid>https://plumephp.com/go-context-values-boundary-2025/</guid><description>&lt;p&gt;&lt;code&gt;context.Context&lt;/code&gt; 是 Go 服务开发里绕不开的类型。它主要负责取消、超时和请求范围的数据。前两者很好理解，第三个“请求范围的数据”最容易被滥用。很多初学者会把业务参数、配置、数据库连接甚至 service 都塞进 context，最后代码变得像隐形全局变量。&lt;/p&gt;</description></item><item><title>RoadRunner项目介绍</title><link>https://plumephp.com/roadrunner%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D/</link><pubDate>Mon, 03 Mar 2025 12:50:04 +0800</pubDate><guid>https://plumephp.com/roadrunner%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D/</guid><description>&lt;h2 id="roadrunner-项目详细介绍"&gt;RoadRunner 项目详细介绍&lt;/h2&gt;
&lt;p&gt;RoadRunner 是一个用 Golang 编写的高性能应用服务器和进程管理器，主要用于服务 PHP 应用程序，但其架构和插件机制也使得它适用于其它语言和多种应用场景。RoadRunner 的设计初衷是解决传统 PHP 应用在每次请求都要重复初始化环境（如引导文件、依赖注入、框架初始化等）所带来的性能瓶颈问题。借助 RoadRunner，可以将 PHP 应用以常驻进程的方式运行，从而大幅度降低请求响应时间，提高系统吞吐量。&lt;/p&gt;</description></item><item><title>Go HTTP gzip 压缩入门：什么时候压缩响应，什么时候不要压</title><link>https://plumephp.com/go-http-gzip-middleware-2025/</link><pubDate>Mon, 24 Feb 2025 18:05:00 +0800</pubDate><guid>https://plumephp.com/go-http-gzip-middleware-2025/</guid><description>&lt;p&gt;HTTP 响应压缩是一个常见优化。JSON、HTML、CSS、文本日志这类内容通常能压缩很多，网络传输更省；图片、视频、已经压缩过的 zip 文件再压缩意义不大，还会浪费 CPU。Go 标准库有 &lt;code&gt;compress/gzip&lt;/code&gt;，可以写一个简单中间件理解压缩流程。&lt;/p&gt;</description></item><item><title>Grpc Gateway初探</title><link>https://plumephp.com/grpc-gateway%E5%88%9D%E6%8E%A2/</link><pubDate>Mon, 24 Feb 2025 11:57:41 +0800</pubDate><guid>https://plumephp.com/grpc-gateway%E5%88%9D%E6%8E%A2/</guid><description>&lt;h2 id="1-引言"&gt;1. 引言&lt;/h2&gt;
&lt;p&gt;在微服务架构中，gRPC 作为一种高性能、跨语言的远程过程调用（RPC）框架被越来越多的项目采用。gRPC 默认使用 Protocol Buffers 作为数据序列化格式，具有数据紧凑、解析速度快等优点。但在实际生产环境中，我们常常需要同时支持传统的 RESTful API 接口，原因包括兼容历史系统、满足前端或移动端使用 HTTP/JSON 协议等。为此，grpc‑gateway 应运而生，它可以通过极少的额外配置，将 gRPC 服务暴露为标准的 RESTful 接口，从而实现“写一次，跑多端”的目标。&lt;/p&gt;</description></item><item><title>Go JSON 流式响应入门：边查边写，避免一次性占满内存</title><link>https://plumephp.com/go-json-stream-response-2025/</link><pubDate>Tue, 18 Feb 2025 09:51:00 +0800</pubDate><guid>https://plumephp.com/go-json-stream-response-2025/</guid><description>&lt;p&gt;很多列表接口一开始会把所有数据查出来，放进切片，再 &lt;code&gt;json.NewEncoder(w).Encode(slice)&lt;/code&gt;。数据少时没问题，数据一多，内存会飙升，用户也要等全部数据准备好才能收到响应。对于导出类接口，可以考虑流式 JSON 响应：边查、边编码、边写给客户端。&lt;/p&gt;</description></item><item><title>Go1.24发布说明</title><link>https://plumephp.com/go1.24%E5%8F%91%E5%B8%83%E8%AF%B4%E6%98%8E/</link><pubDate>Mon, 17 Feb 2025 17:03:34 +0800</pubDate><guid>https://plumephp.com/go1.24%E5%8F%91%E5%B8%83%E8%AF%B4%E6%98%8E/</guid><description>&lt;p&gt;以下是 Go 1.24 的详细功能和变更说明，结合语言特性、性能优化、标准库改进等核心内容整理而成：&lt;/p&gt;
&lt;h2 id="一语言特性与语法变更"&gt;一、语言特性与语法变更&lt;/h2&gt;
&lt;h3 id="1-泛型类型别名正式支持"&gt;1. &lt;strong&gt;泛型类型别名正式支持&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;在 Go 1.24 中，&lt;strong&gt;带类型参数的泛型类型别名&lt;/strong&gt;从实验特性转正为默认支持。开发者可以为泛型类型定义别名，简化代码编写：&lt;/p&gt;</description></item><item><title>Go 可观测性：OpenTelemetry 完全指南</title><link>https://plumephp.com/94-opentelemetry-go/</link><pubDate>Sat, 15 Feb 2025 16:10:00 +0800</pubDate><guid>https://plumephp.com/94-opentelemetry-go/</guid><description>&lt;h2 id="go-可观测性opentelemetry-完全指南"&gt;Go 可观测性：OpenTelemetry 完全指南&lt;/h2&gt;
&lt;p&gt;你有没有经历过这样的夜晚：凌晨两点被报警电话叫醒，服务出了故障，但你盯着满屏的日志，完全搞不清楚请求到底经过了哪些服务、在哪个环节出了问题、为什么突然变慢？如果你有微服务架构的经验，这个场景一定不陌生。&lt;/p&gt;</description></item><item><title>Go sync.Once 入门：懒加载资源时只初始化一次</title><link>https://plumephp.com/go-sync-once-lazy-init-2025/</link><pubDate>Mon, 10 Feb 2025 16:18:00 +0800</pubDate><guid>https://plumephp.com/go-sync-once-lazy-init-2025/</guid><description>&lt;p&gt;Go 服务里经常有一些资源只需要初始化一次：解析模板、加载规则、创建昂贵对象、读取本地配置。你可以在程序启动时全部准备好，也可以第一次使用时再懒加载。懒加载时最怕并发：多个请求同时进来，发现资源还没初始化，于是重复执行。&lt;code&gt;sync.Once&lt;/code&gt; 就是用来保证某段初始化逻辑只执行一次的。&lt;/p&gt;</description></item><item><title>Go 入门：Cookie、Session 和一个简单登录状态</title><link>https://plumephp.com/go-http-cookie-session-basics-2025/</link><pubDate>Fri, 07 Feb 2025 14:18:00 +0800</pubDate><guid>https://plumephp.com/go-http-cookie-session-basics-2025/</guid><description>&lt;p&gt;登录状态是 Web 开发绕不开的话题。很多初学者一听到 Session，就会把它想成某种神秘机制。其实从 HTTP 角度看，Cookie 是浏览器保存并自动带回服务端的一小段数据；Session 是服务端用这段数据找到用户状态的一种做法。Cookie 在客户端，Session 数据通常在服务端。&lt;/p&gt;</description></item><item><title>Go http.ServeContent 入门：文件下载、修改时间和断点续传</title><link>https://plumephp.com/go-servecontent-downloads-2025/</link><pubDate>Tue, 28 Jan 2025 10:12:00 +0800</pubDate><guid>https://plumephp.com/go-servecontent-downloads-2025/</guid><description>&lt;p&gt;很多 Go 初学者写文件下载接口时，会直接 &lt;code&gt;os.ReadFile&lt;/code&gt; 把文件读进内存，再 &lt;code&gt;w.Write&lt;/code&gt; 返回。文件很小时这样能跑，但报表、日志包、图片、压缩文件一大，就会浪费内存，也错过了 HTTP 已经提供的能力：修改时间、Range 请求、缓存协商、内容长度。标准库的 &lt;code&gt;http.ServeContent&lt;/code&gt; 正是为这类场景准备的。&lt;/p&gt;</description></item><item><title>Go 入门：用 flag 写一个不慌不忙的命令行配置</title><link>https://plumephp.com/go-cli-flag-config-2025/</link><pubDate>Fri, 24 Jan 2025 09:36:00 +0800</pubDate><guid>https://plumephp.com/go-cli-flag-config-2025/</guid><description>&lt;p&gt;很多人第一次写 Go 程序，是从一个 HTTP 服务开始。但在真实团队里，命令行小工具同样常见：清理过期文件、导入一批 CSV、生成报表、检查接口是否可用。小工具看起来简单，最容易写成“先能跑再说”的样子：路径写死在代码里，超时时间散落在函数里，出了错只打印一行“failed”。等这个工具被同事拿去每天跑，问题就来了。&lt;/p&gt;</description></item><item><title>Go 1.25 前瞻：持续进化的 Go 生态</title><link>https://plumephp.com/91-go-1.25-features/</link><pubDate>Sat, 18 Jan 2025 09:30:00 +0800</pubDate><guid>https://plumephp.com/91-go-1.25-features/</guid><description>&lt;h2 id="引言"&gt;引言&lt;/h2&gt;
&lt;p&gt;各位 Go 友人们，新年好！2025 年才刚刚开始，Go 社区已经热闹起来了——按照 Go 团队雷打不动的发布节奏，Go 1.25 预计在 2025 年 8 月正式发布。还记得当初 Go 1.18 泛型发布时整个社区的狂欢吗？每半年的版本迭代，Go 都在悄无声息地变强，就像一个低调的武林高手，每次出手都让人眼前一亮。&lt;/p&gt;</description></item><item><title>Go httptest.Server 入门：给 HTTP 客户端写可信测试</title><link>https://plumephp.com/go-httptest-server-client-2025/</link><pubDate>Thu, 16 Jan 2025 09:34:00 +0800</pubDate><guid>https://plumephp.com/go-httptest-server-client-2025/</guid><description>&lt;p&gt;很多 Go 初学者会给 HTTP handler 写测试，却不知道怎么给 HTTP 客户端写测试。比如你的服务要调用一个用户中心接口，代码里有 &lt;code&gt;http.Client&lt;/code&gt;、URL、请求头和 JSON 解码。测试时不可能真的去调用线上用户中心，也不应该为了单元测试启动一整套依赖。标准库里的 &lt;code&gt;net/http/httptest&lt;/code&gt; 提供了一个很实用的工具：&lt;code&gt;httptest.Server&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Go 语言学习路线图：从入门到精通</title><link>https://plumephp.com/00-learning-roadmap/</link><pubDate>Wed, 15 Jan 2025 09:00:00 +0800</pubDate><guid>https://plumephp.com/00-learning-roadmap/</guid><description>&lt;h2 id="-go-语言学习路线图"&gt;🗺️ Go 语言学习路线图&lt;/h2&gt;
&lt;p&gt;欢迎来到 Go 语言的学习之旅！本系列包含 &lt;strong&gt;100 篇&lt;/strong&gt;精心编写的教程文章，涵盖从基础语法到前沿技术的完整知识体系。这份学习路线图将帮助你：&lt;/p&gt;</description></item><item><title>Go 时区处理入门：time.Location、UTC 和用户本地时间</title><link>https://plumephp.com/go-timezone-location-basics-2025/</link><pubDate>Thu, 09 Jan 2025 11:08:00 +0800</pubDate><guid>https://plumephp.com/go-timezone-location-basics-2025/</guid><description>&lt;p&gt;时间处理是后端里最容易“本地没问题，线上出事故”的部分。尤其是预约、提醒、账单、报表这类功能，用户看到的是本地时间，系统存储和计算却最好使用统一时间。Go 的 &lt;code&gt;time&lt;/code&gt; 包很强，但初学者如果只会 &lt;code&gt;time.Now()&lt;/code&gt; 和 &lt;code&gt;Format&lt;/code&gt;，很容易在时区上踩坑。&lt;/p&gt;</description></item><item><title>Go 配置分层入门：默认值、环境变量和启动校验</title><link>https://plumephp.com/go-env-config-layering-2024/</link><pubDate>Thu, 19 Dec 2024 19:04:00 +0800</pubDate><guid>https://plumephp.com/go-env-config-layering-2024/</guid><description>&lt;p&gt;配置是后端服务里最容易被低估的部分。初学者常常先把端口、数据库地址、超时时间写死在代码里，等部署到不同环境时再匆忙改成环境变量。结果是默认值散落各处，启动时不校验，线上才发现某个配置拼错了。&lt;/p&gt;</description></item><item><title>Go 小项目发布前检查清单：从能跑到敢交付</title><link>https://plumephp.com/go-release-checklist-for-beginners-2024/</link><pubDate>Thu, 12 Dec 2024 17:02:00 +0800</pubDate><guid>https://plumephp.com/go-release-checklist-for-beginners-2024/</guid><description>&lt;h2 id="能跑和敢交付之间还有一段路"&gt;能跑和敢交付之间还有一段路&lt;/h2&gt;
&lt;p&gt;很多 Go 小项目写到最后，&lt;code&gt;go run .&lt;/code&gt; 能跑，接口也能返回结果，于是就想直接上线或交给别人使用。可是真正交付前，还应该做一轮基础检查：测试是否通过，代码是否格式化，构建是否可复现，配置是否清楚，日志是否足够，外部调用是否有超时，错误是否会泄露敏感信息，README 是否告诉别人怎么运行。&lt;/p&gt;</description></item><item><title>Go 项目实战：从零构建完整的 Web 应用</title><link>https://plumephp.com/90-fullstack-project/</link><pubDate>Fri, 15 Nov 2024 10:00:00 +0800</pubDate><guid>https://plumephp.com/90-fullstack-project/</guid><description>&lt;h2 id="go-项目实战从零构建完整的-web-应用"&gt;Go 项目实战：从零构建完整的 Web 应用&lt;/h2&gt;
&lt;p&gt;学了这么多 Go 的理论和零散的知识点，是时候来一个完整的项目实战了。今天，我们将从零开始构建一个真实的项目——&lt;strong&gt;在线任务管理系统（TaskMaster）&lt;/strong&gt;。&lt;/p&gt;</description></item><item><title>Go 轻量分层入门：Handler、Service、Store 怎么刚刚好</title><link>https://plumephp.com/go-clean-architecture-lightweight-2024/</link><pubDate>Tue, 05 Nov 2024 10:44:00 +0800</pubDate><guid>https://plumephp.com/go-clean-architecture-lightweight-2024/</guid><description>&lt;h2 id="分层不是为了画图好看"&gt;分层不是为了画图好看&lt;/h2&gt;
&lt;p&gt;Go 项目写到一定规模后，总会遇到一个问题：HTTP handler 里东西越来越多。它既解析 JSON，又校验参数，又查数据库，又拼响应，还打日志。短期写得快，长期很难改。于是很多人开始学习“Clean Architecture”“DDD”“六边形架构”。这些思想有价值，但入门项目照搬全套目录，很容易过度设计。&lt;/p&gt;</description></item><item><title>Go 测试替身入门：用小接口写出容易测试的业务代码</title><link>https://plumephp.com/go-test-doubles-interfaces-2024/</link><pubDate>Thu, 24 Oct 2024 09:46:00 +0800</pubDate><guid>https://plumephp.com/go-test-doubles-interfaces-2024/</guid><description>&lt;p&gt;很多初学者一听到“单元测试”，就想到 mock 框架。其实 Go 里最常用、最舒服的测试替身，往往只是一个小结构体。你定义一个刚好够用的小接口，测试里手写 fake 或 spy，就能验证业务逻辑，而不用真的发邮件、调支付或连外部 API。&lt;/p&gt;</description></item><item><title>Go 日志脱敏入门：不要把密码、Token 和身份证写进日志</title><link>https://plumephp.com/go-log-redaction-basics-2024/</link><pubDate>Thu, 17 Oct 2024 11:28:00 +0800</pubDate><guid>https://plumephp.com/go-log-redaction-basics-2024/</guid><description>&lt;p&gt;日志是排查问题的基础，但日志也很容易泄露敏感信息。密码、验证码、session ID、访问 token、身份证号、银行卡号、完整手机号，如果进入日志系统，就可能被更多人看到，也很难彻底删除。初学者常见错误是为了调试方便，把整个请求体或结构体直接打印出来。&lt;/p&gt;</description></item><item><title>Go 黄金文件测试入门：适合输出很长的文本和 JSON</title><link>https://plumephp.com/go-golden-file-tests-2024/</link><pubDate>Tue, 08 Oct 2024 10:12:00 +0800</pubDate><guid>https://plumephp.com/go-golden-file-tests-2024/</guid><description>&lt;p&gt;有些函数的输出很长，比如生成 Markdown、渲染配置文件、格式化 JSON、生成 SQL。你当然可以在测试里写一个很长的字符串常量，但测试文件会变得难读。黄金文件测试，也叫 golden file test，就是把期望输出放在单独文件里，测试时读取文件并比较。&lt;/p&gt;</description></item><item><title>Go 与区块链：从概念到实现</title><link>https://plumephp.com/89-blockchain-basics/</link><pubDate>Mon, 30 Sep 2024 12:00:00 +0800</pubDate><guid>https://plumephp.com/89-blockchain-basics/</guid><description>&lt;h2 id="go-与区块链从概念到实现"&gt;Go 与区块链：从概念到实现&lt;/h2&gt;
&lt;p&gt;区块链，这个听起来很神秘的技术，其实核心思想非常简单：&lt;strong&gt;去中心化的、不可篡改的数据账本&lt;/strong&gt;。你可能听说过比特币、以太坊，但你知道区块链的底层原理吗？&lt;/p&gt;</description></item><item><title>Go HTTP 中间件入门：日志、恢复、鉴权和请求 ID 怎么串起来</title><link>https://plumephp.com/go-http-middleware-patterns-2024/</link><pubDate>Fri, 27 Sep 2024 18:31:00 +0800</pubDate><guid>https://plumephp.com/go-http-middleware-patterns-2024/</guid><description>&lt;h2 id="中间件本质上是在包装-handler"&gt;中间件本质上是在包装 Handler&lt;/h2&gt;
&lt;p&gt;Go 的 HTTP handler 是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;中间件就是接收一个 handler，返回一个新的 handler。新 handler 可以在调用原 handler 前后做事情，比如记录日志、恢复 panic、检查登录状态、加请求 ID、统计耗时、设置响应头。&lt;/p&gt;</description></item><item><title>Golang1.23版本功能说明</title><link>https://plumephp.com/golang1.23%E7%89%88%E6%9C%AC%E5%8A%9F%E8%83%BD%E8%AF%B4%E6%98%8E/</link><pubDate>Tue, 24 Sep 2024 10:37:42 +0800</pubDate><guid>https://plumephp.com/golang1.23%E7%89%88%E6%9C%AC%E5%8A%9F%E8%83%BD%E8%AF%B4%E6%98%8E/</guid><description>&lt;h2 id="主要更新内容"&gt;主要更新内容&lt;/h2&gt;
&lt;p&gt;Go 1.23 版本带来了许多新特性和改进，以下是一些重点更新：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;用户定义的迭代器&lt;/strong&gt;：Go 1.23 支持了用户定义的迭代器，这意味着你可以创建自定义的迭代行为。例如，你可以使用 &lt;code&gt;func(func(K) bool)&lt;/code&gt; 这样的函数来定义一个迭代器，这为处理任意序列提供了可能。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下面是一个简单的例子，展示了如何定义一个迭代器，并在 &lt;code&gt;for&lt;/code&gt; 循环中使用它。在这个例子中，我们定义了一个 &lt;code&gt;IntIterator&lt;/code&gt; 类型，它有一个 &lt;code&gt;Next&lt;/code&gt; 方法。&lt;/p&gt;</description></item><item><title>Go 泛型集合入门：自己写 Set 和 Stack 时要注意什么</title><link>https://plumephp.com/go-generic-set-stack-2024/</link><pubDate>Wed, 18 Sep 2024 15:52:00 +0800</pubDate><guid>https://plumephp.com/go-generic-set-stack-2024/</guid><description>&lt;p&gt;Go 有了泛型以后，很多人第一反应是写集合库。虽然实际项目里不要急着造大而全的数据结构库，但写一个小 &lt;code&gt;Set&lt;/code&gt; 和 &lt;code&gt;Stack&lt;/code&gt; 很适合理解泛型：类型参数怎么写，&lt;code&gt;comparable&lt;/code&gt; 是什么，方法接收者怎么设计，零值能不能直接用。&lt;/p&gt;</description></item><item><title>Go 部分更新 API 入门：区分未提交、空值和清空字段</title><link>https://plumephp.com/go-json-patch-style-update-2024/</link><pubDate>Thu, 05 Sep 2024 17:19:00 +0800</pubDate><guid>https://plumephp.com/go-json-patch-style-update-2024/</guid><description>&lt;p&gt;创建接口比较简单，用户提交什么就创建什么。更新接口尤其是部分更新，会遇到一个麻烦问题：字段没提交、提交空字符串、提交 &lt;code&gt;null&lt;/code&gt;，这三件事含义可能不同。比如昵称没提交表示不改，提交空字符串表示设置为空字符串，提交 &lt;code&gt;null&lt;/code&gt; 表示清空昵称。&lt;/p&gt;</description></item><item><title>Go 1.23 迭代器入门：range over func 能解决什么问题</title><link>https://plumephp.com/go-iterators-range-over-func-2024/</link><pubDate>Fri, 30 Aug 2024 15:33:00 +0800</pubDate><guid>https://plumephp.com/go-iterators-range-over-func-2024/</guid><description>&lt;p&gt;Go 1.23 让 &lt;code&gt;range&lt;/code&gt; 可以遍历函数形式的迭代器。对初学者来说，这个特性第一眼可能有点陌生：既然我们已经能遍历切片、map、channel，为什么还需要遍历函数？答案通常和“惰性生成”和“避免一次性装进内存”有关。&lt;/p&gt;</description></item><item><title>Go GC 调优实战：从理论到实践</title><link>https://plumephp.com/88-gc-tuning/</link><pubDate>Sun, 25 Aug 2024 15:40:00 +0800</pubDate><guid>https://plumephp.com/88-gc-tuning/</guid><description>&lt;h2 id="go-gc-调优实战从理论到实践"&gt;Go GC 调优实战：从理论到实践&lt;/h2&gt;
&lt;p&gt;你有没有遇到过这样的场景：你的 Go 服务跑着跑着，CPU 突然飙高，响应延迟抖动明显，过了几十毫秒又恢复正常？恭喜你，你遇到了 GC pause。&lt;/p&gt;</description></item><item><title>Go slices 和 maps 包入门：少写一点重复集合代码</title><link>https://plumephp.com/go-slices-maps-packages-2024/</link><pubDate>Mon, 19 Aug 2024 09:15:00 +0800</pubDate><guid>https://plumephp.com/go-slices-maps-packages-2024/</guid><description>&lt;h2 id="集合工具终于进入标准库"&gt;集合工具终于进入标准库&lt;/h2&gt;
&lt;p&gt;Go 早期处理切片和 map 时，经常要自己写一些小工具函数：克隆切片、判断相等、删除元素、提取 map 的 key、排序。后来随着泛型进入语言，标准库也逐步提供了 &lt;code&gt;slices&lt;/code&gt; 和 &lt;code&gt;maps&lt;/code&gt; 这类集合辅助包。它们不会把 Go 变成链式集合语言，但能减少很多重复样板代码。&lt;/p&gt;</description></item><item><title>Go 信号处理入门：让 CLI 和服务收到 Ctrl+C 后体面退出</title><link>https://plumephp.com/go-signal-shutdown-cli-2024/</link><pubDate>Wed, 14 Aug 2024 11:09:00 +0800</pubDate><guid>https://plumephp.com/go-signal-shutdown-cli-2024/</guid><description>&lt;p&gt;你在终端里按 &lt;code&gt;Ctrl+C&lt;/code&gt;，程序会收到中断信号。如果程序只是立刻退出，可能会留下半写入文件、未完成请求、没有关闭的连接。对小脚本来说这不一定严重，但对长期运行的服务、消费者、同步工具来说，体面退出很重要。&lt;/p&gt;</description></item><item><title>Go 配置热重载入门：哪些配置能重载，哪些不该重载</title><link>https://plumephp.com/go-config-reload-boundary-2024/</link><pubDate>Tue, 06 Aug 2024 09:43:00 +0800</pubDate><guid>https://plumephp.com/go-config-reload-boundary-2024/</guid><description>&lt;p&gt;配置热重载听起来很诱人：不用重启服务，改配置就生效。但不是所有配置都适合热重载。日志级别、开关、限流阈值通常可以；数据库连接地址、监听端口、加密密钥就要谨慎。入门阶段最重要的是先划清边界。&lt;/p&gt;</description></item><item><title>Go语言优秀资源整理</title><link>https://plumephp.com/go%E8%AF%AD%E8%A8%80%E4%BC%98%E7%A7%80%E8%B5%84%E6%BA%90%E6%95%B4%E7%90%86/</link><pubDate>Thu, 01 Aug 2024 14:59:39 +0800</pubDate><guid>https://plumephp.com/go%E8%AF%AD%E8%A8%80%E4%BC%98%E7%A7%80%E8%B5%84%E6%BA%90%E6%95%B4%E7%90%86/</guid><description>&lt;h2 id="go-awesome"&gt;go-awesome&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Go 语言优秀资源整理，为项目落地加速🏃&lt;/p&gt;
&lt;p&gt;在 &lt;a href="https://github.com/shockerli/go-awesome"&gt;GitHub&lt;/a&gt; 上稳定更新，觉得不错请点个 Star ❤️&lt;/p&gt;
&lt;p&gt;如转载分享，请保留出处，谢谢 😆&lt;/p&gt;
&lt;p&gt;原文地址: &lt;a href="https://shockerli.net/post/go-awesome"&gt;https://shockerli.net/post/go-awesome&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a href="https://github.com/shockerli/go-awesome"&gt;https://github.com/shockerli/go-awesome&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;官网: &lt;a href="https://golang.org"&gt;https://golang.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;国内官网镜像(访问快): &lt;a href="https://golang.google.cn"&gt;https://golang.google.cn&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a href="https://github.com/golang/go"&gt;https://github.com/golang/go&lt;/a&gt;&lt;/p&gt;</description></item><item><title>适合新手入门的Golang开源项目推荐</title><link>https://plumephp.com/%E9%80%82%E5%90%88%E6%96%B0%E6%89%8B%E5%85%A5%E9%97%A8%E7%9A%84golang%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E6%8E%A8%E8%8D%90/</link><pubDate>Fri, 26 Jul 2024 11:54:31 +0800</pubDate><guid>https://plumephp.com/%E9%80%82%E5%90%88%E6%96%B0%E6%89%8B%E5%85%A5%E9%97%A8%E7%9A%84golang%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E6%8E%A8%E8%8D%90/</guid><description>&lt;h2 id="推荐项目列表"&gt;推荐项目列表&lt;/h2&gt;
&lt;p&gt;以下是一些适合新手入门的 Golang 开源项目推荐，包含具体的链接和项目介绍：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Go 语言核心库&lt;/strong&gt;：学习 Go，首先要了解的就是 Go 语言本身。Go 是由 Google 开发的开源编程语言，适合构建简单、可靠和高效的软件。&lt;a href="https://github.com/golang/go"&gt;Go GitHub 地址&lt;/a&gt;&lt;/p&gt;</description></item><item><title>用golang实现一个并发爬虫</title><link>https://plumephp.com/%E7%94%A8golang%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E5%B9%B6%E5%8F%91%E7%88%AC%E8%99%AB/</link><pubDate>Wed, 24 Jul 2024 22:53:23 +0800</pubDate><guid>https://plumephp.com/%E7%94%A8golang%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E5%B9%B6%E5%8F%91%E7%88%AC%E8%99%AB/</guid><description>&lt;p&gt;在Go语言中实现一个并发爬虫，可以使用标准库中的&lt;code&gt;net/http&lt;/code&gt;包来发送HTTP请求，使用&lt;code&gt;golang.org/x/net/html&lt;/code&gt;包来解析HTML，以及使用&lt;code&gt;sync&lt;/code&gt;包中的&lt;code&gt;WaitGroup&lt;/code&gt;来同步并发的goroutines。下面是一个简单的并发爬虫示例，它会并发地访问网页，并打印出网页中的所有链接。&lt;/p&gt;</description></item><item><title>Golang进阶教程</title><link>https://plumephp.com/golang%E8%BF%9B%E9%98%B6%E6%95%99%E7%A8%8B/</link><pubDate>Tue, 23 Jul 2024 20:27:55 +0800</pubDate><guid>https://plumephp.com/golang%E8%BF%9B%E9%98%B6%E6%95%99%E7%A8%8B/</guid><description>&lt;p&gt;Go 语言进阶教程将涵盖一些高级主题和最佳实践，这些是超越基础语法和常规用法的。以下是一些进阶主题的详细介绍：&lt;/p&gt;
&lt;h2 id="1-接口interfaces"&gt;1. 接口（Interfaces）&lt;/h2&gt;
&lt;p&gt;接口在 Go 中是一种强大的方式来实现多态和动态派发。接口是一个定义了一组方法的类型。&lt;/p&gt;</description></item><item><title>如何使用Go实现生产者-消费者模式</title><link>https://plumephp.com/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8go%E5%AE%9E%E7%8E%B0%E7%94%9F%E4%BA%A7%E8%80%85-%E6%B6%88%E8%B4%B9%E8%80%85%E6%A8%A1%E5%BC%8F/</link><pubDate>Tue, 23 Jul 2024 18:18:11 +0800</pubDate><guid>https://plumephp.com/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8go%E5%AE%9E%E7%8E%B0%E7%94%9F%E4%BA%A7%E8%80%85-%E6%B6%88%E8%B4%B9%E8%80%85%E6%A8%A1%E5%BC%8F/</guid><description>&lt;p&gt;在 Go 语言中，使用 channels 实现生产者-消费者模式是一种常见的并发编程模式。生产者负责生成数据，消费者负责处理数据。通过 channels，生产者和消费者可以安全地交换数据。&lt;/p&gt;</description></item><item><title>Go语言中的并发模型简介</title><link>https://plumephp.com/go%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%9A%84%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B%E7%AE%80%E4%BB%8B/</link><pubDate>Tue, 23 Jul 2024 18:15:08 +0800</pubDate><guid>https://plumephp.com/go%E8%AF%AD%E8%A8%80%E4%B8%AD%E7%9A%84%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B%E7%AE%80%E4%BB%8B/</guid><description>&lt;p&gt;Go 语言的并发模型主要基于 goroutines 和 channels。下面我将分别解释它们是如何工作的，并提供一个简单的例子。&lt;/p&gt;
&lt;h2 id="1-goroutines"&gt;1. Goroutines&lt;/h2&gt;
&lt;p&gt;Goroutines 是 Go 语言实现并发的核心。它们是 Go 程序中轻量级的执行线程，由 Go 运行时管理。Goroutine 的调度是由 Go 运行时进行的，与操作系统的线程调度是分开的。&lt;/p&gt;</description></item><item><title>Golang快速入门</title><link>https://plumephp.com/golang%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/</link><pubDate>Tue, 23 Jul 2024 13:16:38 +0800</pubDate><guid>https://plumephp.com/golang%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8/</guid><description>&lt;p&gt;Go 语言（通常称为 Golang）是一种开源编程语言，由 Google 的 Robert Griesemer、Rob Pike 和 Ken Thompson 在 2007 年设计并开发。它以简洁、高效、性能优异而著称，特别适合用于构建大型软件系统、网络服务和并发程序。&lt;/p&gt;</description></item><item><title>Golang学习路线参考</title><link>https://plumephp.com/golang%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF%E5%8F%82%E8%80%83/</link><pubDate>Tue, 23 Jul 2024 13:11:46 +0800</pubDate><guid>https://plumephp.com/golang%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF%E5%8F%82%E8%80%83/</guid><description>&lt;p&gt;Go 语言（Golang）是一种静态类型、编译型语言，设计上注重简洁和高效，非常适合用来构建大型软件系统。以下是一份详细的 Go 语言学习路线：&lt;/p&gt;
&lt;h2 id="2025-go-入门专题入口"&gt;2025 Go 入门专题入口&lt;/h2&gt;
&lt;p&gt;如果你希望按具体场景学习标准库和工程实践，可以先打开这份专题目录：&lt;a href="https://plumephp.com/go-2025-beginner-learning-path"&gt;Go 2025 入门专题目录：从能写小程序到能维护小服务&lt;/a&gt;。它把 2025 年的 Go 入门文章按基础工具、HTTP、数据文件、并发、测试、安全网络、观测部署等方向整理成阅读路径，适合在掌握基础语法后继续系统练习。&lt;/p&gt;</description></item><item><title>Go channel 流水线入门：生产、处理、取消和关闭怎么配合</title><link>https://plumephp.com/go-channel-cancellation-pipeline-2024/</link><pubDate>Mon, 22 Jul 2024 08:49:00 +0800</pubDate><guid>https://plumephp.com/go-channel-cancellation-pipeline-2024/</guid><description>&lt;p&gt;channel 是 Go 很有代表性的特性，但很多初学者会把它写成“能跑就行”的并发代码：谁关闭 channel 不清楚，错误怎么返回不清楚，取消后 goroutine 会不会退出也不清楚。短期看只是偶尔卡住，长期看就是 goroutine 泄漏和线上不稳定。&lt;/p&gt;</description></item><item><title>Go 与消息队列：Kafka 和 RabbitMQ 实战</title><link>https://plumephp.com/86-message-queues/</link><pubDate>Sat, 20 Jul 2024 13:30:00 +0800</pubDate><guid>https://plumephp.com/86-message-queues/</guid><description>&lt;h2 id="go-与消息队列kafka-和-rabbitmq-实战"&gt;Go 与消息队列：Kafka 和 RabbitMQ 实战&lt;/h2&gt;
&lt;p&gt;凌晨三点，你的电商系统正在做促销活动。突然，订单量暴增 10 倍。数据库扛不住了，CPU 飙到 100%，订单服务开始超时，支付回调丢失，库存扣减不一致……整个系统像多米诺骨牌一样倒下了。&lt;/p&gt;</description></item><item><title>Go 内存缓存入门：TTL、并发安全和清理策略</title><link>https://plumephp.com/go-inmemory-cache-ttl-2024/</link><pubDate>Tue, 16 Jul 2024 16:07:00 +0800</pubDate><guid>https://plumephp.com/go-inmemory-cache-ttl-2024/</guid><description>&lt;p&gt;不是所有缓存都需要 Redis。很多小型服务只想把低频变化的配置、字典表、权限规则在进程内缓存几分钟，减少数据库查询。Go 里写一个简单内存缓存并不难，但要注意并发安全、过期时间和清理策略。&lt;/p&gt;</description></item><item><title>Go 构建版本信息入门：把 commit、版本号和构建时间写进程序</title><link>https://plumephp.com/go-build-version-info-2024/</link><pubDate>Tue, 09 Jul 2024 14:28:00 +0800</pubDate><guid>https://plumephp.com/go-build-version-info-2024/</guid><description>&lt;p&gt;程序上线后，最常见的排查问题之一是：现在跑的到底是哪一个版本？代码仓库里修了 bug，不代表服务器已经部署了。镜像构建成功，不代表线上实例都更新了。把版本号、Git commit 和构建时间写进程序，是一个很小但很有用的习惯。&lt;/p&gt;</description></item><item><title>Go 分页 API 入门：limit、cursor 和稳定排序</title><link>https://plumephp.com/go-pagination-api-2024/</link><pubDate>Thu, 27 Jun 2024 13:25:00 +0800</pubDate><guid>https://plumephp.com/go-pagination-api-2024/</guid><description>&lt;p&gt;列表接口一开始很容易写成“查全部”。数据少时没问题，数据变多后就会慢、占内存，还会让前端一次拿到太多无用数据。分页是 API 的基本功。Go 写分页不难，难的是把参数校验、排序稳定性和响应结构设计清楚。&lt;/p&gt;</description></item><item><title>Go context 取消原因入门：让超时和主动取消更容易排查</title><link>https://plumephp.com/go-context-cancellation-cause-2024/</link><pubDate>Fri, 21 Jun 2024 08:57:00 +0800</pubDate><guid>https://plumephp.com/go-context-cancellation-cause-2024/</guid><description>&lt;h2 id="取消不只是停止还要知道为什么停止"&gt;取消不只是停止，还要知道为什么停止&lt;/h2&gt;
&lt;p&gt;Go 的 &lt;code&gt;context.Context&lt;/code&gt; 常用于请求范围控制：客户端断开，请求超时，后台任务停止，外部调用取消。早期我们通常只能通过 &lt;code&gt;ctx.Err()&lt;/code&gt; 知道是 &lt;code&gt;context.Canceled&lt;/code&gt; 还是 &lt;code&gt;context.DeadlineExceeded&lt;/code&gt;。这已经有用，但在复杂服务里，有时还想知道更具体的原因：是用户主动取消，还是权限检查失败后取消后续任务，还是上游熔断导致停止。&lt;/p&gt;</description></item><item><title>Go 文件上传入门：限制大小、校验类型和安全保存</title><link>https://plumephp.com/go-file-upload-server-2024/</link><pubDate>Tue, 18 Jun 2024 18:26:00 +0800</pubDate><guid>https://plumephp.com/go-file-upload-server-2024/</guid><description>&lt;p&gt;文件上传看起来只是接收一个 multipart 表单，但真正写到服务里，会遇到不少边界：用户上传超大文件怎么办，文件名里带路径怎么办，扩展名能不能信，保存失败如何处理，旧文件要不要删除。初学者最容易写出能跑但不安全的代码。&lt;/p&gt;</description></item><item><title>Go 1.24 新特性：持续进化的 Go</title><link>https://plumephp.com/83-go-1.24-features/</link><pubDate>Mon, 10 Jun 2024 11:00:00 +0800</pubDate><guid>https://plumephp.com/83-go-1.24-features/</guid><description>&lt;h2 id="go-124-新特性持续进化的-go"&gt;Go 1.24 新特性：持续进化的 Go&lt;/h2&gt;
&lt;p&gt;2024 年 8 月，Go 1.24 正式发布。这是 Go 语言诞生 15 年来的又一个&amp;quot;稳步前行&amp;quot;的版本。&lt;/p&gt;
&lt;p&gt;如果说 Go 1.23 的迭代器是&amp;quot;重大突破&amp;quot;，那 Go 1.24 就是&amp;quot;精雕细琢&amp;quot;。这个版本没有改变语言的核心语法，但在标准库、性能、工具链方面做了大量改进。&lt;code&gt;weak&lt;/code&gt; 包的引入让弱引用终于有了官方支持，&lt;code&gt;net/http&lt;/code&gt; 的增强让标准库的路由能力更上一层楼，PGO 的成熟让性能优化变得更加自动化。&lt;/p&gt;</description></item><item><title>Go 邮件模板入门：把验证码和通知邮件渲染清楚</title><link>https://plumephp.com/go-email-template-rendering-2024/</link><pubDate>Mon, 03 Jun 2024 10:31:00 +0800</pubDate><guid>https://plumephp.com/go-email-template-rendering-2024/</guid><description>&lt;p&gt;邮件模板听起来像前端工作，但后端经常要负责生成验证码、邀请链接、账单通知和任务提醒。很多初学项目一开始直接用 &lt;code&gt;fmt.Sprintf&lt;/code&gt; 拼 HTML，写到第二封邮件时就会变得难维护。Go 的 &lt;code&gt;html/template&lt;/code&gt; 很适合渲染 HTML 邮件，因为它会自动转义用户数据。&lt;/p&gt;</description></item><item><title>Go 密码重置 Token 入门：随机数、过期时间和一次性使用</title><link>https://plumephp.com/go-password-reset-token-2024/</link><pubDate>Tue, 28 May 2024 08:52:00 +0800</pubDate><guid>https://plumephp.com/go-password-reset-token-2024/</guid><description>&lt;p&gt;找回密码看起来是一个普通功能：用户输入邮箱，系统发送链接，用户点开后设置新密码。真正实现时，最关键的是 token。它必须足够随机，不能长期有效，最好一次性使用，数据库里也不应该明文保存。&lt;/p&gt;</description></item><item><title>Go database/sql 上下文入门：QueryContext、超时和连接释放</title><link>https://plumephp.com/go-sql-query-context-2024/</link><pubDate>Thu, 23 May 2024 11:21:00 +0800</pubDate><guid>https://plumephp.com/go-sql-query-context-2024/</guid><description>&lt;p&gt;&lt;code&gt;database/sql&lt;/code&gt; 是 Go 标准库里非常重要的包。它的 API 看起来不复杂，但初学者常常在几个地方踩坑：查询没有超时、忘记关闭 &lt;code&gt;Rows&lt;/code&gt;、把 &lt;code&gt;context.Background()&lt;/code&gt; 写在仓储层、扫描错误处理不完整。短期看只是代码能不能跑，长期看会影响连接池、请求取消和线上稳定性。&lt;/p&gt;</description></item><item><title>Go 模糊测试入门：让工具帮你发现边界输入</title><link>https://plumephp.com/go-fuzz-testing-introduction-2024/</link><pubDate>Thu, 16 May 2024 11:33:00 +0800</pubDate><guid>https://plumephp.com/go-fuzz-testing-introduction-2024/</guid><description>&lt;h2 id="有些-bug-不是你能手写用例想到的"&gt;有些 bug 不是你能手写用例想到的&lt;/h2&gt;
&lt;p&gt;普通单元测试依赖我们自己列举输入。表驱动测试已经能覆盖很多边界，但面对解析器、编码转换、路径处理、压缩数据、模板变量、协议字段这类函数，人很难穷尽所有奇怪输入。模糊测试的思路是：你写出基本性质和种子用例，让工具不断生成新输入，尝试触发 panic、越界、死循环或违反性质的结果。&lt;/p&gt;</description></item><item><title>Go 与微服务：服务发现与注册实战</title><link>https://plumephp.com/85-service-discovery/</link><pubDate>Sun, 12 May 2024 10:45:00 +0800</pubDate><guid>https://plumephp.com/85-service-discovery/</guid><description>&lt;h2 id="go-与微服务服务发现与注册实战"&gt;Go 与微服务：服务发现与注册实战&lt;/h2&gt;
&lt;p&gt;想象一下这样的场景：你的电商系统从单体架构拆成了 20 个微服务。用户服务、订单服务、支付服务、库存服务……它们分布在不同的服务器上，IP 地址随时可能变化（容器重启、自动扩缩容）。这时候问题来了：&lt;strong&gt;订单服务怎么知道支付服务的地址？&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Go CSV 导入入门：逐行读取、校验和错误报告</title><link>https://plumephp.com/go-csv-import-validation-2024/</link><pubDate>Fri, 10 May 2024 17:45:00 +0800</pubDate><guid>https://plumephp.com/go-csv-import-validation-2024/</guid><description>&lt;p&gt;CSV 导入是很多后台系统都会遇到的功能。用户从表格软件导出一份文件，上传到系统里批量创建用户、商品或订单。Go 标准库的 &lt;code&gt;encoding/csv&lt;/code&gt; 能完成大部分基础工作，但真正难的是边界：表头不对怎么办，某一行邮箱为空怎么办，转换失败怎么告诉用户，文件很大时能不能逐行处理。&lt;/p&gt;</description></item><item><title>Go 结构化错误入门：错误码、包装和 HTTP 映射</title><link>https://plumephp.com/go-structured-error-types-2024/</link><pubDate>Mon, 29 Apr 2024 08:39:00 +0800</pubDate><guid>https://plumephp.com/go-structured-error-types-2024/</guid><description>&lt;p&gt;Go 的错误处理很直接：函数返回 &lt;code&gt;error&lt;/code&gt;，调用方检查它。但项目变大后，如果所有错误都只是 &lt;code&gt;fmt.Errorf(&amp;quot;bad request&amp;quot;)&lt;/code&gt;，HTTP 层就很难判断该返回 400、404 还是 500；日志里也很难分清用户错误和系统错误。结构化错误的目标不是复杂化，而是让错误携带稳定语义。&lt;/p&gt;</description></item><item><title>Go 工作区入门：用 go work 同时开发多个本地模块</title><link>https://plumephp.com/go-workspaces-go-work-2024/</link><pubDate>Wed, 24 Apr 2024 16:19:00 +0800</pubDate><guid>https://plumephp.com/go-workspaces-go-work-2024/</guid><description>&lt;h2 id="多模块本地联调不一定要改-gomod"&gt;多模块本地联调不一定要改 go.mod&lt;/h2&gt;
&lt;p&gt;很多团队会把通用库和业务服务放在不同仓库或不同模块里。比如你正在开发 &lt;code&gt;user-api&lt;/code&gt;，它依赖本地的 &lt;code&gt;kit&lt;/code&gt; 模块。以前常见做法是在 &lt;code&gt;user-api/go.mod&lt;/code&gt; 里写 &lt;code&gt;replace&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;kit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;kit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这能工作，但如果你不小心把个人本地路径提交了，其他同事可能无法构建。&lt;code&gt;go work&lt;/code&gt; 提供了另一种方式：在本地创建工作区，把多个模块临时组合起来开发，而不必修改每个模块的 &lt;code&gt;go.mod&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Go 与 GraphQL：构建灵活的 API</title><link>https://plumephp.com/87-graphql-api/</link><pubDate>Thu, 18 Apr 2024 08:30:00 +0800</pubDate><guid>https://plumephp.com/87-graphql-api/</guid><description>&lt;h2 id="go-与-graphql构建灵活的-api"&gt;Go 与 GraphQL：构建灵活的 API&lt;/h2&gt;
&lt;p&gt;你用过 REST API 吗？肯定用过。但你有没有遇到过这样的场景：前端需要用户信息、用户的订单列表、以及每个订单的商品详情。用 REST 的话，你可能需要调用 &lt;code&gt;/users/123&lt;/code&gt;、&lt;code&gt;/users/123/orders&lt;/code&gt;、然后对每个订单再调用 &lt;code&gt;/orders/456/products&lt;/code&gt;。这就是经典的 N+1 请求问题。&lt;/p&gt;</description></item><item><title>Go 临时文件入门：os.CreateTemp、MkdirTemp 和清理责任</title><link>https://plumephp.com/go-temp-files-safe-cleanup-2024/</link><pubDate>Wed, 17 Apr 2024 18:40:00 +0800</pubDate><guid>https://plumephp.com/go-temp-files-safe-cleanup-2024/</guid><description>&lt;p&gt;临时文件经常出现在后端任务里：下载一个文件后解析，生成报表后上传，图片转换时落盘，或者调用某个只接受文件路径的外部工具。很多初学者会手写 &lt;code&gt;/tmp/demo.txt&lt;/code&gt;，这在并发和安全上都不稳。Go 标准库提供了 &lt;code&gt;os.CreateTemp&lt;/code&gt; 和 &lt;code&gt;os.MkdirTemp&lt;/code&gt;，应该优先使用它们。&lt;/p&gt;</description></item><item><title>Go 超时和重试入门：context、退避和幂等性一起看</title><link>https://plumephp.com/go-timeout-retry-backoff-2024/</link><pubDate>Thu, 11 Apr 2024 14:08:00 +0800</pubDate><guid>https://plumephp.com/go-timeout-retry-backoff-2024/</guid><description>&lt;p&gt;写后端服务时，外部调用失败是常态。网络会抖，依赖会慢，网关会返回 502，数据库也可能短暂不可用。初学者最容易写出两种极端代码：一种完全不重试，偶发错误直接暴露给用户；另一种无脑重试很多次，把一个小抖动放大成更大的压力。正确做法不是“永远重试”或“永不重试”，而是把超时、退避和幂等性一起考虑。&lt;/p&gt;</description></item><item><title>Go JSON API 入门：Handler、请求解析和统一响应怎么组织</title><link>https://plumephp.com/go-http-json-api-structure-2024/</link><pubDate>Tue, 26 Mar 2024 09:37:00 +0800</pubDate><guid>https://plumephp.com/go-http-json-api-structure-2024/</guid><description>&lt;p&gt;Go 标准库写 JSON API 并不难，难的是写到第十个接口时还能保持清楚。很多初学项目一开始把所有逻辑塞进 handler：解析 JSON、校验字段、查数据库、拼响应、写错误。前两个接口还行，接口多了以后，错误格式不统一，测试也不好写。&lt;/p&gt;</description></item><item><title>Go io.Pipe 入门：边生成边上传的流式处理</title><link>https://plumephp.com/go-io-pipe-streaming-2024/</link><pubDate>Mon, 18 Mar 2024 10:57:00 +0800</pubDate><guid>https://plumephp.com/go-io-pipe-streaming-2024/</guid><description>&lt;p&gt;很多导出功能会先把全部数据生成到内存或临时文件，再上传到对象存储。数据少时没问题，数据大时就会占用很多内存或磁盘。Go 的 &lt;code&gt;io.Pipe&lt;/code&gt; 可以把写入端和读取端连接起来：一边生成，一边被另一边读取，适合流式处理。&lt;/p&gt;</description></item><item><title>Go 1.23 回顾：迭代器与新标准库</title><link>https://plumephp.com/82-go-1.23-recap/</link><pubDate>Fri, 15 Mar 2024 14:20:00 +0800</pubDate><guid>https://plumephp.com/82-go-1.23-recap/</guid><description>&lt;h2 id="go-123-回顾迭代器与新标准库"&gt;Go 1.23 回顾：迭代器与新标准库&lt;/h2&gt;
&lt;p&gt;如果说 Go 1.18 的泛型是&amp;quot;众望所归&amp;quot;，那 Go 1.23 的迭代器就是&amp;quot;姗姗来迟&amp;quot;。&lt;/p&gt;
&lt;p&gt;2024 年 8 月，Go 1.23 正式发布。这个版本最引人注目的特性，莫过于 &lt;strong&gt;range over function&lt;/strong&gt;（即迭代器模式）的全面开放。除此之外，&lt;code&gt;unique&lt;/code&gt; 包、&lt;code&gt;time&lt;/code&gt; 的改进、&lt;code&gt;slices&lt;/code&gt; 和 &lt;code&gt;maps&lt;/code&gt; 包的新方法，以及持续的性能优化，都让这个版本值得一探究竟。&lt;/p&gt;</description></item><item><title>Go ServeMux 路由入门：用标准库写更清楚的 HTTP 路由</title><link>https://plumephp.com/go-servemux-routing-2024/</link><pubDate>Tue, 12 Mar 2024 09:42:00 +0800</pubDate><guid>https://plumephp.com/go-servemux-routing-2024/</guid><description>&lt;h2 id="标准库路由也能写得很舒服"&gt;标准库路由也能写得很舒服&lt;/h2&gt;
&lt;p&gt;很长一段时间里，很多人写 Go Web 服务会直接选择 Gin、Echo、Chi 这类框架。它们当然好用，但标准库 &lt;code&gt;net/http&lt;/code&gt; 也一直在进步。现代 Go 的 &lt;code&gt;ServeMux&lt;/code&gt; 已经能表达更清楚的路由模式，包括 HTTP 方法和路径参数。对于小型 JSON API、内部工具和学习项目，标准库完全可以胜任。&lt;/p&gt;</description></item><item><title>Go 限流入门：用令牌桶保护一个 HTTP 接口</title><link>https://plumephp.com/go-rate-limit-token-bucket-2024/</link><pubDate>Thu, 07 Mar 2024 20:17:00 +0800</pubDate><guid>https://plumephp.com/go-rate-limit-token-bucket-2024/</guid><description>&lt;p&gt;限流不是大公司才需要的功能。一个公开表单、一个登录接口、一个 AI 调用入口，如果没有任何限制，很容易被误用、刷爆或拖垮下游依赖。对 Go 初学者来说，限流最值得先理解的是“保护边界”：限制请求进入系统的速度，而不是等数据库或外部服务扛不住后再处理错误。&lt;/p&gt;</description></item><item><title>构建现代 CLI 工具：cobra 框架完全指南</title><link>https://plumephp.com/84-cli-with-cobra/</link><pubDate>Wed, 28 Feb 2024 16:10:00 +0800</pubDate><guid>https://plumephp.com/84-cli-with-cobra/</guid><description>&lt;h2 id="构建现代-cli-工具cobra-框架完全指南"&gt;构建现代 CLI 工具：cobra 框架完全指南&lt;/h2&gt;
&lt;p&gt;你有没有用过 &lt;code&gt;kubectl&lt;/code&gt;、&lt;code&gt;hugo&lt;/code&gt;、&lt;code&gt;docker&lt;/code&gt; 这些命令行工具？它们的用户体验堪称一流：清晰的帮助信息、自动补全、子命令嵌套、丰富的标志参数……每次使用都感觉丝滑得不可思议。你有没有想过，这些工具是怎么做到的？&lt;/p&gt;</description></item><item><title>Go slog 入门：用结构化日志记录更清楚的服务行为</title><link>https://plumephp.com/go-slog-structured-logging-2024/</link><pubDate>Tue, 27 Feb 2024 15:08:00 +0800</pubDate><guid>https://plumephp.com/go-slog-structured-logging-2024/</guid><description>&lt;h2 id="日志从字符串变成结构"&gt;日志从字符串变成结构&lt;/h2&gt;
&lt;p&gt;传统日志经常这样写：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;user %d login failed: %v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它能看，但机器不太好解析。后来很多服务会输出 JSON 日志，字段里有 &lt;code&gt;level&lt;/code&gt;、&lt;code&gt;time&lt;/code&gt;、&lt;code&gt;user_id&lt;/code&gt;、&lt;code&gt;path&lt;/code&gt;、&lt;code&gt;duration_ms&lt;/code&gt;。这样日志平台可以按字段搜索、聚合和告警。Go 标准库里的 &lt;code&gt;log/slog&lt;/code&gt; 就是为了结构化日志提供统一接口。&lt;/p&gt;</description></item><item><title>Go singleflight 思路入门：避免缓存击穿时重复查询</title><link>https://plumephp.com/go-singleflight-cache-miss-2024/</link><pubDate>Wed, 21 Feb 2024 09:18:00 +0800</pubDate><guid>https://plumephp.com/go-singleflight-cache-miss-2024/</guid><description>&lt;p&gt;缓存能减轻数据库压力，但缓存失效那一刻也可能制造压力。比如热门用户资料过期，突然来了 200 个请求，如果每个请求都发现缓存没有，然后同时查数据库，数据库会被瞬间打满。这类问题常被称为缓存击穿。&lt;/p&gt;</description></item><item><title>Go HTML 模板安全入门：自动转义、模板函数和页面渲染边界</title><link>https://plumephp.com/go-template-security-html-2024/</link><pubDate>Fri, 16 Feb 2024 16:42:00 +0800</pubDate><guid>https://plumephp.com/go-template-security-html-2024/</guid><description>&lt;p&gt;很多人第一次用 Go 写 Web 页面，会在 &lt;code&gt;text/template&lt;/code&gt; 和 &lt;code&gt;html/template&lt;/code&gt; 之间犹豫。两个包 API 很像，名字也很像，但用途不一样。只要输出的是 HTML 页面，通常就应该使用 &lt;code&gt;html/template&lt;/code&gt;。它最重要的能力是自动上下文转义：用户输入出现在 HTML 文本、属性、JavaScript 或 URL 位置时，模板引擎会按位置做不同处理，降低 XSS 风险。&lt;/p&gt;</description></item><item><title>Go Cookie 和 Session 入门：登录态到底放在哪里</title><link>https://plumephp.com/go-cookie-session-basics-2024/</link><pubDate>Thu, 08 Feb 2024 13:56:00 +0800</pubDate><guid>https://plumephp.com/go-cookie-session-basics-2024/</guid><description>&lt;p&gt;做 Web 应用时，登录态是绕不开的话题。很多初学者会问：用户登录后，是不是把用户 ID 放到 Cookie 里？Session 和 Cookie 有什么区别？退出登录时要删什么？这些问题看似简单，但如果边界没想清楚，很容易留下安全隐患。&lt;/p&gt;</description></item><item><title>Go 1.22 循环变量入门：为什么子测试和 goroutine 更不容易踩坑了</title><link>https://plumephp.com/go-122-loop-variable-subtests-2024/</link><pubDate>Mon, 29 Jan 2024 10:18:00 +0800</pubDate><guid>https://plumephp.com/go-122-loop-variable-subtests-2024/</guid><description>&lt;p&gt;Go 里有一个经典坑：在循环里启动 goroutine，或者在表驱动测试里写 &lt;code&gt;t.Run&lt;/code&gt;，最后所有闭包都拿到了同一个循环变量。很多老 Go 程序员都会下意识写一行 &lt;code&gt;tt := tt&lt;/code&gt;，这行代码看起来有点奇怪，但它曾经非常重要。到了 Go 1.22，循环变量的语义发生了调整，很多这类问题会自然消失。&lt;/p&gt;</description></item><item><title>Go httptrace 入门：看清一次 HTTP 请求慢在哪里</title><link>https://plumephp.com/go-httptrace-client-debug-2024/</link><pubDate>Thu, 25 Jan 2024 15:36:00 +0800</pubDate><guid>https://plumephp.com/go-httptrace-client-debug-2024/</guid><description>&lt;p&gt;写 Go 服务时，调用外部接口慢是很常见的问题。日志里只看到“请求花了 2 秒”，但这 2 秒到底花在哪里？是 DNS 慢、建立连接慢、TLS 握手慢、服务端处理慢，还是响应体下载慢？如果只能靠猜，排查会很痛苦。&lt;/p&gt;</description></item><item><title>Go 1.22 新特性：range over integers 和 math/rand/v2</title><link>https://plumephp.com/81-go-1.22-features/</link><pubDate>Sat, 20 Jan 2024 09:30:00 +0800</pubDate><guid>https://plumephp.com/81-go-1.22-features/</guid><description>&lt;h2 id="go-122-新特性range-over-integers-和-mathrandv2"&gt;Go 1.22 新特性：range over integers 和 math/rand/v2&lt;/h2&gt;
&lt;p&gt;2024 年 2 月，Go 1.22 正式发布。这是 Go 语言进入第 15 个年头后的第一份&amp;quot;新年礼物&amp;quot;。&lt;/p&gt;
&lt;p&gt;如果你以为这又是一个&amp;quot;修修 bug、调调性能&amp;quot;的小版本，那你可能会大吃一惊。Go 1.22 带来了几个让社区讨论热烈的改动——&lt;code&gt;for range&lt;/code&gt; 终于支持整数了，&lt;code&gt;math/rand&lt;/code&gt; 终于有了 v2 版本，&lt;code&gt;net/http.ServeMux&lt;/code&gt; 终于能按 HTTP Method 路由了。&lt;/p&gt;</description></item><item><title>Go 泛型入门：从重复函数到类型参数的自然过渡</title><link>https://plumephp.com/go-generics-practical-introduction-2024/</link><pubDate>Thu, 18 Jan 2024 10:26:00 +0800</pubDate><guid>https://plumephp.com/go-generics-practical-introduction-2024/</guid><description>&lt;h2 id="泛型不是为了把所有代码都写成抽象"&gt;泛型不是为了把所有代码都写成抽象&lt;/h2&gt;
&lt;p&gt;Go 很长时间没有泛型，所以早期 Go 代码大量依赖具体类型、小接口、函数参数和适度重复。后来 Go 加入类型参数后，很多重复工具函数终于可以写得更自然，比如 &lt;code&gt;Contains&lt;/code&gt;、&lt;code&gt;Map&lt;/code&gt;、&lt;code&gt;Filter&lt;/code&gt;、&lt;code&gt;Set&lt;/code&gt; 这类集合操作。但泛型并不意味着所有代码都应该泛型化。&lt;/p&gt;</description></item><item><title>Go HTTP Basic Auth 入门：用中间件保护内部页面</title><link>https://plumephp.com/go-basic-auth-middleware-2024/</link><pubDate>Thu, 11 Jan 2024 09:24:00 +0800</pubDate><guid>https://plumephp.com/go-basic-auth-middleware-2024/</guid><description>&lt;p&gt;很多小型 Go 服务都有一个内部页面：查看任务队列、触发一次同步、检查缓存状态。它不一定值得接入完整登录系统，但也不能裸奔在公网。HTTP Basic Auth 是一个简单选择。浏览器会弹出用户名密码框，请求头里带上凭据，服务端验证后再允许访问。&lt;/p&gt;</description></item><item><title>2023 年 Go 语言年度回顾与展望</title><link>https://plumephp.com/80-go-2023-recap/</link><pubDate>Wed, 20 Dec 2023 10:00:00 +0800</pubDate><guid>https://plumephp.com/80-go-2023-recap/</guid><description>&lt;h2 id="2023-年-go-语言年度回顾与展望"&gt;2023 年 Go 语言年度回顾与展望&lt;/h2&gt;
&lt;p&gt;2023 年是 Go 语言发展历程中非常重要的一年。从 2009 年开源至今，Go 已经走过了 14 个年头。这一年，Go 发布了两个重要版本（1.20 和 1.21），生态系统持续壮大，社区活跃度再创新高。&lt;/p&gt;</description></item><item><title>Go 配置校验入门：让服务启动时就发现问题</title><link>https://plumephp.com/go-config-validation-2023/</link><pubDate>Thu, 07 Dec 2023 09:26:00 +0800</pubDate><guid>https://plumephp.com/go-config-validation-2023/</guid><description>&lt;h2 id="配置错误应该在启动时暴露"&gt;配置错误应该在启动时暴露&lt;/h2&gt;
&lt;p&gt;很多线上问题不是代码逻辑错，而是配置错：端口写错，数据库地址为空，第三方 API URL 少了协议，生产环境打开了调试开关，超时时间是 0。最糟糕的情况是服务启动成功，直到请求进来才暴露配置问题。&lt;/p&gt;</description></item><item><title>Go 子测试组织入门：表驱动、t.Run 和测试命名怎么写</title><link>https://plumephp.com/go-table-tests-subtests-2023/</link><pubDate>Mon, 04 Dec 2023 14:27:00 +0800</pubDate><guid>https://plumephp.com/go-table-tests-subtests-2023/</guid><description>&lt;p&gt;Go 的测试工具很朴素，没有复杂的断言 DSL，也没有必须学习一整套框架的压力。你只要创建一个 &lt;code&gt;xxx_test.go&lt;/code&gt; 文件，写一个 &lt;code&gt;func TestXxx(t *testing.T)&lt;/code&gt;，再运行 &lt;code&gt;go test&lt;/code&gt;，就已经能开始了。也正因为它朴素，很多初学者会在项目变大后遇到另一个问题：测试越写越散，失败信息不清楚，新增一个场景要复制一段函数，最后大家都不太愿意补测试。&lt;/p&gt;</description></item><item><title>Go Worker 队列入门：用 channel 和 context 处理后台任务</title><link>https://plumephp.com/go-worker-queue-basics-2023/</link><pubDate>Fri, 20 Oct 2023 15:39:00 +0800</pubDate><guid>https://plumephp.com/go-worker-queue-basics-2023/</guid><description>&lt;h2 id="后台任务不应该都塞在-http-请求里"&gt;后台任务不应该都塞在 HTTP 请求里&lt;/h2&gt;
&lt;p&gt;很多 Web 服务会遇到一些不适合在请求里同步完成的事情：发送邮件、生成报表、处理图片、同步第三方数据、写审计日志。简单项目里，你可能先在 handler 里直接调用这些逻辑，但请求会变慢，也更容易受外部系统影响。&lt;/p&gt;</description></item><item><title>Go 基准测试和分配入门：用 -benchmem 看清楚优化是否值得</title><link>https://plumephp.com/go-benchmark-allocations-2023/</link><pubDate>Fri, 13 Oct 2023 09:44:00 +0800</pubDate><guid>https://plumephp.com/go-benchmark-allocations-2023/</guid><description>&lt;p&gt;刚开始写 Go 的时候，很多人会把“性能优化”理解成一种直觉游戏：我觉得这个写法少了一层函数调用，应该更快；我觉得这个循环里用了 &lt;code&gt;append&lt;/code&gt;，应该会慢；我觉得字符串拼接换成 &lt;code&gt;strings.Builder&lt;/code&gt;，一定更专业。直觉有时候能帮你发现问题，但它不能替代测量。尤其在 Go 里，编译器、逃逸分析、运行时分配器都在悄悄做事，很多看上去“聪明”的写法，实际效果可能很小，甚至更差。&lt;/p&gt;</description></item><item><title>Go 安全编程：防范常见漏洞</title><link>https://plumephp.com/79-go-security-best-practices/</link><pubDate>Thu, 12 Oct 2023 15:40:00 +0800</pubDate><guid>https://plumephp.com/79-go-security-best-practices/</guid><description>&lt;h2 id="go-安全编程防范常见漏洞"&gt;Go 安全编程：防范常见漏洞&lt;/h2&gt;
&lt;p&gt;安全是软件开发中最容易被忽视，却又最重要的环节。你可能写出功能完美、性能卓越的代码，但只要一个安全漏洞，就可能让整个系统崩溃，甚至导致用户数据泄露。&lt;/p&gt;</description></item><item><title>Go 1.21 工具链管理入门：go.mod 里的 go 和 toolchain 怎么理解</title><link>https://plumephp.com/go-121-toolchain-management-2023/</link><pubDate>Thu, 14 Sep 2023 18:08:00 +0800</pubDate><guid>https://plumephp.com/go-121-toolchain-management-2023/</guid><description>&lt;h2 id="为什么同一个项目在不同机器上表现不一样"&gt;为什么同一个项目在不同机器上表现不一样&lt;/h2&gt;
&lt;p&gt;Go 项目通常从 &lt;code&gt;go.mod&lt;/code&gt; 开始。你可能早就见过这一行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.21&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;很多初学者会把它理解成“必须用 Go 1.21 才能构建”。这个理解有一部分对，但不完整。&lt;code&gt;go&lt;/code&gt; 指令描述的是模块使用的 Go 语言版本语义和模块行为。到了 Go 1.21，工具链管理也变得更明确，&lt;code&gt;toolchain&lt;/code&gt; 指令开始进入一些项目的 &lt;code&gt;go.mod&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Go 1.23 迭代器：range over func 的革命</title><link>https://plumephp.com/76-iterators-go-1.23/</link><pubDate>Tue, 05 Sep 2023 11:00:00 +0800</pubDate><guid>https://plumephp.com/76-iterators-go-1.23/</guid><description>&lt;h2 id="go-123-迭代器range-over-func-的革命"&gt;Go 1.23 迭代器：range over func 的革命&lt;/h2&gt;
&lt;p&gt;Go 语言有一件事一直让人又爱又恨：&lt;code&gt;for range&lt;/code&gt; 循环。&lt;/p&gt;
&lt;p&gt;爱它是因为它简洁、安全、没有 C 语言那种 &lt;code&gt;for (int i = 0; i &amp;lt; n; i++)&lt;/code&gt; 的啰嗦；恨它是因为它能遍历的东西太少了——数组、切片、map、字符串、channel，就这几样。&lt;/p&gt;</description></item><item><title>Go CLI 子命令入门：用 flag.FlagSet 写清楚的命令行工具</title><link>https://plumephp.com/go-cli-flagset-subcommands-2023/</link><pubDate>Mon, 28 Aug 2023 20:22:00 +0800</pubDate><guid>https://plumephp.com/go-cli-flagset-subcommands-2023/</guid><description>&lt;h2 id="一个命令变多后main-很容易乱"&gt;一个命令变多后，main 很容易乱&lt;/h2&gt;
&lt;p&gt;Go 很适合写命令行工具。最开始你可能只有一个参数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;todo -file tasks.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;后来需求变成：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;todo add -title &lt;span class="s2"&gt;&amp;#34;学习 Go&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;todo list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;todo &lt;span class="k"&gt;done&lt;/span&gt; -id &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这时如果还把所有参数都塞进全局 &lt;code&gt;flag&lt;/code&gt;，&lt;code&gt;main.go&lt;/code&gt; 会很快变乱。标准库 &lt;code&gt;flag.FlagSet&lt;/code&gt; 可以为每个子命令单独解析参数。你不一定要马上引入 Cobra 这类库，小工具用标准库就能组织得很清楚。&lt;/p&gt;</description></item><item><title>Go SQL 事务入门：把转账、订单和库存写得更可靠</title><link>https://plumephp.com/go-sql-transaction-patterns-2023/</link><pubDate>Fri, 11 Aug 2023 10:52:00 +0800</pubDate><guid>https://plumephp.com/go-sql-transaction-patterns-2023/</guid><description>&lt;h2 id="事务保护的是一组必须一起成功的动作"&gt;事务保护的是一组必须一起成功的动作&lt;/h2&gt;
&lt;p&gt;数据库事务的核心不是“高级数据库功能”，而是一个很朴素的需求：几步操作要么全部成功，要么全部失败。转账时，从 A 扣钱和给 B 加钱必须一起发生；创建订单时，写订单和扣库存也必须保持一致。如果前一步成功，后一步失败，就会留下脏数据。&lt;/p&gt;</description></item><item><title>Go 项目结构：构建可维护的大型项目</title><link>https://plumephp.com/78-go-project-structure/</link><pubDate>Tue, 25 Jul 2023 13:15:00 +0800</pubDate><guid>https://plumephp.com/78-go-project-structure/</guid><description>&lt;h2 id="go-项目结构构建可维护的大型项目"&gt;Go 项目结构：构建可维护的大型项目&lt;/h2&gt;
&lt;p&gt;你是否遇到过这样的场景：打开一个 Go 项目，看到所有代码都堆在 &lt;code&gt;main.go&lt;/code&gt; 里，或者目录结构混乱得像一盘意大利面？随着项目规模增长，这种混乱的结构会让你每次修改代码都像是在拆炸弹——稍有不慎就会引发连锁反应。&lt;/p&gt;</description></item><item><title>Go 数据库 NULL 值入门：sql.NullString、指针和业务语义</title><link>https://plumephp.com/go-database-null-values-2023/</link><pubDate>Fri, 14 Jul 2023 09:12:00 +0800</pubDate><guid>https://plumephp.com/go-database-null-values-2023/</guid><description>&lt;h2 id="null-不是空字符串也不是-0"&gt;NULL 不是空字符串，也不是 0&lt;/h2&gt;
&lt;p&gt;数据库里经常会有 NULL。用户昵称可能未设置，文章发布时间可能为空，订单取消时间可能不存在。Go 里字符串零值是 &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;，整数零值是 &lt;code&gt;0&lt;/code&gt;，时间零值是 &lt;code&gt;time.Time{}&lt;/code&gt;。这些零值和数据库 NULL 不是一回事。把 NULL 粗暴扫进普通字段，轻则报错，重则丢失业务语义。&lt;/p&gt;</description></item><item><title>Go 测试覆盖率入门：数字之外更重要的是关键路径</title><link>https://plumephp.com/go-coverage-practical-guide-2023/</link><pubDate>Wed, 28 Jun 2023 15:17:00 +0800</pubDate><guid>https://plumephp.com/go-coverage-practical-guide-2023/</guid><description>&lt;h2 id="覆盖率是信号不是目标本身"&gt;覆盖率是信号，不是目标本身&lt;/h2&gt;
&lt;p&gt;Go 自带测试覆盖率工具。你可以很容易知道哪些代码被测试执行过，哪些没有。这对补测试很有帮助，但覆盖率数字本身并不等于质量。80% 覆盖率可能没有测到最关键的支付路径，40% 覆盖率也可能把核心业务规则保护得很好。&lt;/p&gt;</description></item><item><title>Go 1.21 泛型增强：cmp 包与内置 min/max</title><link>https://plumephp.com/74-go-1.21-generics-improvements/</link><pubDate>Thu, 15 Jun 2023 09:30:00 +0800</pubDate><guid>https://plumephp.com/74-go-1.21-generics-improvements/</guid><description>&lt;h2 id="go-121-泛型增强cmp-包与内置-minmax"&gt;Go 1.21 泛型增强：cmp 包与内置 min/max&lt;/h2&gt;
&lt;p&gt;Go 1.18 引入泛型后，社区反响热烈，但也伴随着一些疑问：&amp;ldquo;泛型是好东西，但标准库什么时候能用上？&amp;rdquo;&lt;/p&gt;</description></item><item><title>Go HTTP 客户端中间件入门：给外部调用加日志、重试和请求 ID</title><link>https://plumephp.com/go-http-client-middleware-2023/</link><pubDate>Fri, 09 Jun 2023 10:48:00 +0800</pubDate><guid>https://plumephp.com/go-http-client-middleware-2023/</guid><description>&lt;h2 id="客户端也需要中间件"&gt;客户端也需要中间件&lt;/h2&gt;
&lt;p&gt;我们经常给 HTTP 服务端写中间件：请求日志、鉴权、恢复 panic、请求 ID。其实 HTTP 客户端也有类似需求。调用外部 API 时，你可能希望统一加 User-Agent、加请求 ID、记录耗时、对 502/503 做有限重试。Go 标准库通过 &lt;code&gt;http.RoundTripper&lt;/code&gt; 支持这种扩展。&lt;/p&gt;</description></item><item><title>Go HTTP ResponseController 入门：更明确地控制响应写入</title><link>https://plumephp.com/go-http-response-controller-2023/</link><pubDate>Thu, 25 May 2023 10:31:00 +0800</pubDate><guid>https://plumephp.com/go-http-response-controller-2023/</guid><description>&lt;h2 id="有些响应不是一次写完的"&gt;有些响应不是一次写完的&lt;/h2&gt;
&lt;p&gt;大多数 HTTP handler 都是一次性返回 JSON：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但有些场景需要更细的控制：服务端持续推送日志，下载大文件时分块写入，长任务边执行边返回进度，或者你希望主动 flush 已写内容。Go 的 &lt;code&gt;net/http&lt;/code&gt; 里有 &lt;code&gt;ResponseController&lt;/code&gt;，它把一些响应控制能力以更明确的方式暴露出来。&lt;/p&gt;</description></item><item><title>Go 测试进阶：从单元测试到基准测试</title><link>https://plumephp.com/77-go-testing-advanced/</link><pubDate>Thu, 18 May 2023 08:30:00 +0800</pubDate><guid>https://plumephp.com/77-go-testing-advanced/</guid><description>&lt;h2 id="go-测试进阶从单元测试到基准测试"&gt;Go 测试进阶：从单元测试到基准测试&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;我的代码能跑就行，测试是浪费时间。&amp;rdquo;&lt;/p&gt;
&lt;p&gt;这句话我听过无数次。每次听到，我都想把这个人的手机拿过来，翻翻他的 Git 提交历史——我敢打赌，里面一定有不少 &lt;code&gt;fix bug&lt;/code&gt;、&lt;code&gt;fix again&lt;/code&gt;、&lt;code&gt;really fix this time&lt;/code&gt; 这样的提交信息。&lt;/p&gt;</description></item><item><title>Go JSON 流式处理入门：不用一次把大文件读进内存</title><link>https://plumephp.com/go-json-streaming-decoder-2023/</link><pubDate>Wed, 17 May 2023 15:13:00 +0800</pubDate><guid>https://plumephp.com/go-json-streaming-decoder-2023/</guid><description>&lt;h2 id="大-json-不应该总是-readall"&gt;大 JSON 不应该总是 ReadAll&lt;/h2&gt;
&lt;p&gt;很多入门代码会这样解析 JSON：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;小数据没问题，但如果请求体或文件很大，这会一次性把所有内容读进内存。导入几万行用户、处理日志导出、读取大数组时，更好的方式是使用 &lt;code&gt;json.Decoder&lt;/code&gt; 流式处理。它可以从 &lt;code&gt;io.Reader&lt;/code&gt; 一边读一边解码，不必先把全部数据变成 &lt;code&gt;[]byte&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Go 1.21 集合操作三剑客：cmp、slices 与 maps 标准库实战</title><link>https://plumephp.com/73-maps-slices-packages/</link><pubDate>Sat, 22 Apr 2023 14:20:00 +0800</pubDate><guid>https://plumephp.com/73-maps-slices-packages/</guid><description>&lt;h2 id="go-121-集合操作三剑客cmpslices-与-maps-标准库实战"&gt;Go 1.21 集合操作三剑客：cmp、slices 与 maps 标准库实战&lt;/h2&gt;
&lt;p&gt;如果你写过足够多的 Go 代码，那么下面这些场景你一定不陌生：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 检查 slice 是否包含某元素 —— 又是一个 5 行的 for 循环&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 获取 map 的所有 key —— 又是一个 5 行的 for 循环&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 求 slice 的最小值 —— 又是一个 5 行的 for 循环&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这些&amp;quot;样板代码&amp;quot;就像每个 Go 开发者都必须缴纳的&amp;quot;语法税&amp;quot;。每交一次，代码就啰嗦一点；每交一次，项目里就多出一堆长得几乎一模一样的辅助函数。&lt;/p&gt;</description></item><item><title>Go 1.21 slices、maps 和 cmp 入门：集合工具终于标准化</title><link>https://plumephp.com/go-121-slices-maps-cmp-2023/</link><pubDate>Tue, 18 Apr 2023 16:06:00 +0800</pubDate><guid>https://plumephp.com/go-121-slices-maps-cmp-2023/</guid><description>&lt;h2 id="很多小工具终于不用自己写"&gt;很多小工具终于不用自己写&lt;/h2&gt;
&lt;p&gt;Go 早期处理切片和 map 时，经常要自己写辅助函数：判断切片是否包含元素、克隆切片、比较切片、复制 map、按字段排序。每个项目都会有一组差不多的 &lt;code&gt;utils&lt;/code&gt;。Go 1.21 把 &lt;code&gt;slices&lt;/code&gt;、&lt;code&gt;maps&lt;/code&gt;、&lt;code&gt;cmp&lt;/code&gt; 等包带进标准库，让这些常见集合操作更统一。&lt;/p&gt;</description></item><item><title>Go pprof 入门：给 HTTP 服务找 CPU 和内存热点</title><link>https://plumephp.com/go-pprof-http-service-2023/</link><pubDate>Mon, 03 Apr 2023 09:20:00 +0800</pubDate><guid>https://plumephp.com/go-pprof-http-service-2023/</guid><description>&lt;h2 id="性能问题要靠数据不靠感觉"&gt;性能问题要靠数据，不靠感觉&lt;/h2&gt;
&lt;p&gt;服务慢了以后，很多人第一反应是猜：是不是 JSON 慢？是不是数据库慢？是不是某个循环太多？猜测可以提供方向，但真正优化前要有证据。Go 的 pprof 可以帮助你看到 CPU 时间花在哪里、内存分配集中在哪里、goroutine 是否堆积。&lt;/p&gt;</description></item><item><title>Go 1.21 slog 入门：不用第三方库也能写结构化日志</title><link>https://plumephp.com/go-121-slog-first-use-2023/</link><pubDate>Thu, 30 Mar 2023 09:42:00 +0800</pubDate><guid>https://plumephp.com/go-121-slog-first-use-2023/</guid><description>&lt;h2 id="日志最好能被机器读懂"&gt;日志最好能被机器读懂&lt;/h2&gt;
&lt;p&gt;过去很多 Go 小项目使用标准库 &lt;code&gt;log&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;user %d login failed: %v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种日志给人看没问题，但给日志系统分析不够方便。如果你想按 &lt;code&gt;user_id&lt;/code&gt; 搜索，按 &lt;code&gt;status&lt;/code&gt; 聚合，按 &lt;code&gt;duration_ms&lt;/code&gt; 排序，结构化日志会更好。Go 1.21 引入了标准库 &lt;code&gt;log/slog&lt;/code&gt;，让结构化日志成为标准能力。&lt;/p&gt;</description></item><item><title>Go 1.21 slog：标准库的结构化日志</title><link>https://plumephp.com/72-go-1.21-slog/</link><pubDate>Fri, 10 Mar 2023 10:45:00 +0800</pubDate><guid>https://plumephp.com/72-go-1.21-slog/</guid><description>&lt;h2 id="go-121-slog标准库的结构化日志"&gt;Go 1.21 slog：标准库的结构化日志&lt;/h2&gt;
&lt;p&gt;日志，是每个后端工程师绕不开的话题。你可能用过 &lt;code&gt;logrus&lt;/code&gt;、&lt;code&gt;zap&lt;/code&gt;、&lt;code&gt;zerolog&lt;/code&gt; 这些第三方库，也可能还在用标准库的 &lt;code&gt;log&lt;/code&gt; 包拼接字符串。不管怎样，Go 1.21 给你带来了一个&amp;quot;迟到但正确&amp;quot;的礼物——&lt;strong&gt;&lt;code&gt;log/slog&lt;/code&gt;&lt;/strong&gt; 包。&lt;/p&gt;</description></item><item><title>Go 竞态检测入门：用 -race 找出并发读写问题</title><link>https://plumephp.com/go-race-detector-practice-2023/</link><pubDate>Wed, 08 Mar 2023 11:27:00 +0800</pubDate><guid>https://plumephp.com/go-race-detector-practice-2023/</guid><description>&lt;h2 id="并发-bug-最怕偶尔出现"&gt;并发 bug 最怕偶尔出现&lt;/h2&gt;
&lt;p&gt;Go 写并发很方便，&lt;code&gt;go func()&lt;/code&gt; 一启动就是一个 goroutine。但多个 goroutine 同时读写同一份数据时，如果没有同步，就会产生数据竞争。数据竞争的麻烦在于它不一定每次都出错。你本地跑十次正常，线上高并发时偶尔错一次，排查成本很高。&lt;/p&gt;</description></item><item><title>HTTP 服务器模式：从入门到生产</title><link>https://plumephp.com/75-http-server-patterns/</link><pubDate>Tue, 28 Feb 2023 16:10:00 +0800</pubDate><guid>https://plumephp.com/75-http-server-patterns/</guid><description>&lt;h2 id="http-服务器模式从入门到生产"&gt;HTTP 服务器模式：从入门到生产&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;我的 HTTP 服务器能跑就行了，为什么要搞那么多花样？&amp;rdquo;&lt;/p&gt;
&lt;p&gt;如果你还在用 &lt;code&gt;http.ListenAndServe(&amp;quot;:8080&amp;quot;, mux)&lt;/code&gt; 一行代码打天下，那你一定还没经历过这些场景：&lt;/p&gt;</description></item><item><title>Go 1.20 context 取消原因入门：让并发任务知道为什么停下</title><link>https://plumephp.com/go-120-context-cause-2023/</link><pubDate>Tue, 21 Feb 2023 14:05:00 +0800</pubDate><guid>https://plumephp.com/go-120-context-cause-2023/</guid><description>&lt;h2 id="取消任务时只知道-canceled-有时不够"&gt;取消任务时，只知道 canceled 有时不够&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;context.Context&lt;/code&gt; 是 Go 并发和服务端代码里非常核心的工具。它可以传递超时、取消信号和请求范围数据。以前我们通常通过 &lt;code&gt;ctx.Err()&lt;/code&gt; 判断 context 为什么结束，常见结果是 &lt;code&gt;context.Canceled&lt;/code&gt; 或 &lt;code&gt;context.DeadlineExceeded&lt;/code&gt;。这对很多场景已经够用，但在复杂任务里，有时你还想知道更具体的原因。&lt;/p&gt;</description></item><item><title>Go embed 入门：把模板和静态文件打进二进制</title><link>https://plumephp.com/go-embed-static-assets-2023/</link><pubDate>Fri, 10 Feb 2023 19:35:00 +0800</pubDate><guid>https://plumephp.com/go-embed-static-assets-2023/</guid><description>&lt;h2 id="为什么要把资源打进二进制"&gt;为什么要把资源打进二进制&lt;/h2&gt;
&lt;p&gt;Go 程序发布时常常是一个二进制文件，这很方便。但只要你的程序依赖模板、CSS、默认配置、SQL 迁移脚本或静态图片，发布就不再只是复制一个文件。你需要保证资源目录也在正确位置，工作目录也正确。很多“小工具在我机器上能跑，复制到服务器就找不到模板”的问题都来自这里。&lt;/p&gt;</description></item><item><title>Go HTTP 服务超时入门：ReadTimeout、WriteTimeout 和请求上下文</title><link>https://plumephp.com/go-http-server-timeouts-2023/</link><pubDate>Fri, 27 Jan 2023 10:16:00 +0800</pubDate><guid>https://plumephp.com/go-http-server-timeouts-2023/</guid><description>&lt;h2 id="没有超时的服务很容易被慢请求拖住"&gt;没有超时的服务很容易被慢请求拖住&lt;/h2&gt;
&lt;p&gt;很多 Go 入门 HTTP 服务都是这样启动的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;:8080&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这段代码能跑，但它没有显式配置超时。开发环境里看不出问题，到了真实网络环境就可能遇到慢客户端、半开连接、上传请求迟迟不结束、响应写不出去等情况。一个服务如果没有基本超时保护，就可能被少量异常连接占住资源。&lt;/p&gt;</description></item><item><title>Go 1.20 新特性：让错误处理更优雅</title><link>https://plumephp.com/71-go-1.20-features/</link><pubDate>Wed, 18 Jan 2023 09:30:00 +0800</pubDate><guid>https://plumephp.com/71-go-1.20-features/</guid><description>&lt;h2 id="go-120-新特性让错误处理更优雅"&gt;Go 1.20 新特性：让错误处理更优雅&lt;/h2&gt;
&lt;p&gt;2023 年 2 月，Go 1.20 正式发布。虽然不像 1.18 那样引入了泛型这样的重大特性，但 1.20 在错误处理、类型转换和性能方面带来了许多实用的改进。&lt;/p&gt;</description></item><item><title>Go 1.20 多错误处理入门：errors.Join 怎么用才自然</title><link>https://plumephp.com/go-120-errors-join-2023/</link><pubDate>Mon, 16 Jan 2023 09:18:00 +0800</pubDate><guid>https://plumephp.com/go-120-errors-join-2023/</guid><description>&lt;h2 id="一个操作可能不只产生一个错误"&gt;一个操作可能不只产生一个错误&lt;/h2&gt;
&lt;p&gt;Go 代码里最常见的错误处理方式是返回一个 &lt;code&gt;error&lt;/code&gt;。这很清楚：函数成功返回 nil，失败返回错误。可是有些场景并不只有一个失败点。比如导入 CSV 时，100 行里有 5 行格式错误；关闭多个资源时，文件关闭失败，网络连接关闭也失败；并发执行多个任务时，几个任务都返回了错误。过去很多代码会只返回第一个错误，或者手写一个 &lt;code&gt;[]error&lt;/code&gt;，再把它格式化成字符串。&lt;/p&gt;</description></item><item><title>错误处理的高级技巧：从基础到企业级</title><link>https://plumephp.com/70-advanced-error-handling/</link><pubDate>Thu, 15 Dec 2022 13:40:00 +0800</pubDate><guid>https://plumephp.com/70-advanced-error-handling/</guid><description>&lt;h2 id="错误处理的高级技巧从基础到企业级"&gt;错误处理的高级技巧：从基础到企业级&lt;/h2&gt;
&lt;p&gt;Go 的错误处理以其简洁性著称，但简洁不等于简单。在实际项目中，我们需要处理复杂的错误场景：错误包装、错误链、错误分类、错误监控等。&lt;/p&gt;
&lt;p&gt;本文将带你从基础的错误处理进阶到企业级的错误处理策略。&lt;/p&gt;</description></item><item><title>Go 小服务可观测性入门：日志、指标和健康检查先做到位</title><link>https://plumephp.com/go-small-service-observability-2022/</link><pubDate>Fri, 09 Dec 2022 17:04:00 +0800</pubDate><guid>https://plumephp.com/go-small-service-observability-2022/</guid><description>&lt;h2 id="小服务也需要知道自己发生了什么"&gt;小服务也需要知道自己发生了什么&lt;/h2&gt;
&lt;p&gt;可观测性听起来像大系统话题，容易让初学者联想到复杂的指标平台、分布式追踪和日志集群。其实对一个 Go 小服务来说，最基础的可观测性很朴素：服务是否还活着？请求进来了多少？哪些请求失败？外部调用耗时多久？启动时用了什么配置？&lt;/p&gt;</description></item><item><title>Go 泛型切片工具入门：自己写 Filter、Map 和 Reduce</title><link>https://plumephp.com/go-118-slices-with-generics-2022/</link><pubDate>Thu, 06 Oct 2022 09:37:00 +0800</pubDate><guid>https://plumephp.com/go-118-slices-with-generics-2022/</guid><description>&lt;h2 id="从切片工具函数学习泛型最自然"&gt;从切片工具函数学习泛型最自然&lt;/h2&gt;
&lt;p&gt;Go 1.18 有了泛型后，最容易上手的练习就是切片工具函数。因为切片处理在业务代码里非常常见：过滤活跃用户、提取 ID、计算总金额、把数据库结果转成响应结构体。过去这些函数要么针对具体类型写，要么使用 &lt;code&gt;interface{}&lt;/code&gt; 损失类型安全。泛型让它们变得更自然。&lt;/p&gt;</description></item><item><title>Go any 和 interface{} 入门：新名字背后的老概念</title><link>https://plumephp.com/go-118-any-vs-interface-2022/</link><pubDate>Thu, 18 Aug 2022 15:46:00 +0800</pubDate><guid>https://plumephp.com/go-118-any-vs-interface-2022/</guid><description>&lt;h2 id="any-只是-interface-的别名"&gt;any 只是 interface{} 的别名&lt;/h2&gt;
&lt;p&gt;Go 1.18 引入泛型时，也引入了一个新预声明标识符：&lt;code&gt;any&lt;/code&gt;。很多初学者会以为它是新类型，其实它只是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也就是说，&lt;code&gt;any&lt;/code&gt; 和 &lt;code&gt;interface{}&lt;/code&gt; 完全等价。它的出现主要是为了让泛型代码更易读：&lt;/p&gt;</description></item><item><title>Go 1.19：稳定中求进步</title><link>https://plumephp.com/65-go-1.19-improvements/</link><pubDate>Fri, 05 Aug 2022 10:20:00 +0800</pubDate><guid>https://plumephp.com/65-go-1.19-improvements/</guid><description>&lt;h2 id="go-119稳定中求进步"&gt;Go 1.19：稳定中求进步&lt;/h2&gt;
&lt;p&gt;2022 年 8 月，Go 1.19 正式发布。虽然不像 1.18 那样引入了泛型这样的重大特性，但 1.19 在稳定性、性能和开发体验方面做了大量改进。&lt;/p&gt;
&lt;p&gt;这篇文章带你了解 Go 1.19 的重要变化。&lt;/p&gt;</description></item><item><title>Go atomic 入门：什么时候需要原子计数，什么时候该用锁</title><link>https://plumephp.com/go-atomic-basics-2022/</link><pubDate>Wed, 27 Jul 2022 11:09:00 +0800</pubDate><guid>https://plumephp.com/go-atomic-basics-2022/</guid><description>&lt;h2 id="并发计数不是-count-那么简单"&gt;并发计数不是 count++ 那么简单&lt;/h2&gt;
&lt;p&gt;多个 goroutine 同时修改一个变量时，普通 &lt;code&gt;count++&lt;/code&gt; 不是安全操作。它看起来是一行，实际包含读取、加一、写回多个步骤。两个 goroutine 交错执行时，更新可能丢失。Go 的 race detector 也会报告数据竞争。&lt;/p&gt;</description></item><item><title>Channel 模式：掌握 Go 并发编程的精髓</title><link>https://plumephp.com/69-channel-patterns/</link><pubDate>Fri, 08 Jul 2022 16:20:00 +0800</pubDate><guid>https://plumephp.com/69-channel-patterns/</guid><description>&lt;h2 id="channel-模式掌握-go-并发编程的精髓"&gt;Channel 模式：掌握 Go 并发编程的精髓&lt;/h2&gt;
&lt;p&gt;Go 语言的 Channel 是并发编程的核心，但仅仅知道如何创建和使用 Channel 是不够的。掌握各种 &lt;strong&gt;Channel 模式&lt;/strong&gt;（Channel Patterns）才能让你写出优雅、高效、可维护的并发程序。&lt;/p&gt;</description></item><item><title>原子操作：无锁并发编程的艺术</title><link>https://plumephp.com/66-atomic-operations/</link><pubDate>Sat, 18 Jun 2022 14:30:00 +0800</pubDate><guid>https://plumephp.com/66-atomic-operations/</guid><description>&lt;h2 id="原子操作无锁并发编程的艺术"&gt;原子操作：无锁并发编程的艺术&lt;/h2&gt;
&lt;p&gt;在并发编程中，我们经常需要保护共享数据。传统的做法是使用互斥锁（mutex），但锁会带来性能开销和死锁风险。&lt;strong&gt;原子操作&lt;/strong&gt;提供了一种无锁的替代方案，在某些场景下性能更优。&lt;/p&gt;</description></item><item><title>Go 构建信息入门：在程序里查看版本、依赖和构建来源</title><link>https://plumephp.com/go-118-build-info-2022/</link><pubDate>Tue, 14 Jun 2022 18:21:00 +0800</pubDate><guid>https://plumephp.com/go-118-build-info-2022/</guid><description>&lt;h2 id="线上运行的到底是哪一版"&gt;线上运行的到底是哪一版&lt;/h2&gt;
&lt;p&gt;当用户反馈问题时，第一件事往往不是看代码，而是确认版本。你以为线上跑的是刚发布的二进制，实际可能是旧版本；你以为某个依赖已经升级，实际构建产物里还是旧依赖。Go 程序可以读取自身构建信息，这对命令行工具、HTTP 服务和排查问题都很有用。&lt;/p&gt;</description></item><item><title>Go TLS 和 HTTP 客户端安全入门：证书、超时和最小信任边界</title><link>https://plumephp.com/go-security-tls-http-client-2022/</link><pubDate>Sun, 22 May 2022 10:47:00 +0800</pubDate><guid>https://plumephp.com/go-security-tls-http-client-2022/</guid><description>&lt;h2 id="https-不是把-url-改成-https-就结束"&gt;HTTPS 不是把 URL 改成 https 就结束&lt;/h2&gt;
&lt;p&gt;Go 调用外部 API 很方便，一个 &lt;code&gt;http.Client&lt;/code&gt; 就能请求 HTTPS 服务。标准库默认会验证服务端证书，这对大多数场景是正确的。但在真实开发中，你可能遇到自签名证书、内网服务、测试环境证书过期、域名不匹配等问题。最危险的解决方式是随手写 &lt;code&gt;InsecureSkipVerify: true&lt;/code&gt;，让证书验证完全失效。&lt;/p&gt;</description></item><item><title>逃逸分析：理解 Go 的内存分配策略</title><link>https://plumephp.com/68-escape-analysis/</link><pubDate>Thu, 12 May 2022 09:15:00 +0800</pubDate><guid>https://plumephp.com/68-escape-analysis/</guid><description>&lt;h2 id="逃逸分析理解-go-的内存分配策略"&gt;逃逸分析：理解 Go 的内存分配策略&lt;/h2&gt;
&lt;p&gt;在 Go 中，你不需要像 C/C++ 那样手动管理内存，但这并不意味着你可以忽视内存分配。理解&lt;strong&gt;逃逸分析&lt;/strong&gt;（Escape Analysis）对于编写高性能的 Go 代码至关重要。&lt;/p&gt;</description></item><item><title>Go netip 入门：更清楚地处理 IP 地址和网段</title><link>https://plumephp.com/go-118-netip-introduction-2022/</link><pubDate>Fri, 29 Apr 2022 16:12:00 +0800</pubDate><guid>https://plumephp.com/go-118-netip-introduction-2022/</guid><description>&lt;h2 id="ip-地址不只是字符串"&gt;IP 地址不只是字符串&lt;/h2&gt;
&lt;p&gt;很多 Web 服务都会遇到 IP 处理：记录客户端 IP、判断内网地址、做白名单、解析 CIDR 网段、限制管理后台来源。初学时你可能会把 IP 当成字符串处理，比如 &lt;code&gt;strings.HasPrefix(ip, &amp;quot;192.168.&amp;quot;)&lt;/code&gt;。这种写法很脆弱，遇到 IPv6、前导格式、网段判断时很容易错。&lt;/p&gt;</description></item><item><title>Go 1.18 any 类型：告别 interface{} 的冗长</title><link>https://plumephp.com/64-any-vs-interface/</link><pubDate>Sun, 10 Apr 2022 11:15:00 +0800</pubDate><guid>https://plumephp.com/64-any-vs-interface/</guid><description>&lt;h2 id="go-118-any-类型告别-interface-的冗长"&gt;Go 1.18 any 类型：告别 interface{} 的冗长&lt;/h2&gt;
&lt;p&gt;Go 1.18 引入了一个简单但意义重大的改进：&lt;code&gt;any&lt;/code&gt; 类型。它是 &lt;code&gt;interface{}&lt;/code&gt; 的内置别名，让代码更加简洁易读。&lt;/p&gt;
&lt;p&gt;虽然只是一个小的语法改进，但它体现了 Go 团队对开发者体验的持续关注。&lt;/p&gt;</description></item><item><title>性能分析实战：pprof 从入门到精通</title><link>https://plumephp.com/67-pprof-practice/</link><pubDate>Fri, 25 Mar 2022 14:30:00 +0800</pubDate><guid>https://plumephp.com/67-pprof-practice/</guid><description>&lt;h2 id="性能分析实战pprof-从入门到精通"&gt;性能分析实战：pprof 从入门到精通&lt;/h2&gt;
&lt;p&gt;当你的 Go 程序运行缓慢或内存占用过高时，你需要一个强大的工具来找出问题所在。&lt;code&gt;pprof&lt;/code&gt; 是 Go 标准库提供的性能分析工具，它能帮你精确定位 CPU、内存、Goroutine 等性能瓶颈。&lt;/p&gt;</description></item><item><title>Go 1.18 Fuzz 测试：发现隐藏 Bug 的利器</title><link>https://plumephp.com/63-fuzz-testing/</link><pubDate>Fri, 25 Mar 2022 09:20:00 +0800</pubDate><guid>https://plumephp.com/63-fuzz-testing/</guid><description>&lt;h2 id="go-118-fuzz-测试发现隐藏-bug-的利器"&gt;Go 1.18 Fuzz 测试：发现隐藏 Bug 的利器&lt;/h2&gt;
&lt;p&gt;Go 1.18 引入了一个令人兴奋的新特性：&lt;strong&gt;Fuzz 测试&lt;/strong&gt;（模糊测试）。这是一种自动化的软件测试技术，通过生成大量随机输入来发现程序中的隐藏 Bug。&lt;/p&gt;</description></item><item><title>Go 工作区入门：go work 如何解决本地多模块联调</title><link>https://plumephp.com/go-workspaces-practical-start-2022/</link><pubDate>Thu, 17 Mar 2022 09:55:00 +0800</pubDate><guid>https://plumephp.com/go-workspaces-practical-start-2022/</guid><description>&lt;h2 id="多个模块一起改时gomod-不一定要被污染"&gt;多个模块一起改时，go.mod 不一定要被污染&lt;/h2&gt;
&lt;p&gt;Go Modules 解决了单个项目依赖管理问题，但当你同时开发多个本地模块时，仍然会遇到麻烦。比如 &lt;code&gt;user-api&lt;/code&gt; 依赖 &lt;code&gt;company-kit&lt;/code&gt;，你现在要同时修改两边。过去常见做法是在 &lt;code&gt;user-api/go.mod&lt;/code&gt; 里写：&lt;/p&gt;</description></item><item><title>Go 模糊测试基础流程：从一个字符串解析函数开始</title><link>https://plumephp.com/go-fuzzing-basic-workflow-2022/</link><pubDate>Wed, 23 Feb 2022 14:38:00 +0800</pubDate><guid>https://plumephp.com/go-fuzzing-basic-workflow-2022/</guid><description>&lt;h2 id="模糊测试适合找你没想到的输入"&gt;模糊测试适合找你没想到的输入&lt;/h2&gt;
&lt;p&gt;普通测试需要我们自己写输入和预期。表驱动测试已经很强，但它仍然依赖人的想象力。解析函数、路径处理、编码转换、协议拆包这类代码，输入空间很大，你很难手写覆盖所有奇怪情况。模糊测试的思路是：你给工具一些种子样本和一个必须保持的性质，让工具不断生成新输入，尝试触发 panic 或违反性质。&lt;/p&gt;</description></item><item><title>Go Workspaces：多模块开发的利器</title><link>https://plumephp.com/62-go-workspaces/</link><pubDate>Sun, 20 Feb 2022 14:45:00 +0800</pubDate><guid>https://plumephp.com/62-go-workspaces/</guid><description>&lt;h2 id="go-workspaces多模块开发的利器"&gt;Go Workspaces：多模块开发的利器&lt;/h2&gt;
&lt;p&gt;在大型项目中，我们经常需要同时开发多个相互依赖的 Go 模块。在 Go 1.18 之前，这需要使用 &lt;code&gt;replace&lt;/code&gt; 指令，非常繁琐。Go 1.18 引入的 &lt;strong&gt;workspace 模式&lt;/strong&gt;彻底解决了这个问题。&lt;/p&gt;</description></item><item><title>Go 1.18 泛型初体验：先从三个小函数开始理解</title><link>https://plumephp.com/go-118-generics-first-look-2022/</link><pubDate>Wed, 19 Jan 2022 09:24:00 +0800</pubDate><guid>https://plumephp.com/go-118-generics-first-look-2022/</guid><description>&lt;h2 id="为什么-2022-年大家都在讨论泛型"&gt;为什么 2022 年大家都在讨论泛型&lt;/h2&gt;
&lt;p&gt;如果你在 2022 年学习 Go，很难绕开一个话题：Go 1.18 带来了泛型。对很多从 Java、C#、TypeScript 转过来的人来说，泛型并不陌生；但对长期写 Go 的人来说，这是语言风格里一次很重要的变化。过去 Go 社区习惯用具体类型、小接口、代码生成和适度重复来解决复用问题。泛型出现后，一些集合类和算法类代码终于可以写得更自然。&lt;/p&gt;</description></item><item><title>Go 1.18 泛型：期待十年的类型参数</title><link>https://plumephp.com/61-generics-deep-dive/</link><pubDate>Sat, 15 Jan 2022 10:30:00 +0800</pubDate><guid>https://plumephp.com/61-generics-deep-dive/</guid><description>&lt;h2 id="go-118-泛型期待十年的类型参数"&gt;Go 1.18 泛型：期待十年的类型参数&lt;/h2&gt;
&lt;p&gt;2022 年 3 月，Go 1.18 正式发布，这是 Go 语言诞生以来最重要的一次更新——&lt;strong&gt;泛型&lt;/strong&gt;（Generics）终于来了！&lt;/p&gt;
&lt;p&gt;从 2010 年社区开始讨论泛型，到 2022 年正式落地，整整 12 年的时间。这期间，Go 团队尝试了无数种方案，最终选择了&amp;quot;类型参数&amp;quot;（Type Parameters）这个设计。&lt;/p&gt;</description></item><item><title>性能优化实战：让你的 Go 应用飞起来</title><link>https://plumephp.com/60-performance-optimization/</link><pubDate>Sat, 29 May 2021 10:00:00 +0800</pubDate><guid>https://plumephp.com/60-performance-optimization/</guid><description>&lt;h2 id="性能优化实战让你的-go-应用飞起来"&gt;性能优化实战：让你的 Go 应用飞起来&lt;/h2&gt;
&lt;p&gt;Go 语言本身性能优秀，但不当的编码方式仍会导致性能问题。本文将介绍如何通过分析和优化，让你的 Go 应用达到最佳性能。&lt;/p&gt;
&lt;h2 id="性能优化原则"&gt;性能优化原则&lt;/h2&gt;
&lt;p&gt;在开始优化之前，记住以下原则：&lt;/p&gt;</description></item><item><title>分布式追踪：跨服务调用链路分析</title><link>https://plumephp.com/59-distributed-tracing/</link><pubDate>Wed, 26 May 2021 16:30:00 +0800</pubDate><guid>https://plumephp.com/59-distributed-tracing/</guid><description>&lt;h2 id="分布式追踪跨服务调用链路分析"&gt;分布式追踪：跨服务调用链路分析&lt;/h2&gt;
&lt;p&gt;在微服务架构中，一个用户请求可能经过多个服务。当出现问题时，如何快速定位是哪个环节出了问题？分布式追踪就是解决这个问题的利器。&lt;/p&gt;
&lt;p&gt;本文将介绍如何使用 OpenTelemetry 实现分布式追踪。&lt;/p&gt;</description></item><item><title>Prometheus 监控：让你的应用可观测</title><link>https://plumephp.com/58-prometheus-monitoring/</link><pubDate>Sun, 23 May 2021 09:45:00 +0800</pubDate><guid>https://plumephp.com/58-prometheus-monitoring/</guid><description>&lt;h2 id="prometheus-监控让你的应用可观测"&gt;Prometheus 监控：让你的应用可观测&lt;/h2&gt;
&lt;p&gt;在生产环境中，了解应用的运行状态至关重要。Prometheus 是一个开源的监控系统，配合 Grafana 可以实现强大的指标收集和可视化。&lt;/p&gt;</description></item><item><title>数据库事务：保证数据一致性的关键</title><link>https://plumephp.com/57-database-transactions/</link><pubDate>Thu, 20 May 2021 14:15:00 +0800</pubDate><guid>https://plumephp.com/57-database-transactions/</guid><description>&lt;h2 id="数据库事务保证数据一致性的关键"&gt;数据库事务：保证数据一致性的关键&lt;/h2&gt;
&lt;p&gt;在数据库操作中，我们经常需要执行多个相关的操作，这些操作要么全部成功，要么全部失败。事务（Transaction）就是用来保证这种原子性的机制。&lt;/p&gt;</description></item><item><title>gRPC 基础：构建高性能微服务通信</title><link>https://plumephp.com/56-grpc-basics/</link><pubDate>Mon, 17 May 2021 11:30:00 +0800</pubDate><guid>https://plumephp.com/56-grpc-basics/</guid><description>&lt;h2 id="grpc-基础构建高性能微服务通信"&gt;gRPC 基础：构建高性能微服务通信&lt;/h2&gt;
&lt;p&gt;在微服务架构中，服务间通信是关键。RESTful API 虽然流行，但在性能、类型安全和代码生成方面存在局限。gRPC 作为 Google 开源的高性能 RPC 框架，提供了更优的解决方案。&lt;/p&gt;</description></item><item><title>Redis 集成：高性能缓存与数据存储</title><link>https://plumephp.com/55-redis-integration/</link><pubDate>Fri, 14 May 2021 16:20:00 +0800</pubDate><guid>https://plumephp.com/55-redis-integration/</guid><description>&lt;h2 id="redis-集成高性能缓存与数据存储"&gt;Redis 集成：高性能缓存与数据存储&lt;/h2&gt;
&lt;p&gt;Redis 是一个高性能的内存数据库，广泛用于缓存、会话存储、消息队列等场景。在 Go 应用中集成 Redis，可以显著提升系统性能和响应速度。&lt;/p&gt;</description></item><item><title>JWT 身份验证：构建安全的 API 认证系统</title><link>https://plumephp.com/54-jwt-authentication/</link><pubDate>Tue, 11 May 2021 13:45:00 +0800</pubDate><guid>https://plumephp.com/54-jwt-authentication/</guid><description>&lt;h2 id="jwt-身份验证构建安全的-api-认证系统"&gt;JWT 身份验证：构建安全的 API 认证系统&lt;/h2&gt;
&lt;p&gt;在构建现代 Web API 时，身份验证是不可或缺的一环。传统的 Session 认证方式在分布式系统中面临诸多挑战，而 JWT（JSON Web Token）提供了一种优雅、无状态的解决方案。&lt;/p&gt;</description></item><item><title>WebSocket 实时通信：构建交互式应用</title><link>https://plumephp.com/53-websocket-realtime/</link><pubDate>Sat, 08 May 2021 10:30:00 +0800</pubDate><guid>https://plumephp.com/53-websocket-realtime/</guid><description>&lt;h2 id="websocket-实时通信构建交互式应用"&gt;WebSocket 实时通信：构建交互式应用&lt;/h2&gt;
&lt;p&gt;传统的 HTTP 请求-响应模型无法满足实时应用的需求。想象一下聊天室、股票行情、在线游戏这些场景，客户端需要即时收到服务器的消息，而不是不断轮询。&lt;/p&gt;</description></item><item><title>RESTful API 设计：构建优雅的 Web 接口</title><link>https://plumephp.com/52-restful-api-design/</link><pubDate>Wed, 05 May 2021 14:20:00 +0800</pubDate><guid>https://plumephp.com/52-restful-api-design/</guid><description>&lt;h2 id="restful-api-设计构建优雅的-web-接口"&gt;RESTful API 设计：构建优雅的 Web 接口&lt;/h2&gt;
&lt;p&gt;在当今的软件开发中，API（应用程序接口）已经成为连接不同系统和服务的桥梁。RESTful API 因其简单性、灵活性和可扩展性，成为了最流行的 API 设计风格之一。&lt;/p&gt;</description></item><item><title>中间件模式：构建灵活的 Web 应用</title><link>https://plumephp.com/51-middleware-patterns/</link><pubDate>Sun, 02 May 2021 09:15:00 +0800</pubDate><guid>https://plumephp.com/51-middleware-patterns/</guid><description>&lt;h2 id="中间件模式构建灵活的-web-应用"&gt;中间件模式：构建灵活的 Web 应用&lt;/h2&gt;
&lt;p&gt;在 Web 开发中，有很多横切关注点（cross-cutting concerns）需要在每个请求中处理：日志记录、身份验证、权限检查、请求限流、CORS 处理等等。如果把这些逻辑都写在每个 handler 里，代码会变得冗余且难以维护。&lt;/p&gt;</description></item><item><title>项目架构：构建可维护的 Go 应用</title><link>https://plumephp.com/40-project-architecture/</link><pubDate>Wed, 28 Apr 2021 10:50:00 +0800</pubDate><guid>https://plumephp.com/40-project-architecture/</guid><description>&lt;h2 id="项目架构构建可维护的-go-应用"&gt;项目架构：构建可维护的 Go 应用&lt;/h2&gt;
&lt;p&gt;当你的 Go 项目从几十行代码的小工具，成长为成千上万行代码的大型应用时，一个清晰的项目架构就变得至关重要了。&lt;/p&gt;
&lt;p&gt;好的架构能让代码：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;易于理解&lt;/strong&gt;：新人能快速找到相关代码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;易于测试&lt;/strong&gt;：各层职责清晰，便于单元测试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;易于维护&lt;/strong&gt;：修改一处不会影响其他部分&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;易于扩展&lt;/strong&gt;：新功能可以无缝集成&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今天我们就来学习如何设计和组织一个可维护的 Go 项目。&lt;/p&gt;</description></item><item><title>Go 最佳实践：写出优雅的 Go 代码</title><link>https://plumephp.com/50-best-practices/</link><pubDate>Mon, 26 Apr 2021 16:10:00 +0800</pubDate><guid>https://plumephp.com/50-best-practices/</guid><description>&lt;h2 id="go-最佳实践写出优雅的-go-代码"&gt;Go 最佳实践：写出优雅的 Go 代码&lt;/h2&gt;
&lt;p&gt;学习一门语言不仅仅是掌握语法，更重要的是理解这门语言的设计哲学和最佳实践。Go 语言有其独特的风格和约定，遵循这些实践能帮你写出更清晰、更健壮、更易维护的代码。&lt;/p&gt;</description></item><item><title>设计模式：Go 语言中的实践</title><link>https://plumephp.com/39-design-patterns/</link><pubDate>Sun, 25 Apr 2021 14:28:00 +0800</pubDate><guid>https://plumephp.com/39-design-patterns/</guid><description>&lt;h2 id="设计模式go-语言中的实践"&gt;设计模式：Go 语言中的实践&lt;/h2&gt;
&lt;p&gt;设计模式是软件开发中经过验证的解决方案。它们不是具体的代码，而是一种思路，帮助你解决特定场景下的常见问题。&lt;/p&gt;
&lt;p&gt;但是，不同的语言有不同的特性，设计模式的实现方式也会有所不同。Go 语言没有类、继承、泛型（1.18 之前），这让很多传统的面向对象设计模式需要&amp;quot;Go 化&amp;quot;。&lt;/p&gt;</description></item><item><title>性能分析：pprof 和 trace 工具</title><link>https://plumephp.com/49-profiling-tools/</link><pubDate>Thu, 22 Apr 2021 14:20:00 +0800</pubDate><guid>https://plumephp.com/49-profiling-tools/</guid><description>&lt;h2 id="性能分析pprof-和-trace-工具"&gt;性能分析：pprof 和 trace 工具&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;我的程序很慢&amp;rdquo;——这是开发者最常遇到的问题之一。但&amp;quot;慢&amp;quot;是一个模糊的描述，真正的瓶颈在哪里？是 CPU 计算太慢？是内存分配太多？是 I/O 等待太久？还是锁竞争太严重？&lt;/p&gt;</description></item><item><title>Docker 部署：让你的应用随处运行</title><link>https://plumephp.com/38-docker-deployment/</link><pubDate>Thu, 22 Apr 2021 09:55:00 +0800</pubDate><guid>https://plumephp.com/38-docker-deployment/</guid><description>&lt;h2 id="docker-部署让你的应用随处运行"&gt;Docker 部署：让你的应用随处运行&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;在我的机器上能跑啊！&amp;quot;——这大概是开发者说过最多的一句话了。&lt;/p&gt;
&lt;p&gt;不同的开发环境、不同的依赖版本、不同的操作系统……这些问题让应用的部署变得复杂而痛苦。&lt;strong&gt;Docker&lt;/strong&gt; 的出现彻底改变了这一局面：把你的应用和它的所有依赖打包成一个容器，在任何地方都能以相同的方式运行。&lt;/p&gt;</description></item><item><title>信号处理：让你的应用优雅地退出</title><link>https://plumephp.com/37-signal-handling/</link><pubDate>Mon, 19 Apr 2021 16:05:00 +0800</pubDate><guid>https://plumephp.com/37-signal-handling/</guid><description>&lt;h2 id="信号处理让你的应用优雅地退出"&gt;信号处理：让你的应用优雅地退出&lt;/h2&gt;
&lt;p&gt;你有没有遇到过这样的情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你的 Web 服务器正在处理请求，突然被 &lt;code&gt;Ctrl+C&lt;/code&gt; 终止，正在处理的请求直接断开&lt;/li&gt;
&lt;li&gt;你的后台任务正在写数据库，进程被 kill 了，数据写了一半&lt;/li&gt;
&lt;li&gt;你的应用收到 &lt;code&gt;SIGTERM&lt;/code&gt; 信号（比如 Kubernetes 要重启你的 Pod），但应用直接退出了，没有做任何清理工作&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题都可以通过&lt;strong&gt;优雅退出&lt;/strong&gt;（Graceful Shutdown）来解决。&lt;/p&gt;</description></item><item><title>内存管理：Go 的垃圾回收机制</title><link>https://plumephp.com/48-memory-management/</link><pubDate>Sun, 18 Apr 2021 09:45:00 +0800</pubDate><guid>https://plumephp.com/48-memory-management/</guid><description>&lt;h2 id="内存管理go-的垃圾回收机制"&gt;内存管理：Go 的垃圾回收机制&lt;/h2&gt;
&lt;p&gt;在 C/C++ 中，你需要手动管理内存：&lt;code&gt;malloc&lt;/code&gt; 分配，&lt;code&gt;free&lt;/code&gt; 释放。忘记释放就会内存泄漏，释放两次就会程序崩溃。&lt;/p&gt;
&lt;p&gt;Go 语言采用了自动垃圾回收（Garbage Collection, GC），让你不再为内存管理烦恼。但理解 GC 的工作原理，能帮你写出更高效的代码。&lt;/p&gt;</description></item><item><title>缓存：让应用飞起来</title><link>https://plumephp.com/36-cache/</link><pubDate>Fri, 16 Apr 2021 13:40:00 +0800</pubDate><guid>https://plumephp.com/36-cache/</guid><description>&lt;h2 id="缓存让应用飞起来"&gt;缓存：让应用飞起来&lt;/h2&gt;
&lt;p&gt;缓存是提升系统性能最有效的手段之一。当你的应用频繁访问数据库、调用远程 API 或执行复杂的计算时，缓存可以显著减少响应时间和系统负载。&lt;/p&gt;
&lt;p&gt;Go 提供了多种缓存方案：从简单的内存缓存到强大的分布式缓存（如 Redis）。今天我们就来学习如何在 Go 中实现和使用缓存。&lt;/p&gt;</description></item><item><title>CGO 入门：Go 与 C 的桥梁</title><link>https://plumephp.com/47-cgo-basics/</link><pubDate>Thu, 15 Apr 2021 15:30:00 +0800</pubDate><guid>https://plumephp.com/47-cgo-basics/</guid><description>&lt;h2 id="cgo-入门go-与-c-的桥梁"&gt;CGO 入门：Go 与 C 的桥梁&lt;/h2&gt;
&lt;p&gt;Go 语言虽然强大，但有时候你不得不和 C 语言打交道：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用一个只有 C 版本的库（如 libjpeg、libpng）&lt;/li&gt;
&lt;li&gt;调用系统底层的 API&lt;/li&gt;
&lt;li&gt;集成遗留的 C 代码&lt;/li&gt;
&lt;li&gt;追求极致性能的关键路径&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这时候，CGO 就是你的桥梁。CGO 是 Go 提供的一种机制，允许 Go 代码调用 C 代码，反之亦然。&lt;/p&gt;</description></item><item><title>配置管理：让你的应用更灵活</title><link>https://plumephp.com/35-configuration-management/</link><pubDate>Tue, 13 Apr 2021 11:18:00 +0800</pubDate><guid>https://plumephp.com/35-configuration-management/</guid><description>&lt;h2 id="配置管理让你的应用更灵活"&gt;配置管理：让你的应用更灵活&lt;/h2&gt;
&lt;p&gt;写代码时，你一定遇到过这样的场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发环境用本地数据库，生产环境用云数据库，难道要改代码？&lt;/li&gt;
&lt;li&gt;API 密钥、数据库密码这些敏感信息能硬编码在代码里吗？&lt;/li&gt;
&lt;li&gt;修改一个配置就要重新编译部署吗？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题的答案都是——&lt;strong&gt;配置管理&lt;/strong&gt;。&lt;/p&gt;</description></item><item><title>reflect 包：运行时的魔法镜子</title><link>https://plumephp.com/46-reflect-package/</link><pubDate>Mon, 12 Apr 2021 13:15:00 +0800</pubDate><guid>https://plumephp.com/46-reflect-package/</guid><description>&lt;h2 id="reflect-包运行时的魔法镜子"&gt;reflect 包：运行时的魔法镜子&lt;/h2&gt;
&lt;p&gt;想象一下，你面前有一面魔法镜子。当你把任何东西放在它面前，它都能告诉你：这是什么类型？有哪些字段？有哪些方法？甚至能帮你修改它的值。&lt;/p&gt;</description></item><item><title>GORM：Go 的 ORM 利器</title><link>https://plumephp.com/34-gorm/</link><pubDate>Sun, 11 Apr 2021 15:30:00 +0800</pubDate><guid>https://plumephp.com/34-gorm/</guid><description>&lt;h2 id="gormgo-的-orm-利器"&gt;GORM：Go 的 ORM 利器&lt;/h2&gt;
&lt;p&gt;在之前的文章中，我们学习了如何使用 &lt;code&gt;database/sql&lt;/code&gt; 直接操作数据库。虽然这种方式灵活且性能高，但在实际项目中，大量的 CRUD 操作写起来非常繁琐，而且容易出错。&lt;/p&gt;</description></item><item><title>unsafe 包：Go 的潘多拉魔盒</title><link>https://plumephp.com/45-unsafe-package/</link><pubDate>Fri, 09 Apr 2021 10:20:00 +0800</pubDate><guid>https://plumephp.com/45-unsafe-package/</guid><description>&lt;h2 id="unsafe-包go-的潘多拉魔盒"&gt;unsafe 包：Go 的&amp;quot;潘多拉魔盒&amp;quot;&lt;/h2&gt;
&lt;p&gt;在 Go 的世界里，安全性是第一优先级。类型检查、边界检查、垃圾回收……这些机制保护你免受大多数底层错误的困扰。&lt;/p&gt;</description></item><item><title>网络编程：用 Go 构建底层网络应用</title><link>https://plumephp.com/33-network-programming/</link><pubDate>Thu, 08 Apr 2021 10:45:00 +0800</pubDate><guid>https://plumephp.com/33-network-programming/</guid><description>&lt;h2 id="网络编程用-go-构建底层网络应用"&gt;网络编程：用 Go 构建底层网络应用&lt;/h2&gt;
&lt;p&gt;虽然我们平时写 Web 应用大多数时候都在用 HTTP，但 HTTP 本身也是建立在 TCP/IP 协议之上的。有时候我们需要更底层的控制，比如写一个游戏服务器、一个物联网设备的数据收集器，或者一个自定义的 RPC 框架，这时候就需要直接操作 TCP 或 UDP 了。&lt;/p&gt;</description></item><item><title>构建约束：条件编译的艺术</title><link>https://plumephp.com/44-build-constraints/</link><pubDate>Wed, 07 Apr 2021 16:45:00 +0800</pubDate><guid>https://plumephp.com/44-build-constraints/</guid><description>&lt;h2 id="构建约束条件编译的艺术"&gt;构建约束：条件编译的艺术&lt;/h2&gt;
&lt;p&gt;你有没有想过：Go 是怎么做到一份代码编译出 Windows、Linux、macOS 等多个平台的可执行文件的？当你在 Linux 上调用系统特有的 API 时，Windows 上那段代码是怎么被&amp;quot;忽略&amp;quot;的？&lt;/p&gt;</description></item><item><title>加密与安全：让你的 Go 程序固若金汤</title><link>https://plumephp.com/32-crypto-and-security/</link><pubDate>Mon, 05 Apr 2021 14:22:00 +0800</pubDate><guid>https://plumephp.com/32-crypto-and-security/</guid><description>&lt;h2 id="加密与安全让你的-go-程序固若金汤"&gt;加密与安全：让你的 Go 程序固若金汤&lt;/h2&gt;
&lt;p&gt;安全不是事后补救，而是从一开始就应该考虑的事情。&lt;/p&gt;
&lt;p&gt;当你存储用户密码时，你有没有想过：如果数据库泄露了，用户的密码会不会被直接看到？当你传输敏感数据时，你有没有想过：中间人会不会窃听你的通信？当用户登录时，你有没有想过：怎么确保这个请求真的来自他本人？&lt;/p&gt;</description></item><item><title>go:embed：把文件打包进二进制</title><link>https://plumephp.com/43-go-embed/</link><pubDate>Mon, 05 Apr 2021 11:30:00 +0800</pubDate><guid>https://plumephp.com/43-go-embed/</guid><description>&lt;h2 id="goembed把文件打包进二进制"&gt;go:embed：把文件打包进二进制&lt;/h2&gt;
&lt;p&gt;你有没有遇到过这样的烦恼？&lt;/p&gt;
&lt;p&gt;编译好的 Go 程序发给别人，结果对方说：&amp;ldquo;怎么打开是 404 啊？&amp;rdquo; 你一看，原来是因为 HTML 模板、CSS 文件、图片这些静态资源没有一起打包过去。&lt;/p&gt;</description></item><item><title>错误处理的高级技巧</title><link>https://plumephp.com/42-advanced-error-handling/</link><pubDate>Sat, 03 Apr 2021 09:15:00 +0800</pubDate><guid>https://plumephp.com/42-advanced-error-handling/</guid><description>&lt;h2 id="错误处理的高级技巧"&gt;错误处理的高级技巧&lt;/h2&gt;
&lt;p&gt;如果你写过 Go 代码，一定对这样的代码不陌生：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Go 的错误处理方式一直备受争议。有人认为它太啰嗦，有人认为它很清晰。不管你怎么看，Go 1.13 引入的错误包装机制让错误处理变得更加优雅和强大。&lt;/p&gt;</description></item><item><title>字符串处理：那些被忽略的细节</title><link>https://plumephp.com/31-string-processing/</link><pubDate>Fri, 02 Apr 2021 09:38:00 +0800</pubDate><guid>https://plumephp.com/31-string-processing/</guid><description>&lt;h2 id="字符串处理那些被忽略的细节"&gt;字符串处理：那些被忽略的细节&lt;/h2&gt;
&lt;p&gt;你可能觉得字符串处理是编程中最简单的事情——不就是拼拼字符串、查查子串、做个替换嘛。直到你遇到中文乱码、emoji 长度不对、URL 编码错误这些坑，才会意识到：字符串处理远没有看起来那么简单。&lt;/p&gt;</description></item><item><title>接口组合：Go 的设计哲学</title><link>https://plumephp.com/41-interface-composition/</link><pubDate>Thu, 01 Apr 2021 14:22:00 +0800</pubDate><guid>https://plumephp.com/41-interface-composition/</guid><description>&lt;h2 id="接口组合go-的设计哲学"&gt;接口组合：Go 的设计哲学&lt;/h2&gt;
&lt;p&gt;在学习 Go 的过程中，你可能会发现一个有趣的现象：Go 标准库中的接口都非常小。&lt;code&gt;io.Reader&lt;/code&gt; 只有一个方法，&lt;code&gt;io.Writer&lt;/code&gt; 也只有一个方法，&lt;code&gt;fmt.Stringer&lt;/code&gt; 同样只有一个方法。&lt;/p&gt;</description></item><item><title>性能优化：让 Go 程序飞起来</title><link>https://plumephp.com/30-performance-optimization/</link><pubDate>Mon, 29 Mar 2021 17:20:00 +0800</pubDate><guid>https://plumephp.com/30-performance-optimization/</guid><description>&lt;h2 id="性能优化让-go-程序飞起来"&gt;性能优化：让 Go 程序飞起来&lt;/h2&gt;
&lt;p&gt;Go 语言本身就很快，但&amp;quot;快&amp;quot;是没有上限的。当你的应用面临高并发、大数据量或严格延迟要求时，性能优化就变得至关重要。&lt;/p&gt;</description></item><item><title>日志处理：构建可观测的应用</title><link>https://plumephp.com/29-logging/</link><pubDate>Fri, 26 Mar 2021 14:30:00 +0800</pubDate><guid>https://plumephp.com/29-logging/</guid><description>&lt;h2 id="日志处理构建可观测的应用"&gt;日志处理：构建可观测的应用&lt;/h2&gt;
&lt;p&gt;日志是应用程序的眼睛。良好的日志系统能帮助我们调试问题、监控系统状态、追踪用户行为。Go 提供了多种日志解决方案，从简单的标准库到强大的第三方框架。&lt;/p&gt;</description></item><item><title>gRPC：构建高性能微服务</title><link>https://plumephp.com/28-grpc/</link><pubDate>Tue, 23 Mar 2021 10:15:00 +0800</pubDate><guid>https://plumephp.com/28-grpc/</guid><description>&lt;h2 id="grpc构建高性能微服务"&gt;gRPC：构建高性能微服务&lt;/h2&gt;
&lt;p&gt;在微服务架构中，服务之间的通信至关重要。传统的 RESTful API 虽然简单易用，但在性能、类型安全和代码生成方面存在不足。&lt;strong&gt;gRPC&lt;/strong&gt; 是 Google 开源的高性能 RPC 框架，它使用 Protocol Buffers 作为接口定义语言和序列化格式，为微服务通信提供了更好的解决方案。&lt;/p&gt;</description></item><item><title>WebSocket：实现实时通信</title><link>https://plumephp.com/27-websockets/</link><pubDate>Sat, 20 Mar 2021 16:45:00 +0800</pubDate><guid>https://plumephp.com/27-websockets/</guid><description>&lt;h2 id="websocket实现实时通信"&gt;WebSocket：实现实时通信&lt;/h2&gt;
&lt;p&gt;HTTP 是请求-响应模式，客户端必须主动发起请求才能获取数据。但在很多场景中（聊天室、实时游戏、股票行情、在线协作等），我们需要服务器主动推送数据给客户端。&lt;/p&gt;</description></item><item><title>模板引擎：text/template 和 html/template</title><link>https://plumephp.com/26-templates/</link><pubDate>Wed, 17 Mar 2021 11:30:00 +0800</pubDate><guid>https://plumephp.com/26-templates/</guid><description>&lt;h2 id="模板引擎texttemplate-和-htmltemplate"&gt;模板引擎：text/template 和 html/template&lt;/h2&gt;
&lt;p&gt;在构建 Web 应用、生成配置文件、发送电子邮件等场景中，我们经常需要把数据填充到预定义的模板中。Go 的标准库提供了两个模板引擎：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;text/template&lt;/code&gt;&lt;/strong&gt;：用于生成纯文本（邮件、配置文件、代码等）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;html/template&lt;/code&gt;&lt;/strong&gt;：用于生成 HTML，自动处理转义防止 XSS 攻击&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今天我们就来全面学习 Go 的模板引擎。&lt;/p&gt;</description></item><item><title>泛型：Go 的类型参数革命</title><link>https://plumephp.com/25-generics/</link><pubDate>Sun, 14 Mar 2021 15:20:00 +0800</pubDate><guid>https://plumephp.com/25-generics/</guid><description>&lt;h2 id="泛型go-的类型参数革命"&gt;泛型：Go 的类型参数革命&lt;/h2&gt;
&lt;p&gt;Go 1.18 引入了一个期待已久的特性——&lt;strong&gt;泛型（Generics）&lt;/strong&gt;。这是 Go 语言诞生以来最大的语法变化之一。&lt;/p&gt;
&lt;p&gt;泛型让你能写出更通用、更灵活的代码，而不需要牺牲类型安全。今天我们就来深入学习 Go 的泛型。&lt;/p&gt;</description></item><item><title>命令行工具：用 Go 构建优雅的 CLI 应用</title><link>https://plumephp.com/24-command-line-tools/</link><pubDate>Thu, 11 Mar 2021 09:45:00 +0800</pubDate><guid>https://plumephp.com/24-command-line-tools/</guid><description>&lt;h2 id="命令行工具用-go-构建优雅的-cli-应用"&gt;命令行工具：用 Go 构建优雅的 CLI 应用&lt;/h2&gt;
&lt;p&gt;Go 语言非常适合构建命令行工具。事实上，很多知名的 CLI 工具都是用 Go 写的：Docker、Kubernetes、Hugo、Terraform……&lt;/p&gt;</description></item><item><title>反射：Go 的元编程魔法</title><link>https://plumephp.com/23-reflection/</link><pubDate>Mon, 08 Mar 2021 14:30:00 +0800</pubDate><guid>https://plumephp.com/23-reflection/</guid><description>&lt;h2 id="反射go-的元编程魔法"&gt;反射：Go 的元编程魔法&lt;/h2&gt;
&lt;p&gt;反射（Reflection）是一种强大的元编程技术，它允许程序在运行时检查和操作自身的结构。在 Go 中，反射主要用于处理未知类型的值、实现通用函数、构建框架等场景。&lt;/p&gt;</description></item><item><title>时间处理：掌握 Go 的 time 包</title><link>https://plumephp.com/22-time-package/</link><pubDate>Sat, 06 Mar 2021 10:15:00 +0800</pubDate><guid>https://plumephp.com/22-time-package/</guid><description>&lt;h2 id="时间处理掌握-go-的-time-包"&gt;时间处理：掌握 Go 的 time 包&lt;/h2&gt;
&lt;p&gt;时间是程序中最常用也是最容易出错的概念之一。时区转换、格式化、时间计算、定时任务……每一个都是坑。&lt;/p&gt;
&lt;p&gt;幸运的是，Go 的 &lt;code&gt;time&lt;/code&gt; 包设计得非常优雅和实用。今天我们就来全面学习它。&lt;/p&gt;</description></item><item><title>数据库操作：Go 与 SQL 的完美邂逅</title><link>https://plumephp.com/21-database-sql/</link><pubDate>Wed, 03 Mar 2021 14:25:00 +0800</pubDate><guid>https://plumephp.com/21-database-sql/</guid><description>&lt;h2 id="数据库操作go-与-sql-的完美邂逅"&gt;数据库操作：Go 与 SQL 的完美邂逅&lt;/h2&gt;
&lt;p&gt;几乎每一个真实的应用都需要和数据库打交道。无论是用户信息、订单数据还是日志记录，数据库都是我们存储和查询数据的核心工具。&lt;/p&gt;
&lt;p&gt;Go 语言通过 &lt;code&gt;database/sql&lt;/code&gt; 包提供了一个优雅而强大的数据库访问接口。它不直接实现任何数据库驱动，而是定义了一套标准接口，让各种数据库驱动（MySQL、PostgreSQL、SQLite 等）来实现具体的连接和操作。&lt;/p&gt;</description></item><item><title>正则表达式：文本处理的瑞士军刀</title><link>https://plumephp.com/20-regex/</link><pubDate>Sat, 27 Feb 2021 16:05:00 +0800</pubDate><guid>https://plumephp.com/20-regex/</guid><description>&lt;h2 id="正则表达式文本处理的瑞士军刀"&gt;正则表达式：文本处理的瑞士军刀&lt;/h2&gt;
&lt;p&gt;如果你要在代码里验证邮箱格式、提取日志中的时间戳、替换文本中的敏感信息、或者解析复杂的配置文件——&lt;strong&gt;正则表达式&lt;/strong&gt;（Regular Expression，简称 regex）就是你的瑞士军刀。&lt;/p&gt;</description></item><item><title>Go Modules：现代化的依赖管理</title><link>https://plumephp.com/19-go-modules/</link><pubDate>Wed, 24 Feb 2021 11:40:00 +0800</pubDate><guid>https://plumephp.com/19-go-modules/</guid><description>&lt;h2 id="go-modules现代化的依赖管理"&gt;Go Modules：现代化的依赖管理&lt;/h2&gt;
&lt;p&gt;在 Go 1.11 之前，Go 的依赖管理一直是个痛点。所有的代码都要放在 GOPATH 下，版本号管理很麻烦，同一个包的不同版本不能共存。社区出现了很多第三方工具——glide、dep、govendor——但每个都有自己的问题。&lt;/p&gt;</description></item><item><title>Go 测试：让代码更可靠的秘密武器</title><link>https://plumephp.com/18-testing/</link><pubDate>Sun, 21 Feb 2021 15:18:00 +0800</pubDate><guid>https://plumephp.com/18-testing/</guid><description>&lt;h2 id="go-测试让代码更可靠的秘密武器"&gt;Go 测试：让代码更可靠的秘密武器&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;我的代码能跑就行，为什么要写测试？&amp;rdquo;&lt;/p&gt;
&lt;p&gt;如果你有过这样的想法，那你还没经历过这样的场景：凌晨三点，客户报告生产环境出了一个 bug，你翻遍了代码也找不到问题在哪；或者你修改了一个看似无关的函数，结果整个系统都崩溃了。&lt;/p&gt;</description></item><item><title>HTTP 编程：用 Go 构建 Web 服务</title><link>https://plumephp.com/17-http-programming/</link><pubDate>Thu, 18 Feb 2021 10:50:00 +0800</pubDate><guid>https://plumephp.com/17-http-programming/</guid><description>&lt;h2 id="http-编程用-go-构建-web-服务"&gt;HTTP 编程：用 Go 构建 Web 服务&lt;/h2&gt;
&lt;p&gt;如果你要问&amp;quot;用 Go 能做什么&amp;quot;，最常见的答案之一就是——&lt;strong&gt;Web 服务&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Go 的标准库 &lt;code&gt;net/http&lt;/code&gt; 提供了强大的 HTTP 客户端和服务器实现。你不需要依赖任何第三方框架，就能构建出生产级的 Web 应用。事实上，很多知名的 Go Web 框架（如 Gin、Echo）底层都是基于标准库实现的。&lt;/p&gt;</description></item><item><title>JSON 处理：Go 与数据交换的桥梁</title><link>https://plumephp.com/16-json/</link><pubDate>Tue, 16 Feb 2021 13:10:00 +0800</pubDate><guid>https://plumephp.com/16-json/</guid><description>&lt;h2 id="json-处理go-与数据交换的桥梁"&gt;JSON 处理：Go 与数据交换的桥梁&lt;/h2&gt;
&lt;p&gt;如果你在做 Web 开发，那你几乎每天都会和 JSON 打交道。JSON（JavaScript Object Notation）已经成为当今互联网最流行的数据交换格式——API 返回 JSON，配置文件用 JSON，消息队列里传输的也是 JSON。&lt;/p&gt;</description></item><item><title>文件 I/O：Go 的文件操作全攻略</title><link>https://plumephp.com/15-file-io/</link><pubDate>Sat, 13 Feb 2021 11:28:00 +0800</pubDate><guid>https://plumephp.com/15-file-io/</guid><description>&lt;h2 id="文件-iogo-的文件操作全攻略"&gt;文件 I/O：Go 的文件操作全攻略&lt;/h2&gt;
&lt;p&gt;几乎所有程序都需要和文件打交道——读配置文件、写日志、处理用户上传的图片、导出数据到 CSV……文件操作是编程中最基础也最实用的技能之一。&lt;/p&gt;</description></item><item><title>Context：并发控制的指挥棒</title><link>https://plumephp.com/14-context/</link><pubDate>Wed, 10 Feb 2021 16:20:00 +0800</pubDate><guid>https://plumephp.com/14-context/</guid><description>&lt;h2 id="context并发控制的指挥棒"&gt;Context：并发控制的指挥棒&lt;/h2&gt;
&lt;p&gt;在构建真实的 Go 应用时，你经常会遇到这样的场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个 HTTP 请求处理到一半，客户端断开了连接——你应该停止后续的工作&lt;/li&gt;
&lt;li&gt;一个数据库查询跑了太久——你应该在超时后取消它&lt;/li&gt;
&lt;li&gt;一个微服务调用了另一个微服务——你应该传递请求 ID 方便追踪&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些场景都涉及到&lt;strong&gt;跨 goroutine 的控制流管理&lt;/strong&gt;——怎么告诉一组相关的 goroutine &amp;ldquo;该停了&amp;quot;或&amp;quot;带上这个信息&amp;rdquo;。&lt;/p&gt;</description></item><item><title>sync 包：Go 的同步工具箱</title><link>https://plumephp.com/13-sync-package/</link><pubDate>Mon, 08 Feb 2021 09:45:00 +0800</pubDate><guid>https://plumephp.com/13-sync-package/</guid><description>&lt;h2 id="sync-包go-的同步工具箱"&gt;sync 包：Go 的同步工具箱&lt;/h2&gt;
&lt;p&gt;前两篇文章我们学了 goroutine 和 channel——Go 并发编程的两大利器。但现实中，不是所有并发问题都适合用 channel 来解决。&lt;/p&gt;
&lt;p&gt;有时候你只需要保护一小段临界区代码，用 channel 反而显得笨重。这时候，传统的同步原语——互斥锁、读写锁、等待组等——就是你的好朋友。&lt;/p&gt;</description></item><item><title>Channel：Go 并发的灵魂</title><link>https://plumephp.com/12-channel/</link><pubDate>Fri, 05 Feb 2021 14:30:00 +0800</pubDate><guid>https://plumephp.com/12-channel/</guid><description>&lt;h2 id="channelgo-并发的灵魂"&gt;Channel：Go 并发的灵魂&lt;/h2&gt;
&lt;p&gt;上一篇我们学了 goroutine，但只学会了启动它们。有一个问题还没解决：&lt;strong&gt;goroutine 之间怎么交流？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在传统的并发编程中，线程之间通常通过&lt;strong&gt;共享内存&lt;/strong&gt;来通信——比如共享变量加锁。这种方式容易出错，你忘了加锁、忘了释放锁、不小心造成死锁……各种坑。&lt;/p&gt;</description></item><item><title>Goroutine：Go 的并发魔法</title><link>https://plumephp.com/11-goroutine/</link><pubDate>Tue, 02 Feb 2021 10:15:00 +0800</pubDate><guid>https://plumephp.com/11-goroutine/</guid><description>&lt;h2 id="goroutinego-的并发魔法"&gt;Goroutine：Go 的并发魔法&lt;/h2&gt;
&lt;p&gt;如果你问我 Go 语言最酷的特性是什么，我会毫不犹豫地回答：&lt;strong&gt;goroutine&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在当今这个多核 CPU 普及的时代，并发编程已经不是什么新鲜事了。但传统的并发模型——不管是 Java 的线程、Python 的多进程，还是 Node.js 的事件循环——都有各自的痛点：要么太重、要么太复杂、要么性能不够。&lt;/p&gt;</description></item><item><title>Go 跨平台路径入门：filepath、path 和文件名处理</title><link>https://plumephp.com/go-cross-platform-paths/</link><pubDate>Sat, 30 May 2020 15:28:00 +0800</pubDate><guid>https://plumephp.com/go-cross-platform-paths/</guid><description>&lt;h2 id="路径问题经常在换系统后暴露"&gt;路径问题经常在换系统后暴露&lt;/h2&gt;
&lt;p&gt;很多 Go 初学者在 macOS 或 Linux 上写路径时，会直接拼字符串：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这在类 Unix 系统上通常能跑，但到了 Windows，路径分隔符、盘符、绝对路径规则都可能不同。Go 标准库提供了 &lt;code&gt;path/filepath&lt;/code&gt; 专门处理本地文件路径，提供了 &lt;code&gt;path&lt;/code&gt; 处理 URL 路径这类斜杠分隔路径。分清它们，能避免很多跨平台问题。&lt;/p&gt;</description></item><item><title>Go 构建参数入门：版本号、环境变量和交叉编译怎么处理</title><link>https://plumephp.com/go-build-flags-and-environments/</link><pubDate>Wed, 27 May 2020 11:52:00 +0800</pubDate><guid>https://plumephp.com/go-build-flags-and-environments/</guid><description>&lt;h2 id="从-go-run-到可发布二进制"&gt;从 go run 到可发布二进制&lt;/h2&gt;
&lt;p&gt;开发时我们常用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go run .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但真正发布命令行工具或服务时，通常会构建二进制：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go build -o app .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Go 的发布体验很直接。很多程序构建后就是一个可执行文件，复制到目标机器就能运行。入门阶段除了 &lt;code&gt;go build&lt;/code&gt;，还值得了解几个常见话题：如何指定输出名，如何注入版本号，如何交叉编译，运行时配置和构建时信息应该怎么分。&lt;/p&gt;</description></item><item><title>Go 随机数入门：math/rand、crypto/rand 和安全 Token</title><link>https://plumephp.com/go-random-numbers-and-tokens/</link><pubDate>Sun, 24 May 2020 18:06:00 +0800</pubDate><guid>https://plumephp.com/go-random-numbers-and-tokens/</guid><description>&lt;h2 id="随机数也分场景"&gt;随机数也分场景&lt;/h2&gt;
&lt;p&gt;很多程序都需要随机数：生成验证码、抽样、打乱列表、测试数据、游戏掉落、会话 token、密码重置链接。它们看起来都叫“随机”，但安全要求完全不同。Go 里常见两个包：&lt;code&gt;math/rand&lt;/code&gt; 和 &lt;code&gt;crypto/rand&lt;/code&gt;。前者适合模拟、测试、非安全业务随机；后者适合安全 token、密钥、密码重置码这类不能被预测的场景。&lt;/p&gt;</description></item><item><title>Go 密码处理入门：为什么不能明文保存密码</title><link>https://plumephp.com/go-password-hashing-basics/</link><pubDate>Thu, 21 May 2020 10:17:00 +0800</pubDate><guid>https://plumephp.com/go-password-hashing-basics/</guid><description>&lt;h2 id="密码不是普通字符串"&gt;密码不是普通字符串&lt;/h2&gt;
&lt;p&gt;做用户系统时，最危险的错误之一是把密码当普通字符串存进数据库。数据库一旦泄漏，所有用户密码都会直接暴露。更糟的是，很多用户会在多个网站重复使用密码，你的泄漏会影响他们在其他服务上的账号。&lt;/p&gt;</description></item><item><title>Go 压缩和归档入门：gzip、tar 和 zip 的基本用法</title><link>https://plumephp.com/go-compress-archive-basics/</link><pubDate>Mon, 18 May 2020 14:39:00 +0800</pubDate><guid>https://plumephp.com/go-compress-archive-basics/</guid><description>&lt;h2 id="压缩和归档是很多后台工具的基础能力"&gt;压缩和归档是很多后台工具的基础能力&lt;/h2&gt;
&lt;p&gt;服务端程序经常需要处理文件：日志按天压缩，报表打包下载，备份目录归档，上传的 zip 解开检查。Go 标准库提供了 &lt;code&gt;compress/gzip&lt;/code&gt;、&lt;code&gt;archive/tar&lt;/code&gt;、&lt;code&gt;archive/zip&lt;/code&gt; 等包，足够覆盖很多入门和中小项目需求。&lt;/p&gt;</description></item><item><title>Go XML 处理入门：用 encoding/xml 解析配置和旧系统数据</title><link>https://plumephp.com/go-xml-processing-basics/</link><pubDate>Fri, 15 May 2020 09:06:00 +0800</pubDate><guid>https://plumephp.com/go-xml-processing-basics/</guid><description>&lt;h2 id="xml-还没有从工程世界消失"&gt;XML 还没有从工程世界消失&lt;/h2&gt;
&lt;p&gt;现在很多新接口都用 JSON，但 XML 依然存在于不少地方：老系统接口、支付或银行协议、配置文件、RSS、Sitemap、办公文档格式。Go 标准库提供了 &lt;code&gt;encoding/xml&lt;/code&gt;，可以像处理 JSON 一样把 XML 映射到结构体，也可以用流式方式逐个 token 解析。&lt;/p&gt;</description></item><item><title>Go CSV 处理入门：读取表格数据、清洗字段和导出报表</title><link>https://plumephp.com/go-csv-processing-basics/</link><pubDate>Tue, 12 May 2020 16:22:00 +0800</pubDate><guid>https://plumephp.com/go-csv-processing-basics/</guid><description>&lt;h2 id="csv-是最朴素也最常见的数据交换格式"&gt;CSV 是最朴素也最常见的数据交换格式&lt;/h2&gt;
&lt;p&gt;很多系统导入导出数据时，最后都会落到 CSV：用户列表、订单明细、库存表、运营报表、日志统计结果。CSV 看起来只是逗号分隔文本，但真正处理时会遇到引号、换行、空字段、表头、编码和字段数量不一致等问题。不要自己用 &lt;code&gt;strings.Split(line, &amp;quot;,&amp;quot;)&lt;/code&gt; 解析 CSV，标准库已经提供了 &lt;code&gt;encoding/csv&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Go io.Reader 和 io.Writer 入门：理解标准库里的数据流</title><link>https://plumephp.com/go-io-reader-writer-practical-guide/</link><pubDate>Sat, 09 May 2020 08:48:00 +0800</pubDate><guid>https://plumephp.com/go-io-reader-writer-practical-guide/</guid><description>&lt;h2 id="两个小接口撑起了很多标准库"&gt;两个小接口撑起了很多标准库&lt;/h2&gt;
&lt;p&gt;Go 标准库里有两个非常重要的接口：&lt;code&gt;io.Reader&lt;/code&gt; 和 &lt;code&gt;io.Writer&lt;/code&gt;。它们看起来小到不能再小，却贯穿文件、网络、压缩、HTTP、模板、JSON、命令行工具和测试。理解它们后，你会发现很多 API 的设计突然变得一致。&lt;/p&gt;</description></item><item><title>Go 空接口和类型断言入门：什么时候需要 interface{}</title><link>https://plumephp.com/go-empty-interface-and-type-assertions/</link><pubDate>Wed, 06 May 2020 20:14:00 +0800</pubDate><guid>https://plumephp.com/go-empty-interface-and-type-assertions/</guid><description>&lt;h2 id="空接口能装任何值但代价是你要自己判断"&gt;空接口能装任何值，但代价是你要自己判断&lt;/h2&gt;
&lt;p&gt;Go 里 &lt;code&gt;interface{}&lt;/code&gt; 是一个很特别的类型。因为它没有要求任何方法，所以所有类型都实现了它。字符串、整数、结构体、切片、指针，都可以赋给 &lt;code&gt;interface{}&lt;/code&gt;。这让它很灵活，也很容易被滥用。&lt;/p&gt;</description></item><item><title>Go 泛型之前怎么复用代码：接口、函数参数和清楚的重复</title><link>https://plumephp.com/go-reuse-before-generics/</link><pubDate>Sun, 03 May 2020 09:31:00 +0800</pubDate><guid>https://plumephp.com/go-reuse-before-generics/</guid><description>&lt;h2 id="没有泛型时go-依然能写可维护代码"&gt;没有泛型时，Go 依然能写可维护代码&lt;/h2&gt;
&lt;p&gt;在 2020 年学习 Go，一个经常被问到的问题是：Go 没有泛型，遇到重复代码怎么办？比如要写 &lt;code&gt;[]int&lt;/code&gt; 的查找，又要写 &lt;code&gt;[]string&lt;/code&gt; 的查找；要写通用队列，又要处理不同类型；要写工具函数，又不想复制很多版本。这个问题很真实，但答案不是“到处用 &lt;code&gt;interface{}&lt;/code&gt;”，也不是“所有重复都必须消灭”。&lt;/p&gt;</description></item><item><title>Go 静态文件服务入门：在没有 embed 之前如何组织资源</title><link>https://plumephp.com/go-static-files-before-embed/</link><pubDate>Tue, 28 Apr 2020 18:22:00 +0800</pubDate><guid>https://plumephp.com/go-static-files-before-embed/</guid><description>&lt;h2 id="静态资源也是-web-服务的一部分"&gt;静态资源也是 Web 服务的一部分&lt;/h2&gt;
&lt;p&gt;写 Go Web 服务时，你可能需要提供 CSS、图片、下载文件、HTML 模板和前端打包结果。现在很多人会想到 &lt;code&gt;embed&lt;/code&gt;，但在 2020 年 4 月的 Go 语境里，&lt;code&gt;embed&lt;/code&gt; 还不是标准库能力。那时更常见的做法是把静态资源作为目录随程序一起部署，用 &lt;code&gt;http.FileServer&lt;/code&gt; 提供访问。&lt;/p&gt;</description></item><item><title>Go 调试入门：从 fmt.Println 到 go test、race 和 Delve</title><link>https://plumephp.com/go-debugging-common-tools/</link><pubDate>Sun, 26 Apr 2020 09:44:00 +0800</pubDate><guid>https://plumephp.com/go-debugging-common-tools/</guid><description>&lt;h2 id="调试不是乱试而是缩小问题范围"&gt;调试不是乱试，而是缩小问题范围&lt;/h2&gt;
&lt;p&gt;写 Go 时，bug 可能来自很多地方：变量被遮蔽、切片共享底层数组、map 遍历顺序不稳定、JSON 字段没导出、goroutine 没退出、并发写 map、错误被忽略。初学阶段最重要的调试能力不是记住某个高级工具，而是学会缩小范围：问题在哪个函数发生？输入是什么？预期是什么？实际是什么？&lt;/p&gt;</description></item><item><title>Go 依赖管理入门：go mod、版本号、replace 和 tidy 怎么用</title><link>https://plumephp.com/go-mod-dependency-management/</link><pubDate>Thu, 23 Apr 2020 15:09:00 +0800</pubDate><guid>https://plumephp.com/go-mod-dependency-management/</guid><description>&lt;h2 id="依赖管理是项目长期维护的一部分"&gt;依赖管理是项目长期维护的一部分&lt;/h2&gt;
&lt;p&gt;Go Modules 让 Go 项目摆脱了早期强依赖 &lt;code&gt;GOPATH&lt;/code&gt; 的方式。现在一个项目只要有 &lt;code&gt;go.mod&lt;/code&gt;，就能记录模块名、Go 版本和外部依赖。你可以在任意目录开发，也能让团队成员拉代码后得到一致依赖版本。&lt;/p&gt;</description></item><item><title>Go 排序和搜索入门：sort 包、稳定排序和业务列表处理</title><link>https://plumephp.com/go-sort-search-and-collections/</link><pubDate>Mon, 20 Apr 2020 11:36:00 +0800</pubDate><guid>https://plumephp.com/go-sort-search-and-collections/</guid><description>&lt;h2 id="业务代码里到处都是排序"&gt;业务代码里到处都是排序&lt;/h2&gt;
&lt;p&gt;文章按发布时间倒序，订单按金额排序，排行榜按分数排序，后台列表按状态和创建时间排序。排序看起来是基础算法，但在业务代码里非常常见。Go 标准库的 &lt;code&gt;sort&lt;/code&gt; 包提供了常用能力：排序字符串、整数、浮点数，按自定义规则排序结构体切片，做稳定排序和二分搜索。&lt;/p&gt;</description></item><item><title>Go 正则表达式入门：用 regexp 做校验、提取和替换</title><link>https://plumephp.com/go-regexp-practical-guide/</link><pubDate>Fri, 17 Apr 2020 19:13:00 +0800</pubDate><guid>https://plumephp.com/go-regexp-practical-guide/</guid><description>&lt;h2 id="正则很好用但不要让它变成谜语"&gt;正则很好用，但不要让它变成谜语&lt;/h2&gt;
&lt;p&gt;正则表达式适合处理文本模式：校验格式、提取字段、替换内容、扫描日志。Go 标准库提供了 &lt;code&gt;regexp&lt;/code&gt; 包，使用起来比较直接。你可以编译一个正则，判断字符串是否匹配，也可以查找子匹配，把日志行拆成结构化数据。&lt;/p&gt;</description></item><item><title>Go bufio 入门：逐行读取、缓冲写入和处理大文件的基本功</title><link>https://plumephp.com/go-bufio-scanner-reader/</link><pubDate>Tue, 14 Apr 2020 08:51:00 +0800</pubDate><guid>https://plumephp.com/go-bufio-scanner-reader/</guid><description>&lt;h2 id="不是所有文件都应该一次读进内存"&gt;不是所有文件都应该一次读进内存&lt;/h2&gt;
&lt;p&gt;入门时我们常用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;access.log&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这很简单，适合小文件。但如果文件有几百 MB，甚至几个 GB，一次读进内存就不合适。日志分析、CSV 处理、文本转换、命令行过滤工具，都更适合边读边处理。Go 标准库的 &lt;code&gt;bufio&lt;/code&gt; 包就是为这类场景准备的。&lt;/p&gt;</description></item><item><title>Go 服务优雅关闭入门：让 HTTP 服务收到信号后稳稳退出</title><link>https://plumephp.com/go-graceful-shutdown/</link><pubDate>Sat, 11 Apr 2020 13:27:00 +0800</pubDate><guid>https://plumephp.com/go-graceful-shutdown/</guid><description>&lt;h2 id="服务退出也需要设计"&gt;服务退出也需要设计&lt;/h2&gt;
&lt;p&gt;很多入门 HTTP 服务最后都是这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;:8080&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它能启动服务，但没有认真处理停止过程。你按 &lt;code&gt;Ctrl+C&lt;/code&gt;，或者部署平台发送 &lt;code&gt;SIGTERM&lt;/code&gt;，进程可能立刻退出，正在处理的请求被中断，日志没来得及刷，后台任务也不知道该停。小练习问题不大，真实服务就不够稳。&lt;/p&gt;</description></item><item><title>Go HTTP Handler 测试入门：不用启动端口也能测接口</title><link>https://plumephp.com/go-http-handler-testing/</link><pubDate>Wed, 08 Apr 2020 10:05:00 +0800</pubDate><guid>https://plumephp.com/go-http-handler-testing/</guid><description>&lt;h2 id="handler-不一定要启动服务才能测试"&gt;Handler 不一定要启动服务才能测试&lt;/h2&gt;
&lt;p&gt;很多人测试 HTTP 接口时，会先 &lt;code&gt;go run .&lt;/code&gt; 启动服务，再用 curl 或浏览器访问。这种手动测试有用，但不适合长期保护代码。每次改 handler 都手动点一遍，很快会漏。Go 标准库提供了 &lt;code&gt;net/http/httptest&lt;/code&gt;，可以在测试里构造请求、记录响应，不需要真的监听端口。&lt;/p&gt;</description></item><item><title>Go JSON 解码细节：字段缺失、未知字段、流式读取和接口边界</title><link>https://plumephp.com/go-json-decoder-details/</link><pubDate>Sun, 05 Apr 2020 17:42:00 +0800</pubDate><guid>https://plumephp.com/go-json-decoder-details/</guid><description>&lt;h2 id="json-能解析成功不代表业务就安全"&gt;JSON 能解析成功，不代表业务就安全&lt;/h2&gt;
&lt;p&gt;前面学习 Go JSON 时，我们通常会写：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CreateUserRequest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这确实是 Go Web API 里很常见的代码。但真实接口里，JSON 解码有很多细节：字段缺失时是什么值？客户端多传字段会怎样？数字会不会溢出？请求体里有两个 JSON 对象怎么办？空字符串和字段不存在能不能区分？这些问题不处理，接口表面能跑，边界却不稳。&lt;/p&gt;</description></item><item><title>Go 反射和结构体标签入门：看懂 JSON、校验和框架背后的机制</title><link>https://plumephp.com/go-reflection-tags-for-beginners/</link><pubDate>Thu, 02 Apr 2020 09:18:00 +0800</pubDate><guid>https://plumephp.com/go-reflection-tags-for-beginners/</guid><description>&lt;h2 id="为什么入门阶段也值得理解一点反射"&gt;为什么入门阶段也值得理解一点反射&lt;/h2&gt;
&lt;p&gt;很多 Go 初学者第一次看到反射，并不是因为主动想学它，而是因为结构体字段后面那一串标签：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;id&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;email&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;你可能会好奇：&lt;code&gt;encoding/json&lt;/code&gt; 为什么知道要把 &lt;code&gt;Email&lt;/code&gt; 变成 &lt;code&gt;email&lt;/code&gt;？Web 框架为什么能自动把请求体绑定到结构体？校验库为什么能通过 &lt;code&gt;validate:&amp;quot;required&amp;quot;&lt;/code&gt; 判断字段必填？这些能力背后都和反射有关。&lt;/p&gt;</description></item><item><title>Go 性能入门：基准测试、pprof 和先测量再优化</title><link>https://plumephp.com/go-benchmark-and-profiling-basics/</link><pubDate>Sun, 29 Mar 2020 15:33:00 +0800</pubDate><guid>https://plumephp.com/go-benchmark-and-profiling-basics/</guid><description>&lt;h2 id="性能优化最怕凭感觉"&gt;性能优化最怕凭感觉&lt;/h2&gt;
&lt;p&gt;Go 运行速度通常不错，但这不意味着所有 Go 代码天然高效。字符串拼接、JSON 编码、数据库查询、锁竞争、内存分配、网络超时，都可能成为瓶颈。初学者常见问题不是不会优化，而是太早优化，或者凭感觉优化。结果代码变复杂了，性能却没变好。&lt;/p&gt;</description></item><item><title>Go 项目结构入门：什么时候需要 cmd、internal 和分层目录</title><link>https://plumephp.com/go-project-layout-for-beginners/</link><pubDate>Thu, 26 Mar 2020 09:58:00 +0800</pubDate><guid>https://plumephp.com/go-project-layout-for-beginners/</guid><description>&lt;h2 id="项目结构不是越复杂越专业"&gt;项目结构不是越复杂越专业&lt;/h2&gt;
&lt;p&gt;Go 新手很容易被各种项目模板吓到：&lt;code&gt;cmd&lt;/code&gt;、&lt;code&gt;internal&lt;/code&gt;、&lt;code&gt;pkg&lt;/code&gt;、&lt;code&gt;api&lt;/code&gt;、&lt;code&gt;configs&lt;/code&gt;、&lt;code&gt;deployments&lt;/code&gt;、&lt;code&gt;scripts&lt;/code&gt;、&lt;code&gt;services&lt;/code&gt;、&lt;code&gt;repositories&lt;/code&gt;。有些结构来自大型项目，有些只是团队习惯。如果你刚写一个小服务，一开始就照搬复杂模板，反而会让代码分散、跳转困难。&lt;/p&gt;</description></item><item><title>Go 命令行程序结构：从一个 main.go 整理成可维护工具</title><link>https://plumephp.com/go-command-line-app-structure/</link><pubDate>Mon, 23 Mar 2020 11:29:00 +0800</pubDate><guid>https://plumephp.com/go-command-line-app-structure/</guid><description>&lt;h2 id="命令行工具也需要结构"&gt;命令行工具也需要结构&lt;/h2&gt;
&lt;p&gt;Go 很适合写命令行工具。编译成一个二进制，复制就能运行，启动快，标准库也覆盖了参数解析、文件读写、HTTP 请求和 JSON 编解码。很多团队会用 Go 写内部运维工具、数据迁移脚本、报表生成器和代码生成器。&lt;/p&gt;</description></item><item><title>Go 模板入门：用 html/template 生成安全的 HTML 页面</title><link>https://plumephp.com/go-html-templates-for-beginners/</link><pubDate>Fri, 20 Mar 2020 16:41:00 +0800</pubDate><guid>https://plumephp.com/go-html-templates-for-beginners/</guid><description>&lt;h2 id="不是所有-web-页面都需要前端框架"&gt;不是所有 Web 页面都需要前端框架&lt;/h2&gt;
&lt;p&gt;Go 经常被用来写 JSON API，但它也很适合生成简单服务端页面。后台管理、内部工具、报表页、文档页、小型内容站，有时用 &lt;code&gt;html/template&lt;/code&gt; 直接渲染 HTML 会比引入前端构建链更省事。页面请求进来，服务端查询数据，套进模板，返回 HTML，流程非常清楚。&lt;/p&gt;</description></item><item><title>Go 数据库入门：用 database/sql 写清楚的查询和事务</title><link>https://plumephp.com/go-database-sql-introduction/</link><pubDate>Tue, 17 Mar 2020 08:52:00 +0800</pubDate><guid>https://plumephp.com/go-database-sql-introduction/</guid><description>&lt;h2 id="数据库代码要先写清楚再谈封装"&gt;数据库代码要先写清楚，再谈封装&lt;/h2&gt;
&lt;p&gt;Go 做后端服务绕不开数据库。很多人会直接找 ORM，但在理解 ORM 之前，最好先用标准库 &lt;code&gt;database/sql&lt;/code&gt; 写几次查询和事务。它能让你看清楚数据库访问的基本动作：打开连接池、执行 SQL、扫描结果、处理空值、使用 context、提交或回滚事务。&lt;/p&gt;</description></item><item><title>Go HTTP 客户端入门：请求、超时、重试和 JSON API 调用</title><link>https://plumephp.com/go-http-client-practical-guide/</link><pubDate>Sat, 14 Mar 2020 20:07:00 +0800</pubDate><guid>https://plumephp.com/go-http-client-practical-guide/</guid><description>&lt;h2 id="调用外部-api-是后端程序的日常"&gt;调用外部 API 是后端程序的日常&lt;/h2&gt;
&lt;p&gt;很多 Go 服务不只是接收请求，也会调用别的服务：支付网关、短信平台、内部用户中心、搜索服务、AI 接口、Webhook 地址。HTTP 客户端代码看似简单，一个 &lt;code&gt;http.Get&lt;/code&gt; 就能拿到响应，但真实项目里需要考虑超时、状态码、JSON 解析、错误消息、重试和日志。&lt;/p&gt;</description></item><item><title>Go 时间处理入门：time 包、时区、定时器和超时控制怎么用</title><link>https://plumephp.com/go-time-date-and-timers/</link><pubDate>Wed, 11 Mar 2020 14:18:00 +0800</pubDate><guid>https://plumephp.com/go-time-date-and-timers/</guid><description>&lt;h2 id="时间问题总比看起来更复杂"&gt;时间问题总比看起来更复杂&lt;/h2&gt;
&lt;p&gt;业务代码里到处都有时间：用户注册时间、订单过期时间、活动开始时间、接口超时、定时任务、缓存有效期、日志时间戳。刚开始你可能只会用 &lt;code&gt;time.Now()&lt;/code&gt;，但很快会遇到格式化、解析、时区和定时器。时间处理一旦写错，问题往往很隐蔽：本地正常，线上差 8 小时；今天正常，月底出错；测试偶尔超时，查不出原因。&lt;/p&gt;</description></item><item><title>Go 日志和配置入门：让程序出问题时能看得懂、改得动</title><link>https://plumephp.com/go-logging-and-configuration/</link><pubDate>Sun, 08 Mar 2020 09:36:00 +0800</pubDate><guid>https://plumephp.com/go-logging-and-configuration/</guid><description>&lt;h2 id="程序能跑还不够还要能排查"&gt;程序能跑还不够，还要能排查&lt;/h2&gt;
&lt;p&gt;很多入门程序只关注“功能能不能运行”。但真实服务上线后，另一个问题会立刻出现：出了问题怎么看？连接哪个端口？数据文件在哪里？请求为什么失败？程序启动时到底读到了什么配置？如果日志混乱、配置散落，排查会非常痛苦。&lt;/p&gt;</description></item><item><title>Go 表驱动测试入门：用一张用例表保护越来越多的业务规则</title><link>https://plumephp.com/go-table-driven-tests/</link><pubDate>Thu, 05 Mar 2020 18:24:00 +0800</pubDate><guid>https://plumephp.com/go-table-driven-tests/</guid><description>&lt;h2 id="测试不是额外负担而是让修改变轻"&gt;测试不是额外负担，而是让修改变轻&lt;/h2&gt;
&lt;p&gt;很多初学者学 Go 时会先写程序，再手动运行几次，看输出差不多就算完成。这个阶段可以理解，但一旦函数里出现业务规则，手动验证很快就不够用了。你今天改了折扣逻辑，明天改了分页边界，后天又修了一个空字符串问题，如果每次都靠肉眼看输出，迟早会漏。&lt;/p&gt;</description></item><item><title>Go 包和模块入门：把代码从一个文件整理成一个小项目</title><link>https://plumephp.com/go-packages-modules-and-imports/</link><pubDate>Mon, 02 Mar 2020 10:12:00 +0800</pubDate><guid>https://plumephp.com/go-packages-modules-and-imports/</guid><description>&lt;h2 id="从一个文件走向一个项目"&gt;从一个文件走向一个项目&lt;/h2&gt;
&lt;p&gt;刚开始学 Go 时，所有代码写在 &lt;code&gt;main.go&lt;/code&gt; 里很正常。几十行代码时，这样最直接；可是一旦你开始写命令行工具、HTTP 服务、批处理脚本，&lt;code&gt;main.go&lt;/code&gt; 很快会变成一个混合了参数解析、业务规则、文件读写和输出格式的长文件。Go 的包和模块，就是用来帮你把这些代码拆开，同时保持引用关系清楚。&lt;/p&gt;</description></item><item><title>错误处理：Go 为什么不用 try-catch？</title><link>https://plumephp.com/10-error-handling/</link><pubDate>Fri, 28 Feb 2020 14:07:00 +0800</pubDate><guid>https://plumephp.com/10-error-handling/</guid><description>&lt;h2 id="错误处理go-为什么不用-try-catch"&gt;错误处理：Go 为什么不用 try-catch？&lt;/h2&gt;
&lt;p&gt;如果你来自 Java、Python 或 C# 的世界，第一次看到 Go 的错误处理代码时，你大概会皱眉头：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doSomethingElse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;result3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doAnotherThing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;ldquo;这什么鬼？&lt;code&gt;if err != nil&lt;/code&gt; 写了一遍又一遍，也太啰嗦了吧？try-catch 不好吗？&amp;rdquo;&lt;/p&gt;</description></item><item><title>Go Web 入门：只用标准库写一个小而清楚的 HTTP 服务</title><link>https://plumephp.com/go-http-server-first-project/</link><pubDate>Fri, 28 Feb 2020 09:35:00 +0800</pubDate><guid>https://plumephp.com/go-http-server-first-project/</guid><description>&lt;h2 id="为什么建议先用标准库写一次-web-服务"&gt;为什么建议先用标准库写一次 Web 服务&lt;/h2&gt;
&lt;p&gt;很多人学 Go Web 的第一步是找框架。Gin、Echo、Fiber 都很好用，但如果完全跳过标准库，后面很容易只会照框架文档写代码，却不理解请求、响应、路由和中间件到底是什么。Go 的 &lt;code&gt;net/http&lt;/code&gt; 标准库已经足够写一个小型 JSON 服务。先用它做一次完整项目，会让你对所有框架的理解都更扎实。&lt;/p&gt;</description></item><item><title>Go 并发入门：goroutine、channel 和 context 怎么一起用</title><link>https://plumephp.com/go-goroutines-channels-and-context/</link><pubDate>Tue, 25 Feb 2020 14:55:00 +0800</pubDate><guid>https://plumephp.com/go-goroutines-channels-and-context/</guid><description>&lt;h2 id="go-的并发为什么容易上手也容易误用"&gt;Go 的并发为什么容易上手也容易误用&lt;/h2&gt;
&lt;p&gt;Go 最吸引人的特性之一是并发。启动一个 goroutine 只需要在函数调用前加 &lt;code&gt;go&lt;/code&gt;，channel 可以在 goroutine 之间传递数据，&lt;code&gt;select&lt;/code&gt; 可以等待多个通信事件，&lt;code&gt;context&lt;/code&gt; 可以传递取消信号和超时。相比手动管理线程，这套模型轻很多。&lt;/p&gt;</description></item><item><title>接口：Go 最优雅的设计哲学</title><link>https://plumephp.com/09-interfaces/</link><pubDate>Tue, 25 Feb 2020 11:15:00 +0800</pubDate><guid>https://plumephp.com/09-interfaces/</guid><description>&lt;h2 id="接口go-最优雅的设计哲学"&gt;接口：Go 最优雅的设计哲学&lt;/h2&gt;
&lt;p&gt;如果你问我 Go 语言中哪个特性最能体现设计之美，我会毫不犹豫地回答：&lt;strong&gt;接口&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在 Go 的世界里，接口不是用来&amp;quot;约束&amp;quot;的，而是用来&amp;quot;描述&amp;quot;的。它描述的是一种能力——&amp;ldquo;你能做什么&amp;rdquo;，而不是&amp;quot;你是什么&amp;quot;。这种思维方式的转变，是理解 Go 接口的关键。&lt;/p&gt;</description></item><item><title>结构体与方法：Go 的''面向对象''之路</title><link>https://plumephp.com/08-structs-and-methods/</link><pubDate>Sat, 22 Feb 2020 09:38:00 +0800</pubDate><guid>https://plumephp.com/08-structs-and-methods/</guid><description>&lt;h2 id="结构体与方法go-的面向对象之路"&gt;结构体与方法：Go 的&amp;quot;面向对象&amp;quot;之路&lt;/h2&gt;
&lt;p&gt;到目前为止，我们已经学了 Go 的基本数据类型、切片、map、指针。但如果你要描述一个更复杂的事物——比如一个&amp;quot;用户&amp;quot;，他有姓名、年龄、邮箱、注册时间等多个属性——用基本类型就不够用了。&lt;/p&gt;</description></item><item><title>Go 文件、JSON 和命令行工具：做一个真正能用的小程序</title><link>https://plumephp.com/go-files-json-and-command-line-tools/</link><pubDate>Fri, 21 Feb 2020 19:25:00 +0800</pubDate><guid>https://plumephp.com/go-files-json-and-command-line-tools/</guid><description>&lt;h2 id="小工具是学习-go-的好方式"&gt;小工具是学习 Go 的好方式&lt;/h2&gt;
&lt;p&gt;学 Go 不一定要从 Web 框架开始。很多时候，一个能解决自己问题的命令行工具更适合入门：代码量不大，不需要数据库，也不需要前端，但会用到文件、JSON、参数解析、错误处理和结构体建模。这些都是 Go 日常开发的基本能力。&lt;/p&gt;</description></item><item><title>指针：没有你想的那么可怕</title><link>https://plumephp.com/07-pointers/</link><pubDate>Wed, 19 Feb 2020 15:22:00 +0800</pubDate><guid>https://plumephp.com/07-pointers/</guid><description>&lt;h2 id="指针没有你想的那么可怕"&gt;指针：没有你想的那么可怕&lt;/h2&gt;
&lt;p&gt;提到&amp;quot;指针&amp;quot;，很多人的第一反应是恐惧。这很正常——在 C/C++ 的世界里，指针确实是很多 bug 的根源：野指针、空指针、内存泄漏、缓冲区溢出……简直是一场噩梦。&lt;/p&gt;</description></item><item><title>Go 错误处理实战：别怕 if err != nil，它是在保护你</title><link>https://plumephp.com/go-error-handling-practical-guide/</link><pubDate>Tue, 18 Feb 2020 11:10:00 +0800</pubDate><guid>https://plumephp.com/go-error-handling-practical-guide/</guid><description>&lt;h2 id="错误处理是-go-风格的核心部分"&gt;错误处理是 Go 风格的核心部分&lt;/h2&gt;
&lt;p&gt;很多初学者看到 Go 代码里反复出现 &lt;code&gt;if err != nil&lt;/code&gt;，第一反应是啰嗦。和异常机制相比，它确实更显眼。可是显眼正是 Go 的设计选择：失败路径应该被看见，调用者应该明确决定如何处理失败，而不是让错误从深层调用栈里悄悄冒出来。&lt;/p&gt;</description></item><item><title>Map 字典：Go 里的哈希表实战</title><link>https://plumephp.com/06-maps/</link><pubDate>Sun, 16 Feb 2020 10:33:00 +0800</pubDate><guid>https://plumephp.com/06-maps/</guid><description>&lt;h2 id="map-字典go-里的哈希表实战"&gt;Map 字典：Go 里的哈希表实战&lt;/h2&gt;
&lt;p&gt;想象一下你走进一家大型图书馆。你想找一本特定的书，如果一本书一本书地找，可能要花上一整天。但如果有一个索引系统——你告诉管理员书名，他直接告诉你书在哪个架子上，是不是快多了？&lt;/p&gt;</description></item><item><title>Go 接口入门：从会写到真正理解隐式实现</title><link>https://plumephp.com/go-interfaces-for-beginners/</link><pubDate>Sat, 15 Feb 2020 16:30:00 +0800</pubDate><guid>https://plumephp.com/go-interfaces-for-beginners/</guid><description>&lt;h2 id="接口不是为了面向对象而是为了边界"&gt;接口不是为了“面向对象”，而是为了边界&lt;/h2&gt;
&lt;p&gt;很多人第一次看到 Go 接口，会下意识把它和 Java、PHP 或 TypeScript 的接口放在一起理解。它们确实都能描述一组方法，但 Go 接口最特别的地方是隐式实现：一个类型不需要声明自己实现了某个接口，只要方法集匹配，就自动满足接口。&lt;/p&gt;</description></item><item><title>数组与切片：Go 里最常被搞混的一对</title><link>https://plumephp.com/05-arrays-and-slices/</link><pubDate>Fri, 14 Feb 2020 13:45:00 +0800</pubDate><guid>https://plumephp.com/05-arrays-and-slices/</guid><description>&lt;h2 id="数组与切片go-里最常被搞混的一对"&gt;数组与切片：Go 里最常被搞混的一对&lt;/h2&gt;
&lt;p&gt;如果你问我 Go 语言里最容易让新手困惑的概念是什么，我一定会提到&lt;strong&gt;切片&lt;/strong&gt;（slice）。&lt;/p&gt;
&lt;p&gt;很多从 Python、Java 或其他语言转过来的开发者，看到 Go 的切片都会觉得似曾相识——它看起来像 Python 的 list，又像 Java 的 ArrayList。但当你真正开始使用它的时候，会发现它的行为和你想的总是不太一样。&lt;/p&gt;</description></item><item><title>Go 结构体和方法：用组合组织真实业务数据</title><link>https://plumephp.com/go-structs-methods-and-composition/</link><pubDate>Wed, 12 Feb 2020 08:45:00 +0800</pubDate><guid>https://plumephp.com/go-structs-methods-and-composition/</guid><description>&lt;h2 id="结构体是-go-里最重要的建模工具"&gt;结构体是 Go 里最重要的建模工具&lt;/h2&gt;
&lt;p&gt;如果说变量和函数让程序能运行，那么结构体让程序开始表达业务。用户、订单、文章、配置、任务、支付记录，这些概念都需要有名字、有字段、有规则。Go 没有传统面向对象语言那种复杂类体系，它用结构体加方法来组织数据和行为，再通过组合表达复用。&lt;/p&gt;</description></item><item><title>Go 函数：把重复的事交给它</title><link>https://plumephp.com/04-functions/</link><pubDate>Tue, 11 Feb 2020 11:08:00 +0800</pubDate><guid>https://plumephp.com/04-functions/</guid><description>&lt;h2 id="go-函数把重复的事交给它"&gt;Go 函数：把重复的事交给它&lt;/h2&gt;
&lt;p&gt;如果你仔细观察日常生活，你会发现很多事情都是重复的。每天早上你要泡咖啡，每周五你要写周报，每个月你要交房租。重复的事情做多了，你就会想：能不能把这些步骤打包，以后只需要一个指令就能搞定？&lt;/p&gt;</description></item><item><title>Go 切片、Map 和字符串：写业务代码绕不开的数据结构</title><link>https://plumephp.com/go-slices-maps-and-strings/</link><pubDate>Sun, 09 Feb 2020 21:05:00 +0800</pubDate><guid>https://plumephp.com/go-slices-maps-and-strings/</guid><description>&lt;h2 id="数据结构决定业务代码的日常手感"&gt;数据结构决定业务代码的日常手感&lt;/h2&gt;
&lt;p&gt;写 Go 后端时，你每天都会处理列表、键值表和文本。用户列表是切片，配置项是 Map，HTTP 请求里的路径、标题和 JSON 字段大多是字符串。语法上它们都很容易上手，但如果不了解一些底层直觉，很容易写出表面能跑、遇到边界就出问题的代码。&lt;/p&gt;</description></item><item><title>Go 控制流：让程序学会做选择</title><link>https://plumephp.com/03-control-flow/</link><pubDate>Sat, 08 Feb 2020 14:52:00 +0800</pubDate><guid>https://plumephp.com/03-control-flow/</guid><description>&lt;h2 id="go-控制流让程序学会做选择"&gt;Go 控制流：让程序学会做选择&lt;/h2&gt;
&lt;p&gt;到目前为止，我们写的程序都是&amp;quot;一条路走到黑&amp;quot;——从第一行开始，一行一行地往下执行，直到结束。但现实世界中的问题往往不是这么简单的。&lt;/p&gt;</description></item><item><title>Go 函数、指针和值语义：为什么代码看起来简单却很讲究</title><link>https://plumephp.com/go-functions-pointers-and-value-semantics/</link><pubDate>Fri, 07 Feb 2020 10:15:00 +0800</pubDate><guid>https://plumephp.com/go-functions-pointers-and-value-semantics/</guid><description>&lt;h2 id="go-的函数为什么让人觉得朴素"&gt;Go 的函数为什么让人觉得朴素&lt;/h2&gt;
&lt;p&gt;Go 的函数没有默认参数，没有函数重载，也没有把异常藏在函数体里。一个函数接收什么，返回什么，基本都写在签名上。刚学时你可能觉得它少了一些方便功能，但写真实项目久了会发现，这种朴素让调用关系更容易推理。&lt;/p&gt;</description></item><item><title>Go 变量与数据类型：给数据贴上标签</title><link>https://plumephp.com/02-variables-and-types/</link><pubDate>Thu, 06 Feb 2020 09:17:00 +0800</pubDate><guid>https://plumephp.com/02-variables-and-types/</guid><description>&lt;h2 id="go-变量与数据类型给数据贴上标签"&gt;Go 变量与数据类型：给数据贴上标签&lt;/h2&gt;
&lt;p&gt;上一篇我们一起搭建了 Go 的开发环境，写出了第一个 Hello World 程序。今天，我们要聊一个更基础的话题——&lt;strong&gt;变量和数据类型&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;你可能会问：变量有什么好讲的？不就是存个值嘛。&lt;/p&gt;</description></item><item><title>Go 变量、类型和流程控制：把基础语法写得清清楚楚</title><link>https://plumephp.com/go-variables-types-and-control-flow/</link><pubDate>Tue, 04 Feb 2020 15:40:00 +0800</pubDate><guid>https://plumephp.com/go-variables-types-and-control-flow/</guid><description>&lt;h2 id="基础语法不是背规则而是建立手感"&gt;基础语法不是背规则，而是建立手感&lt;/h2&gt;
&lt;p&gt;很多人学 Go 的第一反应是：语法不难，但总觉得写起来有些规矩。变量声明有好几种方式，类型写在变量名后面，&lt;code&gt;if&lt;/code&gt; 没有括号，循环只有 &lt;code&gt;for&lt;/code&gt;，&lt;code&gt;switch&lt;/code&gt; 默认不贯穿。单独看每条规则都不复杂，真正需要练的是判断：什么时候该显式写类型，什么时候可以让编译器推断，什么时候应该用 &lt;code&gt;switch&lt;/code&gt; 让代码更清楚。&lt;/p&gt;</description></item><item><title>Go 语言开发环境搭建：从零开始的冒险</title><link>https://plumephp.com/01-environment-setup/</link><pubDate>Mon, 03 Feb 2020 10:24:00 +0800</pubDate><guid>https://plumephp.com/01-environment-setup/</guid><description>&lt;h2 id="go-语言开发环境搭建从零开始的冒险"&gt;Go 语言开发环境搭建：从零开始的冒险&lt;/h2&gt;
&lt;p&gt;嗨，朋友！欢迎来到 Go 语言的世界。&lt;/p&gt;
&lt;p&gt;如果你正在读这篇文章，说明你大概已经听说了 Go 语言的一些好处——也许是它超快的编译速度，也许是它简洁优雅的语法，又或者是它强大的并发能力。不管是什么原因把你带到这里，我都很高兴能陪你一起踏上这段旅程。&lt;/p&gt;</description></item><item><title>Go 语言入门：从安装环境到写出第一个可维护程序</title><link>https://plumephp.com/go-getting-started-from-zero/</link><pubDate>Sun, 02 Feb 2020 09:20:00 +0800</pubDate><guid>https://plumephp.com/go-getting-started-from-zero/</guid><description>&lt;h2 id="为什么很多人把-go-当作第二门后端语言"&gt;为什么很多人把 Go 当作第二门后端语言&lt;/h2&gt;
&lt;p&gt;学习一门新语言时，最容易被问到的问题是：它到底能帮我解决什么？如果你已经写过 PHP、JavaScript、Python 或 Java，Go 初看起来会有一点朴素。它没有复杂的继承体系，没有花哨的语法糖，也不鼓励把一个表达式写成谜语。可是这种朴素恰好是它的特点：代码容易读，程序容易部署，标准库足够扎实，并发模型也比许多传统语言轻。&lt;/p&gt;</description></item></channel></rss>