<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>小鱼不吃鱼.</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>http://192.168.0.234:4000/</id>
  <link href="http://192.168.0.234:4000/" rel="alternate"/>
  <link href="http://192.168.0.234:4000/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 小鱼不吃鱼.</rights>
  <subtitle>代码与幻想，都想认真记录</subtitle>
  <title>小鱼技术栈</title>
  <updated>2026-10-29T08:20:00.000Z</updated>
  <entry>
    <author>
      <name>小鱼不吃鱼.</name>
    </author>
    <category term="建站" scheme="http://192.168.0.234:4000/categories/%E5%BB%BA%E7%AB%99/"/>
    <category term="Hexo" scheme="http://192.168.0.234:4000/tags/Hexo/"/>
    <category term="Butterfly" scheme="http://192.168.0.234:4000/tags/Butterfly/"/>
    <category term="博客搭建" scheme="http://192.168.0.234:4000/tags/%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/"/>
    <content>
      <![CDATA[<p>一直想有一个不依赖平台的地方，存放那些散落在笔记软件、聊天记录和脑子里的技术经验。于是有了这个站点：<strong>小鱼技术栈</strong>。</p><p>它不追热点，也不追求日更，只希望每解决一个问题，就留下一条以后还能找到的路。</p><span id="more"></span><h2 id="为什么是-Hexo-Butterfly"><a href="#为什么是-Hexo-Butterfly" class="headerlink" title="为什么是 Hexo + Butterfly"></a>为什么是 Hexo + Butterfly</h2><p>Hexo 的定位很纯粹：Markdown 写文章，一条命令生成静态页面，没有数据库，也没有需要长期维护的后台。对个人博客来说，这种简单本身就是一种可靠性。</p><p>Butterfly 则补齐了阅读体验：文章卡片、目录、搜索、归档、标签、暗色模式，基本开箱即用。它允许你只改配置就得到一个完整的站点，也留了足够的 CSS 和模板空间做自己的风格。</p><blockquote><p>工具应该降低表达的摩擦，而不是成为表达本身。</p></blockquote><h2 id="这套站点怎么跑起来"><a href="#这套站点怎么跑起来" class="headerlink" title="这套站点怎么跑起来"></a>这套站点怎么跑起来</h2><p>站点完全静态化，文章和图片都放在本地，不依赖外部图床。核心命令只有几条：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 安装依赖</span></span><br><span class="line">npm install</span><br><span class="line"></span><br><span class="line"><span class="comment"># 本地预览（默认 4000 端口）</span></span><br><span class="line">npm run server</span><br><span class="line"></span><br><span class="line"><span class="comment"># 生成静态文件到 public/</span></span><br><span class="line">npm run build</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重新构建并重启本机静态服务</span></span><br><span class="line">npm run release</span><br></pre></td></tr></table></figure><p>生成后的 <code>public/</code> 可以直接交给 Nginx、GitHub Pages、Cloudflare Pages 或任意对象存储，不需要 Node.js 运行时。</p><h2 id="已经打开的能力"><a href="#已经打开的能力" class="headerlink" title="已经打开的能力"></a>已经打开的能力</h2><ul><li><strong>本地全文搜索</strong>：不依赖第三方搜索服务，构建时生成索引。</li><li><strong>亮 &#x2F; 暗色模式</strong>：默认深海蓝暗色主题，也可以切回亮色。</li><li><strong>文章目录与相关推荐</strong>：长文阅读时快速定位。</li><li><strong>归档、分类、标签</strong>：按时间或主题重新组织内容。</li><li><strong>RSS 与 sitemap</strong>：<code>/atom.xml</code> 和 <code>/sitemap.xml</code> 开箱可用。</li><li><strong>自定义 404</strong>：静态站点也可以有完整的错误页。</li></ul><h2 id="接下来写什么"><a href="#接下来写什么" class="headerlink" title="接下来写什么"></a>接下来写什么</h2><p>后端开发、工程实践、工具效率，以及一些真实项目里的取舍和复盘。尽量写“为什么”，也尽量让步骤可复现。</p><p>如果这些内容刚好帮你少踩一个坑，那这个博客就有意义。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>先把第一篇文章发出来，比把博客装修到完美更重要。</p><p>剩下的，慢慢迭代。</p>]]>
    </content>
    <id>http://192.168.0.234:4000/hello-butterfly/</id>
    <link href="http://192.168.0.234:4000/hello-butterfly/"/>
    <published>2026-10-29T02:30:00.000Z</published>
    <summary>用 Hexo 和 Butterfly 搭起「小鱼技术栈」，以及关于技术写作的一点想法。</summary>
    <title>你好，Butterfly：搭一个可持续输出的技术博客</title>
    <updated>2026-10-29T08:20:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>小鱼不吃鱼.</name>
    </author>
    <category term="写作" scheme="http://192.168.0.234:4000/categories/%E5%86%99%E4%BD%9C/"/>
    <category term="Markdown" scheme="http://192.168.0.234:4000/tags/Markdown/"/>
    <category term="效率" scheme="http://192.168.0.234:4000/tags/%E6%95%88%E7%8E%87/"/>
    <content>
      <![CDATA[<p>Markdown 的好处，是让写作重新变成“写”本身。不用在工具栏里寻找按钮，几个符号就能把结构交代清楚。这篇当作自己的速查表，也分享给同样在写字的人。</p><span id="more"></span><h2 id="最常用的几种语法"><a href="#最常用的几种语法" class="headerlink" title="最常用的几种语法"></a>最常用的几种语法</h2><h3 id="标题与强调"><a href="#标题与强调" class="headerlink" title="标题与强调"></a>标题与强调</h3><figure class="highlight markdown"><table><tr><td class="code"><pre><span class="line"><span class="section"># 一级标题</span></span><br><span class="line"><span class="section">## 二级标题</span></span><br><span class="line"></span><br><span class="line"><span class="strong">**加粗**</span></span><br><span class="line"><span class="emphasis">*斜体*</span></span><br><span class="line">~~删除线~~</span><br><span class="line"><span class="code">`行内代码`</span></span><br></pre></td></tr></table></figure><p>正文里少用一级标题，把 <code>##</code> 留给文章的章节，阅读节奏会更稳定。</p><h3 id="列表与引用"><a href="#列表与引用" class="headerlink" title="列表与引用"></a>列表与引用</h3><figure class="highlight markdown"><table><tr><td class="code"><pre><span class="line"><span class="bullet">-</span> 无序列表</span><br><span class="line"><span class="bullet">  -</span> 可以嵌套</span><br><span class="line"><span class="bullet">1.</span> 有序列表</span><br><span class="line"><span class="bullet">2.</span> 第二步</span><br><span class="line"></span><br><span class="line"><span class="quote">&gt; 引用一段别人的话，</span></span><br><span class="line"><span class="quote">&gt; 也可以写给自己看。</span></span><br></pre></td></tr></table></figure><h3 id="链接与图片"><a href="#链接与图片" class="headerlink" title="链接与图片"></a>链接与图片</h3><figure class="highlight markdown"><table><tr><td class="code"><pre><span class="line">[<span class="string">链接文字</span>](<span class="link">https://example.com</span>)</span><br><span class="line">![<span class="string">图片描述</span>](<span class="link">/img/example.jpg</span>)</span><br></pre></td></tr></table></figure><p>图片描述不要省略。它既是给搜索引擎的说明，也是图片加载失败时读者能看到的最后一点信息。</p><h2 id="写长文的三个小习惯"><a href="#写长文的三个小习惯" class="headerlink" title="写长文的三个小习惯"></a>写长文的三个小习惯</h2><ol><li><strong>一段只说一件事。</strong> 中文段落超过五六行，读者就会开始跳读。</li><li><strong>用小标题切分节奏。</strong> 小标题不是目录装饰，而是给读者的一次喘息。</li><li><strong>代码块标注语言。</strong> 高亮和复制按钮都依赖它，别让读者猜。</li></ol><h2 id="一个简单的表格"><a href="#一个简单的表格" class="headerlink" title="一个简单的表格"></a>一个简单的表格</h2><table><thead><tr><th align="left">语法</th><th align="left">用途</th><th align="center">使用频率</th></tr></thead><tbody><tr><td align="left"><code>##</code></td><td align="left">章节标题</td><td align="center">高</td></tr><tr><td align="left"><code>&gt;</code></td><td align="left">引用</td><td align="center">中</td></tr><tr><td align="left"><code>```</code></td><td align="left">代码块</td><td align="center">高</td></tr><tr><td align="left"><code>---</code></td><td align="left">分隔线</td><td align="center">低</td></tr></tbody></table><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>工具越简单，越容易坚持。Markdown 不完美，但它足够快，足够安静，也足够把注意力还给内容本身。</p>]]>
    </content>
    <id>http://192.168.0.234:4000/markdown-guide/</id>
    <link href="http://192.168.0.234:4000/markdown-guide/"/>
    <published>2026-10-26T12:15:00.000Z</published>
    <summary>一份常用 Markdown 语法清单，以及几个让长文更好读的小习惯。</summary>
    <title>Markdown 写作速查：让排版回归简单</title>
    <updated>2026-10-26T12:15:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>小鱼不吃鱼.</name>
    </author>
    <category term="后端" scheme="http://192.168.0.234:4000/categories/%E5%90%8E%E7%AB%AF/"/>
    <category term="性能优化" scheme="http://192.168.0.234:4000/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    <category term="API" scheme="http://192.168.0.234:4000/tags/API/"/>
    <category term="排查" scheme="http://192.168.0.234:4000/tags/%E6%8E%92%E6%9F%A5/"/>
    <content>
      <![CDATA[<p>接口从 200ms 涨到 800ms，监控上没有报错，CPU 也不高。这种问题最容易让人凭直觉改代码，但真正有效的做法只有一个：<strong>先量化，再定位</strong>。</p><p>这篇记录一次典型的接口性能排查过程。</p><span id="more"></span><h2 id="现象：慢，但不知道慢在哪"><a href="#现象：慢，但不知道慢在哪" class="headerlink" title="现象：慢，但不知道慢在哪"></a>现象：慢，但不知道慢在哪</h2><p>一个商品详情接口，平均耗时 800ms，P99 超过 1.5s。调用方开始超时重试，数据库连接数随之上涨。</p><p>先别急着看业务代码。用 <code>curl</code> 打几次，把时间拆开：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">curl -o /dev/null -s -w \</span><br><span class="line">  <span class="string">&#x27;dns: %&#123;time_namelookup&#125;s\ntcp: %&#123;time_connect&#125;s\ntls: %&#123;time_appconnect&#125;s\nttfb: %&#123;time_starttransfer&#125;s\ntotal: %&#123;time_total&#125;s\n&#x27;</span> \</span><br><span class="line">  <span class="string">&#x27;http://127.0.0.1:8080/api/products/42&#x27;</span></span><br></pre></td></tr></table></figure><p>结果很稳定：网络和 TLS 都正常，时间几乎全部花在服务端处理上。问题被缩小到了应用内部。</p><h2 id="第一步：让调用链说话"><a href="#第一步：让调用链说话" class="headerlink" title="第一步：让调用链说话"></a>第一步：让调用链说话</h2><p>给接口加上分段计时，或者直接看 APM 的 span。这个案例里，一次请求里出现了 <strong>1 次主查询 + 3 次关联查询 + 1 次远程调用</strong>，其中三次关联查询是串行的。</p><p>伪代码大概是：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">product = repo.get_product(product_id)</span><br><span class="line">category = repo.get_category(product.category_id)       <span class="comment"># 30ms</span></span><br><span class="line">shop = repo.get_shop(product.shop_id)                   <span class="comment"># 40ms</span></span><br><span class="line">stock = client.get_stock(product.sku)                   <span class="comment"># 120ms</span></span><br><span class="line">reviews = repo.get_review_summary(product_id)           <span class="comment"># 80ms</span></span><br></pre></td></tr></table></figure><p>单看每一步都不算慢，但串起来、再乘以并发，就成了瓶颈。</p><h2 id="第二步：把串行变并行"><a href="#第二步：把串行变并行" class="headerlink" title="第二步：把串行变并行"></a>第二步：把串行变并行</h2><p>没有依赖关系的查询，没有必要排队。用线程池或异步任务把三次独立调用并发出去：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">with</span> ThreadPoolExecutor(max_workers=<span class="number">3</span>) <span class="keyword">as</span> pool:</span><br><span class="line">    category_f = pool.submit(repo.get_category, product.category_id)</span><br><span class="line">    shop_f = pool.submit(repo.get_shop, product.shop_id)</span><br><span class="line">    stock_f = pool.submit(client.get_stock, product.sku)</span><br><span class="line"></span><br><span class="line">    category, shop, stock = category_f.result(), shop_f.result(), stock_f.result()</span><br></pre></td></tr></table></figure><p>耗时从相加变成取最大值，理论收益大约是 2&#x2F;3。但要给线程池设置合理的队列和超时，避免把下游打垮。</p><h2 id="第三步：检查-SQL-和索引"><a href="#第三步：检查-SQL-和索引" class="headerlink" title="第三步：检查 SQL 和索引"></a>第三步：检查 SQL 和索引</h2><p>并发解决的是等待，数据库本身的慢查询还得单独处理。打开慢查询日志后，发现关联查询里有一张表走了全表扫描。</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 优化前：status 区分度很低，优化器放弃索引</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> product_review <span class="keyword">WHERE</span> product_id <span class="operator">=</span> ? <span class="keyword">AND</span> status <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 建立组合索引，并只取需要的列</span></span><br><span class="line"><span class="keyword">CREATE</span> INDEX idx_review_product_status <span class="keyword">ON</span> product_review (product_id, status);</span><br></pre></td></tr></table></figure><p>两个原则：</p><ol><li><strong>组合索引的顺序</strong>：区分度高的列放前面，但也要结合查询模式。</li><li><strong>避免 <code>SELECT *</code></strong>：减少回表、网络传输和序列化成本。</li></ol><h2 id="第四步：给热点数据加缓存"><a href="#第四步：给热点数据加缓存" class="headerlink" title="第四步：给热点数据加缓存"></a>第四步：给热点数据加缓存</h2><p>商品详情是典型读多写少。把商品基础信息、店铺信息和评论摘要放进 Redis，设置 5 分钟过期，并在更新时主动删除缓存。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">cache_key = <span class="string">f&quot;product:detail:<span class="subst">&#123;product_id&#125;</span>&quot;</span></span><br><span class="line">cached = redis.get(cache_key)</span><br><span class="line"><span class="keyword">if</span> cached:</span><br><span class="line">    <span class="keyword">return</span> json.loads(cached)</span><br><span class="line"></span><br><span class="line">data = build_product_detail(product_id)</span><br><span class="line">redis.setex(cache_key, <span class="number">300</span>, json.dumps(data))</span><br><span class="line"><span class="keyword">return</span> data</span><br></pre></td></tr></table></figure><p>缓存的关键不是“加上”，而是想清楚：<strong>什么时候失效、缓存穿透怎么办、数据不一致能接受多久。</strong></p><h2 id="结果"><a href="#结果" class="headerlink" title="结果"></a>结果</h2><table><thead><tr><th align="left">指标</th><th align="right">优化前</th><th align="right">优化后</th></tr></thead><tbody><tr><td align="left">平均耗时</td><td align="right">800ms</td><td align="right">90ms</td></tr><tr><td align="left">P99</td><td align="right">1.5s</td><td align="right">210ms</td></tr><tr><td align="left">数据库 QPS</td><td align="right">偏高</td><td align="right">下降约 60%</td></tr></tbody></table><h2 id="复盘"><a href="#复盘" class="headerlink" title="复盘"></a>复盘</h2><p>这次排查没有用到什么高深技巧，关键步骤是：</p><ol><li>先把“慢”拆成可度量的时间段；</li><li>用调用链找到真正的等待点；</li><li>能并行的并行，能缓存的缓存，该建索引的建索引；</li><li>用数据验证，而不是凭感觉。</li></ol><blockquote><p>性能优化不是把代码写得更复杂，而是让每一次等待都有明确的理由。</p></blockquote>]]>
    </content>
    <id>http://192.168.0.234:4000/api-performance/</id>
    <link href="http://192.168.0.234:4000/api-performance/"/>
    <published>2026-10-21T13:40:00.000Z</published>
    <summary>一次真实接口变慢的排查记录：先量化，再定位，最后用数据验证优化效果。</summary>
    <title>一次 API 性能排查：从 800ms 到 90ms</title>
    <updated>2026-10-21T13:40:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>小鱼不吃鱼.</name>
    </author>
    <category term="工程化" scheme="http://192.168.0.234:4000/categories/%E5%B7%A5%E7%A8%8B%E5%8C%96/"/>
    <category term="Docker" scheme="http://192.168.0.234:4000/tags/Docker/"/>
    <category term="Docker Compose" scheme="http://192.168.0.234:4000/tags/Docker-Compose/"/>
    <category term="本地开发" scheme="http://192.168.0.234:4000/tags/%E6%9C%AC%E5%9C%B0%E5%BC%80%E5%8F%91/"/>
    <content>
      <![CDATA[<p>“在我电脑上是好的”，通常不是一句玩笑，而是环境没有描述清楚。</p><p>把项目依赖的服务写进 <code>docker-compose.yml</code>，再配一个 <code>Makefile</code>，新环境从 clone 到跑起来可以缩短到几分钟。</p><span id="more"></span><h2 id="目标：一条命令启动"><a href="#目标：一条命令启动" class="headerlink" title="目标：一条命令启动"></a>目标：一条命令启动</h2><p>一个典型的后端项目，本地至少需要：</p><ul><li>MySQL：持久化数据</li><li>Redis：缓存和会话</li><li>可选：RabbitMQ、MinIO、邮件沙箱</li></ul><p>与其在文档里写十几步安装教程，不如把这些服务声明成代码。</p><h2 id="目录结构"><a href="#目录结构" class="headerlink" title="目录结构"></a>目录结构</h2><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">project/</span><br><span class="line">├── docker-compose.yml</span><br><span class="line">├── .env.example</span><br><span class="line">├── Makefile</span><br><span class="line">└── app/</span><br></pre></td></tr></table></figure><p><code>docker-compose.yml</code> 放在项目根目录，环境变量放在 <code>.env</code>，模板提交到仓库，真实值不提交。</p><h2 id="Compose-文件"><a href="#Compose-文件" class="headerlink" title="Compose 文件"></a>Compose 文件</h2><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">mysql:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">mysql:8.4</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">$&#123;MYSQL_ROOT_PASSWORD:-root&#125;</span></span><br><span class="line">      <span class="attr">MYSQL_DATABASE:</span> <span class="string">$&#123;MYSQL_DATABASE:-app&#125;</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;3306:3306&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">mysql_data:/var/lib/mysql</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./ops/mysql/init:/docker-entrypoint-initdb.d:ro</span></span><br><span class="line">    <span class="attr">healthcheck:</span></span><br><span class="line">      <span class="attr">test:</span> [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;mysqladmin&quot;</span>, <span class="string">&quot;ping&quot;</span>, <span class="string">&quot;-h&quot;</span>, <span class="string">&quot;localhost&quot;</span>, <span class="string">&quot;-proot&quot;</span>]</span><br><span class="line">      <span class="attr">interval:</span> <span class="string">5s</span></span><br><span class="line">      <span class="attr">timeout:</span> <span class="string">3s</span></span><br><span class="line">      <span class="attr">retries:</span> <span class="number">20</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">redis:7-alpine</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">unless-stopped</span></span><br><span class="line">    <span class="attr">command:</span> [<span class="string">&quot;redis-server&quot;</span>, <span class="string">&quot;--appendonly&quot;</span>, <span class="string">&quot;yes&quot;</span>]</span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;6379:6379&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">redis_data:/data</span></span><br><span class="line">    <span class="attr">healthcheck:</span></span><br><span class="line">      <span class="attr">test:</span> [<span class="string">&quot;CMD&quot;</span>, <span class="string">&quot;redis-cli&quot;</span>, <span class="string">&quot;ping&quot;</span>]</span><br><span class="line">      <span class="attr">interval:</span> <span class="string">5s</span></span><br><span class="line">      <span class="attr">timeout:</span> <span class="string">3s</span></span><br><span class="line">      <span class="attr">retries:</span> <span class="number">20</span></span><br><span class="line"></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">mysql_data:</span></span><br><span class="line">  <span class="attr">redis_data:</span></span><br></pre></td></tr></table></figure><p>几个容易忽略的点：</p><ul><li><strong>healthcheck 很重要</strong>：应用启动脚本应该等依赖健康，而不是简单 <code>sleep 10</code>。</li><li><strong>数据卷命名</strong>：不要随便 <code>down -v</code>，否则本地数据会一起消失。</li><li><strong>端口冲突</strong>：用 <code>${MYSQL_PORT:-3306}:3306</code> 把宿主机端口做成可配置项。</li><li><strong>初始化脚本只执行一次</strong>：MySQL 的 <code>initdb</code> 目录只在数据卷为空时运行。</li></ul><h2 id="用-Makefile-收敛命令"><a href="#用-Makefile-收敛命令" class="headerlink" title="用 Makefile 收敛命令"></a>用 Makefile 收敛命令</h2><p>把常用操作固定下来，团队里就不需要每个人记一套命令：</p><figure class="highlight makefile"><table><tr><td class="code"><pre><span class="line"><span class="meta"><span class="keyword">.PHONY</span>: up down logs psql redis reset</span></span><br><span class="line"></span><br><span class="line"><span class="section">up:</span></span><br><span class="line">docker compose up -d --wait</span><br><span class="line"></span><br><span class="line"><span class="section">down:</span></span><br><span class="line">docker compose down</span><br><span class="line"></span><br><span class="line"><span class="section">logs:</span></span><br><span class="line">docker compose logs -f --tail=100</span><br><span class="line"></span><br><span class="line"><span class="section">psql:</span></span><br><span class="line">docker compose exec mysql mysql -uroot -proot app</span><br><span class="line"></span><br><span class="line"><span class="section">redis:</span></span><br><span class="line">docker compose exec redis redis-cli</span><br><span class="line"></span><br><span class="line"><span class="section">reset:</span></span><br><span class="line">docker compose down -v</span><br><span class="line">docker compose up -d --wait</span><br></pre></td></tr></table></figure><p><code>--wait</code> 会等待 healthcheck 通过，比 <code>sleep</code> 可靠得多。</p><h2 id="应用容器化：开发阶段先别急"><a href="#应用容器化：开发阶段先别急" class="headerlink" title="应用容器化：开发阶段先别急"></a>应用容器化：开发阶段先别急</h2><p>本地开发时，我通常只把<strong>基础设施</strong>放进 Docker，应用仍然在宿主机上跑。这样可以保留断点调试、热更新和 IDE 的完整体验。</p><p>等需要验证“干净环境”时，再用一个 <code>Dockerfile</code> 构建应用镜像：</p><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> eclipse-temurin:<span class="number">21</span>-jre</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> target/app.jar app.jar</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8080</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;java&quot;</span>, <span class="string">&quot;-jar&quot;</span>, <span class="string">&quot;/app/app.jar&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>如果确实要把应用也放进 Compose，可以加一个 <code>app</code> 服务，并用 <code>depends_on</code> 加上健康检查条件：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">depends_on:</span></span><br><span class="line">  <span class="attr">mysql:</span></span><br><span class="line">    <span class="attr">condition:</span> <span class="string">service_healthy</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">condition:</span> <span class="string">service_healthy</span></span><br></pre></td></tr></table></figure><h2 id="不要提交的东西"><a href="#不要提交的东西" class="headerlink" title="不要提交的东西"></a>不要提交的东西</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">.env</span><br><span class="line">*.log</span><br><span class="line">data/</span><br></pre></td></tr></table></figure><p>同时提供 <code>.env.example</code>，把变量名和默认值写清楚。新同事复制一份就能启动。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>Docker Compose 的价值不是“用了容器”，而是把环境变成<strong>可读、可复现、可版本控制</strong>的代码。</p><p>当环境不再是口头约定，排查问题才能从“你那边是什么版本”回到真正的代码和数据。</p><blockquote><p>好的开发环境应该像一个好的函数：输入明确，输出可复现。</p></blockquote>]]>
    </content>
    <id>http://192.168.0.234:4000/docker-compose-dev/</id>
    <link href="http://192.168.0.234:4000/docker-compose-dev/"/>
    <published>2026-10-16T10:20:00.000Z</published>
    <summary>把数据库、缓存和依赖服务交给 Docker Compose，让新同事一条命令跑起项目。</summary>
    <title>用 Docker Compose 搭一套可复现的本地开发环境</title>
    <updated>2026-10-16T10:20:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>小鱼不吃鱼.</name>
    </author>
    <category term="效率" scheme="http://192.168.0.234:4000/categories/%E6%95%88%E7%8E%87/"/>
    <category term="RSS" scheme="http://192.168.0.234:4000/tags/RSS/"/>
    <category term="工具" scheme="http://192.168.0.234:4000/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="信息管理" scheme="http://192.168.0.234:4000/tags/%E4%BF%A1%E6%81%AF%E7%AE%A1%E7%90%86/"/>
    <content>
      <![CDATA[<p>有一天我数了数，自己在几个内容平台之间来回切换的次数，远远超过了真正读完一篇文章的次数。算法很懂怎么留住我，却不太关心我到底想读什么。</p><p>于是我把 RSS 重新放回了信息食谱。</p><span id="more"></span><h2 id="RSS-没有死"><a href="#RSS-没有死" class="headerlink" title="RSS 没有死"></a>RSS 没有死</h2><p>很多人以为 RSS 是被算法淘汰的。其实它只是从大众视野里退场，变成了一个更安静的工具：你决定订阅谁，就只看见谁。</p><p>没有“猜你喜欢”，没有无限下拉，也没有为了停留时长而设计的标题。打开阅读器，列表有尽头，读完就可以关掉。</p><h2 id="我的订阅原则"><a href="#我的订阅原则" class="headerlink" title="我的订阅原则"></a>我的订阅原则</h2><ol><li><strong>少而精</strong>：订阅源控制在 30 个以内，超过就删。</li><li><strong>按主题分组</strong>：技术、阅读、设计、朋友。不同状态打开不同分组。</li><li><strong>不追求清零</strong>：未读数不是任务，过期未读就批量标记已读。</li><li><strong>保留原文链接</strong>：阅读器只是入口，重要的文章回到原站读。</li></ol><h2 id="一个小工作流"><a href="#一个小工作流" class="headerlink" title="一个小工作流"></a>一个小工作流</h2><p>看到值得收藏的文章，不再只是点个赞，而是：</p><ul><li>用稍后读工具存下；</li><li>周末统一读；</li><li>值得留下的，整理成笔记；</li><li>特别好的，写进博客。</li></ul><p>这个流程有点慢，但慢下来之后，信息才真正变成了自己的东西。</p><h2 id="推荐几个订阅"><a href="#推荐几个订阅" class="headerlink" title="推荐几个订阅"></a>推荐几个订阅</h2><ul><li><strong>阮一峰的网络日志</strong>：每周五的科技动态，信息密度很高。</li><li><strong>少数派</strong>：效率工具与数字生活，适合按需翻阅。</li><li><strong>The Marginalian</strong>：关于书、艺术与生活的长文，适合周末。</li><li><strong>Hacker News</strong>：技术社区的高信号讨论，建议只看前两页。</li></ul><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>RSS 不会替你筛选出“最重要”的信息，但它把选择权还给了你。</p><p>在一个越来越吵的互联网里，能自己决定听什么，已经是一种奢侈。</p>]]>
    </content>
    <id>http://192.168.0.234:4000/rss-again/</id>
    <link href="http://192.168.0.234:4000/rss-again/"/>
    <published>2026-10-11T01:30:00.000Z</published>
    <summary>关掉推荐流之后，我重新开始用 RSS 阅读。它没有让我读得更多，却让我读得更清楚。</summary>
    <title>把 RSS 重新放回信息食谱</title>
    <updated>2026-10-11T01:30:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>小鱼不吃鱼.</name>
    </author>
    <category term="技术" scheme="http://192.168.0.234:4000/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="工具" scheme="http://192.168.0.234:4000/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="效率" scheme="http://192.168.0.234:4000/tags/%E6%95%88%E7%8E%87/"/>
    <category term="终端" scheme="http://192.168.0.234:4000/tags/%E7%BB%88%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>终端对我来说不是“极客表演”，而是一张顺手的工作台。用得久了，会留下一些其貌不扬、但每天都离不开的工具。</p><p>这份清单不追求全，只记录真正在我的日常里反复出现的东西。</p><span id="more"></span><h2 id="基础三件套"><a href="#基础三件套" class="headerlink" title="基础三件套"></a>基础三件套</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 找文件，比 find 更好记</span></span><br><span class="line">fd <span class="string">&quot;config&quot;</span> ~/projects</span><br><span class="line"></span><br><span class="line"><span class="comment"># 找内容，默认尊重 .gitignore</span></span><br><span class="line">rg <span class="string">&quot;TODO&quot;</span> src/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 看目录，带图标和 git 状态</span></span><br><span class="line">eza -la --git</span><br></pre></td></tr></table></figure><p><code>fd</code>、<code>rg</code>、<code>eza</code> 分别替代了 <code>find</code>、<code>grep</code>、<code>ls</code>。它们不是必需品，但用过之后很难回去。</p><h2 id="让-shell-更顺手"><a href="#让-shell-更顺手" class="headerlink" title="让 shell 更顺手"></a>让 shell 更顺手</h2><h3 id="历史记录搜索"><a href="#历史记录搜索" class="headerlink" title="历史记录搜索"></a>历史记录搜索</h3><p>把上下键绑定到前缀搜索，输入几个字母就能找到之前的命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># ~/.inputrc</span></span><br><span class="line"><span class="string">&quot;\e[A&quot;</span>: history-search-backward</span><br><span class="line"><span class="string">&quot;\e[B&quot;</span>: history-search-forward</span><br></pre></td></tr></table></figure><h3 id="别名不是偷懒"><a href="#别名不是偷懒" class="headerlink" title="别名不是偷懒"></a>别名不是偷懒</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">alias</span> gs=<span class="string">&quot;git status -sb&quot;</span></span><br><span class="line"><span class="built_in">alias</span> gd=<span class="string">&quot;git diff --stat&quot;</span></span><br><span class="line"><span class="built_in">alias</span> ll=<span class="string">&quot;eza -la --git&quot;</span></span><br><span class="line"><span class="built_in">alias</span> ..=<span class="string">&quot;cd ..&quot;</span></span><br></pre></td></tr></table></figure><p>别名省下的不是几秒钟，而是每次切换上下文时的注意力。</p><h2 id="Git-的几个小习惯"><a href="#Git-的几个小习惯" class="headerlink" title="Git 的几个小习惯"></a>Git 的几个小习惯</h2><ul><li>提交前先 <code>git diff --staged</code>，确认没有误加文件。</li><li>用 <code>git switch -c</code> 代替 <code>git checkout -b</code>，语义更清楚。</li><li>提交信息写“为什么”，而不只是“做了什么”。</li><li>定期 <code>git fetch --prune</code>，清理已经消失的远程分支。</li></ul><h2 id="一个够用的编辑器配置"><a href="#一个够用的编辑器配置" class="headerlink" title="一个够用的编辑器配置"></a>一个够用的编辑器配置</h2><p>无论用 Vim 还是 Neovim，先把这几件事做好：</p><ol><li>相对行号；</li><li>快速模糊查找文件；</li><li>保存时自动格式化；</li><li>一套看得舒服的配色。</li></ol><p>插件不必多。配置是为了减少摩擦，不是为了增加仪式感。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>工具的意义，是让人忘记工具的存在。真正重要的，始终是用它写出的代码、解决的问题，以及省下来的那点时间用来做什么。</p><p>如果这份清单里只能留下一个建议，那会是：<strong>把每天重复三次以上的动作，变成一条命令。</strong></p>]]>
    </content>
    <id>http://192.168.0.234:4000/terminal-toolkit/</id>
    <link href="http://192.168.0.234:4000/terminal-toolkit/"/>
    <published>2026-10-06T14:10:00.000Z</published>
    <summary>不追求酷炫，只留下每天真正会用的命令行工具和小习惯。</summary>
    <title>在终端里安家：一份轻量命令行清单</title>
    <updated>2026-10-06T14:10:00.000Z</updated>
  </entry>
</feed>
