<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>OneBoy</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://oneboy.app/blog/</id>
  <link href="https://oneboy.app/blog/" rel="alternate"/>
  <link href="https://oneboy.app/blog/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, OneBoy</rights>
  <subtitle>OneBoy 的个人博客，记录数据开发、技术与生活，自 2015 年持续写作。</subtitle>
  <title>OneBoy</title>
  <updated>2026-11-08T21:24:25.547Z</updated>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="数据仓库" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/"/>
    <category term="SQL" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/SQL/"/>
    <content>
      <![CDATA[<h3 id="常用的窗口函数"><a href="#常用的窗口函数" class="headerlink" title="常用的窗口函数"></a>常用的窗口函数</h3><table><thead><tr><th align="left">函数</th><th align="left">作用描述</th></tr></thead><tbody><tr><td align="left">ROW_NUMBER()</td><td align="left">分组内唯一递增序号</td></tr><tr><td align="left">RANK()</td><td align="left">分组内排名（有并列，排名有跳跃）</td></tr><tr><td align="left">DENSE_RANK()</td><td align="left">分组内连续排名（无跳跃）</td></tr><tr><td align="left">NTILE(n)</td><td align="left">分桶编号（分位数）</td></tr><tr><td align="left">LEAD(expr, n, def)</td><td align="left">取当前行后第 n 行的值</td></tr><tr><td align="left">LAG(expr, n, def)</td><td align="left">取当前行前第 n 行的值</td></tr><tr><td align="left">FIRST_VALUE(expr)</td><td align="left">窗口内首值</td></tr><tr><td align="left">LAST_VALUE(expr)</td><td align="left">窗口内尾值</td></tr><tr><td align="left">NTH_VALUE(expr, n)</td><td align="left">窗口内第 n 个值</td></tr><tr><td align="left">CUME_DIST()</td><td align="left">累计分布百分比</td></tr><tr><td align="left">PERCENT_RANK()</td><td align="left">百分比排名（当前行排名在分组中的百分比）</td></tr><tr><td align="left">SUM(expr)</td><td align="left">求和（聚合&#x2F;窗口均可）</td></tr><tr><td align="left">AVG(expr)</td><td align="left">平均值（聚合&#x2F;窗口均可）</td></tr><tr><td align="left">COUNT(expr)</td><td align="left">计数（聚合&#x2F;窗口均可）</td></tr><tr><td align="left">MIN(expr)</td><td align="left">最小值（聚合&#x2F;窗口均可）</td></tr><tr><td align="left">MAX(expr)</td><td align="left">最大值（聚合&#x2F;窗口均可）</td></tr></tbody></table><h3 id="常见用法"><a href="#常见用法" class="headerlink" title="常见用法"></a>常见用法</h3><h4 id="1-去重时使用row-number"><a href="#1-去重时使用row-number" class="headerlink" title="1. 去重时使用row_number"></a>1. 去重时使用row_number</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">OR</span> REPLACE TEMP <span class="keyword">VIEW</span> t1 <span class="keyword">AS</span></span><br><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">      id</span><br><span class="line">    , name</span><br><span class="line">    , modify_ts</span><br><span class="line">    , dt</span><br><span class="line">    , <span class="built_in">ROW_NUMBER</span>() <span class="keyword">OVER</span>(<span class="keyword">PARTITION</span> <span class="keyword">BY</span> id <span class="keyword">ORDER</span> <span class="keyword">BY</span> modify_ts <span class="keyword">desc</span>) <span class="keyword">AS</span> rn</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line"><span class="keyword">WHERE</span> dt <span class="operator">=</span> <span class="string">&#x27;xxx&#x27;</span></span><br><span class="line">;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 在使用时候（关联或者直接使用），通过 条件rn = 1 来保证获取的数据没有重复</span></span><br><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">      id</span><br><span class="line">    , name</span><br><span class="line">    , modify_ts</span><br><span class="line"><span class="keyword">FROM</span> t1</span><br><span class="line"><span class="keyword">WHERE</span> rn <span class="operator">=</span> <span class="number">1</span></span><br><span class="line">;</span><br></pre></td></tr></table></figure><h4 id="2-求窗口内的累计值"><a href="#2-求窗口内的累计值" class="headerlink" title="2. 求窗口内的累计值"></a>2. 求窗口内的累计值</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line"> id</span><br><span class="line">,name</span><br><span class="line">,<span class="built_in">SUM</span>(amount) <span class="keyword">OVER</span> (<span class="keyword">PARTITION</span> <span class="keyword">BY</span> id <span class="keyword">ORDER</span> <span class="keyword">BY</span> dt) <span class="keyword">as</span> total_amt</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>一般用于在窗口内，求某个时刻的累积值。比如张三2025年经常在沃尔玛超市购物，想要知道张三在每个时刻点，当年累计花的钱数。<br>窗口是2025年，分区是张三的id，排序是购物时间</p><h4 id="3-求窗口内的累计数"><a href="#3-求窗口内的累计数" class="headerlink" title="3. 求窗口内的累计数"></a>3. 求窗口内的累计数</h4><p>类似2，求某个时刻的累计数，即窗口内，累计购物次数，这里不再赘述。</p><h4 id="4-动态窗口"><a href="#4-动态窗口" class="headerlink" title="4. 动态窗口"></a>4. 动态窗口</h4><p>这个比较有意思，窗口可以是动态的。比如在商店场景下，分析每一位顾客在最近的7次购物中，累计购物金额。</p><p>用法如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line"> id</span><br><span class="line">,<span class="built_in">SUM</span>(amount) <span class="keyword">OVER</span>(<span class="keyword">PARTITION</span> <span class="keyword">BY</span> id <span class="keyword">ORDER</span> <span class="keyword">BY</span> dt <span class="keyword">ROWS</span> <span class="number">6</span> PRECEDING) last_7times_amount</span><br><span class="line"><span class="keyword">FROM</span> table_name</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p><strong>ROWS</strong> 关键字可以定义窗口的范围，常见用法有：</p><blockquote><p>当前行的前一行到后一行:</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ROWS</span> <span class="keyword">BETWEEN</span> <span class="number">1</span> PRECEDING <span class="keyword">AND</span> <span class="number">1</span> FOLLOWING</span><br></pre></td></tr></table></figure><br><blockquote><p>单边写法，前三行及当前行:</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ROWS</span> <span class="number">3</span> PRECEDING</span><br></pre></td></tr></table></figure><p>等价于 <code>ROWS BETWEEN 3 PRECEDING AND CURRENT ROW</code><br><br></p><blockquote><p>后2行及当前行:</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ROWS</span> <span class="number">2</span> FOLLOWING</span><br></pre></td></tr></table></figure><p>等价于 <code>ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING</code><br><br></p><blockquote><p>从分组的第一行到当前行:</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ROWS</span> <span class="keyword">BETWEEN</span> UNBOUNDED PRECEDING <span class="keyword">AND</span> <span class="keyword">CURRENT</span> <span class="type">ROW</span></span><br></pre></td></tr></table></figure><p>这也是默认策略，可以省略不写。<br><br></p><blockquote><p>从当前行到分组的最后一行:</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ROWS</span> <span class="keyword">BETWEEN</span> <span class="keyword">CURRENT</span> <span class="type">ROW</span> <span class="keyword">AND</span> UNBOUNDED FOLLOWING</span><br></pre></td></tr></table></figure><p>这种用法比较少见，一般从上往下累计，通过 <strong>ORDER BY</strong> 来控制升降序。</p><h4 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h4><p>动态窗口在使用时，应该确保ROWS的范围和业务范围一致。<br>例如按id+date 分区，需要确保date为连续且不重复的，否则往前xx行，不等于往前xx天。</p>]]>
    </content>
    <id>https://oneboy.app/blog/f858.html</id>
    <link href="https://oneboy.app/blog/f858.html"/>
    <published>2025-06-30T08:23:40.000Z</published>
    <summary>
      <![CDATA[<h3 id="常用的窗口函数"><a href="#常用的窗口函数" class="headerlink" title="常用的窗口函数"></a>常用的窗口函数</h3><table>
<thead>
<tr>
<th align="left">函数</th>
<th ali]]>
    </summary>
    <title>窗口函数总结</title>
    <updated>2026-11-08T21:24:25.547Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="算法" scheme="https://oneboy.app/blog/categories/%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<p>研究了一下堆排序，还挺有趣，记录一下。</p><p>原理：</p><ol><li>首先把待排序的数组放入堆中，然后构建大顶堆。</li><li>将堆顶元素（最大元素）和最后一个元素交换，把最后一个元素脱离堆结构。</li><li>重复步骤2，直到堆中只剩下一个元素。</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MAXITEM 100</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 构建大顶堆</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">Heapify</span><span class="params">(<span class="type">int</span> R[MAXITEM], <span class="type">int</span> v, <span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="type">int</span> i, j;</span><br><span class="line">    i = v;</span><br><span class="line">    j = <span class="number">2</span> * i;</span><br><span class="line"></span><br><span class="line">    R[<span class="number">0</span>] = R[i];</span><br><span class="line">    <span class="keyword">while</span> (j &lt;= n) &#123;</span><br><span class="line">        <span class="keyword">if</span> (j &lt; n &amp;&amp; R[j] &lt; R[j + <span class="number">1</span>]) &#123;</span><br><span class="line">            j++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (R[<span class="number">0</span>] &lt; R[j]) &#123;</span><br><span class="line">            R[i] = R[j];</span><br><span class="line">            i = j;</span><br><span class="line">            j = <span class="number">2</span> * i;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            j = n + <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    R[i] = R[<span class="number">0</span>];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 堆排序</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">HeapSort</span><span class="params">(<span class="type">int</span> R[MAXITEM], <span class="type">int</span> n)</span> &#123;</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="keyword">for</span> (i = n / <span class="number">2</span>; i &gt;= <span class="number">1</span>; i--) &#123;</span><br><span class="line">        Heapify(R, i, n);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (i = n; i &gt;= <span class="number">2</span>; i--) &#123;</span><br><span class="line">        R[<span class="number">0</span>] = R[i];</span><br><span class="line">        R[i] = R[<span class="number">1</span>];</span><br><span class="line">        R[<span class="number">1</span>] = R[<span class="number">0</span>];</span><br><span class="line">        Heapify(R, <span class="number">1</span>, i - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    <span class="type">int</span> R[] = &#123;<span class="number">0</span>, <span class="number">12</span>, <span class="number">29</span>, <span class="number">8</span>, <span class="number">17</span>, <span class="number">1</span>, <span class="number">5</span>, <span class="number">24</span>, <span class="number">3</span>, <span class="number">-2</span>, <span class="number">6</span>&#125;;</span><br><span class="line">    HeapSort(R, <span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; <span class="number">11</span>; i++) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;%d &quot;</span>, R[i]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://oneboy.app/blog/b66a.html</id>
    <link href="https://oneboy.app/blog/b66a.html"/>
    <published>2025-05-19T13:17:33.000Z</published>
    <summary>
      <![CDATA[<p>研究了一下堆排序，还挺有趣，记录一下。</p>
<p>原理：</p>
<ol>
<li>首先把待排序的数组放入堆中，然后构建大顶堆。</li>
<li>将堆顶元素（最大元素）和最后一个元素交换，把最后一个元素脱离堆结构。</li>
<li>重复步骤2，直到堆中只剩下一个元素]]>
    </summary>
    <title>堆排序</title>
    <updated>2026-11-08T21:24:25.547Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="运维" scheme="https://oneboy.app/blog/categories/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<p>甲骨文云提供了Ubuntu，CentOS系，Windows，以及Oracl Linux这几个版本的预装系统。我更习惯使用Debian作为服务器的操作系统。</p><p>本文记录如何使用netboot来更换当前的操作系统。</p><ol><li>下载 netboot，打开官网<a href="https://boot.netboot.xyz/">https://boot.netboot.xyz/</a> 找到自己当前的适配自己系统版本的efi文件。</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> /boot/efi/EFI/</span><br><span class="line">wget https://boot.netboot.xyz/ipxe/netboot.xyz.efi</span><br></pre></td></tr></table></figure><ol start="2"><li><p>进入控制台连接 Console connection，登录ssh，然后重启系统。</p></li><li><p>开机不停点按ESC，进入BIOS，选择从<code>netboot.xyz.efi</code>文件启动。</p></li><li><p>遇到卡在EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID device path<br>解决方式:</p><blockquote><p>Once you have booted into netboot.xyz on x86_64, if you plan on using Linux images, you must still set up custom Kernel cmdline params under Utilities (UEFI).</p><p>Set Kernel cmdline params: [] to console&#x3D;ttyS0,9600.</p><p>If you make a mistake, move with arrow keys, and use the Delete key.</p><p>Once you have typed it in, you might have to press the Escape key twice.</p><p><a href="https://netboot.xyz/docs/kb/providers/oci/#x86_64---console-quirks-linux">https://netboot.xyz/docs/kb/providers/oci/#x86_64---console-quirks-linux</a></p></blockquote></li></ol><p>简单来说，配置<code>Kernel cmdline params: []</code> 为 <code>console=ttyS0,9600</code></p><ol start="5"><li>返回重新选择Debian系统安装，后续就是一切顺利如同在本机安装一样。</li></ol>]]>
    </content>
    <id>https://oneboy.app/blog/61c.html</id>
    <link href="https://oneboy.app/blog/61c.html"/>
    <published>2025-04-09T07:44:37.000Z</published>
    <summary>
      <![CDATA[<p>甲骨文云提供了Ubuntu，CentOS系，Windows，以及Oracl Linux这几个版本的预装系统。我更习惯使用Debian作为服务器的操作系统。</p>
<p>本文记录如何使用netboot来更换当前的操作系统。</p>
<ol>
<li>下载 netboot，打]]>
    </summary>
    <title>甲骨文云安装非预装系统</title>
    <updated>2026-11-08T21:24:25.547Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="工具" scheme="https://oneboy.app/blog/categories/%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<p>在办理一些业务时，可能需要提交一些涉及个人隐私或敏感数据的文件，如身份证复印件、户口本复印件、银行卡复印件、护照复印件等。在办理的过程中涉及的环节较多，而每一个环节都有可能出现问题。当前的办事人员并不总是值得信任，存在以权谋私、倒卖数据的情况（具体案例可参见一些社工库机器人的付费查询）。所以不能将信任完全寄托在他人身上。为了保护个人隐私，在文件复印件上添加一些水印下，显得非常有必要。</p><p>可能有人会说，在如今高超的PS技术下，添加的水印分分钟被去除。但你杠就是你对，我能做的就是尽量增加数据被泄露后的利用难度，尽可能减小对自身的影响。除非你选择不办任何业务、不使用手机、不使用互联网，否则我们仍然需要采取一些保护措施。</p><p>有网友开发了一个小工具，用于给照片添加水印。确认源码没有问题后，便fork了一份，并增加了一个按钮以保存离线使用。最后部署在Vercel上，地址为：<a href="https://safemark.vip.oneboy.app/">https://safemark.vip.oneboy.app/</a> 。</p><p>这个工具不联网，没有任何外链JavaScript，不会上传任何数据，可以直接保存到本地进行离线使用。</p><p><a href="https://web.archive.org/web/2020/https://www.gov.cn/xinwen/2014-09/04/content_2745541.htm">参考1</a><br><a href="https://github.com/Yanyaoli/SafeMark">参考2</a></p>]]>
    </content>
    <id>https://oneboy.app/blog/5696.html</id>
    <link href="https://oneboy.app/blog/5696.html"/>
    <published>2025-03-07T09:21:13.000Z</published>
    <summary>
      <![CDATA[<p>在办理一些业务时，可能需要提交一些涉及个人隐私或敏感数据的文件，如身份证复印件、户口本复印件、银行卡复印件、护照复印件等。在办理的过程中涉及的环节较多，而每一个环节都有可能出现问题。当前的办事人员并不总是值得信任，存在以权谋私、倒卖数据的情况（具体案例可参见一些社工库机器人]]>
    </summary>
    <title>照片添加水印小工具</title>
    <updated>2026-11-08T21:24:25.547Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="随笔" scheme="https://oneboy.app/blog/categories/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<p>在软件开发的过程中，接手或者写出低质量代码（屎山代码）几乎是每一位程序员都会遇到的事情，不管是在小公司还是在互联网大厂，接手代码后越写越恶心，就像在像屎一样的代码里加新功能和改需求。</p><h2 id="低质量代码是如何写出来的"><a href="#低质量代码是如何写出来的" class="headerlink" title="低质量代码是如何写出来的"></a>低质量代码是如何写出来的</h2><p>为什么要写出低质量代码，为什么不一开始就严格按照规范要求来写出高质量，维护性好，可读性高的代码？</p><h3 id="1-需求的不断变化"><a href="#1-需求的不断变化" class="headerlink" title="1. 需求的不断变化"></a>1. 需求的不断变化</h3><p>在软件开发的初期阶段，团队通常会详细讨论项目的需求和设计。然而，事先定好的需求往往会随着业务环境的变化、市场的动态调整以及客户反馈的更新而被推翻或修改。这种变化性要求开发人员具备灵活性，但灵活性往往伴随着妥协，导致代码结构复杂化。说人话就是技术必须向业务妥协，毕竟直接挣钱的是业务，而不是代码。</p><h3 id="2-新需求的增加与原始设计的脱节"><a href="#2-新需求的增加与原始设计的脱节" class="headerlink" title="2. 新需求的增加与原始设计的脱节"></a>2. 新需求的增加与原始设计的脱节</h3><p>随着项目的发展，新的需求不断涌现。理想情况下，开发团队会对整个代码进行重构，以适应新的需求。然而，重写整体代码需要大量的时间和资源，现实中往往难以实现。因此，开发人员通常选择在代码的局部进行定制化修改，久而久之，代码变得难以理解，维护困难。</p><h2 id="如何提高代码质量"><a href="#如何提高代码质量" class="headerlink" title="如何提高代码质量"></a>如何提高代码质量</h2><h3 id="1-定期进行代码评审"><a href="#1-定期进行代码评审" class="headerlink" title="1. 定期进行代码评审"></a>1. 定期进行代码评审</h3><p>代码评审是确保代码质量的重要手段。通过定期的代码评审，团队成员可以相互学习，识别潜在的问题，并在早期阶段进行修正。这不仅能提高代码质量，还能促进团队的知识共享。</p><h3 id="2-引入自动化测试"><a href="#2-引入自动化测试" class="headerlink" title="2. 引入自动化测试"></a>2. 引入自动化测试</h3><p>自动化测试可以帮助开发人员快速识别代码中的错误和潜在问题。通过编写单元测试、集成测试和端到端测试，可以确保代码在修改后仍然保持其功能完整性。</p><h3 id="3-定期进行代码重构"><a href="#3-定期进行代码重构" class="headerlink" title="3. 定期进行代码重构"></a>3. 定期进行代码重构</h3><p>代码重构是改善代码结构和可维护性的重要步骤。通过定期重构，开发人员可以消除冗余代码、优化代码架构，并使代码更易于理解和维护。</p><h3 id="4-使用清晰的编码规范"><a href="#4-使用清晰的编码规范" class="headerlink" title="4. 使用清晰的编码规范"></a>4. 使用清晰的编码规范</h3><p>制定并遵循统一的编码规范可以提高代码的可读性和一致性。编码规范应包括命名约定、代码格式化规则以及注释标准等。</p><h3 id="5-加强与业务方的沟通"><a href="#5-加强与业务方的沟通" class="headerlink" title="5. 加强与业务方的沟通"></a>5. 加强与业务方的沟通</h3><p>与业务方保持良好的沟通可以减少需求变更带来的影响。通过定期的需求评审和反馈会议，开发团队可以更准确地理解业务需求，从而减少不必要的修改。</p><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>低质量代码的出现是软件开发过程中常见的挑战，但通过采取适当的措施，开发团队可以显著提高代码质量。通过代码评审、自动化测试、定期重构以及加强沟通，团队可以更好地应对复杂和变化的需求，保持代码的可维护性和稳定性。</p>]]>
    </content>
    <id>https://oneboy.app/blog/ce2a.html</id>
    <link href="https://oneboy.app/blog/ce2a.html"/>
    <published>2025-02-12T06:42:48.000Z</published>
    <summary>
      <![CDATA[<p>在软件开发的过程中，接手或者写出低质量代码（屎山代码）几乎是每一位程序员都会遇到的事情，不管是在小公司还是在互联网大厂，接手代码后越写越恶心，就像在像屎一样的代码里加新功能和改需求。</p>
<h2 id="低质量代码是如何写出来的"><a href="#低质量代码是如何写]]>
    </summary>
    <title>屎山代码是不可避免的吗</title>
    <updated>2026-11-08T21:24:25.547Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="工具" scheme="https://oneboy.app/blog/categories/%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<h3 id="约定式提交"><a href="#约定式提交" class="headerlink" title="约定式提交"></a>约定式提交</h3><p>最近想给一个开源项目添加一些功能，fork之后提交到自己仓库被拒了，查看了下使用了Commitlint，提交信息不符合规范。</p><p>继续探索后找到了这个<a href="https://www.conventionalcommits.org/zh-hans/v1.0.0/">文档</a>，这是一份约定俗成的提交规范，详细解释了什么类型的提交应该写什么样的提交信息。</p><p>现在公司的代码发布有特殊的方式，有两个仓库分别是dev仓库和prod仓库，个人提交代码流程如下：</p><ol><li>在jira建立ticket，修改release日期为今天；</li><li>修改代码并提交（commit），按照jira ticket的编号给代码打tag；</li><li>push 这个tag到dev仓库，拿到一个commit链接；</li><li>把commit链接贴到jira ticket中，发给同事进行code review；</li><li>同事评估代码，然后在jira ticket中点code signoff；</li><li>去一个特定的代码发布页面，把tag名字写进去，jira ticket的链接会自动填写，点击relase按钮；</li><li>release流程会自动把dev仓库的这个tag下的改动，自动合并到prod仓库。</li></ol><p>这个流程中只要求了tag名字的规范，并没有要求commit信息如何填写，所以每个人都写的很随意。</p><p>通过这个文档学习一下commit规范，规范自己的编码习惯。</p><h3 id="语义化版本"><a href="#语义化版本" class="headerlink" title="语义化版本"></a>语义化版本</h3><p>公司使用Github enterprise版本，且开发流程没有具体限制，全靠个人习惯。而我在发布新功能时候，版本号也仅仅是在原来小数点后面加1，并没有细究版本号的含义。</p><p>找到了一份语义化版本的<a href="https://semver.org/lang/zh-CN/">文档</a>，详细阐述了X.Y.Z的版本号中，什么时候要修改哪一位，每一位的意义是什么。</p>]]>
    </content>
    <id>https://oneboy.app/blog/d06b.html</id>
    <link href="https://oneboy.app/blog/d06b.html"/>
    <published>2025-01-14T08:32:17.000Z</published>
    <summary>
      <![CDATA[<h3 id="约定式提交"><a href="#约定式提交" class="headerlink" title="约定式提交"></a>约定式提交</h3><p>最近想给一个开源项目添加一些功能，fork之后提交到自己仓库被拒了，查看了下使用了Commitlint，提交信息不符]]>
    </summary>
    <title>提交信息和版本号</title>
    <updated>2026-11-08T21:24:25.547Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="前端" scheme="https://oneboy.app/blog/categories/%E5%89%8D%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>之前一直使用一个静态页面作为首页（手动爬下来的<a href="https://themes.gohugo.io/themes/hugo-goa/">hugo-goa</a>主题的首页），用了好多年，现在希望把首页功能拓展一下，计划使用Docusaurus来构建网站的首页。</p><ol><li>由于已经有了本博客，所以Docusaurus内提供的博客可以去掉；</li><li>希望加上Github Action持续部署；</li><li>内容上可能会和博客有冲突，待定</li></ol><p>（本文记录的是当时的改造计划，后续实施情况未再更新。）</p>]]>
    </content>
    <id>https://oneboy.app/blog/b54.html</id>
    <link href="https://oneboy.app/blog/b54.html"/>
    <published>2025-01-13T08:51:05.000Z</published>
    <summary>
      <![CDATA[<p>之前一直使用一个静态页面作为首页（手动爬下来的<a href="https://themes.gohugo.io/themes/hugo-goa/">hugo-goa</a>主题的首页），用了好多年，现在希望把首页功能拓展一下，计划使用Docusaurus来构建网站的首页。]]>
    </summary>
    <title>升级站点主页</title>
    <updated>2026-11-08T21:24:25.547Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="运维" scheme="https://oneboy.app/blog/categories/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>之前一直使用的搬瓦工（2018网络星期一）年付$27配置，2核 1G RAM + 40G SSD + 1000G&#x2F;月 Bindwidth + 洛杉矶DC2机房，从2018年开始一共续费了6年。<br>最初的目的很简单，为了加速访问一些网站，比如Github等等。<br>随着对Linux的熟悉，陆续搭建了一些服务，其中也有重装过几次系统，然后学习了容器化，这简直是一把非常好用的工具。虽然docker已经逐渐式微，被K8S移除支持，但对于个人项目使用是完全够用的。<br>虽然Podman等也很优秀，解决了一些问题，但有一些项目支持的并不太友好，为了节约时间少走弯路，还是选择使用Docker。</p><p>2024年黑五看到RN的促销活动，加量还降价，果断入手了一台RackNerd的黑五套餐二，年付$18，配置：2核 2.5G RAM + 40G SSD + 3000G&#x2F;月 Bindwidth。</p><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>比较喜欢Debian系，下单时会让选择安装什么系统，我喜欢Debian系，就选了Debian12系统，非常稳定。</p><p>付款后，服务商会提供root密码，或者让你上传ssh key，然后通过ssh访问服务器。</p><h3 id="基本配置"><a href="#基本配置" class="headerlink" title="基本配置"></a>基本配置</h3><ol><li>ssh使用root账户+密码登录；</li><li>创建新用户，设置密码，把用户添加到sudoer；</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">useradd -m -G sudo -s /bin/bash 用户名</span><br></pre></td></tr></table></figure><ol start="3"><li>修改ssh端口为高位端口，禁止root用户密码登录，禁止用户密码登录，重启sshd服务；</li><li>尝试使用新用户+密钥登录，若无法登录则去检测上一步配置；</li><li>安装常用软件&#x2F;配置UFW防火墙：</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update</span><br><span class="line">sudo apt-get install -y curl vim ufw</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">sudo ufw default allow outgoing # 默认允许所有数据出站</span><br><span class="line">sudo ufw default deny incoming # 默认禁止所有数据入站</span><br><span class="line"></span><br><span class="line">sudo ufw allow 80,443/tcp # 开启80，443端口的tcp出/入站</span><br><span class="line">sudo ufw allow 443/udp # 开启443端口的udp出/入站</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">开启刚才设置的ssh高位端口，例如54311</span></span><br><span class="line">sudo ufw allow 54321</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">开启UFW防火墙</span></span><br><span class="line">sudo ufw enable</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">把 Cloudflare的ip加入白名单</span></span><br><span class="line">RULES=$(sudo ufw status numbered | grep &#x27;Cloudflare IP&#x27; | awk -F&quot;[][]&quot; &#x27;&#123;print $2&#125;&#x27; | sort -nr)</span><br><span class="line">for RULE in $RULES; do</span><br><span class="line">    echo &quot;Deleting rule $RULE&quot;</span><br><span class="line">    echo &quot;y&quot; | sudo ufw delete $RULE</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">for cfip in `curl -sw &#x27;\n&#x27; https://www.cloudflare.com/ips-v&#123;4,6&#125;`; do ufw allow proto tcp from $cfip to any port 443 comment &#x27;Cloudflare IP&#x27;; done</span><br><span class="line"></span><br><span class="line">ufw reload &gt; /dev/null</span><br></pre></td></tr></table></figure><ol start="6"><li>开启BBR内核加速<br>编辑<code>/etc/sysctl.conf</code>文件，添加以下两行以启用BBR：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">net.core.default_qdisc = fq</span><br><span class="line">net.ipv4.tcp_congestion_control = bbr</span><br></pre></td></tr></table></figure><p>应用更改，然后检测是否生效</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo sysctl -p</span><br><span class="line">sysctl net.ipv4.tcp_congestion_control</span><br></pre></td></tr></table></figure><ol start="7"><li>参考<a href="https://docs.docker.com/engine/install/">Docker官网文档</a>，安装Docker：</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Add Docker<span class="string">&#x27;s official GPG key:</span></span></span><br><span class="line">sudo apt-get update</span><br><span class="line">sudo apt-get install ca-certificates curl</span><br><span class="line">sudo install -m 0755 -d /etc/apt/keyrings</span><br><span class="line">sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc</span><br><span class="line">sudo chmod a+r /etc/apt/keyrings/docker.asc</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="string">Add the repository to Apt sources:</span></span></span><br><span class="line">echo \</span><br><span class="line">  &quot;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \</span><br><span class="line"><span class="meta prompt_">  $</span><span class="language-bash"><span class="string">(. /etc/os-release &amp;&amp; echo &quot;$VERSION_CODENAME&quot;) stable&quot; | \</span></span></span><br><span class="line"><span class="string"><span class="language-bash">  sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null</span></span></span><br><span class="line"></span><br><span class="line">sudo apt-get update</span><br><span class="line"></span><br><span class="line">sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y</span><br></pre></td></tr></table></figure><ol start="8"><li>切换到刚创建的用户，给用户docker权限，并创建两个文件夹用来放容器数据：</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Add user to docker group</span></span><br><span class="line">groupadd docker</span><br><span class="line">usermod -aG docker $USER</span><br><span class="line">newgrp docker</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Create 2 folder <span class="keyword">for</span> docker</span></span><br><span class="line">mkdir ~/docker</span><br><span class="line">mkdir ~/compose</span><br></pre></td></tr></table></figure><ol start="9"><li>创建一个docker子网，例如创建名为<code>npm_default</code>的子网，并指定网段为<code>172.20.0.0/24</code>：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">docker network create \</span><br><span class="line"> --driver=bridge \</span><br><span class="line"> --subnet=172.20.0.0/24 \</span><br><span class="line"> --gateway=172.20.0.1 \</span><br><span class="line"> npm_default</span><br></pre></td></tr></table></figure><ol start="10"><li>在Cloudflare中使用A记录方式绑定一个子域名，例如<code>host.yourdomain.com</code>，不开云朵。后续所有新记录均使用<code>CNAME</code>方式添加，并开启云朵；</li><li>安装的第一个容器为<code>Nginx Proxy Manager</code>（下称npm），先用 <code>http://ip:81</code> 的方式登录npm的管理界面，修改用户名+密码，同时在CF中使用CNAME方式绑定子域名，例如<code>npm.yourdomain.com</code>，然后把81端口反代到这这个域名上。尝试通过 <code>npm.yourdomain.com</code> 访问npm，成功后进行下一步;<br>compose.yml文件示例：</li></ol> <figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">default:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">$&#123;DOCKER_MY_NETWORK&#125;</span></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">app:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">jc21/nginx-proxy-manager:latest</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">npm</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">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">80</span><span class="string">:80</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">443</span><span class="string">:443</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">81</span><span class="string">:81</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">DISABLE_IPV6:</span> <span class="string">&quot;true&quot;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;DOCKER_HOME&#125;/npm/data:/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;DOCKER_HOME&#125;/npm/letsencrypt:/etc/letsencrypt</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/etc/localtime:/etc/localtime:ro</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/etc/timezone:/etc/timezone:ro</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="attr">default:</span></span><br><span class="line">        <span class="attr">ipv4_address:</span> <span class="number">172.20</span><span class="number">.0</span><span class="number">.2</span></span><br><span class="line">    <span class="attr">deploy:</span></span><br><span class="line">      <span class="attr">resources:</span></span><br><span class="line">        <span class="attr">limits:</span></span><br><span class="line">          <span class="attr">cpus:</span> <span class="string">&quot;0.5&quot;</span></span><br><span class="line">          <span class="attr">memory:</span> <span class="string">300M</span></span><br></pre></td></tr></table></figure><p><code>compose.yml</code>同目录下的<code>.env</code>文件:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">DOCKER_HOME=/home/username/docker</span><br><span class="line">DOCKER_MY_NETWORK=npm_default</span><br></pre></td></tr></table></figure><p>如果已经可以通过域名访问npm，则可以把81端口也注释掉，然后<code>docker compose up -d</code> 重新部署npm：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ports:</span><br><span class="line">  - 80:80</span><br><span class="line">  - 443:443</span><br><span class="line"># - 81:81</span><br></pre></td></tr></table></figure><h3 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h3><p>用户home目录结构，也就是 <code>/home/username</code>目录：<br>其中 <code>~/docker</code> 用来存放容器运行产生的需要持久化的数据。个人不喜欢用volumn的方式，感觉管理不方便，所以采用目录方式来管理数据。</p><p><code>~/compose</code>顾名思义，用来存放<code>docker compose</code>文件，可以很方便的统一化管理<code>compose.yml</code>，对每个容器的配置可以做到固定，方便更新，重启，迁移。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">~</span><br><span class="line">│</span><br><span class="line">│─ docker</span><br><span class="line">│   ├── npm</span><br><span class="line">│   │    ├──data</span><br><span class="line">│   │    └──letsencrypt</span><br><span class="line">│   └─── nexterm</span><br><span class="line">│        └───data</span><br><span class="line">├── compose</span><br><span class="line">│       ├── npm</span><br><span class="line">│       │   ├─compose.yml</span><br><span class="line">│       │   └─.env</span><br><span class="line">│       ├── nexterm</span><br><span class="line">│       │   ├─compose.yml</span><br><span class="line">│       │   └─.env</span><br><span class="line">│       ├── ...</span><br><span class="line">│       ├── ...</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>上述步骤已经完成了VPS基本配置，所有服务皆通过<code>Nginx</code>反向代理，通过<code>https</code>在<code>443</code>端口访问，开启<code>Cloudflare</code>的云朵，开启一切皆容器之旅吧。</p>]]>
    </content>
    <id>https://oneboy.app/blog/59db.html</id>
    <link href="https://oneboy.app/blog/59db.html"/>
    <published>2025-01-13T07:28:55.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>之前一直使用的搬瓦工（2018网络星期一）年付$27配置，2核 1G RAM + 40G SSD + 1000G&#x2F;月 Bindwi]]>
    </summary>
    <title>VPS到手后的配置</title>
    <updated>2026-11-08T21:24:25.547Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="前端" scheme="https://oneboy.app/blog/categories/%E5%89%8D%E7%AB%AF/"/>
    <content>
      <![CDATA[<p>本篇文章使用Github Action部署，通过配置<code>.github/workflows/deploy.yml</code>，实现推送Markdown文件后，文章内容自动发布到仓库。</p><p>如果可以看到这篇文章，则说明Github Action正确配置。</p><p><code>.github/workflows/deploy.yml</code>具体内容如下：</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Hexo</span> <span class="string">Deploy</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">main</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">build-deploy:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Node.js</span></span><br><span class="line">      <span class="attr">uses:</span> <span class="string">actions/setup-node@v2</span></span><br><span class="line">      <span class="attr">with:</span></span><br><span class="line">        <span class="attr">node-version:</span> <span class="string">&#x27;22&#x27;</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Dependencies</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        npm install hexo-cli -g</span></span><br><span class="line"><span class="string">        npm install</span></span><br><span class="line"><span class="string"></span>    </span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Remove</span> <span class="string">unnessary</span> <span class="string">Dependency</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">npm</span> <span class="string">uninstall</span> <span class="string">hexo-generator-index</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Generate</span> <span class="string">Files</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">npx</span> <span class="string">hexo</span> <span class="string">generate</span></span><br><span class="line"></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Configure</span> <span class="string">Git</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        git config --global user.name &quot;$&#123;&#123; vars.GIT_USERNAME &#125;&#125;&quot;</span></span><br><span class="line"><span class="string">        git config --global user.email &quot;$&#123;&#123; vars.GIT_EMAIL &#125;&#125;&quot;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">SSH</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">        mkdir ~/.ssh</span></span><br><span class="line"><span class="string">        echo &quot;$&#123;&#123; secrets.PRIVATE_KEY &#125;&#125;&quot; &gt; ~/.ssh/id_rsa</span></span><br><span class="line"><span class="string">        chmod 600 ~/.ssh/id_rsa</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span></span><br><span class="line">      <span class="attr">run:</span> <span class="string">npx</span> <span class="string">hexo</span> <span class="string">deploy</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://oneboy.app/blog/39cb.html</id>
    <link href="https://oneboy.app/blog/39cb.html"/>
    <published>2025-01-10T09:00:31.000Z</published>
    <summary>
      <![CDATA[<p>本篇文章使用Github Action部署，通过配置<code>.github/workflows/deploy.yml</code>，实现推送Markdown文件后，文章内容自动发布到仓库。</p>
<p>如果可以看到这篇文章，则说明Github Action正确配置。<]]>
    </summary>
    <title>Github持续部署测试</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="数据仓库" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/"/>
    <category term="Spark" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/Spark/"/>
    <content>
      <![CDATA[<p>在开发中，遇到了一个奇怪的Join结果。简化之后的模型如下，目测执行的结果第一个为<code>true</code>，第二个为<code>false</code>，而实际的结果却是两个True。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">or</span> replace temp <span class="keyword">view</span> test1 <span class="keyword">as</span></span><br><span class="line"><span class="keyword">select</span></span><br><span class="line">      <span class="string">&#x27;23843968833490313&#x27;</span> <span class="keyword">as</span> s1</span><br><span class="line">    , <span class="built_in">cast</span>(<span class="string">&#x27;23843968833490313&#x27;</span> <span class="keyword">as</span> <span class="type">decimal</span>(<span class="number">18</span>,<span class="number">0</span>)) <span class="keyword">as</span> s2</span><br><span class="line">    , <span class="built_in">cast</span>(<span class="string">&#x27;23843968833490312&#x27;</span> <span class="keyword">as</span> <span class="type">decimal</span>(<span class="number">18</span>,<span class="number">0</span>)) <span class="keyword">as</span> s4</span><br><span class="line">;</span><br><span class="line"><span class="keyword">select</span></span><br><span class="line">      s1 <span class="operator">=</span> s2</span><br><span class="line">    , s1 <span class="operator">=</span> s4</span><br><span class="line"><span class="keyword">from</span> test1</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>究其原因是在不同类型的比较中，Spark会把其先转为Double类型再进行比较，而Double类型的精度有限，这样就会出现让人匪夷所思的现象。在实际复杂的Join中，往往会忽视数据类型的检查。</p><p>所以在实际开发中，一定要保证join两边的数据类型一致，特别是join条件两边存在表达式的情况，例如：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> tableA </span><br><span class="line"><span class="keyword">left</span> <span class="keyword">join</span> tableB</span><br><span class="line"><span class="keyword">on</span> <span class="built_in">coalesce</span>(tableA.id1 , tableA.id2) <span class="operator">=</span> tableB.id</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>建议写成：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> tableA </span><br><span class="line"><span class="keyword">left</span> <span class="keyword">join</span> tableB</span><br><span class="line"><span class="keyword">on</span> <span class="built_in">cast</span>(<span class="built_in">coalesce</span>(tableA.id1 , tableA.id2) <span class="keyword">AS</span> decmial(<span class="number">18</span>,<span class="number">0</span>)) <span class="operator">=</span> tableB.id</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>在有表达式的地方，加上类型转换。</p><p>如果能在建表之初，根据业务含义和开发规范合适的选择字段的类型，则可以在很大程度上避免这类情况的发生。</p><p>比如xxx_ID 统一使用<code>Decimal(位数,0)</code> ，xxFlag统一使用<code>Boolean</code>等，而不是String和这类情况混用。</p>]]>
    </content>
    <id>https://oneboy.app/blog/a1cd.html</id>
    <link href="https://oneboy.app/blog/a1cd.html"/>
    <published>2023-07-11T13:39:49.000Z</published>
    <summary>
      <![CDATA[<p>在开发中，遇到了一个奇怪的Join结果。简化之后的模型如下，目测执行的结果第一个为<code>true</code>，第二个为<code>false</code>，而实际的结果却是两个True。</p>
<figure class="highlight sql"><tabl]]>
    </summary>
    <title>SparkSQL踩坑</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="运维" scheme="https://oneboy.app/blog/categories/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<p>Docker在个人vps玩家中是非常好用的容器工具。只需要创建一个Nginx容器，接管80和443端口，所有服务都通过Nginx进行反代。</p><p>这样即主机只需要开放三个端口，80&#x2F;443&#x2F;22(建议改为ssh非标端口)</p><p>Docker 创建一个名为mynet的网络</p><figure class="highlight docker"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker network create --driver bridge --subnet <span class="number">172.20</span>.<span class="number">0.0</span>/<span class="number">24</span> --gateway <span class="number">172.20</span>.<span class="number">0.1</span> mynet</span><br></pre></td></tr></table></figure><p>在docker compose文件中，使用如下方式即可指定一个地址为<code>172.20.0.8:6379</code>的redis容器，这个地址即可通过Nginx反代到别的端口</p><p>而port无需手动指定映射，主机的46379端口并不会开放。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&#x27;3&#x27;</span></span><br><span class="line"><span class="attr">networks:</span> </span><br><span class="line">  <span class="attr">default:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">mynet</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">redis:</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">redis7</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">redis:7</span></span><br><span class="line">    <span class="comment"># ports:</span></span><br><span class="line">    <span class="comment">#   - &quot;46379: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">./build/redis/redis.conf:/etc/redis/redis.conf</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./build/redis/data:/data</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">TZ:</span> <span class="string">Asia/Shanghai</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">command:</span> <span class="string">redis-server</span> <span class="string">/etc/redis/redis.conf</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="attr">default:</span></span><br><span class="line">        <span class="attr">ipv4_address:</span> <span class="number">172.20</span><span class="number">.0</span><span class="number">.8</span></span><br></pre></td></tr></table></figure><p>这样启动的容器，重启也不会改变容器的ip地址。</p><p>若是使用docker命令,则类似下面这样，创建了一个<code>172.20.0.3</code>地址的portainer容器。再使用Nginx对该内网地址进行反代即可。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">docker run -d \</span><br><span class="line">--name=portainer \</span><br><span class="line">--restart=always \</span><br><span class="line">--network mynet \</span><br><span class="line">--ip 172.20.0.3 \</span><br><span class="line">-v /var/run/docker.sock:/var/run/docker.sock \</span><br><span class="line">-v portainer_data:/data \</span><br><span class="line">portainer/portainer-ce:2.16.2</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://oneboy.app/blog/8d39.html</id>
    <link href="https://oneboy.app/blog/8d39.html"/>
    <published>2023-06-24T06:25:45.000Z</published>
    <summary>
      <![CDATA[<p>Docker在个人vps玩家中是非常好用的容器工具。只需要创建一个Nginx容器，接管80和443端口，所有服务都通过Nginx进行反代。</p>
<p>这样即主机只需要开放三个端口，80&#x2F;443&#x2F;22(建议改为ssh非标端口)</p>
<p>Docke]]>
    </summary>
    <title>docker自定义容器ip的方法</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="数据仓库" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/"/>
    <content>
      <![CDATA[<p>数据工程师需要悲观的心态去处理数据，对上游数据无条件的信任往往会让自己负责的数据变的不可信任。</p><p>你永远不知道上游表数据质量如何，即使在文档中写的主键也会产生重复。</p><p>数据处理之前对上游数据可以做一些Data Quality Rule检测，及时抛出异常情况并反馈上游。</p><p>例如拉链表的一些数据质量检查：时间段重叠，消失，重复。</p><p>明细表的主键检查，指标检查。</p><p>对于自己输出的表，也同样需要做数据质量检测，为自己的数据质量负责。</p>]]>
    </content>
    <id>https://oneboy.app/blog/8432.html</id>
    <link href="https://oneboy.app/blog/8432.html"/>
    <published>2023-05-12T02:12:10.000Z</published>
    <summary>
      <![CDATA[<p>数据工程师需要悲观的心态去处理数据，对上游数据无条件的信任往往会让自己负责的数据变的不可信任。</p>
<p>你永远不知道上游表数据质量如何，即使在文档中写的主键也会产生重复。</p>
<p>数据处理之前对上游数据可以做一些Data Quality Rule检测，及时抛出异]]>
    </summary>
    <title>数据处理的一些心得</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="数据仓库" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/"/>
    <category term="Spark" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/Spark/"/>
    <content>
      <![CDATA[<p>在开发中，遇到了一个奇怪的join结果。简化之后的模型如下，脑机执行的结果第一个为true，第二个为false，而实际的结果却是两个True。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">or</span> replace temp <span class="keyword">view</span> test1 <span class="keyword">as</span></span><br><span class="line"><span class="keyword">select</span></span><br><span class="line">      <span class="string">&#x27;23843968833490313&#x27;</span> <span class="keyword">as</span> s1</span><br><span class="line">    , <span class="built_in">cast</span>(<span class="string">&#x27;23843968833490313&#x27;</span> <span class="keyword">as</span> <span class="type">decimal</span>(<span class="number">18</span>,<span class="number">0</span>)) <span class="keyword">as</span> s2</span><br><span class="line">    , <span class="built_in">cast</span>(<span class="string">&#x27;23843968833490312&#x27;</span> <span class="keyword">as</span> <span class="type">decimal</span>(<span class="number">18</span>,<span class="number">0</span>)) <span class="keyword">as</span> s4</span><br><span class="line">;</span><br><span class="line"><span class="keyword">select</span></span><br><span class="line">      s1 <span class="operator">=</span> s2</span><br><span class="line">    , s1 <span class="operator">=</span> s4</span><br><span class="line"><span class="keyword">from</span> test1</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>究其原因是在不同类型的比较中，spark会把其先转为Double类型再进行比较，而Double类型的精度有限，这样就会出现让人匪夷所思的现象。在实际复杂的join中，往往会忽视数据类型的检查。</p><p>所以在实际开发中，一定要保证join两边的数据类型一致，特别是join条件两边存在表达式的情况，例如：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> tableA </span><br><span class="line"><span class="keyword">left</span> <span class="keyword">join</span> tableB</span><br><span class="line"><span class="keyword">on</span> <span class="built_in">coalesce</span>(tableA.id1 , tableA.id2) <span class="operator">=</span> tableB.id</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>建议写成：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> tableA </span><br><span class="line"><span class="keyword">left</span> <span class="keyword">join</span> tableB</span><br><span class="line"><span class="keyword">on</span> <span class="built_in">cast</span>(<span class="built_in">coalesce</span>(tableA.id1 , tableA.id2) <span class="keyword">AS</span> decmial(<span class="number">18</span>,<span class="number">0</span>)) <span class="operator">=</span> tableB.id</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>在有表达式的地方，加上显式类型转换可以避免此。</p>]]>
    </content>
    <id>https://oneboy.app/blog/f583.html</id>
    <link href="https://oneboy.app/blog/f583.html"/>
    <published>2023-02-10T07:38:32.000Z</published>
    <summary>
      <![CDATA[<p>在开发中，遇到了一个奇怪的join结果。简化之后的模型如下，脑机执行的结果第一个为true，第二个为false，而实际的结果却是两个True。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><]]>
    </summary>
    <title>Spark自动类型转换导致的坑</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="数据仓库" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/"/>
    <category term="SQL" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/SQL/"/>
    <content>
      <![CDATA[<p>在开发中，遇到了一个奇怪的join结果。简化之后的模型如下，目测执行的结果第一个为true，第二个为false，而实际的结果却是两个True。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">or</span> replace temp <span class="keyword">view</span> test1 <span class="keyword">as</span></span><br><span class="line"><span class="keyword">select</span></span><br><span class="line">      <span class="string">&#x27;23843968833490313&#x27;</span> <span class="keyword">as</span> s1</span><br><span class="line">    , <span class="built_in">cast</span>(<span class="string">&#x27;23843968833490313&#x27;</span> <span class="keyword">as</span> <span class="type">decimal</span>(<span class="number">18</span>,<span class="number">0</span>)) <span class="keyword">as</span> s2</span><br><span class="line">    , <span class="built_in">cast</span>(<span class="string">&#x27;23843968833490312&#x27;</span> <span class="keyword">as</span> <span class="type">decimal</span>(<span class="number">18</span>,<span class="number">0</span>)) <span class="keyword">as</span> s4</span><br><span class="line">;</span><br><span class="line"><span class="keyword">select</span></span><br><span class="line">      s1 <span class="operator">=</span> s2</span><br><span class="line">    , s1 <span class="operator">=</span> s4</span><br><span class="line"><span class="keyword">from</span> test1</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>究其原因是在不同类型的比较中，spark会把其先转为Double类型再进行比较，而Double类型的精度有限，这样就会出现让人匪夷所思的现象。在实际复杂的join中，往往会忽视数据类型的检查。</p><p>所以在实际开发中，一定要保证join两边的数据类型一致，特别是join条件两边存在表达式的情况，例如：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> tableA </span><br><span class="line"><span class="keyword">left</span> <span class="keyword">join</span> tableB</span><br><span class="line"><span class="keyword">on</span> <span class="built_in">coalesce</span>(tableA.id1 , tableA.id2) <span class="operator">=</span> tableB.id</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>建议写成：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> tableA </span><br><span class="line"><span class="keyword">left</span> <span class="keyword">join</span> tableB</span><br><span class="line"><span class="keyword">on</span> <span class="built_in">cast</span>(<span class="built_in">coalesce</span>(tableA.id1 , tableA.id2) <span class="keyword">AS</span> decmial(<span class="number">18</span>,<span class="number">0</span>)) <span class="operator">=</span> tableB.id</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>在有表达式的地方，加上类型转换。</p>]]>
    </content>
    <id>https://oneboy.app/blog/1fbc.html</id>
    <link href="https://oneboy.app/blog/1fbc.html"/>
    <published>2023-02-10T07:38:32.000Z</published>
    <summary>
      <![CDATA[<p>在开发中，遇到了一个奇怪的join结果。简化之后的模型如下，目测执行的结果第一个为true，第二个为false，而实际的结果却是两个True。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><]]>
    </summary>
    <title>奇怪的join结果</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="运维" scheme="https://oneboy.app/blog/categories/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<p>在 Linux 上有一个大家用惯的 systemd，在 MacOS 上有一个与之相对应的工具，launchd。</p><p>Linux 下，systemd 的 pid 是 1，系统启动后，它是第一个被拉起来的，其它进程都是它的子进程。</p><p>在MacOS 下面也有一个这样的超级进程，所有的其它进程都是它产生的，挂为它的子进程、孙子进程…… 它就是 launchd。launchd 对应的管理工具就是 launchctl。</p><p>plist配置模板</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br><span class="line">&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;</span><br><span class="line">&lt;plist version=&quot;1.0&quot;&gt;</span><br><span class="line">    &lt;dict&gt;</span><br><span class="line">        &lt;key&gt;Label&lt;/key&gt;</span><br><span class="line">        &lt;string&gt;tech.mapan.macos.alist&lt;/string&gt;</span><br><span class="line">        &lt;key&gt;KeepAlive&lt;/key&gt;</span><br><span class="line">        &lt;true/&gt;</span><br><span class="line">        &lt;key&gt;ProcessType&lt;/key&gt;</span><br><span class="line">        &lt;string&gt;Background&lt;/string&gt;</span><br><span class="line">        &lt;key&gt;RunAtLoad&lt;/key&gt;</span><br><span class="line">        &lt;true/&gt;</span><br><span class="line">        &lt;key&gt;WorkingDirectory&lt;/key&gt;</span><br><span class="line">        &lt;string&gt;path/to/alist/working&lt;/string&gt;</span><br><span class="line">        &lt;key&gt;ProgramArguments&lt;/key&gt;</span><br><span class="line">        &lt;array&gt;</span><br><span class="line">            &lt;string&gt;path/to/alist&lt;/string&gt;</span><br><span class="line">            &lt;string&gt;server&lt;/string&gt;</span><br><span class="line">        &lt;/array&gt;</span><br><span class="line">    &lt;/dict&gt;</span><br><span class="line">&lt;/plist&gt;</span><br></pre></td></tr></table></figure><p>其中Label应该与plsit的文件名一致，例如本例子的文件名应该是<code>~/Library/LaunchAgents/tech.mapan.macos.alist.plist</code></p><ul><li><p><code>RunAtLoad</code> 代表开机自启动</p></li><li><p><code>WorkingDirectory</code> 代表工作目录</p></li><li><p><code>ProgramArguments</code>  代表运行命令</p><p>本例中的命令等价于在<code>/Users/mapan/alist</code>目录下 执行 <code>/Users/mapan/bin/alist server</code>命令。</p></li></ul><p>配置好plist文件后重启电脑后即可生效。</p><p>如果遇到问题可以使用<code>LaunchControl</code>这个可视化工具进行排查。</p><p>安装方式：<code>brew install --cask launchcontrol</code></p><p>ps: 这个工具是收费软件，但可以试用，试用版对于调试看报错信息足够了。</p>]]>
    </content>
    <id>https://oneboy.app/blog/9add.html</id>
    <link href="https://oneboy.app/blog/9add.html"/>
    <published>2023-01-16T04:05:11.000Z</published>
    <summary>
      <![CDATA[<p>在 Linux 上有一个大家用惯的 systemd，在 MacOS 上有一个与之相对应的工具，launchd。</p>
<p>Linux 下，systemd 的 pid 是 1，系统启动后，它是第一个被拉起来的，其它进程都是它的子进程。</p>
<p>在MacOS 下面也有]]>
    </summary>
    <title>launchctl的简单使用</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="运维" scheme="https://oneboy.app/blog/categories/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<p>最近有个需求每天07:00 和 23:00各运行一个脚本（断网，为了健康夜里就不上网了吧），有现成的可视化框架可以用，那就是青龙面板。但用青龙有点杀鸡用牛刀的感觉，所以就学习一下crontab的用法。</p><p>首先编写两个需要运行的脚本 <code>0700.sh</code> 和<code>2300.sh</code>（根据自己需求来），并给予运行权限。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看当前的crontab</span></span><br><span class="line">crontab -l</span><br><span class="line"></span><br><span class="line"><span class="comment"># 编辑crontab</span></span><br><span class="line">crontab -e</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用vim命令添加如下两行</span></span><br><span class="line">* 7 * * * bash /path/to/file/0700.sh</span><br><span class="line">* 7 * * * bash /path/to/file/2300.sh</span><br><span class="line"><span class="comment"># 这里建议使用绝对路径</span></span><br></pre></td></tr></table></figure><p>测试脚步正常，收工。</p>]]>
    </content>
    <id>https://oneboy.app/blog/6711.html</id>
    <link href="https://oneboy.app/blog/6711.html"/>
    <published>2022-09-24T11:45:00.000Z</published>
    <summary>
      <![CDATA[<p>最近有个需求每天07:00 和 23:00各运行一个脚本（断网，为了健康夜里就不上网了吧），有现成的可视化框架可以用，那就是青龙面板。但用青龙有点杀鸡用牛刀的感觉，所以就学习一下crontab的用法。</p>
<p>首先编写两个需要运行的脚本 <code>0700.sh</]]>
    </summary>
    <title>crontab用法</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="运维" scheme="https://oneboy.app/blog/categories/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<p>在服务器上部署了许多服务，通过nginx反向代理共用443端口，通过不同的域名来区分不同的服务。</p><p>但是搬瓦工默认的防火墙是不开的，也就是说这些服务除了443端口能访问外，原始端口也可以访问。对此我有一些安全上的顾虑，所以考虑到把防火墙打开，只把443端口和ssh的端口放出去。</p><p>UFW</p><p>ufw是一个主机端的iptables防火墙配置工具，帮助我们简单化配置iptables。</p><p>查看iptable规则</p><p><code>sudo iptables --list-rule</code></p><p>UFW常用命令：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install ufw #安装</span><br><span class="line">sudo ufw enable #启动</span><br><span class="line">sudo ufw disable #禁用</span><br><span class="line">sudo ufw reload #重新载入</span><br><span class="line">sudo ufw reset #回复初始化设置</span><br><span class="line">sudo ufw status #防火墙状态</span><br></pre></td></tr></table></figure><p>UFW防火墙的默认行为是阻止所有传入和转发流量，并允许所有出站流量。</p><p>在开启UFW之前，首先把ssh端口加入到允许列表</p><p>ssh端口是7722，设置如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo ufw allow 7722/tcp</span><br></pre></td></tr></table></figure><p>打开443端口，作为网站默认的端口</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> ufw allow 443/tcp</span><br></pre></td></tr></table></figure><p>另外发现在设置好防火墙后，我的docker服务中的其他端口，依然能通过外网访问？</p><p>查了<a href="https://askubuntu.com/questions/652556/uncomplicated-firewall-ufw-is-not-blocking-anything-when-using-docker">相关问题</a>，<a href="https://www.zhaokeli.com/article/8613.html">相关问题</a>，发现是docker也会修改防火墙的设置</p><p>把docker的配置文件<code>/etc/docker/daemon.json</code> （没有则新建）加一行，重启docker服务即可。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123; &quot;iptables&quot; : false &#125;</span><br></pre></td></tr></table></figure><p>UFW 删除规则</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> ufw status numbered <span class="comment"># 列出所有规则</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> ufw delete 3 <span class="comment"># 删除3号规则</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://oneboy.app/blog/fa86.html</id>
    <link href="https://oneboy.app/blog/fa86.html"/>
    <published>2022-09-10T03:15:50.000Z</published>
    <summary>
      <![CDATA[<p>在服务器上部署了许多服务，通过nginx反向代理共用443端口，通过不同的域名来区分不同的服务。</p>
<p>但是搬瓦工默认的防火墙是不开的，也就是说这些服务除了443端口能访问外，原始端口也可以访问。对此我有一些安全上的顾虑，所以考虑到把防火墙打开，只把443端口和ss]]>
    </summary>
    <title>debian设置UFW防火墙</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="数据仓库" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/"/>
    <content>
      <![CDATA[<p>在使用上游提供的表的时候，我们默认是信任的。但现实往往不如人意，上游表的质量不能保证，需要自己先检验再使用。最近我们的数据出现问题，排查到源头是上游一个拉链表出现了数据重复&#x2F;重叠。</p><p>解决方法是我们下游设置一个Data Quality Rule，每天对上游的这个拉链表进行检测，如果发现重叠&#x2F;重复，就发邮件给Owner解决。</p><p>一个正常的拉链表应该如同<code>user_id = 101</code>的数据这样衔接，而不是像<code>user_id = 102</code> 这样时间范围有重叠，从业务角度来说，一个用户在同一个时间点不能有两个互斥状态（比如一个用户，在同一个时刻即是男又是女）。</p><table><thead><tr><th>user_id</th><th>status_cd</th><th>start_ts</th><th>end_ts</th></tr></thead><tbody><tr><td>101</td><td>A</td><td><code>2022-01-01 00:00:00</code></td><td><code>2022-01-08 00:00:00</code></td></tr><tr><td>101</td><td>B</td><td><code>2022-01-08 00:00:00</code></td><td><code>2022-01-12 00:00:00</code></td></tr><tr><td>101</td><td>C</td><td><code>2022-01-12 00:00:00</code></td><td><code>NULL</code></td></tr><tr><td>102</td><td>A</td><td><code>2022-01-01 00:00:00</code></td><td><code>2022-01-08 00:00:00</code></td></tr><tr><td>102</td><td>B</td><td><code>2022-01-06 00:00:00</code></td><td><code>2022-01-10 00:00:00</code></td></tr><tr><td>102</td><td>C</td><td><code>2022-01-10 00:00:00</code></td><td><code>NULL</code></td></tr></tbody></table><p>检测Overlap：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span></span><br><span class="line"><span class="operator">*</span></span><br><span class="line"><span class="keyword">from</span> <span class="keyword">table</span> t1</span><br><span class="line"><span class="keyword">join</span> <span class="keyword">table</span> t2</span><br><span class="line"><span class="keyword">on</span> t1.user_id <span class="operator">=</span> t2.user_id</span><br><span class="line"><span class="keyword">where</span> t1.start_ts <span class="operator">&lt;</span> t2.end_ts</span><br><span class="line"><span class="keyword">and</span> t2.start_ts <span class="operator">&lt;</span> t1.end_ts</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>正常情况下，是没有数据可以被查询出来的，如果有重叠的情况则可以查出重叠的数据。</p><p>检测Duplicate：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span></span><br><span class="line">     user_id</span><br><span class="line">   , start_ts</span><br><span class="line">   , end_ts</span><br><span class="line">   , <span class="built_in">count</span>(<span class="number">1</span>) <span class="keyword">as</span> cnt</span><br><span class="line"><span class="keyword">from</span> <span class="keyword">table</span></span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> <span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span></span><br><span class="line"><span class="keyword">having</span> cnt <span class="operator">&gt;</span> <span class="number">1</span></span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>如果某用户有多条起止日期相同的记录，则会被查询出来。</p><p>可以用上述SQL设置为Daily DQ Rule来监测上游的数据质量。</p>]]>
    </content>
    <id>https://oneboy.app/blog/f729.html</id>
    <link href="https://oneboy.app/blog/f729.html"/>
    <published>2022-06-05T14:15:58.000Z</published>
    <summary>
      <![CDATA[<p>在使用上游提供的表的时候，我们默认是信任的。但现实往往不如人意，上游表的质量不能保证，需要自己先检验再使用。最近我们的数据出现问题，排查到源头是上游一个拉链表出现了数据重复&#x2F;重叠。</p>
<p>解决方法是我们下游设置一个Data Quality Rule，每天对]]>
    </summary>
    <title>拉链表的数据质量</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="数据仓库" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/"/>
    <category term="Hive" scheme="https://oneboy.app/blog/categories/%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93/Hive/"/>
    <content>
      <![CDATA[<p>公司的数据处理是用的Spark on Hive, 在处理时间戳的时候，又踩了坑。在一个过滤条件中，带毫秒的时间戳没有得到期待的结果。</p><p>比如下面的SQL中，按照理解49ms早于118毫秒的，所以SQL执行结果是 <code>True</code>,但实际结果却是<code>False</code></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> </span><br><span class="line"><span class="built_in">cast</span>(<span class="string">&#x27;2022-05-19 21:09:24.49&#x27;</span> <span class="keyword">as</span> <span class="type">timestamp</span>) <span class="operator">&lt;=</span> <span class="built_in">cast</span>(<span class="string">&#x27;2022-05-19 21:09:24.118&#x27;</span> <span class="keyword">as</span> <span class="type">timestamp</span>)</span><br><span class="line">;</span><br></pre></td></tr></table></figure><p>若想要得到期待的结果，需要转换成13位的，带毫秒的时间戳，再进行转换。</p><p>可以使用如下的方式（其中ts2ms为自定义UDF函数）：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span></span><br><span class="line">ts2ms(<span class="built_in">cast</span>(<span class="string">&#x27;2022-05-19 21:09:24.49&#x27;</span> <span class="keyword">as</span> <span class="type">timestamp</span>), <span class="string">&#x27;yyyy-MM-dd HH:mm:ss.SSS&#x27;</span>) <span class="operator">&lt;=</span></span><br><span class="line">ts2ms(<span class="built_in">cast</span>(<span class="string">&#x27;2022-05-19 21:09:24.118&#x27;</span> <span class="keyword">as</span> <span class="type">timestamp</span>), <span class="string">&#x27;yyyy-MM-dd HH:mm:ss.SSS&#x27;</span>)</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://oneboy.app/blog/2a2e.html</id>
    <link href="https://oneboy.app/blog/2a2e.html"/>
    <published>2022-05-30T07:20:09.000Z</published>
    <summary>
      <![CDATA[<p>公司的数据处理是用的Spark on Hive, 在处理时间戳的时候，又踩了坑。在一个过滤条件中，带毫秒的时间戳没有得到期待的结果。</p>
<p>比如下面的SQL中，按照理解49ms早于118毫秒的，所以SQL执行结果是 <code>True</code>,但实际结果却是]]>
    </summary>
    <title>Hive时间戳的踩坑</title>
    <updated>2026-11-08T21:24:25.545Z</updated>
  </entry>
  <entry>
    <author>
      <name>OneBoy</name>
    </author>
    <category term="运维" scheme="https://oneboy.app/blog/categories/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<p>在Docker中安装gitea</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> gitea &amp;&amp; <span class="built_in">cd</span> gitea &amp;&amp; <span class="built_in">touch</span> docker-compose.yml</span><br></pre></td></tr></table></figure><p>在docker-compose.yml中填入下面内容</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">gitea:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">server:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">gitea/gitea:1.16.6</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">gitea</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">USER_UID=1001</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">USER_GID=1001</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DB_TYPE=postgres</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DB_HOST=db:5432</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DB_NAME=gitea</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DB_USER=gitea</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">DB_PASSWD=gitea</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">GITEA_CUSTOM=/data/custom</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">gitea</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./gitea:/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/etc/timezone:/etc/timezone:ro</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/etc/localtime:/etc/localtime:ro</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;3000:3000&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;2222:22&quot;</span></span><br><span class="line">    <span class="attr">depends_on:</span></span><br><span class="line">       <span class="bullet">-</span> <span class="string">db</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">db:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">postgres:14</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">POSTGRES_USER=gitea</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">POSTGRES_PASSWORD=gitea</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">POSTGRES_DB=gitea</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">gitea</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./postgres:/var/lib/postgresql/data</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>最后执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker-compose up -d</span><br></pre></td></tr></table></figure><p>gitea即安装完成，然后访问<code>localhost:3000</code> 即可开启gitea之旅。在主页填写好地址，邮箱，用户等信息即可。</p><p>可以把3000端口映射到公网443端口，绑定好域名</p><p>另外把2222端口绑定到公网22端口，即可（官网有ssh和gitea复用22端口的方法，我尝试没有成功。但我通过路由器UPnP端口转发搞定了22端口）</p>]]>
    </content>
    <id>https://oneboy.app/blog/913d.html</id>
    <link href="https://oneboy.app/blog/913d.html"/>
    <published>2022-04-25T15:31:08.000Z</published>
    <summary>
      <![CDATA[<p>在Docker中安装gitea</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class]]>
    </summary>
    <title>搭建gitea服务</title>
    <updated>2026-11-08T21:24:25.546Z</updated>
  </entry>
</feed>
