<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>吼姆小行星</title>
  
  
  <link href="https://blog.homu.space/atom.xml" rel="self"/>
  
  <link href="https://blog.homu.space/"/>
  <updated>2026-05-10T10:49:07.012Z</updated>
  <id>https://blog.homu.space/</id>
  
  <author>
    <name>夜焰</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Hexo博客的使用笔记</title>
    <link href="https://blog.homu.space/posts/369146d4/"/>
    <id>https://blog.homu.space/posts/369146d4/</id>
    <published>2026-05-10T10:49:07.012Z</published>
    <updated>2026-05-10T10:49:07.012Z</updated>
    
    <content type="html"><![CDATA[<h1 id="文章置顶与置底"><a href="#文章置顶与置底" class="headerlink" title="文章置顶与置底"></a>文章置顶与置底</h1><p>在 Front-matter 区域添加 sticky 字段可以设定文章的置顶与置底，亦可以设置文章的排列顺序</p><p>这个功能是 <code>hexo-generator-index</code> 插件所提供的</p><p><a href="https://github.com/hexojs/hexo-generator-index">具体文档在这里</a></p><p>将 <code>sticky</code> 设置为 <code>true</code> 或者大于 1 的数字，可以设置置顶</p><p>将 <code>sticky</code> 设置为 <code>-1</code> 可以设置置底</p><h1 id="文章隐藏"><a href="#文章隐藏" class="headerlink" title="文章隐藏"></a>文章隐藏</h1><p>如果有一些文章只想记录不想发布在网站上，可以使用<code>hexo-hide-posts</code>插件</p><p>在 Front-matter 中设置 <code>hidden</code> 属性为 <code>true</code> 即可</p><p><a href="https://github.com/prinsss/hexo-hide-posts">文档在这里</a></p><h1 id="视频标签"><a href="#视频标签" class="headerlink" title="视频标签"></a>视频标签</h1><p>一直用 <code>video</code> 标签插入视频，但是每次都要写好麻烦，而且不同浏览器的原生 video 标签样式是不同的</p><p>于是找到了 <code>hexo-tag-dplayer</code> 这个插件，用法很简单：</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;% dplayer url=&quot;&quot; key=value ... %&#125;</span><br></pre></td></tr></table></figure><h1 id="hideToggle-标签"><a href="#hideToggle-标签" class="headerlink" title="hideToggle 标签"></a>hideToggle 标签</h1><p>有时候想要隐藏一些内容，手动点击才打开</p><details class="toggle" ><summary class="toggle-button" style="">点我打开</summary><div class="toggle-content"><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">&#123;% hideToggle 点我打开 %&#125;</span><br><span class="line">&#123;% endhideToggle %&#125;</span><br></pre></td></tr></table></figure></div></details><h1 id="更多标签"><a href="#更多标签" class="headerlink" title="更多标签"></a>更多标签</h1><p><a href="https://butterfly.js.org/posts/4aa8abbe/#%E6%A8%99%E7%B1%A4%E5%A4%96%E6%8E%9B%EF%BC%88Tag-Plugins%EF%BC%89">butterfly.org 文档</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;文章置顶与置底&quot;&gt;&lt;a href=&quot;#文章置顶与置底&quot; class=&quot;headerlink&quot; title=&quot;文章置顶与置底&quot;&gt;&lt;/a&gt;文章置顶与置底&lt;/h1&gt;&lt;p&gt;在 Front-matter 区域添加 sticky 字段可以设定文章的置顶与置底，亦可以设置文章的</summary>
      
    
    
    
    <category term="Blog" scheme="https://blog.homu.space/categories/Blog/"/>
    
    
    <category term="hexo" scheme="https://blog.homu.space/tags/hexo/"/>
    
  </entry>
  
  <entry>
    <title>和朋友讨论职业生涯的可能性</title>
    <link href="https://blog.homu.space/posts/dc7794aa/"/>
    <id>https://blog.homu.space/posts/dc7794aa/</id>
    <published>2025-08-18T01:11:59.000Z</published>
    <updated>2026-05-10T10:49:07.012Z</updated>
    
    <content type="html"><![CDATA[<p>在白船里和群友讨论职业生涯的话题</p><p>时间：2025 年 8 月 14 日 夜晚</p><p>内容基本都是聊天记录直接搬过来的</p><hr><p><strong>回顾工作经历，得到从小白到专业的思考</strong></p><p>我过去的人生里，大学毕业之前都是在完成社会基础任务。这是社会框架下每个人都必须做的事情</p><p>学历是这个框架下的层次证明，是综合了性格、家族条件、童年印象等过程的结果，是很难改变的事情。但是完成了这个任务之后。我并没有工作和收入</p><p>然而真正进入现在的行业，我大概花了不到一年的时间，在这个行业上花了不到五年左右的时间。我从一个几乎是外行的人，进入到知名企业，可以独当一面完成项目，应该称得上是专业的程度</p><p>在专科+函授本科的起点上。一个行业外的人 -&gt; 决定了一个方向 -&gt; 花时间学习社会接轨的知识 -&gt; 进入行业五年 -&gt; 达到专业的水平</p><p>五年的时间并不长，人的职业生涯至少有 30 年，六个五年。也就是说，行动力最强的情况下，我还有至少五次的机会去尝试新的技能，并且达到专业水平…..</p><p>人们总是说在一个行业深耕十年二十年，能成为大牛。但实际上更多是成为了“老油条”</p><p>还有些感叹… 人熟练一份普通的工作真的很快，从入行到瓶颈，只有三五年的时间</p><hr><p>所以说不定… 职业生涯上做一个突破性的决定，进入新的行业，并不是坏事。1+1 往往是大于 2 的</p><p><strong>进入行业的困境和一点学习交流的幻想</strong></p><p>我缺少一个突破性的决定和一个学习的途径</p><p>我需要一个导师</p><p>认真的讲，说不定未来有一天，我们（聊天记录里指白船，实际上并非只有 QQ 群）真的可以交换技能，把其他人带进自己的行业，让自己进入其他人的行业</p><p>比如我花三个月的时间跟帆哥学习市场销售，或者一些户外用品店的常识性技能，或者帆哥内推我进入他的部门，从最基础的做起。逐渐熟悉零售行业的管理方式行业规则</p><p>再比如我找林克学习单片机，林克可以找我学习 web。互相学技能的时间也不会超过三个月，只要有不错的作品，就可以打败 70% 的竞争者</p><p>我知道这是很理想化，很不稳定的假设。但是也是一种可能性</p><p>当做一件事情时间久了腻掉了。互相就是导师</p><hr><p><strong>粗糙的规划</strong></p><p>在职业生涯中，我认为最困难的事情是入行。只要解决了这个问题，在行业里一定是能发展的</p><p>就拿我自己来说，我的学历就是我在任何行业里的天花板。</p><p>如何让自己的未来更精彩，更多样，我觉得方法是在饿不死的情况下尝试更多的事情….</p><p>我应该在现在这份工作上积累资源，同时寻找契机</p><p>未来尝试其他类型的工作时，不能带有焦虑情绪。而是类似“开启新的生活方式”这种憧憬和挑战的心态</p><p>资源就是钱和人，契机就是运气和意愿</p><p>钱可以让我不焦虑，焦虑就会草率决定生活和工作</p><p>人是互补技能的导师</p><p>运气就是帆哥在招人但是招不到靠谱的，但我就很靠谱（×</p><p>愿望就是需要我的时候，我刚好可以做这种工作</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/qq_pic_merged_1755702142948.jpg"></p><hr><p>我不会为了让自己变强而变学习</p><p>我会为了新鲜的事情和理想的生活状态而探索</p><p>我觉得人的存在目的不是为了单纯的“向上走”或者“进步”，而是随着自己的变化，不断做出各种尝试去接近理想的生活方式</p><p>我要成为华子姐那样自由奔放的社会人士 ——《mono 女孩》</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/qq_pic_merged_1755702308711.jpg"></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/qq_pic_merged_1755702535602.jpg"></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/qq_pic_merged_1755702550305.jpg"></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/qq_pic_merged_1755702561777.jpg"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在白船里和群友讨论职业生涯的话题&lt;/p&gt;
&lt;p&gt;时间：2025 年 8 月 14 日 夜晚&lt;/p&gt;
&lt;p&gt;内容基本都是聊天记录直接搬过来的&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;回顾工作经历，得到从小白到专业的思考&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我过去的人生里，大学毕业</summary>
      
    
    
    
    <category term="日常和心情" scheme="https://blog.homu.space/categories/%E6%97%A5%E5%B8%B8%E5%92%8C%E5%BF%83%E6%83%85/"/>
    
    
  </entry>
  
  <entry>
    <title>做一个好玩的聊天机器人</title>
    <link href="https://blog.homu.space/posts/a590ccae/"/>
    <id>https://blog.homu.space/posts/a590ccae/</id>
    <published>2025-01-31T17:26:08.000Z</published>
    <updated>2026-05-10T10:49:07.012Z</updated>
    
    <content type="html"><![CDATA[<h1 id="一些准备"><a href="#一些准备" class="headerlink" title="一些准备"></a>一些准备</h1><ul><li>服务器（运行机器人等物理设备）</li><li>机器人账号和登录状态（提供运行环境和 Api）</li><li>Api：OneBot 等（提供与机器人框架对接的方式以及功能开发接口）</li><li>机器人框架：Koishi，Nonebot，Mirai 等</li></ul><h1 id="Koishi"><a href="#Koishi" class="headerlink" title="Koishi"></a>Koishi</h1><p><a href="https://koishi.chat/zh-CN/">Koishi 是一个开源的跨平台机器人框架</a></p><p><a href="https://thwiki.cc/%E5%8F%A4%E6%98%8E%E5%9C%B0%E6%81%8B">Koishi 的意思是古明地恋，东方 Project 中的角色</a></p><h1 id="docker-部署一个对接-qq-的机器人"><a href="#docker-部署一个对接-qq-的机器人" class="headerlink" title="docker 部署一个对接 qq 的机器人"></a>docker 部署一个对接 qq 的机器人</h1><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></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">services:</span></span><br><span class="line">  <span class="attr">bot:</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">koishi</span></span><br><span class="line">    <span class="comment"># 如果服务器性能不佳，或者确认完全不需要puppeteer功能，那么可以不安装chromium</span></span><br><span class="line">    <span class="comment"># https://koishi.chat/zh-CN/manual/starter/docker.html#%E5%90%AF%E5%8A%A8%E5%AE%B9%E5%99%A8</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker.1ms.run/koishijs/koishi</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">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./koishi-data:/koishi</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">TZ=Asia/Shanghai</span></span><br><span class="line">    <span class="attr">network_mode:</span> <span class="string">host</span></span><br><span class="line">    <span class="comment"># ports:</span></span><br><span class="line">    <span class="comment">#   - &quot;5140:5140&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="attr">qq:</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">llonebot</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">docker.1ms.run/initialencounter/llonebot:latest</span></span><br><span class="line">    <span class="attr">privileged:</span> <span class="literal">true</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="bullet">-</span> <span class="string">VNC_PASSWD=llonebotpass</span></span><br><span class="line">    <span class="attr">network_mode:</span> <span class="string">host</span></span><br><span class="line">    <span class="comment"># ports:</span></span><br><span class="line">    <span class="comment">#   - &quot;7081:7081&quot; # login</span></span><br><span class="line">    <span class="comment">#   - &quot;3000:3000&quot; # http</span></span><br><span class="line">    <span class="comment">#   - &quot;3001:3001&quot; # ws</span></span><br></pre></td></tr></table></figure><h1 id="聊天机器人的控制台"><a href="#聊天机器人的控制台" class="headerlink" title="聊天机器人的控制台"></a>聊天机器人的控制台</h1><p>docker 服务成功运行后，浏览器访问云服务器的 5140 端口，可以看到控制台</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20250309133457188.png"></p><p>控制台提供了对 bot 的所有管理能力，比如运行状态，插件安装卸载，日志等</p><h2 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a>安装插件</h2><p>相关文档：<a href="https://koishi.chat/zh-CN/manual/usage/market.html">安装和配置插件</a></p><p>插件系统是 Koishi 的核心，bot 所有的功能都来自于插件</p><p>Koishi 的插件是以”koishi-plugin-“开头的 npm 包，安装插件的过程实际上也是安装了一个 npm 包</p><p>由于 Koishi 官方插件市场镜像长期没有更新，可以使用官方插件提供的社区镜像源</p><p>进入插件配置，找到分组：console 下的 market（插件市场本身也是插件，可以在插件配置中修改）</p><p>可以看到说明中会提供几个社区镜像，选择一个填入 search.endpoint 就可以了</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20250309151110390.png"></p><p>因为现在 Koishi 控制台部署在公网上，任何人都可以通过 ip:5140 访问到控制台，会有安全隐患</p><p>现在尝试在控制台种安装<code>auth</code>插件，这个插件可以给控制台提供用户和密码登录的功能</p><p>在插件市场里搜索<code>auth</code>，安装后进入插件配置页面</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1741506958020.png"></p><p>进入<code>auth</code>的插件配置，设置密码，点击右上角的三角形按钮启用插件</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1741507006519.png"></p><p>至此，完成了一次插件的安装和配置，以及控制台的密码登录设置</p><p>koishi 的插件大致可以分为以下几类：</p><ul><li>适配器（adapter），用来对接不同的 IM 平台或者 API 协议，如 qq、telegram、onebot 等</li><li>控制台（console）增强控制台功能的插件，如 auth 等</li><li>服务类，不直接在聊天中体现，但是给其他插件添加依赖，如 puppeteer，mysql，webdav 等</li><li>功能类，直接在聊天中体现，如 翻译、图片搜索、天气查询等</li></ul><h1 id="对接-qq"><a href="#对接-qq" class="headerlink" title="对接 qq"></a>对接 qq</h1><h2 id="登录-qq（llonebot）"><a href="#登录-qq（llonebot）" class="headerlink" title="登录 qq（llonebot）"></a>登录 qq（llonebot）</h2><p>在终端中输入 <code>docker logs llonebot</code> 可以获取二维码，用手机 qq 扫描后就可以登录</p><p>登录后，使用 vnc 连接到服务器地址的 7081 端口，输入密码<code>llonebotpassword</code>，可以对 docker 中的 qq 进行设置</p><p>这里我用的是 <a href="https://nxshell.github.io/">NxShell</a> 进行 vnc 连接</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/b1e6cc7f8ba18afa936a62e74be12ab0.png"></p><p>设置 ws 反向监听地址（不设置也没问题，可以在适配器里设置正向 ws 监听。不同的适配器会要求不同的监听方式）</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1738331009172.png"></p><h2 id="连接-onebot"><a href="#连接-onebot" class="headerlink" title="连接 onebot"></a>连接 onebot</h2><p>配置 adapter-onebot</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1738331334759.png"></p><p>启用插件之后，会发现控制台右下角变绿，并出现上下行流量的变化。表示已经配置成功，koishi 平台已经与 qq 建立联系</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1741508101862.png"></p><h2 id="排错"><a href="#排错" class="headerlink" title="排错"></a>排错</h2><p>如果是首次设置，可以进入依赖管理中更新官方插件版本</p><p>如果是连接问题，则需要考虑 qq 是否在有 llonebot 的客户端中登录，以及对接的 ip 和端口是否正确</p><p>如果是功能性插件出现错误，可以在控制台中查看日志</p><p>koishi 的插件的可用性和安全性并不可靠，有些甚至需要去查看源码，安装的时候需要仔细甄别</p><h1 id="使用插件市场里的插件丰富-bot-的功能"><a href="#使用插件市场里的插件丰富-bot-的功能" class="headerlink" title="使用插件市场里的插件丰富 bot 的功能"></a>使用插件市场里的插件丰富 bot 的功能</h1><p>接下来就可以进入插件市场挑选自己想加入的插件</p><p>在论坛中可以找到有趣的推荐，以及一些问题的解决方式</p><p><a href="https://forum.koishi.xyz/">Koishi 论坛</a></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/424a0512db78bd1815dc9d1b5be4d7c8.jpeg"></p><h1 id="开发自己的插件"><a href="#开发自己的插件" class="headerlink" title="开发自己的插件"></a>开发自己的插件</h1><p>插件市场虽然很丰富，但是很难完全满足自己的定制化需求，这个时候就需要自己开发插件了</p><p><a href="https://koishi.chat/zh-CN/guide/">插件开发文档</a></p><p>创建一个本地的 koishi 插件开发环境</p><figure class="highlight sh"><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">yarn create koishi</span><br><span class="line"></span><br><span class="line">yarn dev</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在工作区创建插件</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn setup [name]</span><br></pre></td></tr></table></figure><p>为插件添加依赖</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn workspace koishi-plugin-[name] add [...deps]</span><br></pre></td></tr></table></figure><p>koishi 的开发工作区默认是 monorepo 的结构，创建好的插件在 external 文件夹下</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></pre></td><td class="code"><pre><span class="line">root</span><br><span class="line">├── external</span><br><span class="line">│   └── example</span><br><span class="line">│       ├── src</span><br><span class="line">│       │   └── index.ts</span><br><span class="line">│       └── package.json</span><br><span class="line">├── koishi.yml</span><br><span class="line">└── package.json</span><br></pre></td></tr></table></figure><p>开发完成后，可以<a href="https://koishi.chat/zh-CN/guide/develop/publish.html">把插件发布到插件市场上</a></p><p>在发布之前，为插件的 package.json 补充信息，所补充的信息会在插件市场中展示</p><p>其中比较值得注意的是 koishi 字段，这个字段会影响到插件配置页面的展示以及其所依赖的其他服务</p><p>在 koishi.service 中可以声明此插件所依赖的服务</p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;koishi-plugin-example&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;contributors&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="comment">// 贡献者</span></span><br><span class="line">    <span class="string">&quot;Alice &lt;alice@gmail.com&gt;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;Bob &lt;bob@gmail.com&gt;&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;license&quot;</span><span class="punctuation">:</span> <span class="string">&quot;MIT&quot;</span><span class="punctuation">,</span> <span class="comment">// 许可证</span></span><br><span class="line">  <span class="attr">&quot;homepage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://example.com&quot;</span><span class="punctuation">,</span> <span class="comment">// 主页</span></span><br><span class="line">  <span class="attr">&quot;repository&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="comment">// 源码仓库</span></span><br><span class="line">    <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;git&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;url&quot;</span><span class="punctuation">:</span> <span class="string">&quot;git+https://github.com/alice/koishi-plugin-example.git&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;keywords&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;example&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="comment">// 关键词</span></span><br><span class="line">  <span class="attr">&quot;peerDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;koishi&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.3.2&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;koishi&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="comment">// 不同语言的插件描述</span></span><br><span class="line">      <span class="attr">&quot;en&quot;</span><span class="punctuation">:</span> <span class="string">&quot;English Description&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;zh&quot;</span><span class="punctuation">:</span> <span class="string">&quot;中文描述&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;service&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;database&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="comment">// 必需的服务</span></span><br><span class="line">      <span class="attr">&quot;optional&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;assets&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="comment">// 可选的服务</span></span><br><span class="line">      <span class="attr">&quot;implements&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;dialogue&quot;</span><span class="punctuation">]</span> <span class="comment">// 实现的服务</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="获取表情图并存到云盘的插件"><a href="#获取表情图并存到云盘的插件" class="headerlink" title="获取表情图并存到云盘的插件"></a>获取表情图并存到云盘的插件</h2><h3 id="动机和期望"><a href="#动机和期望" class="headerlink" title="动机和期望"></a>动机和期望</h3><p>由于 qq 和微信的某次更新，无法直接保存其他人发送的表情包，所以我就想把自己的表情图独立于平台，保存到自己的网盘上，可以在任何设备任何软件上使用。</p><p>可以说是盗图的终极方案</p><p><strong>期望</strong></p><p>我想象中的这个插件的用法大概是这样的：</p><ol><li>我看到了一张表情图，摩拳擦掌想塞进自己的口袋</li><li>我把这张表情图发给 bot，（用指令）告诉 bot，把这张图片存下来</li><li>bot 获取到这张图片，并且对图片做统一处理（尺寸，质量，格式）</li><li>将处理好的图片保存在云盘上，云盘同步设备文件夹。</li><li>使用的时候直接选择图片发送，图片不会被放大，动画效果不会丢失</li><li>可以存官方表情图，也可以存自己的表情图</li></ol><h3 id="实现方式"><a href="#实现方式" class="headerlink" title="实现方式"></a>实现方式</h3><p>通过引用消息告诉 bot 要存哪张图片：session.quote.elements</p><figure class="highlight ts"><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">quote.<span class="property">elements</span>.<span class="title function_">forEach</span>(<span class="function">(<span class="params">el</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (el.<span class="property">type</span> === <span class="string">&quot;img&quot;</span>) &#123;</span><br><span class="line">    images.<span class="title function_">push</span>(el.<span class="property">attrs</span> <span class="keyword">as</span> <span class="title class_">ImageElement</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (el.<span class="property">type</span> === <span class="string">&quot;mface&quot;</span>) &#123;</span><br><span class="line">    mfaces.<span class="title function_">push</span>(el.<span class="property">attrs</span> <span class="keyword">as</span> <span class="title class_">MfaceElement</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>注册指令，然后在控制台中配置别名，增加参数可以指定存储到云盘的某个子文件夹</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line">ctx</span><br><span class="line">  .<span class="title function_">command</span>(<span class="string">&quot;save-sticker &lt;folder:text&gt;&quot;</span>, <span class="string">&quot;获取表情包&quot;</span>, &#123;</span><br><span class="line">    <span class="comment">// @ts-ignore-next-line</span></span><br><span class="line">    <span class="attr">hidden</span>: <span class="literal">true</span>, <span class="comment">// 隐藏指令，不会在help指令中被发送</span></span><br><span class="line">    <span class="attr">captureQuote</span>: <span class="literal">false</span>, <span class="comment">// 运行指令避免捕获引用内容 https://github.com/koishijs/koishi/issues/1432</span></span><br><span class="line">  &#125;)</span><br><span class="line">  .<span class="title function_">alias</span>(<span class="string">&quot;盗图&quot;</span>, <span class="string">&quot;保存图片&quot;</span>);</span><br></pre></td></tr></table></figure><p>使用 webdav 的方式连接云盘（坚果云），并将图片上传到云盘</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 引入纯ESM包</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">loadWebDAV</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> webdav = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">&quot;webdav&quot;</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(webdav);</span><br><span class="line">  <span class="keyword">return</span> webdav;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> &#123; createClient &#125; = <span class="keyword">await</span> <span class="title function_">loadWebDAV</span>();</span><br><span class="line"><span class="comment">// 创建webdav客户端</span></span><br><span class="line"><span class="keyword">const</span> webdavClient = <span class="title function_">createClient</span>(webDavUrl, &#123;</span><br><span class="line">  <span class="attr">username</span>: ctx.<span class="property">config</span>.<span class="property">webdavUsername</span>,</span><br><span class="line">  <span class="attr">password</span>: ctx.<span class="property">config</span>.<span class="property">webdavPassword</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>将图片处理成特定格式，默认 gif，并且将宽度固定到 300px，提供两个配置项，可以在控制台中配置</p><p>为什么要处理成 gif 和 300px</p><ol><li>透明度：gif 可以保留表情图的透明度，同时不会变黑（经测试 png 会出现背景变黑的问题，qq 应该不支持 png 的复杂透明度）</li><li>gif 是官方表情包（mface）的格式</li><li>gif 支持动图</li><li>300 像素是比较通用的尺寸，发送后不会被放大，不太清楚 qq 和微信识别表情包的逻辑，经过尝试得到的结果</li></ol><h2 id="最终效果"><a href="#最终效果" class="headerlink" title="最终效果"></a>最终效果</h2><p><img src="https://oss.homu.space/imgs/2569b559fd71d9a8af6435c729b6d283.jpg"></p><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><figure class="highlight ts"><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><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="keyword">type</span> <span class="title class_">Context</span>, <span class="title class_">Random</span>, <span class="title class_">Schema</span>, <span class="title class_">Session</span> &#125; <span class="keyword">from</span> <span class="string">&quot;koishi&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> sharp <span class="keyword">from</span> <span class="string">&quot;sharp&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; imageSize &#125; <span class="keyword">from</span> <span class="string">&quot;image-size&quot;</span>;</span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">loadWebDAV</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> webdav = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">&quot;webdav&quot;</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(webdav);</span><br><span class="line">  <span class="comment">// 这里可以使用 webdav</span></span><br><span class="line">  <span class="keyword">return</span> webdav;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">loadImageType</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> imageType = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">&quot;image-type&quot;</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(imageType);</span><br><span class="line">  <span class="keyword">return</span> imageType.<span class="property">default</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> name = <span class="string">&quot;webdav-stickers&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> usage = <span class="string">`</span></span><br><span class="line"><span class="string">### 你这图很不错，可惜下一秒就是我的了！</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">这个插件可以把你看上的表情图片保存到支持 WebDAV 的云盘上，通过云盘，你可以将图片分享给其他朋友，或者同步到自己的其他设备上。</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&lt;a href=&quot;https://oss.homu.space/imgs/2569b559fd71d9a8af6435c729b6d283.jpg&quot; target=&quot;_blank&quot;&gt;使用效果&lt;/a&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">使用方法：</span></span><br><span class="line"><span class="string">1. 从支持 WebDAV 的云盘（如坚果云等）获取 WebDAV 地址和授权密码，并设置一个存放图片的根目录</span></span><br><span class="line"><span class="string">2. 配置允许使用该插件的用户</span></span><br><span class="line"><span class="string">3. 为命令[设置别名](/commands/save-sticker)</span></span><br><span class="line"><span class="string">4. 把表情图发送给bot可以接收到的地方。引用表情图，使用命令</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">---</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">特性：</span></span><br><span class="line"><span class="string">- 支持批量保存和动图保存</span></span><br><span class="line"><span class="string">- 静态图片统一使用了png格式（支持透明背景）</span></span><br><span class="line"><span class="string">- 可设置表情包的最大宽度，避免了表情包尺寸很大的情况</span></span><br><span class="line"><span class="string">- 支持保存mface表情（官方表情图片）</span></span><br><span class="line"><span class="string">- 可以通过 folder 参数，将表情图保存到云盘的不同文件夹中</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">`</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> inject = [<span class="string">&quot;http&quot;</span>];</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">Config</span> &#123;</span><br><span class="line">  <span class="attr">webdavUrl</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">webdavUsername</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">webdavPassword</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">rootFolder</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">allowUsers</span>: <span class="built_in">string</span>[];</span><br><span class="line">  <span class="comment">// triggerWords: string[];</span></span><br><span class="line">  <span class="attr">successMessage</span>: <span class="built_in">string</span>[];</span><br><span class="line">  <span class="attr">stickerWidth</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">staticImageFormat</span>: <span class="string">&quot;png&quot;</span> | <span class="string">&quot;gif&quot;</span> | <span class="string">&quot;jpg&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">ImageElement</span> &#123;</span><br><span class="line">  <span class="attr">file</span>: <span class="built_in">string</span>; <span class="comment">// 文件名</span></span><br><span class="line">  <span class="attr">fileSize</span>: <span class="built_in">string</span>; <span class="comment">// 文件大小</span></span><br><span class="line">  <span class="attr">src</span>: <span class="built_in">string</span>; <span class="comment">// 文件地址</span></span><br><span class="line">  <span class="attr">subType</span>: <span class="built_in">string</span>; <span class="comment">// 文件类型</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">MfaceElement</span> &#123;</span><br><span class="line">  <span class="attr">emojiId</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">emojiPackageId</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">key</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">summary</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">url</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title class_">Config</span>: <span class="title class_">Schema</span>&lt;<span class="title class_">Config</span>&gt; = <span class="title class_">Schema</span>.<span class="title function_">intersect</span>([</span><br><span class="line">  <span class="title class_">Schema</span>.<span class="title function_">object</span>(&#123;</span><br><span class="line">    <span class="attr">webdavUrl</span>: <span class="title class_">Schema</span>.<span class="title function_">string</span>().<span class="title function_">required</span>().<span class="title function_">description</span>(<span class="string">&quot;WebDAV 服务器地址&quot;</span>),</span><br><span class="line">    <span class="attr">webdavUsername</span>: <span class="title class_">Schema</span>.<span class="title function_">string</span>().<span class="title function_">required</span>().<span class="title function_">description</span>(<span class="string">&quot;WebDAV 用户名&quot;</span>),</span><br><span class="line">    <span class="attr">webdavPassword</span>: <span class="title class_">Schema</span>.<span class="title function_">string</span>()</span><br><span class="line">      .<span class="title function_">role</span>(<span class="string">&quot;secret&quot;</span>)</span><br><span class="line">      .<span class="title function_">required</span>()</span><br><span class="line">      .<span class="title function_">description</span>(<span class="string">&quot;WebDAV 授权密码&quot;</span>),</span><br><span class="line">    <span class="attr">rootFolder</span>: <span class="title class_">Schema</span>.<span class="title function_">string</span>()</span><br><span class="line">      .<span class="title function_">default</span>(<span class="string">&quot;Stickers&quot;</span>)</span><br><span class="line">      .<span class="title function_">description</span>(<span class="string">&quot;设定一个存放图片的根目录&quot;</span>),</span><br><span class="line">  &#125;).<span class="title function_">description</span>(<span class="string">&quot;WebDAV 配置&quot;</span>),</span><br><span class="line">  <span class="title class_">Schema</span>.<span class="title function_">object</span>(&#123;</span><br><span class="line">    <span class="attr">allowUsers</span>: <span class="title class_">Schema</span>.<span class="title function_">array</span>(<span class="title class_">Schema</span>.<span class="title function_">string</span>())</span><br><span class="line">      .<span class="title function_">required</span>()</span><br><span class="line">      .<span class="title function_">description</span>(<span class="string">&quot;允许使用该插件的用户（qq号）&quot;</span>),</span><br><span class="line">    <span class="attr">successMessage</span>: <span class="title class_">Schema</span>.<span class="title function_">array</span>(<span class="title class_">Schema</span>.<span class="title function_">string</span>())</span><br><span class="line">      .<span class="title function_">default</span>([<span class="string">&quot;搞定！&quot;</span>])</span><br><span class="line">      .<span class="title function_">description</span>(<span class="string">&quot;获取成功后的消息&quot;</span>),</span><br><span class="line">  &#125;).<span class="title function_">description</span>(<span class="string">&quot;用户配置&quot;</span>),</span><br><span class="line">  <span class="title class_">Schema</span>.<span class="title function_">object</span>(&#123;</span><br><span class="line">    <span class="attr">stickerWidth</span>: <span class="title class_">Schema</span>.<span class="title function_">number</span>()</span><br><span class="line">      .<span class="title function_">default</span>(<span class="number">300</span>)</span><br><span class="line">      .<span class="title function_">description</span>(<span class="string">&quot;表情包的宽度，默认300px，超过尺寸的静态图片会被压缩&quot;</span>),</span><br><span class="line">    <span class="attr">staticImageFormat</span>: <span class="title class_">Schema</span>.<span class="title function_">union</span>([<span class="string">&quot;png&quot;</span>, <span class="string">&quot;gif&quot;</span>, <span class="string">&quot;jpg&quot;</span>])</span><br><span class="line">      .<span class="title function_">default</span>(<span class="string">&quot;gif&quot;</span>)</span><br><span class="line">      .<span class="title function_">description</span>(<span class="string">&quot;静态图片的格式，默认gif，不会处理mface&quot;</span>),</span><br><span class="line">  &#125;).<span class="title function_">description</span>(<span class="string">&quot;图片配置&quot;</span>),</span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">apply</span>(<span class="params"><span class="attr">ctx</span>: <span class="title class_">Context</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; createClient &#125; = <span class="keyword">await</span> <span class="title function_">loadWebDAV</span>();</span><br><span class="line">  <span class="keyword">const</span> imageType = <span class="keyword">await</span> <span class="title function_">loadImageType</span>();</span><br><span class="line">  ctx.<span class="property">logger</span>.<span class="title function_">info</span>(<span class="string">&quot;成功加载 imageType 和 webdav&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> webDavUrl = ctx.<span class="property">config</span>.<span class="property">webdavUrl</span>.<span class="title function_">endsWith</span>(<span class="string">&quot;/&quot;</span>)</span><br><span class="line">    ? ctx.<span class="property">config</span>.<span class="property">webdavUrl</span></span><br><span class="line">    : ctx.<span class="property">config</span>.<span class="property">webdavUrl</span> + <span class="string">&quot;/&quot;</span> + ctx.<span class="property">config</span>.<span class="property">rootFolder</span>;</span><br><span class="line">  ctx.<span class="property">logger</span>.<span class="title function_">info</span>(<span class="string">&quot;webDavUrl: &quot;</span> + webDavUrl);</span><br><span class="line">  <span class="keyword">const</span> webdavClient = <span class="title function_">createClient</span>(webDavUrl, &#123;</span><br><span class="line">    <span class="attr">username</span>: ctx.<span class="property">config</span>.<span class="property">webdavUsername</span>,</span><br><span class="line">    <span class="attr">password</span>: ctx.<span class="property">config</span>.<span class="property">webdavPassword</span>,</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">saveImage</span>(<span class="params"><span class="attr">image</span>: <span class="title class_">ImageElement</span>, folder?: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (folder &amp;&amp; !(<span class="keyword">await</span> webdavClient.<span class="title function_">exists</span>(<span class="string">`/<span class="subst">$&#123;folder&#125;</span>`</span>))) &#123;</span><br><span class="line">      <span class="keyword">await</span> webdavClient.<span class="title function_">createDirectory</span>(<span class="string">`/<span class="subst">$&#123;folder&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">let</span> buffer = <span class="keyword">await</span> ctx.<span class="property">http</span>.<span class="title function_">get</span>(image.<span class="property">src</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检测文件是否是gif</span></span><br><span class="line">    <span class="keyword">const</span> fileType = <span class="keyword">await</span> <span class="title function_">imageType</span>(buffer);</span><br><span class="line">    ctx.<span class="property">logger</span>.<span class="title function_">info</span>(<span class="string">`文件类型: <span class="subst">$&#123;fileType.ext&#125;</span>`</span>);</span><br><span class="line">    <span class="keyword">let</span> <span class="attr">filename</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (fileType.<span class="property">ext</span> === <span class="string">&quot;gif&quot;</span>) &#123;</span><br><span class="line">      filename = <span class="string">`<span class="subst">$&#123;<span class="built_in">Date</span>.now()&#125;</span>.gif`</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      filename = <span class="string">`<span class="subst">$&#123;<span class="built_in">Date</span>.now()&#125;</span>.<span class="subst">$&#123;ctx.config.staticImageFormat&#125;</span>`</span>;</span><br><span class="line">      <span class="keyword">const</span> size = <span class="title function_">imageSize</span>(<span class="keyword">new</span> <span class="title class_">Uint8Array</span>(buffer));</span><br><span class="line">      <span class="keyword">if</span> (size.<span class="property">width</span> &gt; ctx.<span class="property">config</span>.<span class="property">stickerWidth</span>) &#123;</span><br><span class="line">        buffer = <span class="keyword">await</span> <span class="title function_">sharp</span>(buffer)</span><br><span class="line">          .<span class="title function_">resize</span>(&#123; <span class="attr">width</span>: ctx.<span class="property">config</span>.<span class="property">stickerWidth</span> &#125;)</span><br><span class="line">          .<span class="title function_">toFormat</span>(ctx.<span class="property">config</span>.<span class="property">staticImageFormat</span>)</span><br><span class="line">          .<span class="title function_">toBuffer</span>();</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        buffer = <span class="keyword">await</span> <span class="title function_">sharp</span>(buffer)</span><br><span class="line">          .<span class="title function_">toFormat</span>(ctx.<span class="property">config</span>.<span class="property">staticImageFormat</span>)</span><br><span class="line">          .<span class="title function_">toBuffer</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="comment">// 如果图片大于设定大小，并且不是gif，则压缩</span></span><br><span class="line">    <span class="keyword">const</span> savePath = <span class="string">`<span class="subst">$&#123;folder ? folder + <span class="string">&quot;/&quot;</span> : <span class="string">&quot;&quot;</span>&#125;</span><span class="subst">$&#123;filename&#125;</span>`</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">await</span> webdavClient.<span class="title function_">putFileContents</span>(savePath, buffer, &#123;</span><br><span class="line">      <span class="attr">overwrite</span>: <span class="literal">true</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    ctx.<span class="property">logger</span>.<span class="title function_">info</span>(</span><br><span class="line">      <span class="string">`<span class="subst">$&#123;filename&#125;</span> 已保存至 <span class="subst">$&#123;ctx.config.webdavUrl&#125;</span>/<span class="subst">$&#123;ctx.config.rootFolder&#125;</span>/<span class="subst">$&#123;savePath&#125;</span>`</span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">saveMface</span>(<span class="params"><span class="attr">mface</span>: <span class="title class_">MfaceElement</span>, folder?: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (folder &amp;&amp; !(<span class="keyword">await</span> webdavClient.<span class="title function_">exists</span>(<span class="string">`/<span class="subst">$&#123;folder&#125;</span>`</span>))) &#123;</span><br><span class="line">      <span class="keyword">await</span> webdavClient.<span class="title function_">createDirectory</span>(<span class="string">`/<span class="subst">$&#123;folder&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> extension = mface.<span class="property">url</span>.<span class="title function_">split</span>(<span class="string">&quot;.&quot;</span>).<span class="title function_">pop</span>();</span><br><span class="line">    <span class="keyword">const</span> filename = <span class="string">`<span class="subst">$&#123;mface.emojiPackageId&#125;</span>-<span class="subst">$&#123;mface.summary&#125;</span>.<span class="subst">$&#123;extension&#125;</span>`</span>;</span><br><span class="line">    <span class="keyword">const</span> savePath = <span class="string">`<span class="subst">$&#123;folder ? folder + <span class="string">&quot;/&quot;</span> : <span class="string">&quot;&quot;</span>&#125;</span><span class="subst">$&#123;filename&#125;</span>`</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> buffer = <span class="keyword">await</span> ctx.<span class="property">http</span>.<span class="title function_">get</span>(mface.<span class="property">url</span>);</span><br><span class="line">    <span class="keyword">await</span> webdavClient.<span class="title function_">putFileContents</span>(savePath, buffer, &#123;</span><br><span class="line">      <span class="attr">overwrite</span>: <span class="literal">true</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    ctx.<span class="property">logger</span>.<span class="title function_">info</span>(</span><br><span class="line">      <span class="string">`<span class="subst">$&#123;filename&#125;</span> 已保存至 <span class="subst">$&#123;ctx.config.webdavUrl&#125;</span>/<span class="subst">$&#123;ctx.config.rootFolder&#125;</span>/<span class="subst">$&#123;savePath&#125;</span>`</span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ctx</span><br><span class="line">    .<span class="title function_">user</span>(...ctx.<span class="property">config</span>.<span class="property">allowUsers</span>)</span><br><span class="line">    .<span class="title function_">command</span>(<span class="string">&quot;save-sticker &lt;folder:text&gt;&quot;</span>, <span class="string">&quot;获取表情包&quot;</span>, &#123;</span><br><span class="line">      <span class="comment">// @ts-ignore-next-line</span></span><br><span class="line">      <span class="attr">hidden</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">captureQuote</span>: <span class="literal">false</span>,</span><br><span class="line">    &#125;)</span><br><span class="line">    .<span class="title function_">alias</span>(<span class="string">&quot;盗图&quot;</span>, <span class="string">&quot;保存图片&quot;</span>)</span><br><span class="line">    .<span class="title function_">action</span>(<span class="title function_">async</span> (&#123; session &#125;, folder) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> quote = session.<span class="property">quote</span>;</span><br><span class="line">      ctx.<span class="property">logger</span>.<span class="title function_">info</span>(quote, <span class="string">&quot;quote&quot;</span>);</span><br><span class="line">      <span class="keyword">if</span> (!quote) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> <span class="attr">images</span>: <span class="title class_">Array</span>&lt;<span class="title class_">ImageElement</span>&gt; = [];</span><br><span class="line">      <span class="keyword">const</span> <span class="attr">mfaces</span>: <span class="title class_">Array</span>&lt;<span class="title class_">MfaceElement</span>&gt; = [];</span><br><span class="line"></span><br><span class="line">      quote.<span class="property">elements</span>.<span class="title function_">forEach</span>(<span class="function">(<span class="params">el</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (el.<span class="property">type</span> === <span class="string">&quot;img&quot;</span>) &#123;</span><br><span class="line">          images.<span class="title function_">push</span>(el.<span class="property">attrs</span> <span class="keyword">as</span> <span class="title class_">ImageElement</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (el.<span class="property">type</span> === <span class="string">&quot;mface&quot;</span>) &#123;</span><br><span class="line">          mfaces.<span class="title function_">push</span>(el.<span class="property">attrs</span> <span class="keyword">as</span> <span class="title class_">MfaceElement</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="keyword">if</span> (images.<span class="property">length</span> === <span class="number">0</span> &amp;&amp; mfaces.<span class="property">length</span> === <span class="number">0</span>) &#123;</span><br><span class="line">        session.<span class="title function_">send</span>(<span class="string">&quot;引用的消息里没有图片吧&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      ctx.<span class="property">logger</span>.<span class="title function_">info</span>(&#123; images, mfaces, folder &#125;);</span><br><span class="line">      <span class="keyword">const</span> promises = [</span><br><span class="line">        ...images.<span class="title function_">map</span>(<span class="function">(<span class="params">image</span>) =&gt;</span> <span class="title function_">saveImage</span>(image, folder)),</span><br><span class="line">        ...mfaces.<span class="title function_">map</span>(<span class="function">(<span class="params">mface</span>) =&gt;</span> <span class="title function_">saveMface</span>(mface, folder)),</span><br><span class="line">      ];</span><br><span class="line"></span><br><span class="line">      <span class="title class_">Promise</span>.<span class="title function_">allSettled</span>(promises).<span class="title function_">then</span>(<span class="function">(<span class="params">results</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> rejected = results.<span class="title function_">filter</span>(<span class="function">(<span class="params">r</span>) =&gt;</span> r.<span class="property">status</span> === <span class="string">&quot;rejected&quot;</span>);</span><br><span class="line">        <span class="keyword">const</span> fulfilled = results.<span class="title function_">filter</span>(<span class="function">(<span class="params">r</span>) =&gt;</span> r.<span class="property">status</span> === <span class="string">&quot;fulfilled&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (fulfilled.<span class="property">length</span> === <span class="number">0</span>) &#123;</span><br><span class="line">          session.<span class="title function_">send</span>(<span class="string">&quot;没有拿到图片，好像出了点问题...&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (fulfilled.<span class="property">length</span> &gt; <span class="number">0</span> &amp;&amp; rejected.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">          session.<span class="title function_">send</span>(<span class="string">&quot;部分图片没有拿到...&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          session.<span class="title function_">send</span>(<span class="title class_">Random</span>.<span class="title function_">pick</span>(ctx.<span class="property">config</span>.<span class="property">successMessage</span>));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (rejected.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">          ctx.<span class="property">logger</span>.<span class="title function_">error</span>(rejected.<span class="title function_">map</span>(<span class="function">(<span class="params">r</span>) =&gt;</span> r.<span class="property">reason</span>));</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="抽签"><a href="#抽签" class="headerlink" title="抽签"></a>抽签</h2>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;一些准备&quot;&gt;&lt;a href=&quot;#一些准备&quot; class=&quot;headerlink&quot; title=&quot;一些准备&quot;&gt;&lt;/a&gt;一些准备&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;服务器（运行机器人等物理设备）&lt;/li&gt;
&lt;li&gt;机器人账号和登录状态（提供运行环境和 Api）&lt;/li&gt;
&lt;l</summary>
      
    
    
    
    <category term="魔女的帽子" scheme="https://blog.homu.space/categories/%E9%AD%94%E5%A5%B3%E7%9A%84%E5%B8%BD%E5%AD%90/"/>
    
    
  </entry>
  
  <entry>
    <title>第一次摩旅：洛阳-西安</title>
    <link href="https://blog.homu.space/posts/70ac33cd/"/>
    <id>https://blog.homu.space/posts/70ac33cd/</id>
    <published>2024-10-02T19:56:34.000Z</published>
    <updated>2026-05-10T10:49:07.012Z</updated>
    
    <content type="html"><![CDATA[<p>写下这篇文章的时候已经是十一假期了</p><p>我已经在西安暂住下来，并且开始逐渐了解这座城市</p><hr><p>我做了一件很不得了的事情，至少我自己觉得很厉害！</p><p>9.24 和 9.25 这两天的时间，骑上我的本田 cc110，从洛阳到达了西安</p><p>完成了人生第一次摩托车旅行</p><h1 id="每年最完美的季节"><a href="#每年最完美的季节" class="headerlink" title="每年最完美的季节"></a>每年最完美的季节</h1><p>小熊说，Cub 可以带我去任何地方，我还想去更远的地方</p><p>芝麻凛的爷爷说，我刚拿到驾照可能会不知道，季节交替的时候正式摩托旅行的好时机</p><div id="dplayer3" class="dplayer hexo-tag-dplayer-mark" style="margin-bottom: 20px;"></div><script>(function(){var player = new DPlayer({"container":document.getElementById("dplayer3"),"video":{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/Yuru%20Camp%20Season%203%2009%20%E7%89%87%E6%AE%B5.mp4"}});window.dplayers||(window.dplayers=[]);window.dplayers.push(player);})()</script><p>摩托旅行是视野最开阔的旅行方式，有速度，有风，眼前所有的东西全是景色</p><h1 id="出发前的心情和准备"><a href="#出发前的心情和准备" class="headerlink" title="出发前的心情和准备"></a>出发前的心情和准备</h1><h2 id="喜欢的事情"><a href="#喜欢的事情" class="headerlink" title="喜欢的事情"></a>喜欢的事情</h2><p>早在今年一月份的时候，看了动画片《本田小狼与我》</p><p>再之后，看了《摇曳露营 3》</p><p>这两个动画片让我知道摩托车可以是生活的一部分，是一种人生态度，是可以陪伴到老的东西</p><p>是接近我的理想，值得我去向往和追求的事物</p><p>七月份拿到驾照和车之后，我就摩拳擦掌想要试一次摩托旅行。于是从家里骑到了陆浑水库</p><p>在陆浑环湖骑行了一圈。我确信我做了正确的决定，我喜欢的就是这样的东西</p><p>我觉得这是给自己放暑假期间做的最有意义的事情了</p><h2 id="悠闲，还有些害怕"><a href="#悠闲，还有些害怕" class="headerlink" title="悠闲，还有些害怕"></a>悠闲，还有些害怕</h2><p>暑假过的很快，弟弟的暑假在九月份结束了，我意识到我差不多也该重新组织自己的生活了</p><p>我想把骑行继续下去，所以目光看向了洛阳旁边的城市西安，我觉得西安应该是一个很舒服的地方，吃得惯住得惯，骑行环境好，机车天堂</p><p>了解长途骑行的知识，护具的选购，防御性驾驶等等</p><p>买了护膝护肘护腰，一个很满意的后座包…</p><p>不害怕是不可能的，我知道国道路况没那么好，国道上非常多的挂车，可能会遇到横风，甚至落石</p><p>我也知道，如果出事故，一切都会结束。是很可怕的事情…</p><p>除此之外，我去西安是找工作的，如果没找到的话会怎样，重新安排之后的事情…</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240923_180908.jpg" alt="前一天的黄昏"></p><p>做出决定的时候，是稍微有些焦虑的，甚至犹豫这趟危险的骑行是不是不对的</p><p>后来朋友一句话把我点醒了</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20241005135813.png" alt="生活是体验"></p><p>在后座包上扣了芙莉莲的吧唧，伊蕾娜和小熊的挂饰，还有友人送的护身符</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20241005_230238.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20241005_230216.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>带着些许畏惧，还有许许多多期待，开始了第一次摩旅</p><h1 id="出发，我一直都在路上"><a href="#出发，我一直都在路上" class="headerlink" title="出发，我一直都在路上"></a>出发，我一直都在路上</h1><p>9.24 上午，去邻村喝了碗豆腐脑，吃了两个水煎包回家。把沉重的后座包放在车上</p><p>准备出发的时候遇到老爸，原来是出门给我买了点吃的，几个苹果，一袋玉米肠，还有点心。叮嘱我在路上多休息休息</p><p>由于行李空间有限，我只带了玉米肠和一个苹果还有说不出的感谢</p><p>穿上护具，目标 大雁塔，出发</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_095235.jpg" alt="背景就是我家"></p><h2 id="第一天：洛阳-潼关"><a href="#第一天：洛阳-潼关" class="headerlink" title="第一天：洛阳 - 潼关"></a>第一天：洛阳 - 潼关</h2><p>出发不一会儿就从瀍河上了洛阳段的 310 国道</p><p>一个转弯进入国道，视野开阔起来，洛阳段的国道修得真好，双向六车道，路边是山山水水和村庄</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_112030.jpg"></p><p>也许是因为我的详细计划，选了一个晴天的工作日，国道并没有我想象的那么危险，虽然大车也不少，但是路很宽</p><p>我骑在最右车道最右侧 30 公分的距离，如果左后视镜里有大车出现，我就乖巧地借用非机动车道，然后减速让大车先走</p><p>遵循这样的让车规则，渐渐在国道上稳定下来，不一会儿就到了三门峡</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_115032.jpg"></p><p>中午从义马下了国道，去吃了午饭，在饭店里休息了一个小时，继续出发！</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_123242.jpg"></p><p>下午也是一路骑行，在每个服务区都做了休息</p><p>一下午在服务区一共遇到了五个摩友，他们有来自山西的，来自陕西的，甚至还有江苏的大哥</p><p>在服务区简单聊天十分愉快，大家都会相互夸赞，聊一聊车，聊一聊沿路的景点</p><p>很少和陌生人聊的这么愉快过</p><p>休息之后各自分别，打一个再见的招呼，继续旅行，有几个大哥还会在下个服务区碰面</p><p>实际上，我骑车的速度要比导航计算的速度更慢</p><p>六点的时候太阳落山，骑车是最不舒服的，虽然有遮光镜，但是太阳光依然会直射前方…</p><p>但是有些不服输吧，心里想着第一天要进入陕西界</p><p>终于到了潼关，在此落脚，还吃了个肉夹馍~</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_144022.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_151821.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_151831.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_154609.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_174457.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_181336.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_185622.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_204858.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240924_202615.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>如此，第一天的旅行结束了</p><h2 id="第二天：潼关-西安"><a href="#第二天：潼关-西安" class="headerlink" title="第二天：潼关 - 西安"></a>第二天：潼关 - 西安</h2><p>前一天在酒店提前查了一下陕西段国道的路况（因为在渭南市下国道的时候，那段路实在有些糟糕）</p><p>改变了路线，决定不再走 310 国道，取而代之的是堤顶路</p><p>骑过之后才知道，堤顶路是沿着渭河修建的一条穿过渭南-西安-宝鸡的道路</p><p>这条路简直是为了旅游者而修建的，限高 2.2m，限速 60，意味着没有大车，希望旅行者慢慢走</p><p>这条路两边是田地，村庄，渭河。红绿顶非常少，甚至还有农民在路上晒玉米（毕竟是这个季节嘛</p><p>好在车非常少，景色优美，骑行心情悠然自得，太享受了</p><p>这张照片是从国道走入一条无名道路上，这条道路连接着国道和堤顶路，走上这条无名路我就知道自己选对路了！</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_110425.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_105154.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_105200.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_105601.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_110015.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_110026.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_110319.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>如此惬意，美好，自由</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_111458.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_113452.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_114403.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_114433.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_114758.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_115155.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_120043.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_120051.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_120620.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>沿着这条路继续走下去，穿过了田野，穿过了华阴，见到渭河</p><p>这一路没有什么饭店，但是休息也没有国道上那么嘈杂，想停就停，停下也很安静</p><p>摘掉头盔，大口呼吸空气，吃点火腿肠，咬一口大苹果</p><p>休息的一刻，我也是自然的一部分</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_121533.jpg" alt="源远流长"></p><p>慢慢悠悠地，西安人民欢迎您</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_125837.jpg" alt="西安界"></p><p>在西安界内，也是先沿着渭河堤走，渭河就在旁边</p><p>到西安之后明显发现摩托车变多了，好多酷炫的摩托佬</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_133014.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_131734.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>大概四点半的时候到达大雁塔，大雁塔红绿灯前草草拍了个照片，旅程算是结束了</p><p>我做到了！！</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240925_154043.jpg" alt="到达！"></p><h1 id="到达后的感想"><a href="#到达后的感想" class="headerlink" title="到达后的感想"></a>到达后的感想</h1><details class="toggle" ><summary class="toggle-button" style="">一点点骑行建议</summary><div class="toggle-content"><p><strong>路线</strong></p><p>第一天：洛阳-潼关：310 国道<br>第二天：潼关-西安：堤顶路</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/Screenshot_2024-09-19-18-28-21-200_com.autonavi.m.jpg","alt":"原定路线：310国道"},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/Screenshot_2024-10-05-20-21-46-270_com.autonavi.m.jpg","alt":"第二天的路线：堤顶路"}]</div>      <div class="gallery-items">      </div>    </div><p><strong>准备</strong></p><p>出发前的准备非常重要，除了头盔护具这些提高生存率的东西之外</p><p>头盔最好是全盔，舒适性方面最重要的是不让眼睛吹风，这个非常非常重要，甚至可以准备一点玻璃酸钠滴眼液</p><p>衣服要考虑防风性能，晚上的时候也要兼顾保暖</p><p>护腰可以尝试一下，骑行是非常累的，想办法减轻自己的疲劳</p><p><strong>休息</strong></p><p>带一点咖啡因饮料也许可以集中精神，牛磺酸饮料对减轻疲劳也有用</p><p>合理安排骑行时间，最好每个小时休息一次，休息的时候吃点东西（带糖分的最好</p><p>关注油耗，国道上加油站比较少，加油很可能要下国道</p><p><strong>时间</strong></p><p>朝西方向骑行的时候，五六点钟太阳会直射眼睛，最好在这个时间前就休息吧</p><p>天黑不骑车，危险倍增，夜晚国道上大车会变得很多，最好的时间是工作日的上午和下午</p><p><strong>防御性驾驶</strong></p><p>防御性驾驶一定要学一下，或者锻炼一下</p><p>摩托车在国道上出事一般都是会转生异世界的….</p><p>而摩托车视野最开阔，天然适合防御性驾驶</p><p>预测未来道路会发生的事情，主动让车</p><p>国道非机动车道最好不要走，路的质量很糟糕，还有玻璃碴子…</p><p><strong>横风</strong></p><p>摩托车避免不了横风问题，国道上没有城市的高楼，横风会强劲又突然</p><p>需要特别注意两类横风</p><p>一个是自然横风，经过山和桥的时候，可能会遭遇很突然的风，甚至吹偏一个车道</p><p>一个是大车从身边穿过的风，挂车从身边过去的时候那个压迫感…. 过去之后会有个向大车方向吸过去的风，这个也不容小觑</p><p>对付横风最好的方法是减速，本田小狼速度上 70 就能感觉到横向风的影响，80 的时候会变得严重，骑帅不骑快就好啦！</p><p>对付大车的横风，就是走最右车道和减速让行（不要停车），走最右车道是因为不会被两个大车夹在中间</p></div></details><p>第一次摩旅毕业了！</p><p>出发前是害怕和不安</p><p>出发后是紧张，稳稳骑行，珍惜自己的生命，感慨国道上开阔的视野</p><p>对骑车技术有把握之后，开始欣赏沿路的美景，享受旅行</p><p>到达目的地后，先前的不安和紧张都不再有了，取而代之的是豁然开朗！</p><p>还有对自己的肯定：我做到了！！</p><p>最初考虑，我会在西安住上一个月去找工作</p><p>但是当我到达的时候，已经觉得骑到西安这件事本身的价值已经超越了找工作，担心找不到工作的忧愁也不存在了</p><p>毕竟，西安不是我的终点，只是其中一站而已</p><p>在民宿住下之后去泡了个澡，把疲惫溶解在温泉里…</p><p>真让人脸扁~~</p><p><strong>旅途的风景都在路上，生活是体验。我一直都在路上，我一直都在体验</strong></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20241005223412.png"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;写下这篇文章的时候已经是十一假期了&lt;/p&gt;
&lt;p&gt;我已经在西安暂住下来，并且开始逐渐了解这座城市&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;我做了一件很不得了的事情，至少我自己觉得很厉害！&lt;/p&gt;
&lt;p&gt;9.24 和 9.25 这两天的时间，骑上我的本田 cc110，从洛阳到达了西安&lt;/p&gt;</summary>
      
    
    
    
    <category term="本田小狼与我" scheme="https://blog.homu.space/categories/%E6%9C%AC%E7%94%B0%E5%B0%8F%E7%8B%BC%E4%B8%8E%E6%88%91/"/>
    
    
    <category term="摩托车" scheme="https://blog.homu.space/tags/%E6%91%A9%E6%89%98%E8%BD%A6/"/>
    
  </entry>
  
  <entry>
    <title>长沙香香</title>
    <link href="https://blog.homu.space/posts/7223d12/"/>
    <id>https://blog.homu.space/posts/7223d12/</id>
    <published>2024-08-30T15:31:39.000Z</published>
    <updated>2026-05-10T10:49:07.012Z</updated>
    
    <content type="html"><![CDATA[<p><a href="/gallery/Album/2024/2024.08%E9%95%BF%E6%B2%99/">照片都在这里</a></p><h1 id="出发和到达"><a href="#出发和到达" class="headerlink" title="出发和到达"></a>出发和到达</h1><p>8 月 23 号从洛阳坐高铁到长沙游玩，住在长沙县，星沙地铁站附近。朋友家楼上的公寓式酒店</p><p>去长沙之前，朋友还让我带了几个烧饼（笑</p><p>第一次伴手礼给人带烧饼的</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240822_184157.jpg","alt":"这可太接地气了"},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_124839.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>到达长沙之后，收到了友人出差兰州带的博物馆文创</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_103613.jpg"></p><h2 id="撸猫"><a href="#撸猫" class="headerlink" title="撸猫"></a>撸猫</h2><p>到达长沙的第一件事情就是去朋友家撸猫</p><p>早在两年前，朋友在秦皇岛的时候我去住过，家里的两只猫猫太可爱了</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_175410.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_155100.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_155812.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>喂猫条的是我，被喂猫条的叫黑米，正在被朋友 rua 的猫猫叫正正</p><p>猫猫的记忆是短暂的，他们俩都不认识我了（哭</p><p>小时候我还抱过你呢！</p><h2 id="恰饭"><a href="#恰饭" class="headerlink" title="恰饭"></a>恰饭</h2><p>晚上在楼下吃了一家名叫壹盏灯的长沙菜</p><p>哇 好辣好辣 每个菜都有辣椒</p><p>但是好香，辣椒和肉就好像西红柿和鸡蛋一般相配</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_192203.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_192005.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_191709.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><h1 id="游玩长沙"><a href="#游玩长沙" class="headerlink" title="游玩长沙"></a>游玩长沙</h1><h2 id="松雅湖"><a href="#松雅湖" class="headerlink" title="松雅湖"></a>松雅湖</h2><p>到达第一天晚上，吃过饭之后，去了松雅湖</p><p>在松雅湖租了一辆快乐的三人自行车</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_203120.jpg"></p><p>松雅湖周围是很完善的骑行道，有不少单车佬在这里飙车，道路两边也有足够多的便利店和厕所</p><p>很明显是有市政管理支持的景区</p><p>除了骑行，还有一个人工沙滩（难道是海边错觉？</p><p>沙滩不大，但是好多人在这里玩，城市松弛感的体现</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_210448.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_210220.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_221111.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240823_220545.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>这趟骑行大概花了两个多小时，好累，但是好爽，运动的疲惫在愉快的情绪里是可以转换成轻盈感的</p><h2 id="城区漫步"><a href="#城区漫步" class="headerlink" title="城区漫步"></a>城区漫步</h2><p>第二天主要是逛街，这天走了两万三千步…</p><p>可能是因为在天津的运动量增加了体力上限，感觉好像没那么累</p><p>这一天的路线是：开福寺 - 黄兴路步行街 - 文和友 - 杜甫江阁</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_155834.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_153113.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_150255.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_145412.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_122929.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>进入文和友很震撼，复古做旧的年代风味很有趣。黄兴路步行街展现了长沙现代化的一面。</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_150643.jpg" alt="来长沙的第一口臭豆腐"></p><h2 id="橘子洲"><a href="#橘子洲" class="headerlink" title="橘子洲"></a>橘子洲</h2><p>第三天睡了个大懒觉，下午去橘子洲坐小火车</p><p>年轻时代的毛泽东雕像，眼里有光，有未来，深邃而威严</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_172724.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_173154.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><h2 id="岳麓山的日出"><a href="#岳麓山的日出" class="headerlink" title="岳麓山的日出"></a>岳麓山的日出</h2><p>第四天的一大早，三点起床打车去岳麓山看日出。</p><p>和天津那次的海边不同，这天看到了日出</p><p>但是太阳被该死的国金大厦挡住了 QAQ</p><p>可恶啊！！！</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_053903.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_061642.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>日出真好，日出的时候没有声音</p><p>沿着岳麓山的下山道，逐渐走到了湖南大学</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_074906.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_071502.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>湖南大学好大好大… 上双一流大学是什么感觉捏…</p><h1 id="长沙香香"><a href="#长沙香香" class="headerlink" title="长沙香香"></a>长沙香香</h1><p>要说在长沙印象最好的东西，还是各路美食</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_110613.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_150455.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240824_210425.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_191307.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_191905.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_193143.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_203313.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_204051.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_211915.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240825_212956.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240826_105525.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>常德津市牛肉粉、臭豆腐、东瓜山香肠、紫苏桃子、黄油粑粑、热卤、茶颜悦色</p><p>在长沙没有喝茶颜的奶茶，只喝了果茶和鸡尾酒，鸡尾酒非常好喝，是忘不了的味道</p><p>长沙的臭豆腐和外面的真的不一样，香，脆，辣，呛！</p><p>在长沙不紧不慢过的十分惬意…</p><p>好羡慕友人四处移动的自由生活，把生活本身变成旅游的一部分</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;a href=&quot;/gallery/Album/2024/2024.08%E9%95%BF%E6%B2%99/&quot;&gt;照片都在这里&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;出发和到达&quot;&gt;&lt;a href=&quot;#出发和到达&quot; class=&quot;headerlink&quot; title=&quot;出发和到达&quot;&gt;</summary>
      
    
    
    
    <category term="去玩咯" scheme="https://blog.homu.space/categories/%E5%8E%BB%E7%8E%A9%E5%92%AF/"/>
    
    
  </entry>
  
  <entry>
    <title>一起去天津逛街吧</title>
    <link href="https://blog.homu.space/posts/d571f87f/"/>
    <id>https://blog.homu.space/posts/d571f87f/</id>
    <published>2024-08-18T20:05:30.000Z</published>
    <updated>2026-05-10T10:49:07.012Z</updated>
    
    <content type="html"><![CDATA[<p>早在一年前，朋友就推荐我去天津玩一玩，我自己也对慢节奏的经济特区很有兴趣</p><p>上初中的弟弟也在放暑假，初中的弟弟还没出过远门，也想走出洛阳看看其他城市</p><p>于是，趁着这次休息，仔细计划了一番。和弟弟一起 出发！</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/Image_1723817378813.jpg"></p><h1 id="行程"><a href="#行程" class="headerlink" title="行程"></a>行程</h1><p>试着用多维表记录了一下行程，非常方便呢（安利一下 <a href="https://flowus.cn/">Flow us</a> 这个软件）</p><p>我的行程都在下面的图片里啦，也可以作为天津旅游攻略的参考</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240818201130.png" alt="第一天，到达天津，落脚，探索市区"></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240818201212.png" alt="第二天，天津的近代文化，坐摩天轮"></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240818201328.png" alt="第三天，国家海洋博物馆，天津海岸线"></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240818201420.png" alt="第四天，日出，逛商场和老街，返程"></p><h1 id="游玩印象"><a href="#游玩印象" class="headerlink" title="游玩印象"></a>游玩印象</h1><p><a href="/gallery/Album/2024/2024.08天津/" target="_blank">所有的照片都在这里</a></p><h2 id="到达天津"><a href="#到达天津" class="headerlink" title="到达天津"></a>到达天津</h2><p>高铁下午到达，已经饿得不行了… 直接打车去了西北角美食街</p><p>这里稍微有点失望，就是很普通的县城小吃街（虽然绝对不是小县城</p><p>在这里吃了最顶饿的煎饼果子，还买了炸糕</p><p>煎饼果子是纯咸味的，比起山东的会更干一些，还挺好吃的。炸糕外脆里嫩，但是吃了几口就腻了（也可能是我吃饱了）</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240814_150352.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240814_152949.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><hr><p>吃饱之后去和在天津出差的朋友碰面一起溜街子聊天</p><p>逛了意风区和古文化街，虽说是意风区，但是风味也不是那么浓…</p><p>在天津逛街是很惬意的，因为周围建筑各不相同，新鲜感！</p><p>晚上去了海河公园，绕着某两个桥之间走了走，看到一路上有弹吉他的市民和游泳的，海河是可以游泳的！</p><p>看得出天津真的有呵护这条母亲河</p><p>大光明桥:</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240819111535.png" alt="天津友人的肯定"></p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240814_211627.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240814_212043.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><h2 id="五大道，博物馆，天津之眼"><a href="#五大道，博物馆，天津之眼" class="headerlink" title="五大道，博物馆，天津之眼"></a>五大道，博物馆，天津之眼</h2><p>五大道是万国建筑群，名人故居，大多数故居都不对外开放，但是走在这样的街道里体验民国风情也很有趣</p><p>五大道的中心是视野开阔的民园广场，广场附近有好多小吃</p><p>来一碗津门老味刨冰！是夏天的清爽味道！</p><p>还有拉小提琴的老爷爷（拉的相当好）</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_092544.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_094146.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_095248.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_104255.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><div id="dplayer0" class="dplayer hexo-tag-dplayer-mark" style="margin-bottom: 20px;"></div><script>(function(){var player = new DPlayer({"container":document.getElementById("dplayer0"),"video":{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/VID_20240815_100138.mp4"}});window.dplayers||(window.dplayers=[]);window.dplayers.push(player);})()</script><p>之后去了天津博物馆，博物馆的一层和二层都挺一般的，没有什么亮点</p><p>但是从三层就不同了，来了天津，就一定绕不开近代历史。三楼就是这样展览馆。</p><p>近代历史展馆中有不少历史细节，比如战争的数据统计，交战双方的兵器，老照片等等，相当震撼</p><p>比如下面的兵器对比</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_121630.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_121653.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_121621.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>↑ 这怎么可能打得赢 ↑</p><p>这让我想起了玩文明 6 的时候，游戏玩到后期和落后国家出现文明代差，直升机打弓箭手，坦克推中世纪城墙…..</p><p>对于晚清而言，确实是这样的体验吧 orz</p><p>清代的封闭统治毁了中国…</p><p>但是不能去责怪历史，因为历史不会变</p><p>博物馆四楼是一个临时展厅，内容是波罗的海琥珀展</p><p>有封印了远古虫族的橙黄琥珀化石，还有与金银加工的饰品。大开眼界</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_131006.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_131137.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_131615.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_130555.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>↑ 好像看到了艾尔登法环里的屎壳郎，打死可以获得失色锻造石吗 ↑</p><hr><p>下午和搞定出差工作的朋友碰面，一起去袁世凯家做客（付费做客</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_174244.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_172638.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_173749.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_174214.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>第一张图片是从他家阳台上拍的海河，风景真好（这老东西真会挑地方住）</p><p>想去袁世凯家看看是因为<a href="https://www.bilibili.com/video/BV1Vw411h7Yj">这个视频</a></p><p>参观故居之后可以明显地感受到一些特点。</p><ul><li>拥抱西方先进文化的同时又保守封建思维，家里的佛像和风水讲究。电视机和非常精密的钟表湿度计</li><li>想当皇帝，他是真的相做皇帝 hhhhh，一楼还有个龙椅呢（</li><li>兴趣广泛，麻将象棋牌九老照相机</li><li>胆小多疑，有很多逃生密道，在任何一层楼都可以随时出逃</li></ul><hr><p>晚上去了天津之眼，那个坐落在桥面上，一百多米高的巨大摩天轮</p><p>我只坐过上海动物园的小摩天轮，所以对这个特别向往。早早就订了票</p><p>摩天轮门票是 100/人，一圈大概 2 多分钟。白天和晚上去各有各的美景</p><p>要说有什么美中不足，就是摩天轮舱室的玻璃上有滤光膜，可能是担心白天的太阳炫光。但是这个滤光膜很影响拍照的质量</p><p>（好在手机 AI 相机有颜色校正）</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_183654.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_190228.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_191822.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240815_193747.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><h2 id="国家海洋博物馆"><a href="#国家海洋博物馆" class="headerlink" title="国家海洋博物馆"></a>国家海洋博物馆</h2><p>第三天去了海洋博物馆，建在海边的博物馆</p><p>由于没有预约到门票，只能买馆内活动票送入馆门票… 这也算是变相门票收费了吧，不过很值得</p><p>展馆非常大，足够逛一整天的了，是个溜初中弟弟的好地方（</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_121055.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_122741.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_131429.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_143107.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>午饭是在馆内解决的，抱着食堂味道肯定一般般的心态去了深海中餐厅，结果大受震撼</p><p>这也 <strong>太好吃了叭！！</strong></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_124456.jpg" alt="坚果蔬菜沙拉，虾饺，蒸汽海鲜，芝士榴莲，蒜蓉扇贝"></p><p>吃饭花了大概 160，味道绝赞，如果要去这里玩一定不要错过！</p><hr><p>逛馆子还挺累的，下午稍微休息了一会儿，打车出发去东堤公园看海</p><p>晴天看海，海岸线有栏杆围着，不让下水，但是海岸线非常美</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_170900.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_164656.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_165807.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><hr><p>之后因为走累了就回酒店休息…</p><p>晚上的时候在塘沽找饭店，找到了一家叫红旗饭庄的店。又是一番味觉盛宴</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_195901.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_193831.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_192501.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_192933.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_193448.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>让我来一一介绍！</p><ul><li>第一个菜是茄夹，入口是口水茄子的酸甜咸口，外面的淀粉壳炸得很脆，里面的茄子是软糯的，但是这还没完，茄夹里有一颗不小的虾仁，吃到最后又有肉肉的感觉和鲜味。过口入肚之后会有一丝丝辣感，这才发现这道菜底部是有辣椒油，香辣脆口，味觉口感体验都极其丰富</li><li>第二个菜店里叫麻线。其实是烙饼、香葱和熟虾酱。这个菜弟弟吃不惯，我是因为好奇山东的虾酱风味（虽然是津菜）才点的。香葱，烙饼，虾酱，不论哪个单吃都非常难以下咽。但是加起来就是绝配，香葱掩盖了一小部分的虾酱的臭味，突出了鲜味，烙饼赋予了形状和口感，赞叹食材融合的智慧</li><li>最后一个是店里老板推荐的八珍豆腐，刚端上来的时候没看见豆腐，还以为上错菜了。直到夹了两筷子鱿鱼和黄瓜，才发现豆腐铺在最下面。于是用勺子挖了一勺豆腐和蛤蜊肉，嗯，绝了！</li></ul><p>这个饭馆三道菜给了我巨大的惊喜，食材丰富，味觉和口感都有兼顾。可能这就是津菜的智慧吧！</p><p>后来把这个发给了天津友人，他也知道这家店。评价相当不错，如果要去天津，务必要尝一尝！</p><hr><p>吃完饭之后回酒店的路上，看到有一家三福，以消食为目的去逛了逛，然后痛失一张粉红票票</p><p>前几个月看完葬送的芙莉莲之后，一直想买些小周边纪念一下，但是没有满意的，在三福发现了收藏卡，画面很棒。</p><p>除了芙芙，还看到了两个蝴蝶结，少女心突然颤动，一不小心就买了下来！谁可以拒绝可爱的蝴蝶结呢？</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_205429.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240816_205437.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><h2 id="遗憾日出，返程"><a href="#遗憾日出，返程" class="headerlink" title="遗憾日出，返程"></a>遗憾日出，返程</h2><p>回酒店之后早早的就休息了，因为明天想去海边看日出</p><p>市政很贴心的有一条四点出发的日出东疆公交专线，我们第二天就是乘坐这个去的</p><p>值得一提的是，前一天晚上看天气预报是多云，抱着赌一把的心态出发了</p><p>结果我赌输了 QAQ</p><p>云层很厚，没有橘红色的日出，东疆亲海公园虽然能下水但是也不干净</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_050541.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_050918.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_034417.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>于是回酒店继续睡，算是一个遗憾吧</p><hr><p>睡醒之后去滨海文化中心打卡了滨海图书馆</p><p>书山和科技感的设计新颖独特</p><p>也许是因为成为网红打卡地了吧，游览人数比较多，并没有符合图书馆的那份安静</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_120126.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_121449.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_115822.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><p>下午返回天津站附近，因为离得很近，所以准备看看世纪钟广场。结果世纪钟在修…</p><p>好像几年前世纪钟就坏掉停转了</p><p>然后去转了天津站附近的老街，很喜欢市中心老街的烟火气，如果下次来天津。我会想去哈尔滨路吃几次饭</p><p>可能正是因为这些老街里接地气的小吃，加上海河富有亲和力的市民文化，才会显得天津的节奏会舒缓一些吧</p><div class="gallery-container" data-type="data" data-button="">      <div class="gallery-data">[{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_154133.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_161323.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_174332.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_180213.jpg","alt":""},{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_180041.jpg","alt":""}]</div>      <div class="gallery-items">      </div>    </div><hr><p>最后，到达天津站，卧铺一觉睡回家咯</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240817_192049.jpg" alt="拜拜~"></p><h1 id="回家的路上"><a href="#回家的路上" class="headerlink" title="回家的路上"></a>回家的路上</h1><p>在火车站的时候看到了 c 开头的火车。城际火车，忽然发觉，北京就和天津挨着</p><p>搜了一下从天津到故宫的行程，才一小时四十分钟! 比去滨海新区都快</p><p>如果不打算去天津海边的话，可以第三天去北京转转（？</p><p>要说这趟旅行中的遗憾，莫过于没有亲眼见证日出的那抹橘红了。其他的话，天津好像没有自然海岸线了，在出租车上的时候搜索了一下，十年前的填海造陆，把天然海岸线都埋在了地下，造陆之后利用率却很低</p><hr><p>在天津四天三夜，游玩体验很棒。</p><p>海河，历史建筑，海边的博物馆，美食。都留下了难忘的记忆</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/Cache_-79a86ca7c0c1e7d1.gif"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;早在一年前，朋友就推荐我去天津玩一玩，我自己也对慢节奏的经济特区很有兴趣&lt;/p&gt;
&lt;p&gt;上初中的弟弟也在放暑假，初中的弟弟还没出过远门，也想走出洛阳看看其他城市&lt;/p&gt;
&lt;p&gt;于是，趁着这次休息，仔细计划了一番。和弟弟一起 出发！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http</summary>
      
    
    
    
    <category term="去玩咯" scheme="https://blog.homu.space/categories/%E5%8E%BB%E7%8E%A9%E5%92%AF/"/>
    
    
  </entry>
  
  <entry>
    <title>前端团队的代码质量建设</title>
    <link href="https://blog.homu.space/posts/968a6851/"/>
    <id>https://blog.homu.space/posts/968a6851/</id>
    <published>2024-08-16T13:44:38.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<p>回忆一下在米哈游上班的两年学会最重要的东西，应该是大公司里前端团队的工作方式</p><p>在此之前，从事的项目都是小团队，一般只有一个前端开发（就是我）或者最多两个。代码想怎么写就怎么写。项目体量小所以混乱但高效</p><p>但是米哈游里的项目不同，有技术难点，开发团队人数多，上线质量要求高，沟通场景多，开发流程也比较严格。这种情况下，我欠缺的不再是解决问题的能力，而是对工作环境以及项目质量的适应性</p><h1 id="团队协作"><a href="#团队协作" class="headerlink" title="团队协作"></a>团队协作</h1><p>刚进入项目的时候只有五六个人，后来随着项目被上面重视，开始扩大规模，增加产能。人数最多的时候将近 20 人</p><p>随着人数增加出现过很多问题，比如合并代码丢失，大量代码冲突等问题。为了避免这些问题，做出了不少值得我学习的措施</p><h2 id="迭代开发之前"><a href="#迭代开发之前" class="headerlink" title="迭代开发之前"></a>迭代开发之前</h2><p>正常的流程是: 需求评审 -&gt; 设计评审 -&gt; 前后端技术评审 -&gt; 功能开发 -&gt; 测试用例评审 -&gt; 测试 -&gt; 产品和 UI 验收 -&gt; 发布</p><p>这样的流程在开发时基本上时清晰的，但是不可避免会出现开发测试期间需求变更的问题</p><p>在需求评审和技术评审的时候，程序员应该尽可能构建完整的开发思路，思考产品设计和现有功能的矛盾点，可行性如何，如果有问题，就告诉产品经理。这样做可以从需求合理性的角度避免开发期间的需求变更</p><p>（避免开发期间的需求变化就是<strong>减少加班</strong>）</p><h2 id="编辑器统一"><a href="#编辑器统一" class="headerlink" title="编辑器统一"></a>编辑器统一</h2><p>在项目中创建.vscode 文件夹，不要加入.gitignore，可以让团队每个人的编辑器设置保持一致。避免因为一些插件或者选项导致的提交冲突或者代码问题</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></pre></td><td class="code"><pre><span class="line">./.vscode</span><br><span class="line">├── extensions.json</span><br><span class="line">└── settings.json</span><br></pre></td></tr></table></figure><h2 id="依赖统一"><a href="#依赖统一" class="headerlink" title="依赖统一"></a>依赖统一</h2><p>为了保证所有人的开发环境一致，不要把 lock 文件放入.gitignore</p><p>如果对代码一致性要求严格，可以在 package.json 里锁定版本</p><figure class="highlight json"><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="attr">&quot;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;axios&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.4.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;dayjs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.10.7&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;diff-match-patch&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0.5&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;dompurify&quot;</span><span class="punctuation">:</span> <span class="string">&quot;3.0.6&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;downloadjs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.4.7&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;echarts&quot;</span><span class="punctuation">:</span> <span class="string">&quot;5.3.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;event-source-polyfill&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0.31&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;eventemitter3&quot;</span><span class="punctuation">:</span> <span class="string">&quot;4.0.7&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;exceljs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;4.3.0&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>创建.npmrc 文件，统一 npm 源，以及项目里的一些 npm 设置</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">registry=https://XXX.com</span><br></pre></td></tr></table></figure><h2 id="建立-git-使用规范"><a href="#建立-git-使用规范" class="headerlink" title="建立 git 使用规范"></a>建立 git 使用规范</h2><h3 id="分支管理"><a href="#分支管理" class="headerlink" title="分支管理"></a>分支管理</h3><p>米哈游的本地化项目里有清晰的分支意义，分支与运行环境相关联：</p><p>master：同步预发布环境代码，添加 tag 时可以选择发布生产环境</p><p>pre: 预发布环境（生产环境，测试数据）</p><p>dev: 测试环境（测试环境，测试数据）</p><p>feat: 测试环境</p><p>需求确定后，从 dev 分支拉出自己的开发分支（feat）进行开发，开发完成后合入 dev 分支进入测试阶段</p><h3 id="Merge-Request"><a href="#Merge-Request" class="headerlink" title="Merge Request"></a>Merge Request</h3><p>在此我只知道 pull request，拉去请求。团队内使用 gitlab，每当完成自己的 feat 分支开发时，会在 gitlab 上发起一个 merge request，代码 diff 通过 code review 之后才会合入 dev 分支</p><p>如果是需求开发，commit 的意义不需要保留，那么可以在 mr 的时候将多个 commit 压缩成一个</p><p>测试完成，进入预发环境的时候，会从 dev 提交一个 merge request 到 pre 分支，通过 diff 可以看到一次迭代修改的所有代码。根据 diff 进行 code review</p><h3 id="git-rebase"><a href="#git-rebase" class="headerlink" title="git rebase"></a>git rebase</h3><p>为保证提交历史的干净整洁，团队没有采用传统的 merge 合并代码，而是 rebase。</p><p><a href="/posts/71e03861/">git rebase 相关的内容可以看这篇文章</a></p><p>既然使用了 rebase，有时候就需要对 commit 进行 drop 或者 squash 等一系列的操作。除了手动在命令行中使用<code>git rebase -i</code> 之外。更推荐使用 git 图形工具来做，比如<a href="https://git-fork.com/">fork</a></p><h3 id="commitizen-和-git-cz"><a href="#commitizen-和-git-cz" class="headerlink" title="commitizen 和 git-cz"></a>commitizen 和 git-cz</h3><p>使用 <a href="https://commitizen.github.io/cz-cli/">commitizen</a> 和 <a href="https://github.com/streamich/git-cz">git-cz</a> 来替代 <code>git commit -m</code> 可以约束项目参与者的提交信息</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240908182609.png"></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240908182645.png"></p><h1 id="代码质量"><a href="#代码质量" class="headerlink" title="代码质量"></a>代码质量</h1><p>除了团队协作中的约定，采用一些手段来确保项目成员所编写的代码质量也十分重要</p><h2 id="TypeScript"><a href="#TypeScript" class="headerlink" title="TypeScript"></a>TypeScript</h2><p>如果项目比较大，业务逻辑复杂，引入 TS 是非常必要的。</p><p>编码效率方面，ts 会给出很好用的编辑器辅助</p><p>更重要的是代码的健壮性，给 JavaScript 添加类型约束之后，避免了很多 bug 的出现。而且在添加类型的过程中，也会修复许多的隐藏 bug</p><p><a href="https://www.typescriptlang.org/docs/handbook/tsconfig-json.html">tsconfig 的文档</a></p><h2 id="ESLint"><a href="#ESLint" class="headerlink" title="ESLint"></a>ESLint</h2><p>ESLint 可以保证项目中的语言语法规范。</p><p>可以通过 .eslintrc 文件来约束语法使用，可以用.eslintignore 忽略某些文件的 ESLint 检查</p><p>项目里我们使用 ESLint + Prettier 格式化代码，ESLint 和 Prettier 可能会产生规则冲突，可以使用 <a href="https://github.com/prettier/eslint-config-prettier">eslint-config-prettier</a> 这个工具辅助进行代码格式化</p><h2 id="husky-amp-lint-staged"><a href="#husky-amp-lint-staged" class="headerlink" title="husky &amp; lint-staged"></a>husky &amp; lint-staged</h2><p>除了写代码的时候进行格式化，在提交代码的时候也需要把暂存区的代码都进行一次 lint 和类型检查</p><p><a href="https://github.com/typicode/husky">husky</a>用来在运行 git 命令的时候运行一些函数或者其他命令。比如 pre-commit 钩子</p><p><a href="https://github.com/lint-staged/lint-staged">lint-staged</a>用来过滤出暂存区的更改</p><p>所以 ts + eslint + husky + lint-staged 可以在 pre-commit 的时候 对暂存区的代码进行 lint。从而保证了每次 commit 的代码都是符合 ESLint 规则且没有 TS 问题的</p><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><p>模块之间可以有引用关系，但是引用关系必须是单向的。</p><p>项目中划分模块可以分为下面几种</p><ul><li>通用模块：一些通用逻辑，比如登录，用户信息，全局状态。这些应该被独立为模块</li><li>工具模块：工具函数，网络请求的封装，日志等</li><li>业务模块：业务逻辑的分割</li></ul><h2 id="单元测试"><a href="#单元测试" class="headerlink" title="单元测试"></a>单元测试</h2><p>单元测试是通过一些工具去验证程序的最小可测试部分是否按照期望结果运行</p><p>“单元”可能是一个函数，一个类，或者一个模块</p><p>单元测试的过程是：对单元指定运行环境，入参，期望结果。程序在指定环境中接收入参返回的结果是否与期望结果一致</p><p><a href="https://www.zhihu.com/question/28729261/answer/1058317111">关于什么是单元测试，单元测试的意义，以前的同事给过一篇知乎的答案来解释</a></p><p>在公司项目里，我们曾经使用 vitest 来做单元测试</p><figure class="highlight js"><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="string">&quot;@vitest/coverage-istanbul&quot;</span>: <span class="string">&quot;^0.33.0&quot;</span>,</span><br><span class="line"><span class="string">&quot;@vitest/coverage-v8&quot;</span>: <span class="string">&quot;^0.33.0&quot;</span>,</span><br><span class="line"><span class="string">&quot;@vitest/ui&quot;</span>: <span class="string">&quot;^0.33.0&quot;</span>,</span><br><span class="line"><span class="string">&quot;vitest&quot;</span>: <span class="string">&quot;^0.32.4&quot;</span>,</span><br><span class="line"><span class="string">&quot;vitest-canvas-mock&quot;</span>: <span class="string">&quot;^0.3.2&quot;</span>,</span><br><span class="line"><span class="string">&quot;msw&quot;</span>: <span class="string">&quot;^1.2.2&quot;</span>,  <span class="comment">// 用来mock请求</span></span><br></pre></td></tr></table></figure><figure class="highlight js"><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="comment">// sum.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// sum.spec.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; expect, test &#125; <span class="keyword">from</span> <span class="string">&quot;vitest&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; sum &#125; <span class="keyword">from</span> <span class="string">&quot;./sum.js&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">test</span>(<span class="string">&quot;adds 1 + 2 to equal 3&quot;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">expect</span>(<span class="title function_">sum</span>(<span class="number">1</span>, <span class="number">2</span>)).<span class="title function_">toBe</span>(<span class="number">3</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="UI-自动化测试"><a href="#UI-自动化测试" class="headerlink" title="UI 自动化测试"></a>UI 自动化测试</h2><p>相比单元测试，UI 自动化更偏向于视图的实际表现</p><p>UI 自动化就好像模拟人类在网页上的操作行为，测试用例基本都可以转化为 UI 自动化用例来执行。</p><p>UI 自动化的行为逻辑实际上是：登录（如果需要） -&gt; 找到元素 -&gt; 触发事件 -&gt; 事件触发结果是否符合预期</p><p>可以使用 playwrite 库进行，以下是一个测试登录页面的例子</p><figure class="highlight ts"><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">const</span> <span class="title class_">AccountTest</span> = &#123;</span><br><span class="line">  <span class="attr">SUPER</span>: [<span class="string">&quot;account&quot;</span>, <span class="string">&quot;********&quot;</span>],</span><br><span class="line">&#125; <span class="keyword">as</span> <span class="keyword">const</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> &#123; <span class="title class_">AccountTest</span> <span class="keyword">as</span> <span class="title class_">Account</span> &#125;;</span><br></pre></td></tr></table></figure><p>封装测试工具</p><figure class="highlight ts"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// e2e/utils.ts</span></span><br><span class="line"><span class="keyword">import</span> &#123; test, <span class="keyword">type</span> <span class="title class_">Page</span>, <span class="keyword">type</span> <span class="title class_">BrowserContext</span> &#125; <span class="keyword">from</span> <span class="string">&quot;@playwright/test&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">login</span>(<span class="params"><span class="attr">page</span>: <span class="title class_">Page</span>, <span class="attr">account</span>: <span class="keyword">readonly</span> <span class="built_in">string</span>[]</span>) &#123;</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">goto</span>(<span class="string">&quot;#/login&quot;</span>);</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">getByPlaceholder</span>(<span class="string">&quot;请输入您的用户名&quot;</span>).<span class="title function_">fill</span>(account[<span class="number">0</span>]);</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">getByPlaceholder</span>(<span class="string">&quot;请输入您的密码&quot;</span>).<span class="title function_">fill</span>(account[<span class="number">1</span>]);</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">click</span>(<span class="string">&quot;#login &gt; div.login-wrap &gt; form &gt; button&quot;</span>);</span><br><span class="line">  <span class="keyword">await</span> page.<span class="title function_">waitForURL</span>(<span class="string">&quot;**/*&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> seed = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">randomId</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;<span class="built_in">Date</span>.now().toString(<span class="number">36</span>)&#125;</span>0<span class="subst">$&#123;seed++&#125;</span>`</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">describe</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">title</span>: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">cfg</span>: &#123; account: <span class="keyword">readonly</span> <span class="built_in">string</span>[] &#125;,</span></span><br><span class="line"><span class="params">  <span class="attr">cb</span>: (parm: &#123; page: Page &#125;) =&gt; <span class="built_in">void</span></span></span><br><span class="line"><span class="params"></span>) &#123;</span><br><span class="line">  test.<span class="title function_">describe</span>(title, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> <span class="attr">ctx</span>: <span class="title class_">BrowserContext</span>;</span><br><span class="line">    <span class="keyword">let</span> <span class="attr">page</span>: <span class="title class_">Page</span>;</span><br><span class="line">    test.<span class="title function_">beforeAll</span>(<span class="title function_">async</span> (&#123; browser &#125;) =&gt; &#123;</span><br><span class="line">      ctx = <span class="keyword">await</span> browser.<span class="title function_">newContext</span>();</span><br><span class="line">      page = <span class="keyword">await</span> ctx.<span class="title function_">newPage</span>();</span><br><span class="line">      <span class="keyword">await</span> <span class="title function_">login</span>(page, cfg.<span class="property">account</span>);</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">await</span> page</span><br><span class="line">          .<span class="title function_">getByRole</span>(<span class="string">&quot;button&quot;</span>, &#123; <span class="attr">name</span>: <span class="string">&quot;我知道了&quot;</span> &#125;)</span><br><span class="line">          .<span class="title function_">click</span>(&#123; <span class="attr">timeout</span>: <span class="number">100</span> &#125;);</span><br><span class="line">      &#125; <span class="keyword">catch</span> (error) &#123;&#125;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    test.<span class="title function_">afterAll</span>(<span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">      <span class="keyword">await</span> page.<span class="title function_">close</span>();</span><br><span class="line">      <span class="keyword">await</span> ctx.<span class="title function_">close</span>();</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="title function_">cb</span>(&#123;</span><br><span class="line">      <span class="keyword">get</span> <span class="title function_">page</span>() &#123;</span><br><span class="line">        <span class="keyword">return</span> page;</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>自动化用例编写成本比较高，而且会因为迭代造成较大的变动</p><h2 id="安全性检查"><a href="#安全性检查" class="headerlink" title="安全性检查"></a>安全性检查</h2><p>公司安全组会对项目进行安全性检查，寻找潜在的安全漏洞</p><p>主要包括 SQL 注入、XSS 攻击等</p><p>除此之外，npm 包也会要求使用公司的自建镜像。从而防止供应链攻击</p><h1 id="运行监控"><a href="#运行监控" class="headerlink" title="运行监控"></a>运行监控</h1><p>除了团队协作和代码规范，在代码发布到生产环境之后会采取一些措施来监控代码运行稳定性</p><h2 id="埋点"><a href="#埋点" class="headerlink" title="埋点"></a>埋点</h2><p>通过埋点收集用户行为，埋点可以让产品经理分析用户的使用习惯。也可以给开发者复现生产环境错误提供线索</p><p>这是一个简单的埋点上报接口类型</p><figure class="highlight typescript"><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">interface</span> <span class="title class_">Action</span> &#123;</span><br><span class="line">  <span class="attr">page</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">module</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">action</span>: <span class="string">&quot;click&quot;</span> | <span class="string">&quot;view&quot;</span> | <span class="string">&quot;scroll&quot;</span>;</span><br><span class="line">  <span class="attr">extra</span>: <span class="built_in">any</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="错误监控"><a href="#错误监控" class="headerlink" title="错误监控"></a>错误监控</h2><p>通过 Sentry 等工具对生产环境进行错误监控，可以收集到生产环境下程序运行出现的错误</p><p>许多报错往往看似不影响程序正常执行，但是在某些边界场景中，就有可能造成生产事故</p><p>收集生产环境的错误，及时修复。就如同在清理项目里的定时炸弹</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;回忆一下在米哈游上班的两年学会最重要的东西，应该是大公司里前端团队的工作方式&lt;/p&gt;
&lt;p&gt;在此之前，从事的项目都是小团队，一般只有一个前端开发（就是我）或者最多两个。代码想怎么写就怎么写。项目体量小所以混乱但高效&lt;/p&gt;
&lt;p&gt;但是米哈游里的项目不同，有技术难点，开发团队</summary>
      
    
    
    
    <category term="小笔记" scheme="https://blog.homu.space/categories/%E5%B0%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="代码质量" scheme="https://blog.homu.space/tags/%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F/"/>
    
  </entry>
  
  <entry>
    <title>react18学习笔记</title>
    <link href="https://blog.homu.space/posts/a1fbfde1/"/>
    <id>https://blog.homu.space/posts/a1fbfde1/</id>
    <published>2024-08-05T12:47:45.000Z</published>
    <updated>2026-05-10T10:49:07.012Z</updated>
    
    <content type="html"><![CDATA[<p>为最新的 next.js 做点基础功课，学习 react18 的文档</p><p>记录一些对比之前不太一样的地方或者值得注意的地方</p><p>也会相对用的比较多的 Vue3，谈一些 React18 的理解</p><h1 id="纯函数"><a href="#纯函数" class="headerlink" title="纯函数"></a>纯函数</h1><p>react 特别强调函数要纯</p><p>不论是组件，还是对 state 的修改，一定要保持函数的纯粹性</p><p><a href="https://zh-hans.react.dev/learn/keeping-components-pure">https://zh-hans.react.dev/learn/keeping-components-pure</a></p><blockquote><p>一个函数只做一件事情<br>输入相同，则输出相同<br>纯函数仅仅执行计算，因此调用它们两次不会改变任何东西</p></blockquote><blockquote><p>React 假设你编写的所有组件都是纯函数</p></blockquote><blockquote><p>不应该改变任何用于组件渲染的输入。这包括 props、state 和 context。通过 state 来更新界面，而不要改变预先存在的对象。</p></blockquote><p>编写纯函数是编写 react 组件的前提条件</p><p>与函数的纯粹性相对的副作用，应该在 <code>事件处理</code> 或者 <code>useEffect</code> 中处理</p><h2 id="严格模式"><a href="#严格模式" class="headerlink" title="严格模式"></a>严格模式</h2><p><a href="https://zh-hans.react.dev/learn/keeping-components-pure#detecting-impure-calculations-with-strict-mode">https://zh-hans.react.dev/learn/keeping-components-pure#detecting-impure-calculations-with-strict-mode</a></p><p>React 提供了 “严格模式”，在严格模式下开发时，它将会调用每个组件函数两次。通过重复调用组件函数，严格模式有助于找到违反这些规则的组件。</p><p>引入严格模式，可以用 <code>&lt;React.StrictMode&gt;</code> 包裹根组件。一些框架会默认这样做。</p><h1 id="state-的快照理解和-Immer"><a href="#state-的快照理解和-Immer" class="headerlink" title="state 的快照理解和 Immer"></a>state 的快照理解和 Immer</h1><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722834774421.png"></p><p>下面这个例子，点击一次并不会使 number 的结果从 0 变成 3</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [number, setNumber] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;number&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">          setNumber(number + 1);</span></span><br><span class="line"><span class="language-xml">          setNumber(number + 1);</span></span><br><span class="line"><span class="language-xml">          setNumber(number + 1);</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">        +3</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因为其等价于</p><figure class="highlight jsx"><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">&lt;button</span><br><span class="line">  onClick=&#123;<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">setNumber</span>(<span class="number">0</span> + <span class="number">1</span>);</span><br><span class="line">    <span class="title function_">setNumber</span>(<span class="number">0</span> + <span class="number">1</span>);</span><br><span class="line">    <span class="title function_">setNumber</span>(<span class="number">0</span> + <span class="number">1</span>);</span><br><span class="line">  &#125;&#125;</span><br><span class="line">&gt;</span><br><span class="line">  +<span class="number">3</span></span><br><span class="line">&lt;/button&gt;</span><br></pre></td></tr></table></figure><p>哪怕用 setTimeout 包裹其中一个 setTimeout 也不会影响结果：</p><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line">&lt;h1&gt;&#123;number&#125;&lt;<span class="regexp">/h1&gt;   /</span>/ <span class="number">0</span> -&gt; <span class="number">5</span></span><br><span class="line">&lt;button onClick=&#123;<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">setNumber</span>(number + <span class="number">5</span>);</span><br><span class="line">  <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">alert</span>(number);  <span class="comment">// 0</span></span><br><span class="line">  &#125;, <span class="number">3000</span>);</span><br><span class="line">&#125;&#125;&gt;+<span class="number">5</span>&lt;/button&gt;</span><br></pre></td></tr></table></figure><blockquote><p>一个 state 变量的值永远不会在一次渲染的内部发生变化， 即使其事件处理函数的代码是异步的。<br>React 会使 state 的值始终“固定”在一次渲染的各个事件处理函数内部。</p></blockquote><h2 id="渲染期间改变-state-的值"><a href="#渲染期间改变-state-的值" class="headerlink" title="渲染期间改变 state 的值"></a>渲染期间改变 state 的值</h2><p>将 <code>setNumber</code> 的参数写成一个 <code>纯函数</code>：</p><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [number, setNumber] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;number&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">          setNumber((n) =&gt; n + 1);</span></span><br><span class="line"><span class="language-xml">          setNumber((n) =&gt; n + 1);</span></span><br><span class="line"><span class="language-xml">          setNumber((n) =&gt; n + 1);</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">        +3</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在一次渲染期间，会产生一个 state 的更新队列。例如上面的 state 更新队列可以表示为 <code>[(n) =&gt; n + 1,(n) =&gt; n + 1,(n) =&gt; n + 1]</code></p><h2 id="更新对象形式的-state"><a href="#更新对象形式的-state" class="headerlink" title="更新对象形式的 state"></a>更新对象形式的 state</h2><p><a href="https://zh-hans.react.dev/learn/updating-objects-in-state#treat-state-as-read-only">https://zh-hans.react.dev/learn/updating-objects-in-state#treat-state-as-read-only</a></p><p>如果 state 是一个对象，这个时候直接通过对象的引用关系去修改 state 是不正确的，这不会触发渲染</p><figure class="highlight js"><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">const</span> [position, setPosition] = <span class="title function_">useState</span>(&#123; <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span> &#125;);</span><br><span class="line"></span><br><span class="line">position.<span class="property">x</span> = <span class="number">1</span>; <span class="comment">// 当你这样做时，就制造了一个 mutation, state 应该是只读的</span></span><br></pre></td></tr></table></figure><p>正确的写法应该是</p><figure class="highlight js"><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="title function_">setPosition</span>(&#123;</span><br><span class="line">  <span class="attr">y</span>: <span class="number">0</span>,</span><br><span class="line">  <span class="attr">x</span>: <span class="number">1</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>如果对象复杂嵌套，这样书写也将变得非常繁琐，官方会推荐使用 <a href="https://github.com/immerjs/use-immer"><code>Immer</code></a></p><h2 id="使用-Immer"><a href="#使用-Immer" class="headerlink" title="使用 Immer"></a>使用 Immer</h2><p>Immer 有点像 Vue3 中的 <code>reactive</code>，其原理都是使用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy"><code>Proxy</code></a> 记录对 state 的修改，并创建出新的对象</p><p>安装：<code>npm install use-immer</code></p><figure class="highlight js"><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="keyword">const</span> [position, updatePosition] = <span class="title function_">useImmer</span>(&#123; <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span> &#125;);</span><br><span class="line"><span class="title function_">updatePosition</span>(<span class="function">(<span class="params">draft</span>) =&gt;</span> (draft.<span class="property">x</span> = <span class="number">1</span>));</span><br></pre></td></tr></table></figure><h2 id="更新数组形式的-state"><a href="#更新数组形式的-state" class="headerlink" title="更新数组形式的 state"></a>更新数组形式的 state</h2><p>不论是数组还是对象，都要保持 state 的只读性</p><p>在使用<code>setState</code>去更新数组的时候，应该避免使用会修改原数组的方法，比如 <code>push，unshift，pop，shift，splice，splice，arr[i] = ...，reverse，sort</code></p><p>如果使用 useImmer 声明 state，则可以使用所有的方法</p><h1 id="状态管理的原则"><a href="#状态管理的原则" class="headerlink" title="状态管理的原则"></a>状态管理的原则</h1><p>这一部分算是响应式框架通用的一些原则，遵守这些原则，在代码组织上会显得更加合理</p><h2 id="设计组件的思路"><a href="#设计组件的思路" class="headerlink" title="设计组件的思路"></a>设计组件的思路</h2><p><a href="https://zh-hans.react.dev/learn/reacting-to-input-with-state#thinking-about-ui-declaratively">https://zh-hans.react.dev/learn/reacting-to-input-with-state#thinking-about-ui-declaratively</a></p><ol><li>分析组件所有可能存在的状态</li><li>确定哪些因素可以改变这些状态，人为输入 or 计算机输入</li><li>通过代码表示状态，<code>useState</code> 或者 <code>ref</code></li><li>连接状态和事件处理函数</li></ol><h2 id="构建-state-的原则"><a href="#构建-state-的原则" class="headerlink" title="构建 state 的原则"></a>构建 state 的原则</h2><p><strong>合并相关联的 state</strong></p><p>如果一个动作往往要改动两个 state，两个 state 又相互独立，那么可以把两个 state 用一个对象包裹起来，每次都修改这个对象</p><p><strong>避免矛盾的 state</strong></p><p>用 一个 state 表示 <code>typing</code> <code>sending</code> <code>sent</code> 比 三个 state 表示 <code>isTyping</code> <code>isSending</code> <code>isSent</code> 要更好</p><p><strong>用尽可能少的 state</strong></p><p>相对来说，组件状态越少，组件维护起来的成本就越小。避免冗余的和重复的 state</p><p><a href="https://zh-hans.react.dev/learn/choosing-the-state-structure#don-t-mirror-props-in-state">没有必要把 props 镜像出一个 state</a></p><p><strong>避免深度嵌套的 state</strong></p><p>尽可能创建扁平化的 state</p><h2 id="组件之间的-state"><a href="#组件之间的-state" class="headerlink" title="组件之间的 state"></a>组件之间的 state</h2><p>如果子组件之间有相互影响的 state，那么最好把它交给父组件管理</p><p>对于独特的状态，都应该有单一的数据源（state），这个 state 可以被传递，但是最好不要复制</p><h2 id="组件内-state-的移除和保留"><a href="#组件内-state-的移除和保留" class="headerlink" title="组件内 state 的移除和保留"></a>组件内 state 的移除和保留</h2><p>和 vue 一样，react 也有特殊的属性 <code>key</code>，更新 <code>key</code> 就会强制重置组件</p><h2 id="统一管理状态和动作-编写-reducer"><a href="#统一管理状态和动作-编写-reducer" class="headerlink" title="统一管理状态和动作: 编写 reducer"></a>统一管理状态和动作: 编写 reducer</h2><p><a href="https://zh-hans.react.dev/learn/extracting-state-logic-into-a-reducer#step-2-write-a-reducer-function">https://zh-hans.react.dev/learn/extracting-state-logic-into-a-reducer#step-2-write-a-reducer-function</a></p><p>当组件的状态非常多的情况下，频繁地 setState 会让代码难以维护</p><p>如果能把更新状态的操作封装起来，用一个动作去表示，然后传入参数，就可以使引用更加清晰</p><p>reducer 是一个调度器，接收并执行不同的动作，返回新的结果</p><p>dispatch 用来发出动作，给出动作和计算新状态所需要的额外信息</p><blockquote><p>要将状态设置逻辑从事件处理程序移到 reducer 函数中，你需要：</p><ol><li>声明当前状态（tasks）作为第一个参数；</li><li>声明 action 对象作为第二个参数；</li><li>从 reducer 返回 下一个 状态（React 会将旧的状态设置为这个最新的状态）。</li></ol></blockquote><figure class="highlight js"><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="keyword">function</span> <span class="title function_">reducer</span>(<span class="params">state, action</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span>(action) &#123;</span><br><span class="line">    <span class="attr">case</span>: <span class="string">&#x27;action A&#x27;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> stateA = &#123;</span><br><span class="line">        ...state,</span><br><span class="line">        <span class="comment">// 一些因为 action A 而产生的变化</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> stateA</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="attr">case</span>: <span class="string">&#x27;action B&#x27;</span>&#123;</span><br><span class="line">      <span class="keyword">const</span> stateB = &#123;</span><br><span class="line">        ...state,</span><br><span class="line">        <span class="comment">// 一些因为 action B 而产生的变化</span></span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> stateB</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="attr">default</span>: &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="title class_">Error</span>(<span class="string">&#x27;未知 action: &#x27;</span> + action);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>使用 useReducer 替代 useState</p><p><a href="https://zh-hans.react.dev/reference/react/useReducer#dispatch">https://zh-hans.react.dev/reference/react/useReducer#dispatch</a></p><figure class="highlight js"><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">import</span> &#123; useReducer &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">const</span> [state, dispatch] = <span class="title function_">useReducer</span>(reducer, initStates);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">handleEvent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">dispatch</span>(&#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&quot;action&quot;</span>,</span><br><span class="line">    <span class="comment">// new state</span></span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>与 setState 相同，reducer 也必须是纯函数，在修改对象或者数组的状态时，也需要替换整个数组或者对象</p><p>可以用 <a href="https://github.com/immerjs/use-immer#useimmerreducer"><code>useImmerReducer</code></a> 替换 useReducer 来简化 reducer 的写法</p><h1 id="使用-context-深层传递参数"><a href="#使用-context-深层传递参数" class="headerlink" title="使用 context 深层传递参数"></a>使用 context 深层传递参数</h1><p>React 的 context 特性让我想到了 vue 的 provide 和 inject</p><p>两者都是解决组件参数传递层级过深的问题，只是写法不一样而已</p><h2 id="创建-context"><a href="#创建-context" class="headerlink" title="创建 context"></a>创建 context</h2><figure class="highlight js"><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="comment">// 创建一个context, context.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; createContext &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title class_">Context</span> = <span class="title function_">createContext</span>(<span class="number">1</span>);</span><br></pre></td></tr></table></figure><h2 id="父组件提供-context"><a href="#父组件提供-context" class="headerlink" title="父组件提供 context"></a>父组件提供 context</h2><figure class="highlight jsx"><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">import</span> &#123; <span class="title class_">Context</span> &#125; <span class="keyword">from</span> <span class="string">&quot;context.js&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Section</span>(<span class="params">&#123; level, children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">section</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Context.Provider</span> <span class="attr">value</span>=<span class="string">&#123;level&#125;</span>&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">Context.Provider</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">section</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="子组件接收-context"><a href="#子组件接收-context" class="headerlink" title="子组件接收 context"></a>子组件接收 context</h2><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useContext &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Context</span> &#125; <span class="keyword">from</span> <span class="string">&quot;context.js&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Child</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> level = <span class="title function_">useContext</span>(<span class="title class_">Context</span>);</span><br><span class="line">  <span class="keyword">return</span> (...)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="结合-useReducer-和-context，创建局部-store"><a href="#结合-useReducer-和-context，创建局部-store" class="headerlink" title="结合 useReducer 和 context，创建局部 store"></a>结合 useReducer 和 context，创建局部 store</h2><p><a href="https://zh-hans.react.dev/learn/scaling-up-with-reducer-and-context#combining-a-reducer-with-context">https://zh-hans.react.dev/learn/scaling-up-with-reducer-and-context#combining-a-reducer-with-context</a></p><p><code>useReducer</code> 可以将对数据的修改整理成 <code>action</code> ，通过 <code>dispatch</code> 一个动作更新状态</p><p><code>createContext</code> 提供了一个局部共享的状态</p><p>将 <code>useReducer</code> 所提供的 <code>state</code> 和 <code>dispatch函数</code> 通过 context 传递给子组件，那么子组件就可以通过 <code>action</code> 修改父组件提供的状态了</p><p>这样做的好处是</p><ol><li>将 reducer 封装组件，子组件不需要关心父组件动作的对状态的具体修改，只需要派发动作就可以修改父组件的状态</li><li>把 context 和 reducer 放在一起统一管理，使得负责构建 UI 的文件更加纯粹，视图和逻辑相分离</li></ol><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// AppContext.jsx ---------------------------</span></span><br><span class="line"><span class="keyword">import</span> &#123; useReducer, createContext &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">AppContext</span> = <span class="title function_">createContext</span>();</span><br><span class="line"><span class="comment">// reducer 并不需要暴露出去</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">reducer</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">AppContextProvider</span>(<span class="params">&#123; children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, dispatch] = <span class="title function_">useReducer</span>(reducer, initialState);</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">AppContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;&#123;</span> <span class="attr">state</span>, <span class="attr">dispatch</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">AppContext.Provider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 父组件, 提供 context ------------</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">AppContextProvider</span> &#125; <span class="keyword">from</span> <span class="string">&quot;AppContext.jsx&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> (<span class="params">&#123; children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">AppContextProvider</span>&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">AppContextProvider</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 子组件, 消费 context --------------</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; useContext &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">useAppContext</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> context = <span class="title function_">useContext</span>(<span class="title class_">AppContext</span>);</span><br><span class="line">  <span class="keyword">if</span> (!context) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;useAppContext must be used within an AppProvider&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> context; <span class="comment">// &#123; state, dispatch &#125;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="react-里的-useRef"><a href="#react-里的-useRef" class="headerlink" title="react 里的 useRef"></a>react 里的 useRef</h1><blockquote><p>在我理解中的 Vue 和 React 框架设计的区别是，Vue 是响应性数据驱动更新视图，而 React 是状态驱动更新视图。<br>Vue 中的响应性数据：<code>ref()</code> 和 <code>reactive()</code> 声明的数据承载了表示状态的功能的同时也承载了可变性数据计算的功能。<br>React 的状态和可变性数据是分开的两个概念，<code>state</code> 只能由纯函数修改，并且具有不可变性，用来更新视图。<code>ref</code> 可以被随意修改，用来做与视图无关的计算<br>所以 React 中的 <code>ref</code> 并不会像 Vue 中的那样触发视图更新，只能用来承载组件内的数据计算或者 dom 操作</p></blockquote><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 点击按钮后，打印结果会变成 1 ，但是视图依然是0</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">MyRefComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> ref = <span class="title function_">useRef</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">handleChange</span>(<span class="params"></span>) &#123;</span><br><span class="line">    ref.<span class="property">current</span> += <span class="number">1</span>;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(ref.<span class="property">current</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;ref.current&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;handleChange&#125;</span>&gt;</span>+1<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="useRef-的-dom-操作"><a href="#useRef-的-dom-操作" class="headerlink" title="useRef 的 dom 操作"></a>useRef 的 dom 操作</h2><p>在这方面，react 和 vue 的差别不大</p><figure class="highlight jsx"><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="keyword">import</span> &#123; useRef &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">Form</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> inputRef = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">handleClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">    inputRef.<span class="property">current</span>.<span class="title function_">focus</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&#123;inputRef&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#125;</span>&gt;</span>聚焦输入框<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="使用-flushSync-更新-state"><a href="#使用-flushSync-更新-state" class="headerlink" title="使用 flushSync 更新 state"></a>使用 <a href="https://zh-hans.react.dev/reference/react-dom/flushSync">flushSync</a> 更新 state</h2><blockquote><p>flushSync 可以确保其回调函数内的更新立即反映到 dom 上<br>它和 <code>nextTick</code> 解决的问题相似：视图的更新是异步的，某些情况下希望在操作数据之后立刻对新的 dom 进行操作<br>如果回调内有大量的 dom 更新，将会出现性能问题</p></blockquote><p><a href="https://zh-hans.react.dev/learn/manipulating-the-dom-with-refs#flushing-state-updates-synchronously-with-flush-sync">官方给出的例子</a></p><h1 id="处理渲染本身产生的副作用：Effect"><a href="#处理渲染本身产生的副作用：Effect" class="headerlink" title="处理渲染本身产生的副作用：Effect"></a>处理渲染本身产生的副作用：Effect</h1><p>组件的状态变更会引起渲染，渲染会引起一些副作用，有些时候需要处理渲染引起的副作用，这就会用到 <code>useEffect</code></p><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useEffect &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">MyComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">useEffect</span>(</span><br><span class="line">    <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line">      <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;&#125;; <span class="comment">// 组件在重新执行effect之前，以及组件被卸载的时候，会调用这个清理函数</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><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>useEffect</code> 的回调函数会在渲染的提交阶段（也就是屏幕更新渲染之后）执行，使用 <code>useEffect</code> 需要显式指定依赖<br><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722923497870.png"></p><p><a href="https://zh-hans.react.dev/learn/synchronizing-with-effects#why-was-the-ref-omitted-from-the-dependency-array">指定依赖的数组中可以省略 ref</a></p><h2 id="开发环境中-Effect-的重复执行问题"><a href="#开发环境中-Effect-的重复执行问题" class="headerlink" title="开发环境中 Effect 的重复执行问题"></a>开发环境中 Effect 的重复执行问题</h2><p>重复执行 React 的调试行为，Effect 的双重执行有利于开发者检测自己的 Effect 是否有清理函数终止掉多余的副作用</p><p><a href="https://zh-hans.react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development">https://zh-hans.react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development</a></p><h2 id="Effect-常见用法"><a href="#Effect-常见用法" class="headerlink" title="Effect 常见用法"></a>Effect 常见用法</h2><h3 id="数据获取"><a href="#数据获取" class="headerlink" title="数据获取"></a>数据获取</h3><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useEffect, useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">DataFetchingComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [data, setData] = <span class="title function_">useState</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">fetch</span>(<span class="string">&quot;https://api.example.com/data&quot;</span>)</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">response</span>) =&gt;</span> response.<span class="title function_">json</span>())</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> <span class="title function_">setData</span>(data));</span><br><span class="line">  &#125;, []); <span class="comment">// 空数组表示这个 effect 只在组件挂载时执行一次</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;data ? JSON.stringify(data) : &quot;Loading...&quot;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="订阅或监听事件"><a href="#订阅或监听事件" class="headerlink" title="订阅或监听事件"></a>订阅或监听事件</h3><p>在组件挂载时注册事件监听器或订阅某些服务，并在组件卸载时取消订阅。这些操作包括 WebSocket 连接、Redux store 订阅、浏览器事件（如键盘或鼠标事件）等。</p><figure class="highlight js"><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 class="keyword">import</span> &#123; useEffect &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">EventListenerComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">handleResize</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Window resized&quot;</span>);</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;resize&quot;</span>, handleResize);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 清理函数，组件卸载时移除事件监听器</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(<span class="string">&quot;resize&quot;</span>, handleResize);</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;, []); <span class="comment">// 空数组，effect 只在挂载和卸载时执行</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>Resize the window and check the console.<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="定时器或者间隔"><a href="#定时器或者间隔" class="headerlink" title="定时器或者间隔"></a>定时器或者间隔</h3><figure class="highlight js"><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="keyword">import</span> &#123; useEffect, useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">TimerComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> interval = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">setCount</span>(<span class="function">(<span class="params">c</span>) =&gt;</span> c + <span class="number">1</span>);</span><br><span class="line">    &#125;, <span class="number">1000</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 清理函数，组件卸载时清除定时器</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="built_in">clearInterval</span>(interval);</span><br><span class="line">  &#125;, []); <span class="comment">// 空数组，effect 只在挂载时执行一次</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>Count: &#123;count&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="依赖变化"><a href="#依赖变化" class="headerlink" title="依赖变化"></a>依赖变化</h3><p>类似于 Vue 中的 watch</p><figure class="highlight js"><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">import</span> &#123; useEffect, useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">CounterComponent</span>(<span class="params">&#123; count &#125;</span>) &#123;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Count changed:&quot;</span>, count);</span><br><span class="line">  &#125;, [count]); <span class="comment">// 只有 count 改变时，effect 才会重新执行</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>Count: &#123;count&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="手动操作-dom"><a href="#手动操作-dom" class="headerlink" title="手动操作 dom"></a>手动操作 dom</h3><p>利用了 <code>useEffect</code> 在组件<strong>挂载之后</strong>执行一次的特性</p><h2 id="不需要-Effect-的常见场景"><a href="#不需要-Effect-的常见场景" class="headerlink" title="不需要 Effect 的常见场景"></a>不需要 Effect 的常见场景</h2><p><a href="https://zh-hans.react.dev/learn/you-might-not-need-an-effect">https://zh-hans.react.dev/learn/you-might-not-need-an-effect</a></p><h2 id="Effect-的生命周期"><a href="#Effect-的生命周期" class="headerlink" title="Effect 的生命周期"></a>Effect 的生命周期</h2><p>在 React 中，React 希望开发者将 Effect 的生命周期与组件的生命周期区分开来看待</p><p>组件的生命周期：</p><p>挂载 -&gt; 更新 -&gt; 卸载</p><p>Effect 的生命周期：</p><p>启动（Effect 函数执行） -&gt; 依赖项更新（Effect 函数执行） -&gt; 停止（清理函数执行）</p><p>即使没有依赖项，Effect 也会按照这个生命周期执行（仅执行一次，因为没有依赖项，自然也没有更新）</p><h1 id="使用自定义-hook-抽离公共逻辑"><a href="#使用自定义-hook-抽离公共逻辑" class="headerlink" title="使用自定义 hook 抽离公共逻辑"></a>使用自定义 hook 抽离公共逻辑</h1><p>Vue3 中也有类似的例子，在组件 setup 的时候，执行一些方法（同样约定以 use 开头），为组件提供一些预先封装好的功能</p><p>这是 React 给出的一个自定义 hook，用来给组件提供一个封装好 <code>online</code> 和 <code>offline</code> 事件并给出 <code>isOnline</code> 返回值的例子</p><figure class="highlight jsx"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">useOnlineStatus</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [isOnline, setIsOnline] = <span class="title function_">useState</span>(<span class="literal">true</span>);</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">handleOnline</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="title function_">setIsOnline</span>(<span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">handleOffline</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="title function_">setIsOnline</span>(<span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;online&quot;</span>, handleOnline);</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;offline&quot;</span>, handleOffline);</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(<span class="string">&quot;online&quot;</span>, handleOnline);</span><br><span class="line">      <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(<span class="string">&quot;offline&quot;</span>, handleOffline);</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;, []);</span><br><span class="line">  <span class="keyword">return</span> isOnline;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="hook-和组件"><a href="#hook-和组件" class="headerlink" title="hook 和组件"></a>hook 和组件</h2><p>React 应用是由组件构成，而组件由内置或自定义 Hook 构成。</p><blockquote><p>React 约定<br>React 组件名称必须以大写字母开头<br>Hook 的名称必须以<code>use</code>开头，hook 可以返回任意值</p></blockquote><p><a href="https://zh-hans.react.dev/learn/reusing-logic-with-custom-hooks#should-all-functions-called-during-rendering-start-with-the-use-prefix">关于 hook 的命名原则，是否所有渲染期间的调用函数都应该以 use 开头</a></p><p>使用 hook 封装的公共方法，应该被视为 hook，并以<code>use</code>作以标识</p><h2 id="hook-的内容"><a href="#hook-的内容" class="headerlink" title="hook 的内容"></a>hook 的内容</h2><p>自定义 hook 虽然可以提供 state，但是其重点在于所封装的逻辑，同时必须保持每次调用 hook 的独立性</p><p>自定义 hook 必须是纯函数</p><blockquote><p>自定义 Hook 共享的只是状态逻辑而不是状态本身。对 Hook 的每个调用完全独立于对同一个 Hook 的其他调用。</p></blockquote><h1 id="学习-react18-过程中的术语概念"><a href="#学习-react18-过程中的术语概念" class="headerlink" title="学习 react18 过程中的术语概念"></a>学习 react18 过程中的术语概念</h1><p><code>state</code> &amp; <code>快照</code></p><p><code>纯函数</code> &amp; <code>effect</code></p><p><code>ref</code></p><p><code>prop</code> &amp; <code>context</code></p><p><code>hook</code></p><p><code>reducer</code> &amp; <code>dispatch</code> &amp; <code>action</code></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;为最新的 next.js 做点基础功课，学习 react18 的文档&lt;/p&gt;
&lt;p&gt;记录一些对比之前不太一样的地方或者值得注意的地方&lt;/p&gt;
&lt;p&gt;也会相对用的比较多的 Vue3，谈一些 React18 的理解&lt;/p&gt;
&lt;h1 id=&quot;纯函数&quot;&gt;&lt;a href=&quot;#纯函数&quot;</summary>
      
    
    
    
    <category term="小笔记" scheme="https://blog.homu.space/categories/%E5%B0%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="react" scheme="https://blog.homu.space/tags/react/"/>
    
  </entry>
  
  <entry>
    <title>休息一下继续出发</title>
    <link href="https://blog.homu.space/posts/eb63e3a5/"/>
    <id>https://blog.homu.space/posts/eb63e3a5/</id>
    <published>2024-08-02T13:35:33.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<div class="note orange flat"><p>文章写于 8 月份，这一年还没过完呢，后续还会更新</p></div><h1 id="摇曳吼姆"><a href="#摇曳吼姆" class="headerlink" title="摇曳吼姆"></a>摇曳吼姆</h1><p><a href="/gallery/Album/2024">2024 年所有的照片都在这里</a></p><p>封面：《变成菜变成花》</p><h2 id="天冷我不冷，逛街！"><a href="#天冷我不冷，逛街！" class="headerlink" title="天冷我不冷，逛街！"></a>天冷我不冷，逛街！</h2><p>我说谎了，其实我超懒的，我好怕冷，我好懒….</p><p>是有一个朋友把我给拉出来了，一月一号元旦，我们去了徐汇的美罗城</p><p>目的地是橡子共和国，吉卜力的周边专买店，以及美罗城其他的一些二次元店铺（太有活力了！）</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240802193120.png" alt="战利品！呜呜呜，花了七百多！"></p><p>算是给魔女宅急便这个动画片补票，所以相当满足</p><hr><p>之后春节，在武汉的朋友回家之前来上海旅游，我就请了假和他一起逛二次元圣地魔都（？）</p><p>我们在 B 站的会员购上发现了一个 LoveLive 同好会的售票，最初以为是小型漫展。感觉只是逛店也好无聊，所以两个人就一起参加了这个同好会</p><p>这是我头一次去 Live house 在此之前我只在孤独摇滚里见过 Live House</p><p>这个不能被称之为漫展，因为这就是同好会！</p><p>先从检票说起，门口的检票是很无所谓的，甚至… 后面直接把检票给撤了。检票的好处是可以拿电子票换取两张明信片</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240131_201232.jpg" alt="还有一个吧唧"></p><p>因为是 Live House 所以主要是演出，除了演出没有其他的</p><p>这是我参加过最最最有 ACG 氛围的演出了，台上的 coser 用心的表演，台下的每个观众都跟着唱</p><p>刚进去的一瞬间我是震惊的，我是不是来错地方了… 哇！我的浓度远远不够</p><p>后面，我和朋友推测这是一个社团搞的活动，台下的大都是熟人，他们好像都认识？</p><div id="dplayer2" class="dplayer hexo-tag-dplayer-mark" style="margin-bottom: 20px;"></div><script>(function(){var player = new DPlayer({"container":document.getElementById("dplayer2"),"video":{"url":"https://honyan-gallery.oss-cn-hangzhou.aliyuncs.com/Album/2024/2024.01%E6%84%9A%E5%9B%AD%E8%B7%AF%E6%96%B0%E6%AD%8C%E7%A9%BA%E9%97%B4LoveLive%E5%90%8C%E5%A5%BD%E4%BC%9A/VID_20240131_154109.mp4"}});window.dplayers||(window.dplayers=[]);window.dplayers.push(player);})()</script><p>总之，这是我去过最棒的一次 ACG 演出，没有任何一次漫展或者剧场比得上这次！</p><h2 id="自驾舟山，学习开车"><a href="#自驾舟山，学习开车" class="headerlink" title="自驾舟山，学习开车"></a>自驾舟山，学习开车</h2><p>清明节假期的时候，我和两位关系很好的同事一起，租车去舟山自驾游玩。其目的一是玩，二是学习开车。</p><p>我高中毕业的时候就拿到驾照了，但是家里只有手动挡的车，我一开车老爸就骂我。索性不开了，反正我在外面也是打车比较多。目前没有开车的场景</p><p>有计划回家自驾游之后，开车就变成了比较重要的技能，于是下决心自己在上海学车！</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240803101800.png" alt="跨海大桥，自由的空气"></p><p>旁边有老司机同事坐着，也不会像老爸一样怼我。车撞坏了也有全险，没有心理包袱很快就上手了：在舟山三天是我开的，高速上也开了一段。开车还是好玩的~ 稍微有些累</p><p>在舟山前两天玩的蛮开心！去了海滩，第一天吃的鱼很棒，之后去普陀岛逛的有些累。第三天从绍兴回上海</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240803101844.png"></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240803101922.png" alt="背着我的鹿乃一起去的"></p><p>从结交到朋友这方面出发，我还是非常感谢米哈游的</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/2024-%E8%88%9F%E5%B1%B1.jpg" alt="观沧海的两位大哥"><br>在舟山岛留下了不错的回忆</p><h2 id="跑路跑路"><a href="#跑路跑路" class="headerlink" title="跑路跑路"></a>跑路跑路</h2><p>在<a href="/posts/3625dd20/">去年的文章</a>里提到过，现在的工作因为一些没意义的东西变得令人失望。</p><p>到今年三月份的时候，身边的同事都要走光了，原来十几个人的前端团队，现在只剩下五六个了</p><p>三月份我也接到了裁员的通知，意料之中，或者有点期待吧，因为早先熟悉的同事都不在了，大家上班也没有了最初的冲劲，每天打交道的人都满口黑话，不切实际，需求还没进入开发就先推卸未来的事故责任，说几句就令人厌烦。</p><p>二月份的时候就在想，如果离职了，应该好好休息几个月。因为现在工作不好找，既然不好找就更不应该在裁员潮和人竞争。家里也有不少事情，家里在盖房子，而且这几年都没回家，没有关注过家人的生活状态。回家之后也想给自己充充电，学点新技能，寻找一些新的爱好</p><p>和长沙工作的朋友，活力满满的天津友人，以及几个无所不能的网友讨论了上班和休息的事情，也得到了支持</p><p>四月底的时候离职了，把行李打包回家，我在上海的行李好多（我真的是来过日子的，不只是打工），邮寄花了一千还多。</p><p>走之前逛了好多馆子，一直想去但是没去成的天文馆水族馆自然博物馆。上海的展馆还是非常棒的</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240803103021.png" alt="sakana~~~ chinanago~~"></p><h2 id="从杭州回家"><a href="#从杭州回家" class="headerlink" title="从杭州回家"></a>从杭州回家</h2><p>行李寄回家之后，先去了杭州，游玩了西湖和良渚。</p><p>杭州的风景真棒啊！</p><p>吃了传说中无敌难吃的杭帮菜，西湖醋鱼</p><p>但是不够难吃，有点失望，后来问了店家，店家说稍微有些改进，把做醋鱼的西湖草鱼换成了鲈鱼（我就说嘛，吃起来没有土腥味也没有刺）</p><p>群友描述醋鱼的味道是这样的：浓郁的土腥伴随着酸涩不和谐的浓醋在嘴里爆开，漱口都难以清除的味道</p><p>嗯… 出于以上好奇去吃的… 所以稍微有点落差（不够难吃）</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240803103352.png"></p><h2 id="回到洛阳生活"><a href="#回到洛阳生活" class="headerlink" title="回到洛阳生活"></a>回到洛阳生活</h2><p>虽然在外很长时间，但是各种生活习惯还是源自于洛阳的，比如自己做晚饭的话喜欢弄汤菜馍。</p><p>回洛阳之后才知道洛阳的美食是什么，油泼面，烧烤（每个地方的烧烤都不一样），糖葫芦，米皮凉皮擀面皮馄饨细米线，牛肉汤羊肉汤驴肉汤。卤肉和烧饼，烧鸡….</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240628_194847.jpg" alt="emmmmmm"></p><p>回家之后想做的事情</p><ul><li>拿到本科毕业证和学士学位证</li><li>考取摩托车驾照，买摩托车</li><li>装饰一下自己的屋子，搞出一张理想的桌面</li><li>更换证件，身份证，驾照</li><li>开车自驾游</li><li>玩好多我想玩的游戏</li><li>关注一下家里盖房子的事情</li><li>整理网站，更换域名和服务器</li><li>学习一下服务端渲染框架</li><li>思考未来去哪里生活</li></ul><p>事情算是不少的，不过时间很充裕</p><p>基本上是都完成了</p><h1 id="本田小狼与我"><a href="#本田小狼与我" class="headerlink" title="本田小狼与我"></a>本田小狼与我</h1><p>因为今年看了一个动画片，<a href="https://bangumi.tv/subject/294337">本田小狼与我</a></p><p>印象里，摩托车是很猛汉有些危险的东西，但是以这个动画片为契机去深入了解之后，我也懂得了摩托车的魅力</p><p>作为交通工具，有他独特的浪漫，就像小狼说的，Cub 可以带我去任何地方，但是 Cub 也有做不到的事情…</p><p>本田小狼有独特的青春的味道，不同于挥洒汗水映出彩虹的热血，而是温和平静的自我满足和愉悦，发动机的震动和开阔的视野在心里开拓出了一片新世界</p><p>最好的风景在路上</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240624_132101.jpg" alt="我的Cub"></p><p>六月底，拿到驾照之后，骑着摩托车去了一次洛阳陆浑，绕湖骑行的感觉真好</p><h1 id="可爱的安达达和岛村村"><a href="#可爱的安达达和岛村村" class="headerlink" title="可爱的安达达和岛村村"></a>可爱的安达达和岛村村</h1><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/Image_1707734292627.jpg"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;div class=&quot;note orange flat&quot;&gt;&lt;p&gt;文章写于 8 月份，这一年还没过完呢，后续还会更新&lt;/p&gt;
&lt;/div&gt;

&lt;h1 id=&quot;摇曳吼姆&quot;&gt;&lt;a href=&quot;#摇曳吼姆&quot; class=&quot;headerlink&quot; title=&quot;摇曳吼姆&quot;&gt;&lt;/a&gt;摇曳吼</summary>
      
    
    
    
    <category term="年末记忆" scheme="https://blog.homu.space/categories/%E5%B9%B4%E6%9C%AB%E8%AE%B0%E5%BF%86/"/>
    
    
  </entry>
  
  <entry>
    <title>搭建一个简单的直播服务(livego)</title>
    <link href="https://blog.homu.space/posts/bdd7a439/"/>
    <id>https://blog.homu.space/posts/bdd7a439/</id>
    <published>2024-07-26T00:38:11.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<p>平时偶尔会把动画片下载下来，在 B 站上直播（拒绝做正版受害者）</p><p>但是近两年 B 站总是掐我的直播，因为版权问题或者不够和谐的问题… 直播间甚至被封过好几次</p><p>我还是挺珍惜自己的 6 级号的，但是也确实想以这种最直接的方式给朋友安利动画</p><p>从直播技术的角度看，B 站也好，斗鱼也好，本质都是本地推流，服务器处理流，客户端通过服务器链接接收流并播放</p><p>那么也许我可以自己搭建一个直播间</p><p>经过一番谷歌和 ChatGPT，最后找到了 <a href="https://github.com/gwuhaolin/livego/blob/master/README_cn.md">livego</a> 这个项目</p><p>livego 的优点很明显，就是简单，非常简单，熟悉一下文档之后，一行命令就可以跑起来</p><h1 id="关于直播服务器"><a href="#关于直播服务器" class="headerlink" title="关于直播服务器"></a>关于直播服务器</h1><p>在印象里，直播一向都是很吃性能的一件事，他要捕捉画面，把画面转换成数据流，实时发送给服务端</p><p>但是以上这些都是客户端做的事情，实际上服务端对于性能的压力并不大，我测试过 1 核 2G 的服务器足够运行 livego 了</p><p>直播者 (OBS &amp; 推流链接) -&gt; 服务器 (livego) -&gt; 用户 (播放链接 &amp; 播放器)</p><h2 id="推流比特率和分辨率"><a href="#推流比特率和分辨率" class="headerlink" title="推流比特率和分辨率"></a>推流比特率和分辨率</h2><p>选择视频分辨率和相应的比特率。例如：</p><ul><li>720p (HD): 大约 2.5 Mbps</li><li>1080p (Full HD): 大约 5 Mbps</li><li>4K (Ultra HD): 大约 15-25 Mbps</li></ul><p>比特率越高，视频质量越好，但所需带宽也越高。</p><h2 id="带宽"><a href="#带宽" class="headerlink" title="带宽"></a>带宽</h2><p>带宽涉及到本地推流带宽和服务器带宽</p><p><strong>本地推流，本地上行带宽</strong></p><p>我家网络很菜，上行带宽是 5Mbps，也就是说，我在使用 obs 推流的时候，码率不能超过 5Mbps，否则直播会出现断断续续的情况。保险起见，我在 obs 设置的码率是 4000Kbps，勉强可以让 1920*1080 分辨率不那么失真</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722309514234.png"></p><p><strong>直播服务器带宽</strong></p><p>服务器需要接收到推流，并且把流分发给客户端。</p><p>所以，用户数量越大，所需要的带宽就越大，简要的计算方式如下</p><blockquote><p>总带宽需求 = 单用户带宽需求 × 同时观看人数</p></blockquote><p>以我个人的直播来讲，如果有 10 个人看，也就是需要 50Mbps 的带宽</p><h2 id="服务器选择"><a href="#服务器选择" class="headerlink" title="服务器选择"></a>服务器选择</h2><p>总结下来，我的需求是：</p><p>直播观看用户不多，但是也需要不小的带宽</p><p>直播次数不多，所以直播流量不大，可能一个月只有几个 G</p><p>CPU 和内存要求不高</p><p>那么哪里有这样的服务器呢…. 高带宽，低流量，低性能，这种配置也太奇怪了</p><p>首先排除轻量应用服务器，限定死带宽的服务器一定不行。</p><p>经过一番努力，我终于找到了合适的，很便宜，并且满足需求的！</p><p>阿里云 99 块钱一年的服务器</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722310478256.png"></p><p>虽然这个的固定带宽是 3M，但是可以通过升降配置把它改成按流量计费的</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722310567727.png"></p><p>改成按流量计费之后，甚至还退了 49 块钱！</p><p>也就是，50 块钱拿下了一个 2 核 2G 大带宽的服务器</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722310777439.png"></p><p>大概费用：每有一个人看，每小时大概 1 块钱流量费。我感觉很能接受</p><h1 id="部署-livego-和使用"><a href="#部署-livego-和使用" class="headerlink" title="部署 livego 和使用"></a>部署 livego 和使用</h1><h2 id="使用-docker-部署"><a href="#使用-docker-部署" class="headerlink" title="使用 docker 部署"></a>使用 docker 部署</h2><p>首先安装 docker，使用官方脚本安装即可</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">curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun</span><br></pre></td></tr></table></figure><p>安装好之后，从 docker 启动，并且给容器添加一个 name，方便容器挂掉之后重启</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 run --name livego-server -p 1935:1935 -p 7001:7001 -p 7002:7002 -p 8090:8090 -d gwuhaolin/livego</span><br></pre></td></tr></table></figure><p>在安全组放行端口，上面端口的用处，可以看这张图片的备注列</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722311180697.png"></p><h2 id="使用-livego"><a href="#使用-livego" class="headerlink" title="使用 livego"></a>使用 livego</h2><p>创建房间，像文档里一样，访问地址：</p><p><a href="http://ip/">http://ip</a> 地址:8090/control/get?room=movie</p><p>页面会出现一个 json，里面包含了一个 key</p><h3 id="推流"><a href="#推流" class="headerlink" title="推流"></a>推流</h3><p>使用 rmtp 协议进行推流</p><p>obs 相关设置</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722311564690.png"></p><h2 id="播放"><a href="#播放" class="headerlink" title="播放"></a>播放</h2><p>播放支持多种协议，也就是对应上面的端口</p><ul><li>RTMP: rtmp://ip:1935/live/movie</li><li>FLV: <a href="http://ip:7001/live/movie.flv">http://ip:7001/live/movie.flv</a></li><li>HLS: <a href="http://ip:7002/live/movie.m3u8">http://ip:7002/live/movie.m3u8</a></li></ul><p>安卓可以使用<a href="https://kmplayer.com/home">KMSPlayer</a>播放</p><p>windows 可以使用<a href="https://potplayer.daum.net/">potplayer</a></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722311925134.png"></p><p>macos 可以使用<a href="https://iina.io/">IINA</a></p><p>也可以自己使用 <a href="https://github.com/gwuhaolin/blog/issues/3">flv.js</a> 构建一个网页播放器</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;平时偶尔会把动画片下载下来，在 B 站上直播（拒绝做正版受害者）&lt;/p&gt;
&lt;p&gt;但是近两年 B 站总是掐我的直播，因为版权问题或者不够和谐的问题… 直播间甚至被封过好几次&lt;/p&gt;
&lt;p&gt;我还是挺珍惜自己的 6 级号的，但是也确实想以这种最直接的方式给朋友安利动画&lt;/p&gt;
&lt;</summary>
      
    
    
    
    <category term="魔女的帽子" scheme="https://blog.homu.space/categories/%E9%AD%94%E5%A5%B3%E7%9A%84%E5%B8%BD%E5%AD%90/"/>
    
    
    <category term="docker" scheme="https://blog.homu.space/tags/docker/"/>
    
  </entry>
  
  <entry>
    <title>使用Caddy和docker部署hexo博客</title>
    <link href="https://blog.homu.space/posts/992525a2/"/>
    <id>https://blog.homu.space/posts/992525a2/</id>
    <published>2024-07-25T14:46:58.000Z</published>
    <updated>2026-05-10T10:49:07.012Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>以前这个博客是部署在阿里云的一台服务器上的，域名是 shirofune.cn</p><p>后来这台云服务器被我拿去<a href="/posts/bdd7a439/">直播服务器</a>了</p><p>现在想把域名换成 homu.space ，同时换一台服务器，所以就重新部署一下</p><p>借着这个动机，顺便维护一下博客年久失修的依赖</p><h1 id="新服务器的准备"><a href="#新服务器的准备" class="headerlink" title="新服务器的准备"></a>新服务器的准备</h1><h2 id="添加虚拟内存"><a href="#添加虚拟内存" class="headerlink" title="添加虚拟内存"></a>添加虚拟内存</h2><p>买了 99r 一年的服务器之后先 swap 一些虚拟内存</p><p>我用的是一个开箱即用的<a href="https://github.com/BlueSkyXN/SKY-BOX">工具脚本</a></p><p>关于 swap 的一些介绍，还有具体的命令行操作可以看<a href="https://www.bilibili.com/video/BV1kb41167cb">这个视频</a></p><h2 id="更新-package"><a href="#更新-package" class="headerlink" title="更新 package"></a>更新 package</h2><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">sudo</span> -i</span><br><span class="line">apt update -y <span class="comment"># 升级 packages</span></span><br></pre></td></tr></table></figure><h2 id="安装-docker"><a href="#安装-docker" class="headerlink" title="安装 docker"></a>安装 docker</h2><p>caddy 是一个 nginx 的替代</p><p>我希望通过 docker 来运行 caddy</p><p>官方提供了一个一键安装的脚本，脚本包含了 docker-compose</p><p>但是需要注意的是，要加上 <code>--mirror</code> 参数，指定国内镜像</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">curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun</span><br></pre></td></tr></table></figure><details class="toggle" ><summary class="toggle-button" style="">命令解释</summary><div class="toggle-content"><p>来自 chatgpt 的命令详解</p><p>curl: 这是一个命令行工具，用于从或向服务器传输数据。它支持许多协议，包括 HTTP、HTTPS、FTP 等。</p><p>-f: 如果请求失败，curl 会输出错误并退出，而不是返回页面内容。</p><p>-s: 使 curl 静默模式，不显示进度条或错误信息。</p><p>-S: 显示错误信息（通常与 -s 一起使用，以确保如果发生错误时仍能看到错误信息）。</p><p>-L: 如果请求的 URL 被重定向，curl 将自动跟随重定向。</p><p><a href="https://get.docker.com/">https://get.docker.com</a>: 这是 Docker 官方提供的一个脚本，用于简化 Docker 的安装过程。</p><p>|: 管道符，用于将前一个命令的输出作为后一个命令的输入。</p><p>bash: 这是一个命令行解释器，它将执行通过管道传递过来的脚本。</p><p>-s docker –mirror Aliyun: 这些是传递给脚本的参数：</p><p>-s docker: 指定要安装 Docker。</p><p>–mirror Aliyun: 指定使用阿里云的镜像源进行安装，通常在中国境内使用以提高下载速度和成功率。</p></div></details><p>设置一下 docker 镜像源，因为官方的 dockerhub 被墙了</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><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></pre></td><td class="code"><pre><span class="line">vim /etc/docker/daemon.json</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">&quot;registry-mirrors&quot;</span>: [</span><br><span class="line">    <span class="string">&quot;https://ustc-edu-cn.mirror.aliyuncs.com&quot;</span>,</span><br><span class="line">    <span class="string">&quot;https://registry.docker-cn.com&quot;</span>,</span><br><span class="line">    <span class="string">&quot;http://hub-mirror.c.163.com&quot;</span>,</span><br><span class="line">    <span class="string">&quot;https://dockerhub.azk8s.cn&quot;</span>,</span><br><span class="line">    <span class="string">&quot;https://ccr.ccs.tencentyun.com&quot;</span>,</span><br><span class="line">    <span class="string">&quot;https://registry.cn-hangzhou.aliyuncs.com&quot;</span>,</span><br><span class="line">    <span class="string">&quot;https://docker.mirrors.ustc.edu.cn&quot;</span>,</span><br><span class="line">    <span class="string">&quot;https://docker.m.daocloud.io&quot;</span>,</span><br><span class="line">    <span class="string">&quot;https://noohub.ru&quot;</span>,</span><br><span class="line">    <span class="string">&quot;https://huecker.io&quot;</span>,</span><br><span class="line">    <span class="string">&quot;https://dockerhub.timeweb.cloud&quot;</span></span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">systemctl daemon-reload</span><br><span class="line">systemctl restart docker</span><br><span class="line"></span><br><span class="line">docker info</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="博客存放路径和-git-空仓库"><a href="#博客存放路径和-git-空仓库" class="headerlink" title="博客存放路径和 git 空仓库"></a>博客存放路径和 git 空仓库</h2><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">mkdir</span> -p ~/site/hexo</span><br><span class="line"><span class="built_in">mkdir</span> -p ~/repo</span><br><span class="line">git init --bare ~/repo/hexo-blog.git</span><br></pre></td></tr></table></figure><p>编辑 git 钩子</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></pre></td><td class="code"><pre><span class="line">vim ~/repo/hexo-blog.git/hooks/post-receive</span><br><span class="line"></span><br><span class="line"><span class="comment">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line">git --work-tree=/home/username/site/hexo --git-dir=/home/username/repo/hexo-blog.git checkout -f</span><br></pre></td></tr></table></figure><h2 id="运行-caddy"><a href="#运行-caddy" class="headerlink" title="运行 caddy"></a>运行 caddy</h2><p>我使用了 docker-compose 来编排 caddy 的运行参数</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></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">services:</span></span><br><span class="line">  <span class="attr">caddy:</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">caddy</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">caddy:latest</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./Caddyfile:/etc/caddy/Caddyfile</span> <span class="comment"># 同目录下写好 Caddyfile 配置文件</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/home/ubuntu/site/hexo/:/srv</span> <span class="comment"># 把静态资源映射到docker容器内的/srv</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">caddy_data:/data</span> <span class="comment"># 存放caddy的ssl证书</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">caddy_config:/config</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;80:80&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;443:443&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;443:443/udp&quot;</span></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line">  <span class="attr">caddy_data:</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">caddy_config:</span></span><br></pre></td></tr></table></figure><p>Caddyfile 是 caddy 的特殊配置文件，写法很简洁</p><p>Caddyfile:</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">blog.homu.space &#123;</span><br><span class="line">  root * /srv</span><br><span class="line">  enable gzip</span><br><span class="line">  file_server</span><br><span class="line">  try_file &#123;path&#125;.html &#123;path&#125;/index.html # 省略后缀名</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>启动 caddy 镜像</p><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">docker volume create caddy_data</span><br><span class="line">docker-compose up -d</span><br></pre></td></tr></table></figure><h2 id="免密推送"><a href="#免密推送" class="headerlink" title="免密推送"></a>免密推送</h2><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">ssh-keygen -t rsa</span><br><span class="line"><span class="built_in">cat</span> ~/.ssh/id_rsa.pub | ssh username@remote_host <span class="string">&quot;mkdir -p ~/.ssh &amp;&amp; cat &gt;&gt; ~/.ssh/authorized_keys&quot;</span></span><br></pre></td></tr></table></figure><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">hexo d</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line">pnpm run deploy <span class="comment"># package.json script</span></span><br></pre></td></tr></table></figure><h1 id="域名备案"><a href="#域名备案" class="headerlink" title="域名备案"></a>域名备案</h1><p>前面说是想换域名到新的服务器</p><p>域名在阿里云买的，服务器在腾讯云…</p><p>在腾讯云选择备案服务。填写一堆表单之后提交就好了</p><p>备案周期很长，还会被驳回。建议拿到域名之后就备案</p><h2 id="备案遇到的各种问题"><a href="#备案遇到的各种问题" class="headerlink" title="备案遇到的各种问题"></a>备案遇到的各种问题</h2><p>由于先前我已经备案过一次了，shirofune.cn 这个域名。</p><p>所以工信部那边有我的备案主体和备案网站。</p><p>我再次进行个人备案，备案新的网站的时候，接到了云服务商的电话，说我上一个网站由于已经关停，变成了空壳网站，我也就变成了空壳主体，需要先注销掉空壳主体后执行初次备案。或者重新把之前的网站开起来</p><p>重新开起来网站这个好麻烦，备案的那个服务器已经被上一家云服务商回收了。所以只能注销</p><p>好在阿里云可以在控制台中注销备案</p><p>坏在我把网站注销备案之后，并没有注销掉主体（这个时候我才知道，不论是个人还是企业，主体和网站是两个东西）</p><p>注销主体就无敌麻烦了！注销主体需要提供纸质材料邮寄到线下，或者去线下办理。</p><p>众所周知工信部办事效率很低… 且不说邮寄东西花的时间，动不动就是 20 天，40 天的办事周期（真是可悲）</p><p>再稍微等等，如果重新备案这条路太难走的话，还是考虑选择香港的服务器，买一台香港的 VPS 去部署服务（一年会贵几百块钱）</p><h1 id="整理博客"><a href="#整理博客" class="headerlink" title="整理博客"></a>整理博客</h1><p>因为博客本身很长时间没有维护，所以花时间整理了一下相关代码</p><p>解决一些命令行报错</p><p>删除了一些用不到的依赖（package.json）</p><p>使用 pnpm</p><p>放弃全局 hexo-cli，使用项目内的命令来操作 hexo</p><p>更新 hexo 依赖版本，更新 butterfly 主题版本</p><p>删除（或隐藏）了一些文章</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;以前这个博客是部署在阿里云的一台服务器上的，域名是 shirofune.cn&lt;/p&gt;
&lt;p&gt;后来这台云服务器被我拿去&lt;a href=&quot;/po</summary>
      
    
    
    
    <category term="Blog" scheme="https://blog.homu.space/categories/Blog/"/>
    
    
    <category term="git" scheme="https://blog.homu.space/tags/git/"/>
    
    <category term="hexo" scheme="https://blog.homu.space/tags/hexo/"/>
    
    <category term="docker" scheme="https://blog.homu.space/tags/docker/"/>
    
    <category term="caddy" scheme="https://blog.homu.space/tags/caddy/"/>
    
  </entry>
  
  <entry>
    <title>在重复的每一天里看星星</title>
    <link href="https://blog.homu.space/posts/3625dd20/"/>
    <id>https://blog.homu.space/posts/3625dd20/</id>
    <published>2024-03-02T19:04:41.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<h1 id="星星点点的记忆"><a href="#星星点点的记忆" class="headerlink" title="星星点点的记忆"></a>星星点点的记忆</h1><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/1686050041079.jpg"></p><h2 id="上海大雪"><a href="#上海大雪" class="headerlink" title="上海大雪"></a>上海大雪</h2><p>虽然不知道地球什么时候毁灭，但是上海确实下大雪了，虽然很短暂</p><video controls width="100%">  <source src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/video_20230115_124246.mp4" type="video/mp4">  Your browser does not support the video tag.</video><p>(视频里大名鼎鼎的七宝万科，好像已经没了)</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/af3320469f48a72a80be9b9a3bb7cb12.jpeg" alt="雪花飘飘和飞机"></p><h2 id="春节回家"><a href="#春节回家" class="headerlink" title="春节回家"></a>春节回家</h2><p>在洛河边拍出了一张朦朦胧胧的照片，非常喜欢</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20240731233101.jpg"></p><p>春节假期，在洛阳去了偃师的虎头山和首阳山，普通的散步道，简单愉快</p><p><a href="/gallery/Album/2023/2023.01%E6%B4%9B%E9%98%B3%E5%81%83%E5%B8%88/">照片在这里</a></p><p>从洛阳回上海坐飞机，到达上海的时候是夜晚</p><p>有幸看到了非常漂亮的俯瞰夜景</p><video controls width="100%">  <source src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/video_20230130_182343.mp4" type="video/mp4">  Your browser does not support the video tag.</video><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20230130_181334.jpg"></p><h2 id="露营的想法"><a href="#露营的想法" class="headerlink" title="露营的想法"></a>露营的想法</h2><p>三月份是最有活力的时候，工作稳定，生活轻松。加上天气慢慢温暖，有了倾向户外的想法</p><p>春天的时候，去公园里和朋友一起坐着晒太阳，打打羽毛球。很舒服呢</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20230225_134635.jpg" alt="一些露营装备"></p><h2 id="团建"><a href="#团建" class="headerlink" title="团建"></a>团建</h2><p>在米哈游的团建，实际上就是出去吃饭。不论是正式员工还是 mi 岗员工，每个月都有 170 的团建报销额度。</p><p>所以团建单纯就是把这 170 给霍霍掉，总不能便宜了公司对吧</p><p>印象里项目组整体出去有两次，一次去吃了高丽菜，产品同事带领的。一次是项目管理带领的去玩（我没参加）</p><p>相比项目组整体，前端自己去的次数比较多（目的地海底捞居多）</p><p>不过捏… 据同事的不完全统计，每次一起下馆子，就会有个人被辞退或者离职</p><p>hhh…</p><h2 id="用心推荐的上海动物园"><a href="#用心推荐的上海动物园" class="headerlink" title="用心推荐的上海动物园"></a>用心推荐的上海动物园</h2><p>四月份，已经暖和起来了，去了上海动物园。</p><p>十分感叹，这才是我梦想中的动物园啊…</p><p><a href="/gallery/Album/2023/2023.04%E4%B8%8A%E6%B5%B7%E5%8A%A8%E7%89%A9%E5%9B%AD/">这里是在动物园拍的照片</a></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240801231933.png" alt="我喜欢小熊猫，最初就是冲着小熊猫去的"></p><h2 id="喜欢的生日礼物"><a href="#喜欢的生日礼物" class="headerlink" title="喜欢的生日礼物"></a>喜欢的生日礼物</h2><p>今年过生日收到了一盏星空投影灯，在夜晚，我的天花板会变成一片星空</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20230525_225140.jpg" alt="我喜欢星星灯！"></p><h2 id="小浪底水库"><a href="#小浪底水库" class="headerlink" title="小浪底水库"></a>小浪底水库</h2><p>六月端午，刚好是小浪底水库调水，和老爸开车就去了，场面相当壮观</p><div id="dplayer1" class="dplayer hexo-tag-dplayer-mark" style="margin-bottom: 20px;"></div><script>(function(){var player = new DPlayer({"container":document.getElementById("dplayer1"),"video":{"url":"https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/video_20230624_100113.mp4"}});window.dplayers||(window.dplayers=[]);window.dplayers.push(player);})()</script><p><a href="/gallery/Album/2023/2023.06%E6%B4%9B%E9%98%B3%E5%B0%8F%E6%B5%AA%E5%BA%95/">更多照片在这里</a></p><h2 id="BW-amp-南京东路的二次元街道"><a href="#BW-amp-南京东路的二次元街道" class="headerlink" title="BW &amp; 南京东路的二次元街道"></a>BW &amp; 南京东路的二次元街道</h2><p>七月份参加了国内规模比较大的漫展，bilibili world</p><p>和高中时代带我一起玩 ACGN 的大姐姐一起去的，是为我打开新世界大门的大姐姐！</p><p>因为有前辈一起逛街聊天，所以非常愉快，这场漫展本身就是一个消费平台，并没有让人感动的氛围</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240802000238.png" alt="3023年了还能看到乌贼娘的痛车"></p><p><a href="/gallery/Album/2023/2023.07BilibiliWorld/">更多照片在这里哦</a></p><p>第二天去逛了淮海中路那边孤独摇滚联动的索尼店，其实是因为耳机坏了，去取修好的耳机（WF1000xm4）</p><p>不得不说索尼的品控真的很一般</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240802000550.png" alt="这套镭射卡，店里不给卖！结果最后我在网上买到了"></p><p>八月份的时候，有个朋友从亚美利加回国，准备在上海找个短期工作，并且体验一下魔都的二次元味道。</p><p>我们一起去了雾雨咖啡厅，一家东方主题的小咖啡店。店里铺满了店长的周边，是梦幻的小屋呢</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240802092636.png"></p><p>之后一起去了百米香榭，看到了很多二次元遗老的周边。果然心动的东西还是要去中古店找啊</p><p><a href="/gallery/Album/2023/2023.08%E4%BA%8C%E6%AC%A1%E5%85%83%E6%8E%A2%E5%BA%97/">这里是逛街的照片</a></p><h2 id="和表弟一起过十一假期"><a href="#和表弟一起过十一假期" class="headerlink" title="和表弟一起过十一假期"></a>和表弟一起过十一假期</h2><p>十月份的时候，毕业许久没找到工作的表弟决定来上海试试</p><p>但是从一开始我就没有抱有很大的希望</p><p>因为我觉得，找不到工作的原因并不是能力不够或者什么，而是不知道找什么，术业有专攻，没有专攻就不会有术和业</p><p>教给弟弟怎么找房子，然后找工作也给了一些建议，能做的事情也尽可能的帮了</p><p>之后，<a href="/gallery/Album/2023/2023.10%E5%8D%81%E4%B8%80/">十一假期就一起去逛景区啦</a></p><h2 id="函授学位证"><a href="#函授学位证" class="headerlink" title="函授学位证"></a>函授学位证</h2><p>十月底参加了学位证考试，请假去河南焦作的河南理工大学考试</p><p>这是我头一次来学校里面，学校还蛮大的，风景很不错</p><p>考试很简单，单考一门英语，难度还可以，程序员英语勉勉强强给糊弄过去了</p><p>十一月份的时候得到考试通过的结果，剩下的只有毕业论文就毕业啦</p><p>考完试，下午去爬云台山，云台山能玩两天的，不过和家人只逛了一下午，晚上就回家了</p><p><a href="/gallery/Album/2023/2023.10%E7%84%A6%E4%BD%9C/">照片在这里</a></p><h2 id="新的电子产品"><a href="#新的电子产品" class="headerlink" title="新的电子产品"></a>新的电子产品</h2><p>十一月份花了好多钱 QAQ</p><p>先后买了 MacBook 和小米手机</p><p>Mac 主要用来做开发用，写笔记什么的很舒服（但是从事后看来，我还是太理想了，装上 windows 虚拟机打 galgame 也挺舒服的（（（<br><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20231113_205048.jpg"></p><p>自己用贴纸装饰了手机，我喜欢小一点的屏幕，小米 14 是今年发布的手机中唯一一个个头比较小的（除了 iphone15）<br><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/Image_1699971720201.jpg" alt="喵 喵 阿梓喵"></p><h1 id="工作心情"><a href="#工作心情" class="headerlink" title="工作心情"></a>工作心情</h1><p>春节后，工作地点就被调换了。本来是在枫林园区的，那边很好，办公室人也不太多，相对自由。</p><p>工作虽然比较多，但是也能每天安排在下班前搞定。</p><p>在枫林的日子整体是比较轻松愉快的，和同事也建立了深厚的友谊</p><p>由于我们是给原神组（甲方）做平台的，甲方希望我们和他们坐在一起，这样对接更方便…</p><p>听起来是很合理，但是实际上却非常不正确。</p><p>实际上，甲方是提出需求的，而且我们项目组这边有专门负责对接需求并且将需求转化为项目排期和需求文档（PRD）的产品经理和项目管理，实际上甲方对接的目标是两位 PM 就对了（产品和项目管理）</p><p>原神组权力还是比较大的，说让我们过去，我们就得过去。</p><p>理想中的大厂程序员，埋头实现需求，和上下游同事做好沟通就可以了。这样才能把程序员的价值发挥到最大化，就我本身而言，我也期望这样。实现自己价值的同时还能学习，还不被公司里的冗杂事情所干涉。</p><p>过去之后就不一样了，甲方会直接找到我们开发，同时也会给产品和项管更大的压力。总而言之，先前建立的工作流被新插入的，不懂开发的其他组给破坏了</p><p>工作排期出现了严重问题，产品也趋向于不合理，开发也经常做起客服的工作</p><p>除此之外，测试那边填上了一个不解决问题不工作，每天玩企业微信，只会问责的领导。</p><p>从测试那边开始，卷起来了。因为不合理的流程和不干活的人，这份工作逐渐也变得困难</p><p>对大家直接的影响就是，纯粹的工作时间变少了，工作量增加且不合理，排期更紧张，同事关系变得糟糕，压力更大</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20230320_194715.jpg" alt="下班路上哗啦啦的大雨"></p><p>这种崩坏是渐进的，从三月份开始，直到我今年（24）年离职。从我的视角来看，从三月份到 24 年三月份，先前努力工作的人要么被辞退，要么开始摆烂等着被辞退，实际不工作，喜欢搞流程，对上管理的那些人留了下来。</p><p>到 23 年年底，实际上已经有产品缩水的现象了，要么产品妥协，要么砍掉需求。因为实际问题是做不完，所有人每天加班也做不完。</p><p>也就是这个原因吧，我对米哈游失望了。</p><p>我是不怎么加班的，我痛恨体制性加班，为了给人做样子而加班。我会想尽办法去在上班的时间内完成我的工作</p><p>我相信我只要把精力投入工作，上班时间不摸鱼，下班时间自然也能走。</p><p>事实上我做到了，即使恪守不加班原则，我也完成了分配给我的需求，并且高质量完成（没出过什么线上问题）</p><p>所以，可能不太愉快，可能非常辛苦，但是我对自己在米哈游时期的表现是满意的</p><p>即使米哈游的工作氛围辜负了我的期望，我也没有认为自己选择来这里是错误的</p><p>我得到了我想要的东西</p><ul><li>大公司的经验</li><li>喜欢的游戏公司的憧憬</li><li>技术进步，代码更规范</li><li>结交到新的朋友</li></ul><h1 id="失去的东西和得到的东西"><a href="#失去的东西和得到的东西" class="headerlink" title="失去的东西和得到的东西"></a>失去的东西和得到的东西</h1><p>一整年都在殷实的工作，解决过各种问题，也见过职场百态</p><p>失去最多的东西是活力，因为上班很累，回到家之后如果不是有游戏搭子粘着我打游戏，甚至连电脑都懒得开….</p><p>去年写日记的习惯，今年也没有了。虽然在工作里积累了很多经验，但是博客更新却变少了</p><p>看的动画片少了。缺少了努力追逐偶像，追逐角色的动力，现在只是去关注，出周边了买，出视频了看，只能得到一时的满足</p><p>虽然有经常出去玩，但是生活的乐趣确实减少了。</p><p><strong>未来会以守护爱好作为前提去工作</strong>！</p><p>与此同时，得到的也很多嘛</p><p>首先就是钱钱，这一年确实赚到了钱钱，而且买了好多周边和手办，成就了学生时代的梦想（我的圆神手办！）</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240323_185019.jpg"><br><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240323_185416.jpg"></p><p>这一年在互联网上和朋友交流更多，试着和大家见面，收到了不少的礼物。非常开心，非常珍贵</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20240413_190517.jpg"></p><p>各种信件，还有来自霓虹的留学生信件，同事去霓虹旅游带给我的伴手礼护身符，作为生日礼物的 cd…</p><p>这些都是猫咪的宝物！</p><p>这一年吃到了很多美食，国内的国外的各种不同风格的菜。味蕾环游世界</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20230826_122300.jpg" alt="土耳其烤肉"></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/IMG_20231230_185647.jpg" alt="德意志香肠"></p><h1 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h1><p>今年难忘的游戏：塞尔达，星之卡比<br>今年的年度歌手依然是鹿乃<br><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/7bc27ccca67cf84e.jpg"></p><p>总结下来，这是平稳和积累的一年</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/799dc46c3b47fa08.jpg" alt="月光！"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;星星点点的记忆&quot;&gt;&lt;a href=&quot;#星星点点的记忆&quot; class=&quot;headerlink&quot; title=&quot;星星点点的记忆&quot;&gt;&lt;/a&gt;星星点点的记忆&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;https://homu-asteroid.oss-cn-hangzhou.al</summary>
      
    
    
    
    <category term="年末记忆" scheme="https://blog.homu.space/categories/%E5%B9%B4%E6%9C%AB%E8%AE%B0%E5%BF%86/"/>
    
    
  </entry>
  
  <entry>
    <title>实现页面水印效果</title>
    <link href="https://blog.homu.space/posts/71a00ca2/"/>
    <id>https://blog.homu.space/posts/71a00ca2/</id>
    <published>2024-02-03T22:24:12.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<p>工作项目里实现过的需求，学习了相关代码总结出这篇文章</p><h1 id="实现水印的思路"><a href="#实现水印的思路" class="headerlink" title="实现水印的思路"></a>实现水印的思路</h1><p>从页面层次上来看，水印可以理解为覆盖在最高层的一层透明背景，虽然覆盖在最高层，但是不应该影响底层任何事件的触发</p><p>可以通过 canvas 绘制字符串的图片，然后转化为图片作为背景循环平铺到一个 div 里</p><p>这个 div 就是水印图</p><h1 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h1><h2 id="绘制水印图片"><a href="#绘制水印图片" class="headerlink" title="绘制水印图片"></a>绘制水印图片</h2><figure class="highlight typescript"><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><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">setWatermark</span>(<span class="params"><span class="attr">str</span>: <span class="built_in">string</span></span>): <span class="title class_">HTMLDivElement</span> | <span class="literal">null</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> id = <span class="string">&quot;1.1351.1561.215515&quot;</span>;</span><br><span class="line">  <span class="keyword">const</span> lastEle = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(id);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (lastEle !== <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">removeChild</span>(lastEle);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> canvas = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;canvas&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> body = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&quot;body&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> clientWidth = body?.<span class="property">clientWidth</span> ?? <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">const</span> clientHeight = body?.<span class="property">clientHeight</span> ?? <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> ctx = canvas.<span class="title function_">getContext</span>(<span class="string">&quot;2d&quot;</span>);</span><br><span class="line">  <span class="keyword">if</span> (!ctx) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 获取被测量字符串的宽度</span></span><br><span class="line">  <span class="keyword">const</span> strWidth = ctx.<span class="title function_">measureText</span>(str).<span class="property">width</span>;</span><br><span class="line">  <span class="comment">// 通过文字倾斜程度，算出canvas应有的宽高</span></span><br><span class="line">  <span class="keyword">const</span> canvasW = strWidth * <span class="title class_">Math</span>.<span class="title function_">cos</span>((<span class="number">30</span> / <span class="number">180</span>) * <span class="title class_">Math</span>.<span class="property">PI</span>) + <span class="number">100</span>;</span><br><span class="line">  <span class="keyword">const</span> canvasH = strWidth * <span class="title class_">Math</span>.<span class="title function_">sin</span>((<span class="number">30</span> / <span class="number">180</span>) * <span class="title class_">Math</span>.<span class="property">PI</span>) + <span class="number">100</span>;</span><br><span class="line"></span><br><span class="line">  canvas.<span class="property">style</span>.<span class="property">width</span> = <span class="string">`<span class="subst">$&#123;canvasW&#125;</span>px`</span>;</span><br><span class="line">  canvas.<span class="property">style</span>.<span class="property">height</span> = <span class="string">`<span class="subst">$&#123;canvasH&#125;</span>px`</span>;</span><br><span class="line"></span><br><span class="line">  canvas.<span class="property">width</span> = canvasW;</span><br><span class="line">  canvas.<span class="property">height</span> = canvasH;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 绘制文字</span></span><br><span class="line">  ctx.<span class="title function_">rotate</span>((-<span class="number">30</span> * <span class="title class_">Math</span>.<span class="property">PI</span>) / <span class="number">180</span>);</span><br><span class="line">  ctx.<span class="property">font</span> = <span class="string">&quot;14px PingFang SC&quot;</span>;</span><br><span class="line">  ctx.<span class="property">fillStyle</span> = <span class="string">&quot;rgba(0, 0, 0, 0.05)&quot;</span>;</span><br><span class="line">  ctx.<span class="property">textAlign</span> = <span class="string">&quot;left&quot;</span>;</span><br><span class="line">  ctx.<span class="property">textBaseline</span> = <span class="string">&quot;bottom&quot;</span>;</span><br><span class="line">  ctx.<span class="title function_">fillText</span>(str, <span class="number">0</span>, canvas.<span class="property">height</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> ele = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;div&quot;</span>);</span><br><span class="line">  ele.<span class="property">id</span> = id;</span><br><span class="line">  ele.<span class="property">style</span>.<span class="property">pointerEvents</span> = <span class="string">&quot;none&quot;</span>;</span><br><span class="line">  ele.<span class="property">style</span>.<span class="property">top</span> = <span class="string">&quot;0px&quot;</span>;</span><br><span class="line">  ele.<span class="property">style</span>.<span class="property">position</span> = <span class="string">&quot;fixed&quot;</span>;</span><br><span class="line">  ele.<span class="property">style</span>.<span class="property">zIndex</span> = <span class="string">&quot;100000&quot;</span>;</span><br><span class="line">  <span class="comment">// 覆盖body</span></span><br><span class="line">  ele.<span class="property">style</span>.<span class="property">width</span> = <span class="string">`<span class="subst">$&#123;clientWidth&#125;</span>px`</span>;</span><br><span class="line">  ele.<span class="property">style</span>.<span class="property">height</span> = <span class="string">`<span class="subst">$&#123;clientHeight&#125;</span>px`</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// canvas 转 data:url</span></span><br><span class="line">  canvas.<span class="title function_">toBlob</span>(<span class="function">(<span class="params">blob</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> newImg = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;img&quot;</span>);</span><br><span class="line">    <span class="keyword">const</span> url = <span class="variable constant_">URL</span>.<span class="title function_">createObjectURL</span>(blob || <span class="keyword">new</span> <span class="title class_">Blob</span>());</span><br><span class="line">    newImg.<span class="property">onload</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable constant_">URL</span>.<span class="title function_">revokeObjectURL</span>(url);</span><br><span class="line">    &#125;;</span><br><span class="line">    ele.<span class="property">style</span>.<span class="property">background</span> = <span class="string">`url(<span class="subst">$&#123;url&#125;</span>) left top repeat`</span>;</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(ele);</span><br><span class="line">  <span class="keyword">return</span> ele;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight typescript"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Watermark</span> &#123;</span><br><span class="line">  <span class="comment">// 设置水印</span></span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">set</span>(<span class="params"><span class="attr">str</span>: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> ele = <span class="title function_">setWatermark</span>(str);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (</span><br><span class="line">        !ele ||</span><br><span class="line">        <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(ele.<span class="property">id</span>) === <span class="literal">null</span> ||</span><br><span class="line">        !ele.<span class="property">style</span>.<span class="property">background</span></span><br><span class="line">      ) &#123;</span><br><span class="line">        ele = <span class="title function_">setWatermark</span>(str);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;, <span class="number">500</span>);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">onresize</span> = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">setWatermark</span>(str);</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">outWatermark</span>(<span class="params"><span class="attr">id</span>: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> div = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(id);</span><br><span class="line">    <span class="keyword">if</span> (div !== <span class="literal">null</span> &amp;&amp; div) div.<span class="property">style</span>.<span class="property">display</span> = <span class="string">&quot;none&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 隐藏水印</span></span><br><span class="line">  <span class="keyword">static</span> <span class="title function_">out</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> id = <span class="string">&quot;1.1351.1561.215515&quot;</span>;</span><br><span class="line">    <span class="title class_">Watermark</span>.<span class="title function_">outWatermark</span>(id);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="设置水印"><a href="#设置水印" class="headerlink" title="设置水印"></a>设置水印</h1><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Watermark</span>.<span class="title function_">set</span>(<span class="string">`吼姆小行星`</span>);</span><br></pre></td></tr></table></figure><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722697734634.png"></p><p>隐藏水印：</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Watermark</span>.<span class="title function_">out</span>();</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;工作项目里实现过的需求，学习了相关代码总结出这篇文章&lt;/p&gt;
&lt;h1 id=&quot;实现水印的思路&quot;&gt;&lt;a href=&quot;#实现水印的思路&quot; class=&quot;headerlink&quot; title=&quot;实现水印的思路&quot;&gt;&lt;/a&gt;实现水印的思路&lt;/h1&gt;&lt;p&gt;从页面层次上来看，水印可以理解为</summary>
      
    
    
    
    <category term="小笔记" scheme="https://blog.homu.space/categories/%E5%B0%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="JavaScript" scheme="https://blog.homu.space/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Git rebase和cherry-pick</title>
    <link href="https://blog.homu.space/posts/71e03861/"/>
    <id>https://blog.homu.space/posts/71e03861/</id>
    <published>2023-11-03T00:00:00.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<h1 id="认识-rebase"><a href="#认识-rebase" class="headerlink" title="认识 rebase"></a>认识 rebase</h1><p>rebase，正如字面意思，重新选择基点</p><p>比如下面这个图，这张图片里有两个分支 <code>feature</code> 和 <code>master</code></p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/20240803123427.png"></p><p>起初，两个分支均为 <code>commit A</code> 为基点，<code>feature</code> 分支延伸出了 <code>D</code> 和 <code>E</code> 两个 commit，<code>master</code>分支延伸出了<code>B</code>和<code>C</code>两个 commit</p><p>如果执行了在<code>master</code>分支执行</p><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="comment"># at branch feature</span></span><br><span class="line">git rebase master</span><br></pre></td></tr></table></figure><p>则<code>feature</code>分支的基点会从<code>A</code>变成 <code>master</code>的最新 commit <code>C</code>。这就是 rebase</p><p>rebase 之后，不论是 <code>feature</code> 还是 <code>master</code> ，他们都在同一条提交线上</p><h2 id="对比-rebase-和-merge"><a href="#对比-rebase-和-merge" class="headerlink" title="对比 rebase 和 merge"></a>对比 rebase 和 merge</h2><p>还用上面的例子</p><p>有这样的一个提交例子</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722661693998.png"></p><p>现在试一下不同的操作对提交树产生的影响</p><p><strong>如果使用 rebase</strong></p><p>为保证相同的顺序 <code>(ABCDE)</code>，需要切换到 <code>feature</code> 分支，<code>rebase master</code> 分支</p><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="comment"># at branch feature</span></span><br><span class="line">git rebase master</span><br></pre></td></tr></table></figure><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722662018352.png"></p><p><strong>如果使用 merge</strong></p><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="comment"># at branch master</span></span><br><span class="line">git merge feature</span><br></pre></td></tr></table></figure><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722662097912.png"></p><p>使用 merge 依然是两条线，使用 rebase 则是一条线</p><p>使用 merge 还多出了一个 commit</p><h1 id="使用-rebase-整理提交历史"><a href="#使用-rebase-整理提交历史" class="headerlink" title="使用 rebase 整理提交历史"></a>使用 rebase 整理提交历史</h1><p>除了用来合并代码，rebase 还可以用来整理提交历史</p><p>触发交互式 rebase：<code>--interactive</code> <code>-i</code></p><p>继续上面的例子，选定 commit <code>A</code> 为基点，执行命令</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">git rebase -i `commit A`</span><br></pre></td></tr></table></figure><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722664224265.png"></p><p>执行命令后会进入一个编辑界面</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722664299381.png"></p><p>pick（或 p）：保留该提交。<br>reword（或 r）：保留提交但修改提交消息。<br>edit（或 e）：暂停 rebase 并允许你修改该提交，比如修改内容、添加文件等。<br>squash（或 s）：将该提交与前一个提交合并。你可以修改合并后的提交信息。<br>fixup（或 f）：与 squash 类似，但丢弃当前提交的提交信息，直接合并到前一个提交。<br>exec（或 x）：在当前提交点执行一个 shell 命令。<br>drop（或 d）：删除该提交。</p><p>假设希望删除 commit <code>E</code> 这次提交，把 <code>C</code> 和 <code>D</code> 压缩成一个 commit，并且修改 commit 消息为 <code>CD</code>，可以这样做</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722664784869.png"></p><p>保存之后，会提示修改 commit 消息</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722664834165.png"></p><p>之后，提交历史会变成这个样子</p><p><img src="https://homu-asteroid.oss-cn-hangzhou.aliyuncs.com/imgs/QQ_1722664903294.png"></p><h1 id="cherry-pick"><a href="#cherry-pick" class="headerlink" title="cherry-pick"></a>cherry-pick</h1><p>cherry-pick，摘樱桃。很形象地表示这个命令是要从提交树上删除掉一些 commit，和 <code>git rebase -i</code> 中的 <code>drop</code> 操作类似</p><p>这个命令经常配合 rebase 使用，比如某次提交的代码有问题，可以先备份一下，然后从主分支 cherry-pick 掉</p><p>cherry-pick 多个 commit 的时候，需要按照与原始顺序相反的顺序来删除</p><p>如果 commit1 依赖于 commit2，那么应该是</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">git cherry-pick &lt;commit1&gt; &lt;commit2&gt;</span><br></pre></td></tr></table></figure><p>这样有利于减少冲突，避免依赖性问题</p><h1 id="冲突解决"><a href="#冲突解决" class="headerlink" title="冲突解决"></a>冲突解决</h1><p>rebase 和 cherry-pick 也会产生冲突，并且解决步骤和 merge 不同</p><p>merge 是将一条分支上的所有代码汇入另一条分支，其操作的维度是分支，所以只会有一次冲突</p><p>而 rebase 不同，rebase 的操作维度是 commit，以上面 ABCDE 的 rebase 过程为例，<code>D</code>和<code>E</code> 在 rebase 到 <code>C</code> 上面的时候，如果 <code>D</code> 和 <code>E</code> 对比 <code>BC</code> 均有冲突，那么应该解决 <code>D</code> 和 <code>E</code> 造成的两次冲突，rebase 的分支有几个 commit 就可能最多产生多少次的冲突</p><p>如果一条分支不需要 commit 的每个细节，则可以利用 <code>rebase -i</code> 压缩(<code>squash</code>) 掉 commit</p><p>解决冲突的方式也和 merge 的重新提交不同，应该先把冲突的解决结果使用 <code>add</code> 命令提交到暂存区，然后使用 continue 解决下一个 rebase 的冲突。如此循环，直到没有冲突为准</p><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">git add .</span><br><span class="line">git rebase --<span class="built_in">continue</span></span><br></pre></td></tr></table></figure><h1 id="rebase-的优点和缺点"><a href="#rebase-的优点和缺点" class="headerlink" title="rebase 的优点和缺点"></a>rebase 的优点和缺点</h1><p><strong>rebase 的优点</strong></p><ul><li>rebase 保持了提交历史的线性和简洁，方便管理</li><li>有效减少了合并提交，让每个 commit 都有意义</li></ul><p><strong>rebase 的缺点</strong></p><ul><li>操作不当会丢代码，如果没有分支备份，在解决冲突的时候，以及使用 drop 和 cherry-pick 的时候，会造成代码的丢失</li><li>解决冲突的过程比较复杂</li><li>依赖历史 commit 的线性结构</li><li>开发过程中断的情况，如果某个分支被搁置了很久，这个分支再去 rebase 最新代码的时候，可能会产生巨量的冲突需要解决</li></ul><hr><p><strong>分支管理和使用经历</strong></p><p>在米哈游的平台组工作时，前端团队用的就是 rebase 的管理方式。</p><p>维护协作分支 develop(ci 对应 test 环境)，pre 和 master 分支(ci 对应 pre 环境)，tag 为线上环境发布。</p><p>在进行需求开发的时候，每个人会从 develop 分支拉出自己的开发分支，在提测的时候，会将自己的开发分支 rebase 一下 develop，通过 git lab 提交 merge request 进入 develop 测试环境。</p><p>团队大约十几个人，同时开发多个模块，使用 merge 的话会让提交树变得非常复杂，难以从中间摘掉某个提交，或者快速定位和解决问题。rebase 是非常好的管理方式。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;认识-rebase&quot;&gt;&lt;a href=&quot;#认识-rebase&quot; class=&quot;headerlink&quot; title=&quot;认识 rebase&quot;&gt;&lt;/a&gt;认识 rebase&lt;/h1&gt;&lt;p&gt;rebase，正如字面意思，重新选择基点&lt;/p&gt;
&lt;p&gt;比如下面这个图，这张图片里有</summary>
      
    
    
    
    <category term="小笔记" scheme="https://blog.homu.space/categories/%E5%B0%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="git" scheme="https://blog.homu.space/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>实现简单的VDOM</title>
    <link href="https://blog.homu.space/posts/d267999e/"/>
    <id>https://blog.homu.space/posts/d267999e/</id>
    <published>2023-10-04T18:30:45.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<h1 id="VDom-的含义和实现"><a href="#VDom-的含义和实现" class="headerlink" title="VDom 的含义和实现"></a>VDom 的含义和实现</h1><h2 id="原文地址"><a href="#原文地址" class="headerlink" title="原文地址"></a>原文地址</h2><p>原文是一篇讲得很明白的文章，本文基本上是照着写了一遍<br>不但学到了 VDOM 相关的一些东西，原生 JS 的方法也熟悉了一遍，收获颇丰</p><blockquote><p><a href="https://xin-tan.com/2019-11-11-wirte-virtual-dom/">一文说清「VirtualDOM」的含义与实现</a></p></blockquote><h2 id="如何理解-VDom"><a href="#如何理解-VDom" class="headerlink" title="如何理解 VDom"></a>如何理解 VDom</h2><p>前端常做的事情就是根据数据状态的更新，来更新页面视图。然而频繁的更新 DOM 会造成回流或者重绘，引发性能下降，页面卡顿<br>因此我们需要方法<strong>避免频繁更新 DOM 树</strong><br>思路就是对比 DOM 差距，只更新需要更新的节点，而不是整棵树<br>实现这个算法的基础，需要遍历 DOM 树的结点，来进行比较更新<br>为了更快地处理，不使用 DOM 对象，而改用 JS 对象<br>他就像是 JS 与 DOM 之间的一层缓存</p><h2 id="如何表示-VDom"><a href="#如何表示-VDom" class="headerlink" title="如何表示 VDom"></a>如何表示 VDom</h2><p>借助 ES6 的 class，表示 VDom 的语义化更强。一个基础的 VDom 需要有标签名，标签属性以及子节点</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Element</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">tagName, props, children</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">tagName</span> = tagName;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">props</span> = props;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">children</span> = children;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为了更方便调用（不用每次都 new），将其封装返回实例的函数</p><figure class="highlight js"><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">function</span> <span class="title function_">el</span>(<span class="params">tagName, props, children</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Element</span>(tagName, props, children);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>用上面的方法表达 DOM 结构：</p><figure class="highlight html"><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="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;test&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">span</span>&gt;</span>span1<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><p>用 VDom 表示 ↓</p><figure class="highlight js"><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="keyword">const</span> span = <span class="title function_">el</span>(<span class="string">&quot;span&quot;</span>, &#123;&#125;, [<span class="string">&quot;span1&quot;</span>]);</span><br><span class="line"><span class="keyword">const</span> div = <span class="title function_">el</span>(<span class="string">&quot;div&quot;</span>, &#123; <span class="attr">class</span>: <span class="string">&quot;test&quot;</span> &#125;, [span]);</span><br></pre></td></tr></table></figure><p>之后再对比和更新两棵 vdom 树的时候，将会涉及到将 VDom 渲染成真正的 Dom 节点。因此给<code>class Element</code> 增加<code>render</code>方法</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Element</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">tagName, props, children</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">tagName</span> = tagName;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">props</span> = props;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">children</span> = children;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> dom = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="variable language_">this</span>.<span class="property">tagName</span>);</span><br><span class="line">    <span class="comment">// 设置标签属性值</span></span><br><span class="line">    <span class="title class_">Reflect</span>.<span class="title function_">ownKeys</span>(<span class="variable language_">this</span>.<span class="property">props</span>).<span class="title function_">forEach</span>(<span class="function">(<span class="params">name</span>) =&gt;</span></span><br><span class="line">      dom.<span class="title function_">setAttribute</span>(name, <span class="variable language_">this</span>.<span class="property">props</span>[name])</span><br><span class="line">    );</span><br><span class="line">    <span class="comment">// 递归更新子节点</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">children</span>.<span class="title function_">forEach</span>(<span class="function">(<span class="params">child</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> childDom =</span><br><span class="line">        child <span class="keyword">instanceof</span> <span class="title class_">Element</span></span><br><span class="line">          ? child.<span class="title function_">render</span>()</span><br><span class="line">          : <span class="variable language_">document</span>.<span class="title function_">createTextNode</span>(child);</span><br><span class="line">      dom.<span class="title function_">appendChild</span>(childDom);</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> dom;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="如何比较-dom-树，并且进行高效更新"><a href="#如何比较-dom-树，并且进行高效更新" class="headerlink" title="如何比较 dom 树，并且进行高效更新"></a>如何比较 dom 树，并且进行高效更新</h2><p>前面已经说明了 VDom 的用法与含义， 多个 VDom 就会组成一棵虚拟的 VDom 树<br>剩下要做的就是：<strong>根据不同情况，来进行树上结点的增删改操作</strong><br>这个过程分为<code>diff</code>和<code>path</code></p><ul><li>diff: 递归对比两棵 dom 树对应位置的差异</li><li>patch: 根据差异，进行节点的更新</li></ul><p>现在有两种思路，一种是先 diff 一遍，记录所有差异，再统一进行 patch<br>另一种是 diff 同时进行 patch<br>相比较，第二种方法少了一次递归查询，以及不需要构造过多对象，下面采用第二种思路</p><h3 id="变量的含义"><a href="#变量的含义" class="headerlink" title="变量的含义"></a>变量的含义</h3><p>将 diff 和 patch 的过程放入<code>updateEl</code>方法中</p><figure class="highlight js"><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="comment">/**</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">HTMLElement</span>&#125; <span class="variable">$parent</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">Element</span>&#125; <span class="variable">newNode</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">Element</span>&#125; <span class="variable">oldNode</span></span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &#123;<span class="type">Number</span>&#125; <span class="variable">index</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">updateEl</span>(<span class="params">$parent, newNode, oldNode, index = <span class="number">0</span></span>) &#123;&#125;</span><br></pre></td></tr></table></figure><p>所有以$开头的变量，代表真实DOM<br>参数index表示oldNode再$parent 的所有子节点构成的数组的下标</p><h3 id="1-新增节点"><a href="#1-新增节点" class="headerlink" title="1.新增节点"></a>1.新增节点</h3><p>如果 oldNode 为 undefined，说明 newNode 是一个新增的 DOM 节点。将其直接追加到 DOM 中即可</p><figure class="highlight js"><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">function</span> <span class="title function_">updateEl</span>(<span class="params">$parent, newNode, oldNode, index = <span class="number">0</span></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!oldNode) &#123;</span><br><span class="line">    $parent.<span class="title function_">appendChild</span>(newNode.<span class="title function_">render</span>());</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-删除节点"><a href="#2-删除节点" class="headerlink" title="2.删除节点"></a>2.删除节点</h3><p>如果 newNode 为 undefined，说明新的 VDom 中，当前位置没有节点，因此需要将其从实际的 DOM 中删除<br>删除就调用$parent.removeChild(), 通过 index 参数，可以拿到被删除元素的引用</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">updateEl</span>(<span class="params">$parent, newNode, oldNode, index = <span class="number">0</span></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!oldNode) &#123;</span><br><span class="line">    $parent.<span class="title function_">appendChild</span>(newNode.<span class="title function_">render</span>());</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!newNode) &#123;</span><br><span class="line">    $parent.<span class="title function_">removeChild</span>($parent.<span class="property">childNodes</span>[index]);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-变化节点"><a href="#3-变化节点" class="headerlink" title="3.变化节点"></a>3.变化节点</h3><p>对比 oldNode 和 newNode，有三种情况，均可视为改变</p><ul><li>节点类型发生变化，文本变成 vdom，vdom 变成文本</li><li>新旧节点都是文本，内容改变</li><li>节点属性发生变化</li></ul><p>首先，借助 Symbol 更好地语义化声明三种变化</p><figure class="highlight js"><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">const</span> <span class="variable constant_">CHANGE_TYPE_TEXT</span> = <span class="title class_">Symbol</span>(<span class="string">&quot;text&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CHANGE_TYPE_PROP</span> = <span class="title class_">Symbol</span>(<span class="string">&quot;props&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CHANGE_TYPE_REPLACE</span> = <span class="title class_">Symbol</span>(<span class="string">&quot;replace&quot;</span>);</span><br></pre></td></tr></table></figure><p>针对节点属性发生改变，没有现成 API 供我们批量更新，所以封装<code>replaceAttribute</code>方法，将新的 vdom 属性值直接映射到 dom 结构上</p><figure class="highlight js"><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">function</span> <span class="title function_">replaceAttribute</span>(<span class="params">$node, removedAttrs, newAttrs</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!$node) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title class_">Reflect</span>.<span class="title function_">ownKeys</span>(removedAttrs).<span class="title function_">forEach</span>(<span class="function">(<span class="params">attr</span>) =&gt;</span> $node.<span class="title function_">removeAttribute</span>(attr));</span><br><span class="line">  <span class="title class_">Reflect</span>.<span class="title function_">ownKeys</span>(newAttrs).<span class="title function_">forEach</span>(<span class="function">(<span class="params">attr</span>) =&gt;</span></span><br><span class="line">    $node.<span class="title function_">setAttribute</span>(attr, newAttrs[attr])</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编写 checkChangeType 函数判断变化的类型，如果没有变化，则返回空</p><figure class="highlight js"><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="keyword">function</span> <span class="title function_">checkChangeType</span>(<span class="params">newNode, oldNode</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (</span><br><span class="line">    <span class="keyword">typeof</span> newNode !== <span class="keyword">typeof</span> oldNode ||</span><br><span class="line">    newNode.<span class="property">tagName</span> !== oldNode.<span class="property">tagName</span></span><br><span class="line">  ) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable constant_">CHANGE_TYPE_REPLACE</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> newNode === <span class="string">&quot;string&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (newNode !== oldNode) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="variable constant_">CHANGE_TYPE_TEXT</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> propsChanged = <span class="title class_">Reflect</span>.<span class="title function_">ownKeys</span>(newNode.<span class="property">props</span>).<span class="title function_">reduce</span>(</span><br><span class="line">    <span class="function">(<span class="params">prev, name</span>) =&gt;</span> prev || oldNode.<span class="property">props</span>[name] !== newNode.<span class="property">props</span>[name],</span><br><span class="line">    <span class="literal">false</span></span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">if</span> (propsChanged) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable constant_">CHANGE_TYPE_PROP</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 updateEl 中，根据 checkChangeType 返回的变化类型，做出对应处理<br>如果类型为空，则不进行处理</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">updateEl</span>(<span class="params">$parent, newNode, oldNode, index = <span class="number">0</span></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> changeType = <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">if</span> (!oldNode) &#123;</span><br><span class="line">    <span class="comment">// 新增节点</span></span><br><span class="line">    $parent.<span class="title function_">appendChild</span>(newNode.<span class="title function_">render</span>());</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!newNode) &#123;</span><br><span class="line">    <span class="comment">// 删除节点</span></span><br><span class="line">    $parent.<span class="title function_">removeChild</span>($parent.<span class="property">childNodes</span>[index]);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> ((changeType = <span class="title function_">checkChangeType</span>(newNode, oldNode))) &#123;</span><br><span class="line">    <span class="comment">// 检查节点的变更内容</span></span><br><span class="line">    <span class="keyword">if</span> (changeType === <span class="variable constant_">CHANGE_TYPE_TEXT</span>) &#123;</span><br><span class="line">      <span class="comment">// 文字内容变化</span></span><br><span class="line">      $parent.<span class="title function_">replaceChild</span>(</span><br><span class="line">        <span class="variable language_">document</span>.<span class="title function_">createTextNode</span>(newNode),</span><br><span class="line">        $parent.<span class="property">childNodes</span>[index]</span><br><span class="line">      );</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (changeType === <span class="variable constant_">CHANGE_TYPE_REPLACE</span>) &#123;</span><br><span class="line">      <span class="comment">// 元素变更</span></span><br><span class="line">      $parent.<span class="title function_">replaceChild</span>(newNode.<span class="title function_">render</span>(), $parent.<span class="property">childNodes</span>[index]);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (changeType === <span class="variable constant_">CHANGE_TYPE_PROP</span>) &#123;</span><br><span class="line">      <span class="comment">// 元素上的属性变更</span></span><br><span class="line">      <span class="title function_">replaceAttribute</span>($parent.<span class="property">childNodes</span>[index], oldNode.<span class="property">props</span>, newNode.<span class="property">props</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-递归对子节点进行-diff"><a href="#4-递归对子节点进行-diff" class="headerlink" title="4.递归对子节点进行 diff"></a>4.递归对子节点进行 diff</h3><p>如果情况 1，2，3 都没有命重，那说明当前的新旧节点自身并没有变化<br>需要遍历他们的 children 数组，递归进行处理</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">updateEL</span>(<span class="params">$parent, newNode, oldNode, index = <span class="number">0</span></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> changeType = <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">if</span> (!oldNode) &#123;</span><br><span class="line">    $parent.<span class="title function_">appendChild</span>(newNode.<span class="title function_">render</span>());</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!newNode) &#123;</span><br><span class="line">    $parent.<span class="title function_">removeChild</span>($parent.<span class="property">childNodes</span>[index]);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> ((changeType = <span class="title function_">checkChangeType</span>(newNode, oldNode))) &#123;</span><br><span class="line">    <span class="keyword">if</span> (changeType === <span class="variable constant_">CHANGE_TYPE_TEXT</span>) &#123;</span><br><span class="line">      $parent.<span class="title function_">replaceChild</span>(</span><br><span class="line">        <span class="variable language_">document</span>.<span class="title function_">createTextNode</span>(newNode),</span><br><span class="line">        $parent.<span class="property">childNodes</span>[index]</span><br><span class="line">      );</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (changeType === <span class="variable constant_">CHANGE_TYPE_REPLACE</span>) &#123;</span><br><span class="line">      $parent.<span class="title function_">replaceChild</span>(newNode.<span class="title function_">render</span>(), $parent.<span class="property">childNodes</span>[index]);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (changeType === <span class="variable constant_">CHANGE_TYPE_PROP</span>) &#123;</span><br><span class="line">      <span class="title function_">replaceAttribute</span>($parent.<span class="property">childNodes</span>[index], oldNode.<span class="property">props</span>, newNode.<span class="property">props</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (newNode.<span class="property">tagName</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> newLength = newNode.<span class="property">children</span>.<span class="property">length</span>;</span><br><span class="line">    <span class="keyword">const</span> oldLength = oldNode.<span class="property">children</span>.<span class="property">length</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; newLength || i &lt; oldLength; ++i) &#123;</span><br><span class="line">      <span class="title function_">updateEl</span>(</span><br><span class="line">        $parent.<span class="property">childNodes</span>[index],</span><br><span class="line">        newNode.<span class="property">children</span>[i],</span><br><span class="line">        oldNode.<span class="property">children</span>[i]</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;VDom-的含义和实现&quot;&gt;&lt;a href=&quot;#VDom-的含义和实现&quot; class=&quot;headerlink&quot; title=&quot;VDom 的含义和实现&quot;&gt;&lt;/a&gt;VDom 的含义和实现&lt;/h1&gt;&lt;h2 id=&quot;原文地址&quot;&gt;&lt;a href=&quot;#原文地址&quot; class=&quot;</summary>
      
    
    
    
    <category term="小笔记" scheme="https://blog.homu.space/categories/%E5%B0%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="JavaScript" scheme="https://blog.homu.space/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>MySQL学习：增删改查</title>
    <link href="https://blog.homu.space/posts/5b548bd2/"/>
    <id>https://blog.homu.space/posts/5b548bd2/</id>
    <published>2023-09-29T22:12:26.000Z</published>
    <updated>2026-05-10T10:49:07.012Z</updated>
    
    <content type="html"><![CDATA[<h2 id="插入记录的基本语法"><a href="#插入记录的基本语法" class="headerlink" title="插入记录的基本语法"></a>插入记录的基本语法</h2><h3 id="第一种语法"><a href="#第一种语法" class="headerlink" title="第一种语法"></a>第一种语法</h3><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">INSERT</span> <span class="keyword">INTO</span> 表 <span class="keyword">VALUES</span>(值<span class="number">1</span>, 值<span class="number">2</span>, 值<span class="number">3</span>);</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> <span class="keyword">USER</span> <span class="keyword">VALUES</span>(<span class="number">2</span>, <span class="string">&#x27;homu&#x27;</span>, <span class="string">&#x27;homuhomu&#x27;</span>);</span><br></pre></td></tr></table></figure><p>用这种方法插入数据，表中有多少个字段就必须输入多少个值<br>不能多也不能少…<br>若有默认值，不想上传也可以输入 null</p><h3 id="第二种语法"><a href="#第二种语法" class="headerlink" title="第二种语法"></a>第二种语法</h3><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">INSERT</span> <span class="keyword">INTO</span> 表(字段<span class="number">1</span>, 字段<span class="number">2</span>, 字段<span class="number">3</span>, ...字段n) <span class="keyword">VALUES</span>(值<span class="number">1</span>, 值<span class="number">2</span>, 值<span class="number">3</span>, ...值n);</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> users(id, username, password, age) <span class="keyword">VALUES</span>(<span class="number">3</span>, <span class="string">&#x27;akemi&#x27;</span>, <span class="string">&#x27;homuhomu&#x27;</span>, <span class="number">12</span>);</span><br></pre></td></tr></table></figure><p>除非有必填字段需要写入值之外，如果有默认值的不想写可以不写，mysql 会自动补全默认值<br>相比第一种语法，这种更为常用</p><h3 id="插入多条记录"><a href="#插入多条记录" class="headerlink" title="插入多条记录"></a>插入多条记录</h3><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">INSERT</span> <span class="keyword">INTO</span> <span class="keyword">user</span>(username, password) <span class="keyword">values</span>(<span class="string">&#x27;homu&#x27;</span>, <span class="string">&#x27;homuhomu&#x27;</span>), (<span class="string">&#x27;akemi&#x27;</span>, <span class="string">&#x27;homuhomu&#x27;</span>);</span><br></pre></td></tr></table></figure><h2 id="查询记录"><a href="#查询记录" class="headerlink" title="查询记录"></a>查询记录</h2><h3 id="基础查询"><a href="#基础查询" class="headerlink" title="基础查询"></a>基础查询</h3><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 class="operator">*</span> <span class="keyword">from</span> 表;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> users;</span><br></pre></td></tr></table></figure><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><br><span class="line">| id | username | password | age  | mobile      |</span><br><span class="line">+----+----------+----------+------+-------------+</span><br><span class="line">|  1 | akemi    | homuhomu | 11   | 12312312312 |</span><br><span class="line">|  2 | homu     | homuhomu | 12   | 12312312312 |</span><br><span class="line">|  3 | amu      | password | NULL | NULL        |</span><br><span class="line">+----+----------+----------+------+-------------+</span><br><span class="line">3 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure><h3 id="指定字段查询"><a href="#指定字段查询" class="headerlink" title="指定字段查询"></a>指定字段查询</h3><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">select</span> 字段<span class="number">1</span>, 字段<span class="number">2</span>， <span class="keyword">from</span> 表;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">select</span> username <span class="keyword">from</span> users;</span><br><span class="line"><span class="keyword">select</span> username, password <span class="keyword">from</span> users;</span><br></pre></td></tr></table></figure><h3 id="查询单个字段的不重复记录"><a href="#查询单个字段的不重复记录" class="headerlink" title="查询单个字段的不重复记录"></a>查询单个字段的不重复记录</h3><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 class="keyword">distinct</span> 字段 <span class="keyword">from</span> 表;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">select</span> <span class="keyword">distinct</span> username <span class="keyword">from</span> users;</span><br></pre></td></tr></table></figure><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 class="meta prompt_">mysql&gt; </span><span class="language-bash"><span class="keyword">select</span> * from <span class="built_in">users</span>;</span></span><br><span class="line">+----+----------+----------+------+-------------+</span><br><span class="line">| id | username | password | age  | mobile      |</span><br><span class="line">+----+----------+----------+------+-------------+</span><br><span class="line">|  1 | akemi    | homuhomu | 11   | 17612312312 |</span><br><span class="line">|  2 | homu     | homuhomu | 12   | 17612312312 |</span><br><span class="line">|  3 | amu      | password | NULL | NULL        |</span><br><span class="line">|  4 | akemi    | NULL     | NULL | NULL        |</span><br><span class="line">+----+----------+----------+------+-------------+</span><br><span class="line"><span class="meta prompt_">mysql&gt; </span><span class="language-bash"><span class="keyword">select</span> distinct username from <span class="built_in">users</span>;</span></span><br><span class="line">+----------+</span><br><span class="line">| username |</span><br><span class="line">+----------+</span><br><span class="line">| akemi    |</span><br><span class="line">| homu     |</span><br><span class="line">| amu      |</span><br><span class="line">+----------+</span><br></pre></td></tr></table></figure><h3 id="使用-where-条件查询"><a href="#使用-where-条件查询" class="headerlink" title="使用 where 条件查询"></a>使用 where 条件查询</h3><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">select</span> 字段 <span class="keyword">from</span> 表 <span class="keyword">where</span> 条件;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> users <span class="keyword">where</span> age<span class="operator">=</span><span class="number">12</span>;</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> users <span class="keyword">where</span> id <span class="keyword">is</span> <span class="keyword">null</span>;</span><br></pre></td></tr></table></figure><h3 id="where-后面的条件"><a href="#where-后面的条件" class="headerlink" title="where 后面的条件"></a>where 后面的条件</h3><ul><li>比较运算符</li></ul><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">&gt; 大于</span><br><span class="line">&lt; 小于</span><br><span class="line">&gt;=  大于等于</span><br><span class="line">&lt;=  小于等于</span><br><span class="line">!=  不等于</span><br><span class="line">= 等于</span><br></pre></td></tr></table></figure><ul><li>逻辑运算符</li></ul><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">or  或</span><br><span class="line">and 且</span><br></pre></td></tr></table></figure><ul><li>示例</li></ul><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">select</span> <span class="operator">*</span> <span class="keyword">from</span> users <span class="keyword">where</span> id <span class="operator">&gt;</span> <span class="number">3</span> <span class="keyword">and</span> age <span class="operator">=</span> <span class="number">12</span>;</span><br></pre></td></tr></table></figure><h3 id="排序查询到的结果"><a href="#排序查询到的结果" class="headerlink" title="排序查询到的结果"></a>排序查询到的结果</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> 字段 <span class="keyword">from</span> 表 <span class="keyword">order</span> <span class="keyword">by</span> 排序关键字;</span><br><span class="line"><span class="comment">-- 排序关键字</span></span><br><span class="line"><span class="keyword">asc</span> <span class="comment">-- 升序 从小到大 默认</span></span><br><span class="line"><span class="keyword">desc</span> <span class="comment">-- 降序 从大到小</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> users <span class="keyword">where</span> age <span class="keyword">is</span> <span class="keyword">not</span> <span class="keyword">null</span> <span class="keyword">order</span> <span class="keyword">by</span> age <span class="keyword">desc</span>;</span><br></pre></td></tr></table></figure><p>多字段排序<br>在第一个字段两个值相同的情况下，对第二个字段进行排序</p><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">select</span> 字段 <span class="keyword">from</span> 表 <span class="keyword">order</span> <span class="keyword">by</span> 字段<span class="number">1</span> 排序关键字 字段<span class="number">2</span> 排序关键字 字段n 排序关键字;</span><br></pre></td></tr></table></figure><h3 id="结果集限制"><a href="#结果集限制" class="headerlink" title="结果集限制"></a>结果集限制</h3><p>对查询后的结果进行限制，只显示其一部分<br>第一条记录为 0</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 class="keyword">from</span> 表 limit 数量;</span><br><span class="line"><span class="keyword">select</span> 字段 <span class="keyword">from</span> 表 limit 起点, 数量;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> users limit <span class="number">0</span>, <span class="number">2</span>;</span><br><span class="line"><span class="comment">-- 从第一条开始，查询两条数据</span></span><br></pre></td></tr></table></figure><h3 id="统计函数的使用"><a href="#统计函数的使用" class="headerlink" title="统计函数的使用"></a>统计函数的使用</h3><table><thead><tr><th>函数</th><th>作用</th></tr></thead><tbody><tr><td>sum</td><td>求和</td></tr><tr><td>count</td><td>统计总数</td></tr><tr><td>max</td><td>最大值</td></tr><tr><td>min</td><td>最小值</td></tr><tr><td>avg</td><td>平均值</td></tr><tr><td>/</td><td>其他 sql 函数</td></tr></tbody></table><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 class="keyword">from</span> 表;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">select</span> <span class="built_in">count</span>(username) <span class="keyword">from</span>  users;</span><br></pre></td></tr></table></figure><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_">mysql&gt; </span><span class="language-bash"><span class="keyword">select</span> count(<span class="built_in">id</span>) from <span class="built_in">users</span>;</span></span><br><span class="line">+-----------+</span><br><span class="line">| count(id) |</span><br><span class="line">+-----------+</span><br><span class="line">|         4 |</span><br><span class="line">+-----------+</span><br><span class="line">1 row in set (0.00 sec)</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">mysql&gt; </span><span class="language-bash"><span class="keyword">select</span> count(age) from <span class="built_in">users</span>;</span></span><br><span class="line">+------------+</span><br><span class="line">| count(age) |</span><br><span class="line">+------------+</span><br><span class="line">|          2 |</span><br><span class="line">+------------+</span><br><span class="line">1 row in set (0.00 sec)</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">null不会被计数</span></span><br></pre></td></tr></table></figure><h3 id="结果分组"><a href="#结果分组" class="headerlink" title="结果分组"></a>结果分组</h3><p>将筛选出的字段根据某个字段分成组<br>字段 2 中相同的会被视为一组</p><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">select</span> 字段 <span class="keyword">from</span> 表 <span class="keyword">group</span> <span class="keyword">by</span> 字段<span class="number">2</span>;</span><br></pre></td></tr></table></figure><h3 id="分组筛选"><a href="#分组筛选" class="headerlink" title="分组筛选"></a>分组筛选</h3><figure class="highlight sql"><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="keyword">select</span> 字段 <span class="keyword">from</span> 表 <span class="keyword">group</span> <span class="keyword">by</span> 字段<span class="number">2</span> <span class="keyword">having</span> 条件;</span><br><span class="line"><span class="comment">-- 只显示符合条件的分组</span></span><br></pre></td></tr></table></figure><p>和 where 的区别：<br>having 用来筛选组，而 where 用来筛选记录</p><h3 id="组合使用-sql"><a href="#组合使用-sql" class="headerlink" title="组合使用 sql"></a>组合使用 sql</h3><table><thead><tr><th>关键字</th><th>作用</th></tr></thead><tbody><tr><td>select</td><td>选择的列</td></tr><tr><td>from</td><td>表</td></tr><tr><td>where</td><td>查询的条件</td></tr><tr><td>group by</td><td>根据 having 或字段分组</td></tr><tr><td>having</td><td>筛选分组</td></tr><tr><td>order by</td><td>排序属性</td></tr><tr><td>limit</td><td>限制数量</td></tr></tbody></table><h2 id="删除记录"><a href="#删除记录" class="headerlink" title="删除记录"></a>删除记录</h2><h3 id="条件删除"><a href="#条件删除" class="headerlink" title="条件删除"></a>条件删除</h3><p><strong>删除表记录的时候一定要加上 where 条件，否则会删除整张表</strong><br><strong>删除重要数据前一定要做备份</strong></p><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">delete</span> <span class="keyword">from</span> 表 <span class="keyword">where</span> 条件;</span><br></pre></td></tr></table></figure><h3 id="清空表记录"><a href="#清空表记录" class="headerlink" title="清空表记录"></a>清空表记录</h3><p>delete 和 truncate 是类似的<br>但是有一点不同<br>delete 返回被删除的记录的数量<br>truncate 返回 0</p><figure class="highlight sql"><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="keyword">truncate</span> <span class="keyword">table</span> 表;</span><br><span class="line"><span class="comment">-- 清空表数据，并让自增id从1开始</span></span><br></pre></td></tr></table></figure><h2 id="更新记录"><a href="#更新记录" class="headerlink" title="更新记录"></a>更新记录</h2><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">update</span> 表 <span class="keyword">set</span> 字段<span class="number">1</span><span class="operator">=</span>值<span class="number">1</span>, 字段<span class="number">2</span><span class="operator">=</span>值<span class="number">2</span>, 字段n<span class="operator">=</span>值n <span class="keyword">where</span> 条件;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">update</span> users <span class="keyword">set</span> username<span class="operator">=</span><span class="string">&#x27;amurita&#x27;</span>, password<span class="operator">=</span><span class="string">&#x27;yui&#x27;</span> <span class="keyword">where</span> username<span class="operator">=</span><span class="string">&#x27;a&#x27;</span>;</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">update</span> 表<span class="number">1</span>, 表<span class="number">2</span> <span class="keyword">set</span> 字段<span class="number">1</span><span class="operator">=</span>值<span class="number">1</span>, 字段<span class="number">2</span><span class="operator">=</span>值<span class="number">2</span>, 字段n<span class="operator">=</span>值n <span class="keyword">where</span> 条件;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">update</span> suki s, users u <span class="keyword">set</span> s.mono<span class="operator">=</span><span class="string">&#x27;www&#x27;</span>, u.username<span class="operator">=</span><span class="string">&#x27;www&#x27;</span> <span class="keyword">where</span> s.id<span class="operator">=</span>u.id;</span><br><span class="line"><span class="comment">-- 将suki和users中 id相同的行的username和mono字段改为www</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;插入记录的基本语法&quot;&gt;&lt;a href=&quot;#插入记录的基本语法&quot; class=&quot;headerlink&quot; title=&quot;插入记录的基本语法&quot;&gt;&lt;/a&gt;插入记录的基本语法&lt;/h2&gt;&lt;h3 id=&quot;第一种语法&quot;&gt;&lt;a href=&quot;#第一种语法&quot; class=&quot;header</summary>
      
    
    
    
    <category term="小笔记" scheme="https://blog.homu.space/categories/%E5%B0%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="mysql" scheme="https://blog.homu.space/tags/mysql/"/>
    
  </entry>
  
  <entry>
    <title>MySQL学习：索引</title>
    <link href="https://blog.homu.space/posts/76a4ac1e/"/>
    <id>https://blog.homu.space/posts/76a4ac1e/</id>
    <published>2023-09-29T22:11:58.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<h2 id="索引的优点"><a href="#索引的优点" class="headerlink" title="索引的优点"></a>索引的优点</h2><p>索引用于快速找出某个列中有特定值的一行<br>不适用索引，Mysql 必须从第一条记录开始，读完整个表直到找出相关的行。<br>表越大，花费时间越多。<br>如果表中查询的列有一个索引，Mysql 能快速到达一个位置去搜寻到数据文件的中间，没有必要读取所有的数据</p><blockquote><p>但是索引也不宜过多，索引越多，写入和修改的速度就越慢。写入修改数据时，索引也会修改</p></blockquote><h2 id="Mysql-索引类型"><a href="#Mysql-索引类型" class="headerlink" title="Mysql 索引类型"></a>Mysql 索引类型</h2><table><thead><tr><th>索引类型</th><th>功能说明</th></tr></thead><tbody><tr><td>普通索引</td><td>最基本的索引，没有任何限制</td></tr><tr><td>唯一索引</td><td>某一行启用了唯一索引，则不允许这一列行数据中有重复的值，针对这一列的每一行数据都要求是唯一的</td></tr><tr><td>主键索引</td><td>这是一种特殊索引，不允许有空值，一般在建表的时候同时创建主键索引，常用于用户 ID</td></tr><tr><td>全文索引</td><td>对于需要全局搜索的数据，进行全文索引</td></tr></tbody></table><h2 id="普通索引"><a href="#普通索引" class="headerlink" title="普通索引"></a>普通索引</h2><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">alter</span> <span class="keyword">table</span> 表 <span class="keyword">add</span> index(字段);</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> users <span class="keyword">add</span> index(username);</span><br><span class="line"><span class="comment">-- 给users表中的username字段添加索引</span></span><br></pre></td></tr></table></figure><h2 id="唯一索引"><a href="#唯一索引" class="headerlink" title="唯一索引"></a>唯一索引</h2><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">alter</span> <span class="keyword">table</span> 表 <span class="keyword">add</span> <span class="keyword">unique</span>(字段);</span><br></pre></td></tr></table></figure><h2 id="全文索引"><a href="#全文索引" class="headerlink" title="全文索引"></a>全文索引</h2><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">alter</span> <span class="keyword">table</span> 表 <span class="keyword">add</span> fulltext(字段);</span><br></pre></td></tr></table></figure><h2 id="主键索引"><a href="#主键索引" class="headerlink" title="主键索引"></a>主键索引</h2><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">alter</span> <span class="keyword">table</span> 表 <span class="keyword">add</span> <span class="keyword">primary</span> key(字段);</span><br></pre></td></tr></table></figure><h2 id="创建表的时候声明索引"><a href="#创建表的时候声明索引" class="headerlink" title="创建表的时候声明索引"></a>创建表的时候声明索引</h2><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">create</span> <span class="keyword">table</span> 表(字段 数据类型, 字段 数据类型, <span class="keyword">primary</span> key(字段), index(字段), <span class="keyword">unique</span>(字段), fulltext(字段));</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;索引的优点&quot;&gt;&lt;a href=&quot;#索引的优点&quot; class=&quot;headerlink&quot; title=&quot;索引的优点&quot;&gt;&lt;/a&gt;索引的优点&lt;/h2&gt;&lt;p&gt;索引用于快速找出某个列中有特定值的一行&lt;br&gt;不适用索引，Mysql 必须从第一条记录开始，读完整个表直到找出相关的</summary>
      
    
    
    
    <category term="小笔记" scheme="https://blog.homu.space/categories/%E5%B0%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="mysql" scheme="https://blog.homu.space/tags/mysql/"/>
    
  </entry>
  
  <entry>
    <title>MySQL学习：数据类型</title>
    <link href="https://blog.homu.space/posts/55809767/"/>
    <id>https://blog.homu.space/posts/55809767/</id>
    <published>2023-09-10T21:01:52.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>MySQL 中存的是数据。只要是数据，我们就会规定数据的类型。<br>在表的字段中规定了使用的是某个数据类型。那么，在插入的数据中就要使用对应的数据类型。并且，遵守数据类型的长度要求。</p></blockquote><h2 id="所有数据类型"><a href="#所有数据类型" class="headerlink" title="所有数据类型"></a>所有数据类型</h2><ul><li>数值类型<ul><li>整型</li><li>浮点型</li></ul></li><li>字符串类型</li><li>日期时间类型</li><li>复合类型</li><li>空间类型（用的不多）</li></ul><h2 id="整型"><a href="#整型" class="headerlink" title="整型"></a>整型</h2><table><thead><tr><th>MySQL 数据类型</th><th>所占字节</th><th>值范围</th></tr></thead><tbody><tr><td>tinyint</td><td>1 字节</td><td>-127~128</td></tr><tr><td>smallint</td><td>2 字节</td><td>-32768~32767</td></tr><tr><td>mediumint</td><td>3 字节</td><td>-8388608~8388607</td></tr><tr><td>int</td><td>4 字节</td><td>-2147483648~2147483647</td></tr><tr><td>bigint</td><td>5 字节</td><td>+-9.22*10 的 18 次方</td></tr></tbody></table><p>整型长度不同，在实际使用过程也不同</p><blockquote><p>MySQL 以一个可选的显示宽度指示器的形式对 SQL 标准进行扩展，这样当从数据库检索一个值时，可以把这个值加长到指定的长度。例如，指定一个字段的类型为 INT(6)，就可以保证所包含数字少于 6 个的值从数据库中检索出来时能够自动地用空格填充。需要注意的是，使用一个宽度指示器不会影响字段的大小和它可以存储的值的范围。</p></blockquote><p>在实际使用中，性别我们可以使用无符号的 tinyint 来存储<br>同样人的年龄也可以，因为年龄没有负数<br>创建数据表的时候，需要根据实际需求来确定用什么类型</p><h2 id="浮点类型"><a href="#浮点类型" class="headerlink" title="浮点类型"></a>浮点类型</h2><table><thead><tr><th>MySQL 数据类型</th><th>所占字节</th><th>值范围</th></tr></thead><tbody><tr><td>float(m, d)</td><td>4 字节</td><td>单精度浮点型，m 总个数，d 小数位</td></tr><tr><td>double(m, d)</td><td>8 字节</td><td>双精度浮点型，m 总个数，d 小数位</td></tr><tr><td>decimal(m, d)</td><td></td><td>decimal 是存储为字符串的浮点数</td></tr></tbody></table><p>浮点是非准确值，会存在不太准确的情况<br>decimal 叫做定点数，在 MySQL 内部，本质是用字符串存储的。使用过程中如果存在金额之类精度要求比较高的浮点数存储，建议使用 decimal 类型</p><h2 id="字符类型"><a href="#字符类型" class="headerlink" title="字符类型"></a>字符类型</h2><table><thead><tr><th>MySQL 数据类型</th><th>所占字节</th><th>值范围</th></tr></thead><tbody><tr><td>CHAR</td><td>0-255 字节</td><td>定长字符串</td></tr><tr><td>VARCHAR</td><td>0-255 字节</td><td>变长字符串</td></tr><tr><td>TINYBLOB</td><td>0-255 字节</td><td>不超过 255 个字符的二进制字符串</td></tr><tr><td>TINYTEXT</td><td>0-255 字节</td><td>短文本字符串</td></tr><tr><td>BLOB</td><td>0-65535 字节</td><td>二进制形式的长文本数据</td></tr><tr><td>TEXT</td><td>0-65535 字节</td><td>长文本数据</td></tr><tr><td>MEDIUMBLOB</td><td>0-16 777 215 字节</td><td>二进制形式的中等长度文本数据</td></tr><tr><td>MEDIUMTEXT</td><td>0-16 777 215 字节</td><td>中等长度文本数据</td></tr><tr><td>LOGNGBLOB</td><td>0-4 294 967 295 字节</td><td>二进制形式的极大文本数据</td></tr><tr><td>LONGTEXT</td><td>0-4 294 967 295 字节</td><td>极大文本数据</td></tr><tr><td>VARBINARY(M)</td><td>允许长度 0-M 个字节的定长字节符串</td><td>值的长度+1 个字节</td></tr><tr><td>BINARY(M)</td><td>M</td><td>允许长度 0-M 个字节的定长字节符串</td></tr></tbody></table><ul><li><p>CHAR 与 VARCHAR</p><blockquote><p>CHAR 类型用于定长字符串，并且必须在圆括号内用一个大小修饰符来定义，这个大小修饰符的范围从 0-255。比指定长度大的值将被截短，而比指定长度小的值将会用空格作填补。</p></blockquote><blockquote><p>VARCHAR 把这个大小视为值的大小，在长度不足的情况下就用空格补足。而 VARCHAR 类型把它视为最大值并且只使用存储字符串实际需要的长度类型不会被空格填补，但长于指示器的值仍然会被截短。因为 VARCHAR 类型可以根据实际内容动态改变存储值的长度，所以在不能确定字段需要多少字符时使用 VARCHAR 类型可以大大地节约磁盘空间、提高存储效率。</p></blockquote></li><li><p>TEXT 与 BLOB</p><blockquote><p>text 类型与 blob 类型对于字段长度要求超过 255 个的情况下，MySQL 提供了 TEXT 和 BLOB 两种类型。根据存储数据的大小，它们都有不同的子类型。这些大型的数据用于存储文本块或图像、声音文件等二进制数据类型。TEXT 和 BLOB 类型在分类和比较上存在区别。<strong>BLOB 类型区分大小写</strong>，<strong>而 TEXT 不区分大小写</strong>。_大小修饰符不用于各种 BLOB 和 TEXT 子类型_。</p></blockquote></li></ul><h2 id="时间类型"><a href="#时间类型" class="headerlink" title="时间类型"></a>时间类型</h2><table><thead><tr><th>MySQL 数据类型</th><th>所占字节</th><th>值范围</th></tr></thead><tbody><tr><td>date</td><td>3 字节</td><td>日期，格式：2014-09-18</td></tr><tr><td>time</td><td>3 字节</td><td>时间，格式：08:42:30</td></tr><tr><td>datetime</td><td>8 字节</td><td>日期时间，格式：2014-09-18 08:42:30</td></tr><tr><td>timestamp</td><td>4 字节</td><td>自动存储记录修改的时间</td></tr><tr><td>year</td><td>1 字节</td><td>年份</td></tr></tbody></table><blockquote><p>时间类型在 web 系统中用的比较少，很多时候很多人喜欢使用 int 来存储时间。插入时插入的是 unix 时间戳，因为这种方式更方便计算。在前端业务中用 date 类型的函数，再将<strong>unix 时间戳转成人们可识别的时间</strong>。</p></blockquote><blockquote><p>有些人为了在数据库管理中方便查看，也有人使用 datetime 类型来存储时间。</p></blockquote><h2 id="复合类型"><a href="#复合类型" class="headerlink" title="复合类型"></a>复合类型</h2><table><thead><tr><th>MySQL 数据类型</th><th>说明</th><th>举例</th></tr></thead><tbody><tr><td>set</td><td>集合类型</td><td>set(“member”, “member2″, … “member64″)</td></tr><tr><td>enum</td><td>枚举类型</td><td>enum(“member1″, “member2″, … “member65535″)</td></tr></tbody></table><ul><li><p>ENUM</p><blockquote><p>一个 ENUM 类型只允许从一个集合中取得一个值；而 SET 类型允许从一个集合中取得任意多个值。</p></blockquote><blockquote><p>ENUM 类型因为<strong>只允许在集合中取得一个值</strong>，有点类似于单选项。在处理相互排拆的数据时容易让人理解，比如人类的性别。ENUM 类型字段可以从集合中取得一个值或使用 null 值，除此之外的输入将会使 MySQL 在这个字段中插入一个空字符串。另外如果插入值的大小写与集合中值的大小写不匹配，MySQL 会自动使用插入值的大小写转换成与集合中大小写一致的值。</p></blockquote><blockquote><p>ENUM 类型在系统内部可以存储为数字，<strong>并且从 1 开始用数字做索引</strong>。一个 ENUM 类型最多可以包含 65536 个元素，其中一个元素被 MySQL 保留，用来存储错误信息，这个错误值用索引 0 或者一个空字符串表示。</p></blockquote><blockquote><p>MySQL 认为 ENUM 类型集合中出现的值是合法输入，除此之外其它任何输入都将失败。这说明通过搜索包含空字符串或对应数字索引为 0 的行就可以很容易地找到错误记录的位置</p></blockquote></li><li><p>SET</p><blockquote><p>SET 类型与 ENUM 类型相似但不相同。SET 类型可以从预定义的集合中取得任意数量的值。并且与 ENUM 类型相同的是任何试图在 SET 类型字段中插入非预定义的值都会使 MySQL 插入一个空字符串。如果插入一个即有合法的元素又有非法的元素的记录，MySQL 将会保留合法的元素，除去非法的元素。</p></blockquote><blockquote><p>一个 SET 类型最多可以包含 64 个元素。在 SET 元素中值被存储为一个分离的“位”序列，这些“位”表示与它相对应的元素。“位”是创建有序元素集合的一种简单而有效的方式。并且它还去除了重复的元素，所以**<em>SET 类型中不可能包含两个相同的元素</em><strong>。</strong>希望从 SET 类型字段中找出非法的记录只需查找包含空字符串或二进制值为 0 的行。**</p></blockquote></li></ul><h2 id="使用类型"><a href="#使用类型" class="headerlink" title="使用类型"></a>使用类型</h2><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><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">create</span> <span class="keyword">table</span> if <span class="keyword">not</span> <span class="keyword">exists</span> <span class="keyword">user</span> (</span><br><span class="line">  id <span class="type">int</span>(<span class="number">11</span>) <span class="keyword">not</span> <span class="keyword">null</span>,</span><br><span class="line">  username <span class="type">varchar</span>(<span class="number">50</span>) <span class="keyword">not</span> <span class="keyword">null</span>,</span><br><span class="line">  password <span class="type">varchar</span>(<span class="number">30</span>) <span class="keyword">not</span> <span class="keyword">null</span>,</span><br><span class="line">  content longtext <span class="keyword">not</span> <span class="keyword">null</span>,</span><br><span class="line">  create_at datetime <span class="keyword">not</span> <span class="keyword">null</span>,</span><br><span class="line">  sex tinyint(<span class="number">4</span>) <span class="keyword">not</span> <span class="keyword">null</span></span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8;</span><br></pre></td></tr></table></figure><h2 id="字段其他属性"><a href="#字段其他属性" class="headerlink" title="字段其他属性"></a>字段其他属性</h2><h3 id="UNSIGNED（无符号）"><a href="#UNSIGNED（无符号）" class="headerlink" title="UNSIGNED（无符号）"></a>UNSIGNED（无符号）</h3><p>主要用于整型和浮点类型，使用无符号。即，没有前面面的-（负号）。存储位数更长。<br>tinyint 整型的取值区间为，-128~127。而使用无符号后可存储 0-255 个长度。<br>创建时，在整型或浮点型字段语句后接上</p><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">unsigned</span><br></pre></td></tr></table></figure><h3 id="ZEROFILL（0-填充）"><a href="#ZEROFILL（0-填充）" class="headerlink" title="ZEROFILL（0 填充）"></a>ZEROFILL（0 填充）</h3><p>0（不是空格）可以用来真补输出的值。使用这个修饰符可以阻止 MySQL 数据库存储负值。<br>创建时在整型或浮点字段语句后接上：</p><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">zerofill</span><br></pre></td></tr></table></figure><h3 id="default（默认值）"><a href="#default（默认值）" class="headerlink" title="default（默认值）"></a>default（默认值）</h3><p>default 属性确保在没有任何值可用的情况下，赋予某个常量值，这个值必须是常量，因为 MySQL 不允许插入函数或表达式值。此外，此属性无法用于 BLOB 或 TEXT 列。如果已经为此列指定了 NULL 属性，没有指定默认值时默认值将为 NULL，否则默认值将依赖于字段的数据类型。<br>用法：</p><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">defalut <span class="string">&#x27;默认值&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="NOT-NULL（不能为-null）"><a href="#NOT-NULL（不能为-null）" class="headerlink" title="NOT NULL（不能为 null）"></a>NOT NULL（不能为 null）</h3><p>如果将一个列定义为 not null，将不允许向该列插入 null 值。建议在重要情况下始终使用 not null 属性，因为它提供了一个基本验证，确保已经向查询传递了所有必要的值。</p><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">not</span> <span class="keyword">null</span></span><br></pre></td></tr></table></figure><h3 id="null"><a href="#null" class="headerlink" title="null"></a>null</h3><p>null 为列指定 null 属性时，该列可以保持为空，而不论行中其它列是否已经被填充。记住，null 精确的说法是“无”，而不是空字符串或 0。<br>声明的时候不是<code>not null</code>即可</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;MySQL 中存的是数据。只要是数据，我们就会规定数据的类型。&lt;br&gt;在表的字段中规定了使用的是某个数据类型。那么，在插入的数据中就要使用对应的数据类型。并且，遵守数据类型的长度要求。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;所有数据类</summary>
      
    
    
    
    <category term="小笔记" scheme="https://blog.homu.space/categories/%E5%B0%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="mysql" scheme="https://blog.homu.space/tags/mysql/"/>
    
  </entry>
  
  <entry>
    <title>MySQL学习：常用数据字段sql语句</title>
    <link href="https://blog.homu.space/posts/e3b80ef8/"/>
    <id>https://blog.homu.space/posts/e3b80ef8/</id>
    <published>2023-09-10T21:01:24.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<h2 id="修改表字段类型"><a href="#修改表字段类型" class="headerlink" title="修改表字段类型"></a>修改表字段类型</h2><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">alter</span> <span class="keyword">table</span> 表名 modify 字段名 字段类型;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="comment">-- 将user表的password类型改为varchar(30)</span></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> modify password <span class="type">varchar</span>(<span class="number">30</span>);</span><br></pre></td></tr></table></figure><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">Query OK, 0 rows affected (0.02 sec)</span><br><span class="line">Records: 0  Duplicates: 0  Warnings: 0</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">desc</span> <span class="keyword">user</span>;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| Field    | Type        | Null | Key | Default | Extra |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| username | varchar(20) | YES  |     | NULL    |       |</span><br><span class="line">| password | varchar(30) | YES  |     | NULL    |       |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">2 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure><h2 id="增加表字段"><a href="#增加表字段" class="headerlink" title="增加表字段"></a>增加表字段</h2><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><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> 表名 <span class="keyword">add</span> <span class="keyword">column</span> 字段名 类型;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="comment">-- user表增加age字段</span></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> <span class="keyword">add</span> <span class="keyword">column</span> age <span class="type">int</span>(<span class="number">3</span>);</span><br><span class="line"><span class="comment">-- 查看表结构</span></span><br><span class="line"><span class="keyword">desc</span> <span class="keyword">user</span>；</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">Query OK, 0 rows affected, 1 warning (0.02 sec)</span><br><span class="line">Records: 0  Duplicates: 0  Warnings: 1</span><br><span class="line"></span><br><span class="line">&lt;!-- mysql&gt; desc user; --&gt;</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| Field    | Type        | Null | Key | Default | Extra |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| username | varchar(20) | YES  |     | NULL    |       |</span><br><span class="line">| password | varchar(30) | YES  |     | NULL    |       |</span><br><span class="line">| age      | int         | YES  |     | NULL    |       |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">3 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure><h2 id="增加字段时，控制字段顺序"><a href="#增加字段时，控制字段顺序" class="headerlink" title="增加字段时，控制字段顺序"></a>增加字段时，控制字段顺序</h2><p>普通地增加字段，所增加的字段总是在表的最后一列<br>如何增加到指定两个字段之间呢</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">alter</span> <span class="keyword">table</span> 表名 <span class="keyword">add</span> 字段名 类型 after 字段名;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="comment">-- 增加sex字段，位于password后面</span></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> <span class="keyword">add</span> sex <span class="type">int</span>(<span class="number">1</span>) after password;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">mysql&gt; alter table user add sex int(1) after password;</span><br><span class="line">Query OK, 0 rows affected, 1 warning (0.07 sec)</span><br><span class="line">Records: 0  Duplicates: 0  Warnings: 1</span><br><span class="line"></span><br><span class="line">mysql&gt; desc user;</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| Field    | Type        | Null | Key | Default | Extra |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| username | varchar(20) | YES  |     | NULL    |       |</span><br><span class="line">| password | varchar(30) | YES  |     | NULL    |       |</span><br><span class="line">| sex      | int         | YES  |     | NULL    |       |</span><br><span class="line">| age      | int         | YES  |     | NULL    |       |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">4 rows in set (0.00 sec)</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">alter</span> <span class="keyword">table</span> 表名 <span class="keyword">add</span> 字段名 类型 <span class="keyword">first</span>;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="comment">-- 增加id字段，在最前面</span></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> <span class="keyword">add</span> id <span class="type">int</span>(<span class="number">10</span>) <span class="keyword">first</span>;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">mysql&gt; alter table user add id int(10) first;</span><br><span class="line">Query OK, 0 rows affected, 1 warning (0.06 sec)</span><br><span class="line">Records: 0  Duplicates: 0  Warnings: 1</span><br><span class="line"></span><br><span class="line">mysql&gt; desc user;</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| Field    | Type        | Null | Key | Default | Extra |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| id       | int         | YES  |     | NULL    |       |</span><br><span class="line">| username | varchar(20) | YES  |     | NULL    |       |</span><br><span class="line">| password | varchar(30) | YES  |     | NULL    |       |</span><br><span class="line">| sex      | int         | YES  |     | NULL    |       |</span><br><span class="line">| age      | int         | YES  |     | NULL    |       |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">5 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure><h2 id="删除表字段"><a href="#删除表字段" class="headerlink" title="删除表字段"></a>删除表字段</h2><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">alter</span> <span class="keyword">table</span> 表名 <span class="keyword">drop</span> <span class="keyword">column</span> 字段名；</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="comment">-- 删除sex字段</span></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> <span class="keyword">drop</span> <span class="keyword">column</span> sex;</span><br></pre></td></tr></table></figure><h2 id="表字段改名"><a href="#表字段改名" class="headerlink" title="表字段改名"></a>表字段改名</h2><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">alter</span> <span class="keyword">table</span> 表名 change 字段原名 字段新名 字段类型;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> <span class="keyword">add</span> email <span class="type">varchar</span>(<span class="number">30</span>);</span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> change email mobile <span class="type">varchar</span>(<span class="number">30</span>);</span><br></pre></td></tr></table></figure><h2 id="调整字段顺序"><a href="#调整字段顺序" class="headerlink" title="调整字段顺序"></a>调整字段顺序</h2><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">alter</span> <span class="keyword">table</span> 表名 modify 字段名 类型 <span class="keyword">first</span>;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> modify mobile <span class="type">varchar</span>(<span class="number">30</span>) <span class="keyword">first</span>;</span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> modify age <span class="type">varchar</span>(<span class="number">20</span>) after password;</span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> modify mobile <span class="type">varchar</span>(<span class="number">30</span>) after age;</span><br><span class="line"><span class="keyword">desc</span> <span class="keyword">user</span>;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| Field    | Type        | Null | Key | Default | Extra |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| id       | int         | YES  |     | NULL    |       |</span><br><span class="line">| username | varchar(20) | YES  |     | NULL    |       |</span><br><span class="line">| password | varchar(30) | YES  |     | NULL    |       |</span><br><span class="line">| age      | varchar(20) | YES  |     | NULL    |       |</span><br><span class="line">| mobile   | varchar(30) | YES  |     | NULL    |       |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">5 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;修改表字段类型&quot;&gt;&lt;a href=&quot;#修改表字段类型&quot; class=&quot;headerlink&quot; title=&quot;修改表字段类型&quot;&gt;&lt;/a&gt;修改表字段类型&lt;/h2&gt;&lt;p&gt;基本语法&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;</summary>
      
    
    
    
    <category term="小笔记" scheme="https://blog.homu.space/categories/%E5%B0%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="mysql" scheme="https://blog.homu.space/tags/mysql/"/>
    
  </entry>
  
  <entry>
    <title>MySQL学习：常用数据表sql语句-1</title>
    <link href="https://blog.homu.space/posts/86169/"/>
    <id>https://blog.homu.space/posts/86169/</id>
    <published>2023-09-10T21:01:03.000Z</published>
    <updated>2026-05-10T10:49:07.016Z</updated>
    
    <content type="html"><![CDATA[<h2 id="创建表"><a href="#创建表" class="headerlink" title="创建表"></a>创建表</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> 表名(字段<span class="number">1</span> 字段类型, 字段<span class="number">2</span> 字段类型, ...字段n 字段类型);</span><br><span class="line"><span class="comment">-- 示例：创建一个user表，有username和password两个字段</span></span><br><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> <span class="keyword">user</span>(username <span class="type">varchar</span>(<span class="number">20</span>), password <span class="type">varchar</span>(<span class="number">20</span>));</span><br></pre></td></tr></table></figure><p>关于数据类型的知识，会在后面记录</p><h2 id="查看数据表结构"><a href="#查看数据表结构" class="headerlink" title="查看数据表结构"></a>查看数据表结构</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">desc</span> 表名；</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">desc</span> <span class="keyword">user</span>;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| Field    | Type        | Null | Key | Default | Extra |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">| username | varchar(20) | YES  |     | NULL    |       |</span><br><span class="line">| password | varchar(20) | YES  |     | NULL    |       |</span><br><span class="line">+----------+-------------+------+-----+---------+-------+</span><br><span class="line">2 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure><h2 id="查看表的创建语句"><a href="#查看表的创建语句" class="headerlink" title="查看表的创建语句"></a>查看表的创建语句</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">show</span> <span class="keyword">create</span> <span class="keyword">table</span> 表名 \G;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="keyword">show</span> <span class="keyword">create</span> <span class="keyword">table</span> <span class="keyword">user</span> \G;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">*************************** 1. row ***************************</span><br><span class="line">       Table: user</span><br><span class="line">Create Table: CREATE TABLE `user` (</span><br><span class="line">  `username` varchar(20) DEFAULT NULL,</span><br><span class="line">  `password` varchar(20) DEFAULT NULL</span><br><span class="line">) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci</span><br><span class="line">1 row in set (0.00 sec)</span><br><span class="line"></span><br><span class="line">ERROR:</span><br><span class="line">No query specified</span><br></pre></td></tr></table></figure><p>上面表的创建 SQL 语句中，除了可以看到表定义以外，还可以看到表的 engine（存储引擎) 和 charset(字符集)等信息。“\G”选项的含义是使得记录能够按照字段竖着排列，对于内 容比较长的记录更易于显示。</p><h2 id="删除表"><a href="#删除表" class="headerlink" title="删除表"></a>删除表</h2><p>基本用法</p><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">drop</span> <span class="keyword">table</span> 表名;</span><br></pre></td></tr></table></figure><p>删除表。表和数据均会丢失，请勿必删除重要表之前备份数据</p><h2 id="指定表引擎和字符集"><a href="#指定表引擎和字符集" class="headerlink" title="指定表引擎和字符集"></a>指定表引擎和字符集</h2><p>创建表的最后，我们常用 MyISAM 或者 InnoDB 引擎<br>指定引擎时，我们可以用</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">ENGINE=InnoDB</span><br></pre></td></tr></table></figure><p>指定表默认字符集</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">DEFAULT CHARSET=utf8</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">create</span> <span class="keyword">table</span> testEngine(id <span class="type">int</span>(<span class="number">10</span>)) ENGINE<span class="operator">=</span>MyISAM <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8;</span><br><span class="line"><span class="comment">-- Query OK, 0 rows affected, 2 warnings (0.00 sec)</span></span><br><span class="line"><span class="keyword">show</span> <span class="keyword">create</span> <span class="keyword">table</span> testEngine \G;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">*************************** 1. row ***************************</span><br><span class="line">       Table: testEngine</span><br><span class="line">Create Table: CREATE TABLE `testEngine` (</span><br><span class="line">  `id` int DEFAULT NULL</span><br><span class="line">) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3</span><br><span class="line">1 row in set (0.00 sec)</span><br><span class="line"></span><br><span class="line">ERROR:</span><br><span class="line">No query specified</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">drop</span> <span class="keyword">table</span> testEngine;</span><br></pre></td></tr></table></figure><h2 id="重命名数据表"><a href="#重命名数据表" class="headerlink" title="重命名数据表"></a>重命名数据表</h2><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">alter</span> <span class="keyword">table</span> 旧表名 rename 新表名;</span><br><span class="line"><span class="comment">-- 示例</span></span><br><span class="line"><span class="comment">-- 将表名user改为users</span></span><br><span class="line"><span class="keyword">alter</span> <span class="keyword">table</span> <span class="keyword">user</span> rename users;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">mysql&gt; show tablse;</span><br><span class="line">ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near &#x27;tablse&#x27; at line 1</span><br><span class="line">mysql&gt; show tables;</span><br><span class="line">+-----------------+</span><br><span class="line">| Tables_in_study |</span><br><span class="line">+-----------------+</span><br><span class="line">| users           |</span><br><span class="line">+-----------------+</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;创建表&quot;&gt;&lt;a href=&quot;#创建表&quot; class=&quot;headerlink&quot; title=&quot;创建表&quot;&gt;&lt;/a&gt;创建表&lt;/h2&gt;&lt;p&gt;基本语法&lt;/p&gt;
&lt;figure class=&quot;highlight sql&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutte</summary>
      
    
    
    
    <category term="小笔记" scheme="https://blog.homu.space/categories/%E5%B0%8F%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="mysql" scheme="https://blog.homu.space/tags/mysql/"/>
    
  </entry>
  
</feed>
