<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Ygria&#39;s Blog</title>
    <link>https://ygria.site/</link>
    <description>Ygria的数字花园，分享技术，也会记录生活、锻炼、旅游和读书笔记。</description><generator>Hugo -- gohugo.io</generator>
    <language>zh-cn</language>
    <lastBuildDate>Tue, 16 Sep 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://ygria.site/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>样式工程化：如何实现Design System</title>
      <link>https://ygria.site/design-system/</link>
      <pubDate>Tue, 16 Sep 2025 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/design-system/</guid>
      <description>&lt;blockquote class=&#34;callout abstract&#34; data-callout=&#34;abstract&#34;&gt;
    &lt;div class=&#34;callout-title&#34;&gt;
     
        &lt;div class=&#34;callout-title-inner&#34;&gt;
           
            
            
            Abstract
            
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&#34;callout-content&#34;&gt;
        &lt;p&gt;
            &lt;p&gt;最近尝试做跨端的应用，目标是：只需要写一套代码，通过不同的打包配置，各个端都能稳定运行。之前并没有做过适配手机移动端的工作，这次发现需要考虑的细节还是蛮多的，在此做记录。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="样式工程化：如何实现Design System" />


<blockquote class="callout abstract" data-callout="abstract">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            Abstract
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>最近尝试做跨端的应用，目标是：只需要写一套代码，通过不同的打包配置，各个端都能稳定运行。之前并没有做过适配手机移动端的工作，这次发现需要考虑的细节还是蛮多的，在此做记录。</p>
        </p>
    </div>
</blockquote>
<h1 id="从原生css到tailwindcss">从原生CSS到TailwindCSS</h1>
<p>对于样式的处理，我经历了好几个阶段，也对其中的琐碎、麻烦、相互干扰、不够工程化深恶痛绝，可以说这玩意就是我从后端开发转向全栈的一个拦路虎。好在我现在踩了很多坑，也算积累了一点经验了。</p>
<h2 id="原生css"> 原生CSS</h2>
<p>大家应该都知道最初的“三剑客”，也就是Html、JavaScript、CSS。如果只要做一个简单的demo，也就是单网页，我们可以这么做：</p>
<ol>
<li>在<code>html</code>标签上加上CSS类名，比如<code>.my-container</code></li>
<li>在CSS文件中，写CSS样式，例如：</li>
</ol>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> css</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="css" data-theme="breeze" id="code-id-1">.my-container {
    width: 200px;
    height: 200px
}</div>
    </div>
  </div>
</div><ol start="3">
<li>在HTML中使用<code>stylesheet</code>标签引入CSS文件。</li>
</ol>
<p>这也就是最初始的写法，也是我们在网页上F12，去浏览别人的网页看到的样子。这么写有一些缺点：</p>
<ol>
<li>
<p><strong>对于命名能力要求高，心智负担重，往往需要再学习一套命名的规范。</strong> 写函数容易，给函数命名难，所以有了匿名函数，而样式也是一样：写一个标题，CSS类名叫<code>heading</code>， 再写一个标题呢？写一个描述，叫<code>description</code>， 再写一个呢？如果有各种不同样式布局的标题、容器、文本，逐渐就变得越来越搞不清楚哪个对哪个。如果是多人合作的项目，还需要沟通好规范。</p>
</li>
<li>
<p>样式、模板位置分离，难改。我们现在需要改某个页面元素的样式，需要：</p>
</li>
</ol>
<p> ①找到HTML元素的类名（可能有多个应用优先级不同的样式，所以需要判断是哪个决定了它最终呈现出的样式）
 ②跳到CSS文件中，修改样式
 ③查看网页上样式有没有修改成功
 ④检查其他页面使用相同类名的地方是否正常</p>
<p>在多个文件和窗口中跳来跳去，很容易打断思路，也会越来越难以维护。
3. CSS原生写法不支持嵌套，所有伪类（::before, ::after） 样式、状态样式(:hover, :focus)等，都需要独立闭合的大括号括起来，写起来相当麻烦。</p>
<ol start="4">
<li>样式是否真的生效相当难以判断，很多时候都是看页面上样式是不是正常了。这种非强制要求可能导致”技术债“越积越深，直到有一天要求调整样式的时候，发现已经极为混乱和难以维护。</li>
</ol>
<h2 id="工程化框架和postcss"> 工程化、框架和PostCSS</h2>
<p>在原生Html和Javascript有了框架：Vue、React等等后，CSS也有了更好的处理方式。</p>
<ol>
<li>
<p><code>SCSS</code>/ <code>LESS</code> 等CSS后处理器出现，支持嵌套等各种语法糖，让CSS写起来更为简便。</p>
</li>
<li>
<p>支持组件内局部应用的样式。例如：Vue的<!-- raw HTML omitted -->  标签支持加上 <code>scoped</code> 关键字，让这个组件内的样式只在本组件内有效，避免样式污染，也有效降低了心智负担。</p>
</li>
</ol>
<p>原理：通过打包时的插件配置，将开发时所写的样式还原成浏览器可以识别的CSS文件，将嵌套的扁平化，局部或者不同适用场景的统一添加前缀。</p>
<h2 id="tailwindcss-快速入门设计思想">TailwindCSS： 快速入门+设计思想</h2>
<p>一旦接触了<code>TailwindCSS</code> 很难不被它的直接简洁迷上。</p>
<p>它相当于提供了一整套速写词，再也不用去记臃肿的写法了！再也不用三四行来写一个属性了！也不用再到处找样式文件，只需要在<code>html</code>标签里写上一串原子类的<code>className</code>，样式就直接显示到了屏幕上。</p>



<blockquote class="callout example" data-callout="example">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            举例
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>原先设置字号需要设置<code>13px</code>、<code>14px</code> 等，现在只需要写text-xs 或者text-sm。原先需要设置圆角尺寸，都是需要传入特定的值，现在只需要传入 <code>rounded-md</code>、 <code>rounded-lg</code>。 阴影原先需要自己写，现在只需要写 <code>shadow-md</code>和<code>shadow-sm</code>。 换言之，只需要给出语义化的类名，就可以实现想要的效果，再也不需要关心琐碎的细节。</p>
        </p>
    </div>
</blockquote>



<blockquote class="callout tip" data-callout="tip">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            💡
            
            快速的可见即可得
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>从html元素的className中读到<code>bg-red</code>，直接就能看到页面元素的背景是红色的。而原来则需要多一次跳转：先看到类名，再找到样式实现。当整个系统都如此，对于降低心智负担是极为重要的。</p>
        </p>
    </div>
</blockquote>
<p>最棒的就是它内置了一套设计系统，包括尺寸、状态和色彩系统。</p>
<h3 id="1-尺寸">1. <strong>尺寸</strong></h3>
<p>直接使用符合响应式标准的单位<code>rem</code>，和一套语义化的封装： <code>sm</code>、 <code>md</code>、<code>lg</code>……换言之，我们不需要再考虑这个按钮到底应该占多大，而是应该从它实际的功能和使用场景出发，考虑它应该是大、中等还是小。</p>
<h3 id="2--色彩">2.  <strong>色彩</strong></h3>
<p>内置了多种标准颜色的色彩系统，也支持自己定义色调梯度，使用颜色+渐进数值来提供原始色值和类名的对应关系，更符合直觉：需要颜色更深一些，数字就更大，需要颜色更小一些，数字就更浅。</p>
<p>举例：主要按钮使用 <code>bg-blue-600</code>, 背景色使用 <code>bg-blue-100</code> ,而不需要再去调色盘查看哪个是主要的蓝色，哪个是浅蓝色。透明度也不需要自己写了，而是直接使用形如<code>bg-blue-600/80</code>的格式，就是透明度80%的意思。</p>
<h3 id="3-状态">3. <strong>状态</strong></h3>
<p>在写了一套样式后，我们需要考虑它处于不同上下文的种种状态，例如：<strong>在不同屏幕尺寸下/明暗或者不同主题下/用户不同行为下/数据不同状态下</strong>等，我将其统一称之为“状态”。</p>
<p>举例：
① 在电脑上可以排列成每行四个的卡片，在手机屏幕上只能放得下每行一个，不然内容放不下。间距也应该随之缩小。</p>
<p>② 按钮在悬停时应该有特殊的样式，给用户以“我正在交互热区”的正反馈，当按钮不能点击时，应该有<code>disabled</code> 的状态。当正在从接口请求数据时，页面和按钮应该是<code>loading</code>状态。</p>
<p>③ 在黑暗模式下，所有的色彩都需要调整成适配的样子。例如原本是白底黑字，暗黑模式就应该是黑底白字。</p>
<p>TailwindCSS支持使用冒号来响应状态查询、容器查询和媒体查询。例如：<code>bg-red-500 hover:bg-red-600</code> ,意思就是在hover状态下背景颜色加深。</p>
<p>可读性非常强。对于响应式的尺寸设计，也可以通过<code>md: </code> 、 <code>sm:</code> 的格式来实现。</p>
<p>提供了一整套内置的设计系统外，也支持传入特定值来支持特殊的样式，既保留了系统的优雅简洁，也保留了开放和灵活性。目前很多UI框架都使用TailwindCSS实现。</p>
<p>大量使用了TailwindCSS编程后，也会觉得抽象粒度不够，写起来费劲。我们终究是需要抽象出来一套标准的UI组件复用在系统中。那么怎么去设计这一套UI组件呢？</p>
<h1 id="radix-ui-themes完善的样式系统都有哪些东西">Radix UI Themes：完善的样式系统都有哪些东西</h1>
<p>在研究样式系统时，我看到了
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cucmFkaXgtdWkuY29tLw%3d%3d"
    
     target="_blank"
>
Radix UI</a> ， 精致漂亮色彩丰富，完完全全是我喜欢的样子。阅读它<code>Theme</code> 的源码，让我体悟到了样式系统的设计原则。</p>
<h2 id="语义化design-token的运用">语义化：Design Token的运用</h2>
<p>我需要一个创建的按钮，希望它足够醒目、重要。它是蓝色的（蓝色是我们的主色调！）</p>
<p>别再写<code>background-color: blue</code>了！</p>
<p>对，用tailwindcss写起来简洁一些……<code>bg-blue-600</code>……不，也别这么写了！</p>
<p>应该写的是： bg-accent-6，意思是第六梯度的主色调，这样主色调变了，也不需要去改动这一部分的代码。</p>
<p>这就是我们应该做的：在写样式时，我们写语义化的语句（给它重要的颜色，重要的字重，重要的阴影，hover时颜色变深），然后再通过给变量赋值的方式，来使其真正生效（重要的颜色是什么颜色？hover时具体颜色变深多少？），这样我们只需要写一次，就可以实现各种不同的主题。</p>
<h2 id="业务层逻辑的剥离样式系统只负责样式">业务层逻辑的剥离：样式系统只负责样式</h2>
<p>对于一个重要的按钮，我们写出来的可能是这样：</p>
<p><code>bg-accent-6 hover:bg-accent-7 shadow-md text-md</code> ……</p>
<p>把这些封装在工具类中，最终给这个重要的按钮的类名就是 ——</p>
<p><code>btn-primary</code>？</p>
<p>不。这样就是样式系统与业务逻辑再一次重叠和混淆了。primary 的意思是主要的，包含着一种业务逻辑层面的判断。正确的命名应该是只说明样式，这样上层调用时不会造成任何歧义和混淆。</p>
<p>Radix UI的做法是给出了六种变体（variant）：<code>classic</code> 、<code>solid</code> 、 <code>soft</code>、<code>surface</code>、 <code>ghost</code>、 <code>outline</code>。 在使用的时候，当然可以根据业务逻辑去判断出，<code>classic</code>和<code>solid</code>一般用于一级按钮，<code>soft</code>和<code>surface</code>用于二级按钮……但那是业务层关心的事。</p>
<p><img src="https://images.ygria.site/2025/09/2aa2b55c28ff3ca5d4154d830720f821.png" alt="Pasted image 20250916152917" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="足够低的优先度给扩展提供的可能性">足够低的优先度，给扩展提供的可能性</h2>
<p>Radix Theme中所提供出的所有组件，都支持外部传入的样式进行覆盖和改写，保留了足够的灵活度。我们尽可以继续使用TailwindCSS来加上自己喜欢的样式。</p>
<h1 id="总结">总结</h1>
<p>样式系统必不可少！</p>
<p>将复杂琐碎的样式细节隐藏起来，写业务代码时只需要关注交互逻辑、数据流向等，而无需再关注样式……这也就是样式系统的意义所在。</p>
<p>而对于跨端开发的交互方面的抽象，又是另一个问题了……等我踩完坑，再写一篇。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>0成本，使用Github Action做一个外语PDF翻译工作流</title>
      <link>https://ygria.site/pdf-translate/</link>
      <pubDate>Thu, 10 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/pdf-translate/</guid>
      <description>&lt;blockquote class=&#34;callout abstract&#34; data-callout=&#34;abstract&#34;&gt;
    &lt;div class=&#34;callout-title&#34;&gt;
     
        &lt;div class=&#34;callout-title-inner&#34;&gt;
           
            
            
            Abstract
            
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&#34;callout-content&#34;&gt;
        &lt;p&gt;
            &lt;p&gt;看纯英文PDF太费劲，不想用别人的网站或插件，机翻质量差、速度慢、容易卡死，用浏览器插件大PDF容易失败。
不如自己手搓一个工作流来做翻译，只需要往文件夹里一丢，提交到Github仓库，Action工作流会自动触发，自动翻译，翻完了也是自动上传到Github 仓库里。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="0成本，使用Github Action做一个外语PDF翻译工作流" />


<blockquote class="callout abstract" data-callout="abstract">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            Abstract
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>看纯英文PDF太费劲，不想用别人的网站或插件，机翻质量差、速度慢、容易卡死，用浏览器插件大PDF容易失败。
不如自己手搓一个工作流来做翻译，只需要往文件夹里一丢，提交到Github仓库，Action工作流会自动触发，自动翻译，翻完了也是自动上传到Github 仓库里。</p>
        </p>
    </div>
</blockquote>



<blockquote>
    <p>效果预览：出来的结果是一个双语，一个纯汉语，可以打开双语的对照着看，阅读速度大大提升，翻译得有瑕疵也可以看原文澄清歧义。下图是翻译了谷歌新出的提示词工程白皮书</p>
</blockquote>
<p><img src="https://images.ygria.site/2025/04/7daf0284d1c6aebb66930b8cdfbf4dbc.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="pdfmathtranslate">PDFMathTranslate</h2>
<p><code>python</code> 语言的 <code>pdf2zh</code>库，支持多种语种，翻译的同时会保持原有的段落格式，支持传入文件夹，支持多种大语言模型接入。默认的机器翻译的质量很差，必须接入大模型翻译或专有翻译服务。</p>



<blockquote class="callout question" data-callout="question">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            默认谷歌的翻译，把“摘要”翻译成了“抽象的”，翻译质量较差
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            
        </p>
    </div>
</blockquote>
<p><img src="https://images.ygria.site/2025/04/2d52dcffe74ef9c5b5e18bc7694c8ac0.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL0J5YWlkdS9QREZNYXRoVHJhbnNsYXRl"
    
     target="_blank"
>
PDFMathTranslate</a></p>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL0J5YWlkdS9QREZNYXRoVHJhbnNsYXRlL2Jsb2IvbWFpbi9kb2NzL0FEVkFOQ0VELm1kI3NlcnZpY2Vz"
    
     target="_blank"
>
PDFMathTranslate 支持的Service</a></p>
<p>支持的服务如下表</p>
<p><img src="https://images.ygria.site/2025/04/b35c509b217c586d8024132802593a6c.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
<img src="https://images.ygria.site/2025/04/e8c99565a11f5340981a9055eb1d3fc2.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>可以看到，支持了非常多的AI模型和AI开放平台。我试了DeepL、Gemini、Qwen/Qwen2.5-7B-Instruct和Deepseek V3，感觉DeepSeek V3表现较好。</p>
<p>使用方法是通过<code>-s</code>指定服务，并将参数的<code>Value</code>写入到环境变量中。以OPEN_AI举例，Windows环境下用<code>$env:OPENAI_API_KEY=</code>指定，Mac环境用<code>export OPENAI_API_KEY=</code>指定，同时支持传入自定义的提示词等。</p>



<blockquote class="callout hint" data-callout="hint">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            Hint
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>如果有其他语种的翻译输入/输出需求，也可以通过-li和-lo指定，但需要使用的翻译服务能够支持。</p>
        </p>
    </div>
</blockquote>
<h2 id="搭建工作流">搭建工作流</h2>
<p>新建Github项目，在<code>.github/workflows</code>文件夹下新建<code>.yaml</code>文件，写入如下配置内容即可。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> yaml</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="yaml" data-theme="breeze" id="code-id-1">name: Translate PDF with Python
on:
  push:
    paths:
      - &#39;papers/*.pdf&#39;
  workflow_dispatch:
jobs:
  convert:
    runs-on: ubuntu-latest
    env:
      SILICON_API_KEY: ${{ secrets.SILICON_API_KEY }}
      SILICON_MODEL: ${{ secrets.SILICON_MODEL }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3
      - name: Set up Python 3.12
        uses: actions/setup-python@v5
        with:
          python-version: 3.12
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pdf2zh
      - name: Translate all PDFs in papers/
        run: |
          mkdir -p translation_output archive
          pdf2zh --dir papers/ -o translation_output -s silicon
          mv papers/*.pdf archive/
      - name: Commit and Push Translated Files &#43; Archived PDFs
        run: |
          git config --global user.name &#34;github-actions[bot]&#34;
          git config --global user.email &#34;github-actions[bot]@users.noreply.github.com&#34;

          git add translation_output/*.pdf archive/*.pdf
          
          # Stash changes to ensure no conflict when pulling
          git stash || echo &#34;No local changes to stash&#34;

          git commit -m &#34;Auto translate PDF and archive originals&#34; || echo &#34;No changes to commit&#34;

          # Pull with rebase to avoid conflicts
          git pull origin main --rebase || exit 1

          # Apply the stashed changes and commit them
          git stash pop || echo &#34;No changes to apply from stash&#34;

          # Add any unstaged changes from the stash
          git add .

          # Commit and Push
          git commit -m &#34;Reapply stashed changes&#34; || echo &#34;No changes to commit&#34;

          # Retry push if failed
          for i in 1 2 3; do
            git push origin main &amp;&amp; break
            echo &#34;Push failed, retrying in 5 seconds... ($i)&#34;
            sleep 5
            git pull origin main --rebase || break
          done</div>
    </div>
  </div>
</div><h3 id="触发">触发</h3>
<p>我设定的触发条件是在papers/文件夹中提交了pdf文件时触发。这样只要把文件放到papers文件夹下并提交，就会自动运行。增加workflow_dispatch:是也支持手动触发调试。可根据需求，更改为webhook触发、其他工作流触发或者定时触发等。</p>
<h3 id="环境变量">环境变量</h3>
<p>在 <code>/settings/secrets/actions</code> 中可配置使用的Key和模型。</p>
<p>使用硅基流动的API，可以选用多种模型。实测跑一份几十页的PDF大概花费赠送余额1毛多，没有注册的朋友欢迎点下方链接，使用我的邀请码注册，你我都可以获赠赠送余额十四元～</p>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9jbG91ZC5zaWxpY29uZmxvdy5jbi9pL1BjTnpOTzY1"
    
     target="_blank"
>
点击注册硅基流动</a></p>
<p>使劲测试消耗了两元巨款⬇️</p>
<p><img src="https://images.ygria.site/2025/04/50d3dbd9fd36d4353c492d774f1312cb.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2025/04/4ca1f7bd7333af2589a67751072b149f.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="执行">执行</h3>
<p>执行过程就是安装python环境、安装依赖、命令行执行。将执行出的pdf文件放到<code>translation_output</code> 文件夹中，将已翻译好的pdf挪到 <code>archive</code>文件夹中。</p>



<blockquote class="callout hint" data-callout="hint">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            Hint
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>注意最好在<code>papers</code>文件夹下放一个后缀不是pdf文件的文件做占位，不然pdf被挪走后，空文件夹会被自动删掉 。我是放了一个名字就叫placeholder的无后缀空文件做占位。</p>
        </p>
    </div>
</blockquote>
<h3 id="翻译结果提交到库">翻译结果提交到库</h3>
<p>由于翻译可能执行的时间很长，过程中可能库内文件发生了变更，再做提交时就会发生冲突。</p>
<p>所以提交前并先stash当前内容，再做一次拉取，拉取库内最新的文件，再提交。避免因为冲突，导致文件提交失败。</p>
<h1 id="使用">使用</h1>
<p>使用起来很简单，就是把需要翻译的文件放到papers文件夹下，提交到github上。等一段时间（过程较长，大的pdf可能要四五十分钟甚至一个多小时，不过都是自动、在云端完成的）翻译完成了，再拉取一下，可以看到翻译结果已经放到translation_output文件夹下啦。</p>
<p>可以翻译书籍、论文等等～</p>
<p><img src="https://images.ygria.site/2025/04/2d2aa600e0d89f04f7bb93ee2614afce.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
<img src="https://images.ygria.site/2025/04/99f4134b33446ea31b4eae7eafaeddc4.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>用Cloudflare Worker做一个微信封面图生成服务</title>
      <link>https://ygria.site/cloudflare-worker-wx-article-cover/</link>
      <pubDate>Wed, 02 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/cloudflare-worker-wx-article-cover/</guid>
      <description>&lt;p&gt;每次写完微信公众号文章，配封面总是一件麻烦事。内容中的图片常常分辨率不匹配，裁剪之后预览效果不好，AI 生图的效果也不稳定。&lt;/p&gt;
&lt;p&gt;于是我使用这个方案： LLM 推理文章标题、高亮热词、四字总结，使用 Cloudflare Worker 部署一个服务，支持传入 LLM 推理出的结果生成封面图，测试效果如下：&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="用Cloudflare Worker做一个微信封面图生成服务" /><p>每次写完微信公众号文章，配封面总是一件麻烦事。内容中的图片常常分辨率不匹配，裁剪之后预览效果不好，AI 生图的效果也不稳定。</p>
<p>于是我使用这个方案： LLM 推理文章标题、高亮热词、四字总结，使用 Cloudflare Worker 部署一个服务，支持传入 LLM 推理出的结果生成封面图，测试效果如下：</p>
<p><img src="https://images.ygria.site/2025/04/b340549affd0493719e500a565448469.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="cloudflare-worker实现">Cloudflare Worker实现</h1>
<h2 id="如何绘制">如何绘制？</h2>



<blockquote class="callout question" data-callout="question">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            Cloudflare Worker 环境限制
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>Cloudflare Worker 有诸多基于性能和安全考虑的环境限制，比如无法开子进程、无法读写本地文件等，我一开始想的是直接使用大模型生成 html，然后绘制成图。但 Worker 中无法使用浏览器的绘制功能（需要引入Puppeteer 等无头浏览器库），调用外部 API 实现的基本是渲染在一个浏览器上，再截图，这样出来的效果也容易模糊，且依赖外部有不稳定风险。</p>
        </p>
    </div>
</blockquote>
<p>那么直接用 <code>svg</code> 生成，再转成 <code>png</code> 或 <code>jpeg</code>  呢？（微信封面不支持 svg 格式）</p>
<p><strong>SVG 有如下好处：</strong></p>
<ol>
<li>
<p><strong>分辨率灵活，好扩展。</strong> 想要做什么分辨率的图片都可以，不管 1:1 还是 16:9，还是微信公众号封面要求的 2.35:1</p>
</li>
<li>
<p><strong>矢量图</strong>不管渲染到多大尺寸都<strong>清晰</strong>，占用空间小</p>
</li>
<li>
<p><strong>各种样式、图案都可以画出来</strong>，只要定义好模板，替换内容即可。如果是文字排版，那更是只要准备几套就行。</p>
</li>
</ol>
<h2 id="定义-svg-模板">定义 SVG 模板</h2>
<p>在 <code>GPT</code> 老师的帮助下生成了 <code>generateSVG</code> 方法，主要实现的功能是将传入的 <code>title</code> 标题、<code>hightlights</code> 高亮、<code>summary</code> 四个大字填入 svg 中。</p>



<blockquote class="callout tip" data-callout="tip">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            💡
            
            Tip
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>SVG 内容可以后续根据版式设计再调整。我这里是截图了一个别人用 AI 生成的封面，让 GPT 尽量为我还原的。图片左侧可以截图当 2.35:1的封面，右侧四个大字是转发时预览的 1:1封面。
<strong>如下图 , 图片大小为 1283 × 383，这样左侧可以裁出 900 × 383 的 2.35:1封面，右侧是 1 × 1 的消息卡片、转发到朋友圈的视图。注意尺寸必须正确，不然后续发布到草稿箱，裁剪会出错。</strong></p>
<p><img src="https://images.ygria.site/2025/04/7c3bf37707415aef5129a2ef308869a6.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
        </p>
    </div>
</blockquote>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> typescript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="typescript" data-theme="breeze" id="code-id-1">function generateSVG({ highlights, summary, title }) {
    const highlightWords = highlights.split(&#34;, &#34;).map(word =&gt; word.trim());
    // 高亮 title 中的关键词
    function highlightText(text) {
        let parts = [];
        let lastIndex = 0;
        highlightWords.forEach(word =&gt; {
            let index = text.indexOf(word);
            if (index !== -1) {
                if (index &gt; lastIndex) {
                    parts.push(text.slice(lastIndex, index));
                }
                parts.push(`&lt;tspan fill=&#34;#FFD700&#34;&gt;${word}&lt;/tspan&gt;`);
                lastIndex = index &#43; word.length;
            }
        });

        if (lastIndex &lt; text.length) {
            parts.push(text.slice(lastIndex));
        }
        return parts.join(&#34;&#34;);
    }

    // 计算左侧区域的占比
    
    const textStartX = 50; // 文字起始 x 轴
    const maxCharsPerLine = 10; // 每行最多 10 个字符
    const fontSize = 50; // 字体大小
    const lineSpacing = 60; // 行距
    // 处理 title 自动换行
    const titleLines = title.match(new RegExp(`.{1,${maxCharsPerLine}}`, &#34;g&#34;)) || [];
    const titleSVG = titleLines.map((line, index) =&gt;
        `&lt;text x=&#34;${textStartX}&#34; y=&#34;${80 &#43; index * lineSpacing}&#34; font-size=&#34;${fontSize}&#34; fill=&#34;#FFFFFF&#34; font-weight=&#34;bold&#34;&gt;${highlightText(line)}&lt;/text&gt;`

    ).join(&#34;\n&#34;);
    // 右侧大字拆分
    const summaryWords = summary.split(&#34;&#34;);
    const summaryFirstLine = summaryWords.slice(0, 2).join(&#34;&#34;);
    const summarySecondLine = summaryWords.slice(2).join(&#34;&#34;);
    return `&lt;svg width=&#34;1283&#34; height=&#34;383&#34; viewBox=&#34;0 0 1283 383&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;
        &lt;rect width=&#34;100%&#34; height=&#34;100%&#34; fill=&#34;#000&#34;/&gt;
        &lt;!-- 左侧标题 --&gt;
        ${titleSVG}
        &lt;!-- 右侧大字 --&gt;
        &lt;text x=&#34;950&#34; y=&#34;140&#34; font-size=&#34;80&#34; fill=&#34;#FFFFFF&#34; font-weight=&#34;bold&#34;&gt;${summaryFirstLine}&lt;/text&gt;
        &lt;text x=&#34;950&#34; y=&#34;220&#34; font-size=&#34;80&#34; fill=&#34;#FFD700&#34; font-weight=&#34;bold&#34;&gt;${summarySecondLine}&lt;/text&gt;
    &lt;/svg&gt;`;

}</div>
    </div>
  </div>
</div><h2 id="究极折磨如何-svg-转-png">究极折磨：如何 svg 转 png</h2>



<blockquote class="callout question" data-callout="question">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            svg 转 png
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p><code>svg</code> 转 <code>png</code> 在其他环境很简单，比如 <code>node</code> 和浏览器环境。但在 <code>Worker</code> 里，使用 <code>sharp</code> 和 <code>resvg</code> 等绘制库都会报<strong>依赖缺失</strong>。最后只能用 <code>wasm</code> 来做。我这里在 AI 的忽悠下走了很多弯路，自己用 <code>go</code> 手搓再编译成 <code>wasm</code>，再在 worker中引入，还需要给一个 <code>go</code> 运行环境……总之就是折腾了半天也没成功。最后发现 <code>resvg</code> 直接就有  <code>cf-wasm</code> 库，贴心注明了适合在 Cloudflare Worker 环境内运行……走了好多弯路！给我的教训是不要迷信 AI ，某些垂直领域掌握一些搜索技巧，可以获得比 AI 精准很多的结果</p>
        </p>
    </div>
</blockquote>



<blockquote>
    <p>在 npm 使用 <code>wasm</code>  和 <code>@cf-wasm</code> 作为关键字，就可以搜到支持 Cloudflare Worker 环境可以用的 <code>wasm</code> 库。
<img src="https://images.ygria.site/2025/04/5c7bbd2084d7d5498615256f917751cb.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
<img src="https://images.ygria.site/2025/04/c4fbeafdd57df672be75a62b36bc14b8.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
</blockquote>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> typescript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="typescript" data-theme="breeze" id="code-id-2">import { Resvg } from &#34;@cf-wasm/resvg&#34;;

const svg =  generateSVG({highlights, summary, title})
const resvg = new Resvg(svg, opts)
const pngData = resvg.render()
const pngBuffer = pngData.asPng();</div>
    </div>
  </div>
</div><p>获取到了 <code>png</code> 内容，就可以直接调用微信开放平台 API，获取到的 mediaId 可以在后续调用新增微信草稿时使用，作为封面图传入了。</p>
<h2 id="文本出不来手动写入字体数据">文本出不来？手动写入字体数据</h2>
<p>由于 <code>Cloudflare Worker</code> 运行环境无系统字体，所以必须传入字体文件内容。<code>Cloudflare Worker</code> 也不支持读写本地文件，所以要么通过 <code>fetch</code> 从外部环境拉取，要不从存储中读入。</p>
<p>考虑性能，使用 Cloudflare 自带的KV 库存储字体文件。</p>
<h3 id="cloudflare-worker-使用-kv">Cloudflare Worker 使用 KV</h3>
<p>参考 CF 官方文档即可。 👉 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kZXZlbG9wZXJzLmNsb3VkZmxhcmUuY29tL2t2Lw%3d%3d"
    
     target="_blank"
>
https://developers.cloudflare.com/kv/</a></p>
<p>Worker 绑定 KV，在设置-绑定中。注意检查 Worker项目的 <code>wrangler.toml</code> 是否正确。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> toml</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="toml" data-theme="breeze" id="code-id-3">[[kv_namespaces]]
binding = &#34;Fonts&#34;
id = &#34;XXXXXX&#34;  # 替换为你的实际 Namespace ID</div>
    </div>
  </div>
</div><p><img src="https://images.ygria.site/2025/04/aeb997f5a572f3a8b29b0895c1c2c26b.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>CF Dashboard 只支持新增 Value 类型为 <code>string</code> 的条目，字体为二进制文件，所以需要上传。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> bash</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="bash" data-theme="breeze" id="code-id-4">npx wrangler kv key put --binding=&lt;BINDING_NAME&gt; &#34;&lt;KEY&gt;&#34; --path ./font.ttf</div>
    </div>
  </div>
</div><p>注意，CF 区分了本地测试环境和生产环境，这时虽然控制台显示写入正常，但生产环境找不到这条数据。</p>
<p><strong>必须加上 <code>--remote</code>，才是向生产环境写入！</strong></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> bash</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="bash" data-theme="breeze" id="code-id-5">npx wrangler kv key put --binding=&lt;BINDING_NAME&gt; &#34;&lt;KEY&gt;&#34; --path ./font.ttf --remote</div>
    </div>
  </div>
</div><p>写入成功后，可以去 Cloudflare Dashbord 看一眼，KV 库里有没有数据。左上角就是 namespace ID，注意别配错了。</p>
<p><img src="https://images.ygria.site/2025/04/18d11b89d57385794f3b716b64c2d6a4.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>我向 KV 中写入了阿里巴巴普惠字体。之后可以考虑传入一些免费设计字体，用于生成更美观的封面。</p>
<h3 id="读-kv-库内容并传到-resvg-渲染">读 KV 库内容，并传到 resvg 渲染</h3>
<p>在 Worker 中使用：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> typescript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="typescript" data-theme="breeze" id="code-id-6">const font = await env.Fonts.get(&#34;Puhui&#34;, { type: &#34;arrayBuffer&#34; })
const fontBuffer = new Uint8Array(font);
if (!fontBuffer) {
  return new Response(&#34;Font not found in KV&#34;, { status: 404 });

}

const opts = {
        background: &#39;rgba(238, 235, 230, .9)&#39;,
        fitTo: {
            mode: &#39;width&#39;,
            value: 1283,
        },
        font: {
            fontBuffers: [fontBuffer]
        }
    }

const resvg = new Resvg(svg, opts)
//  ...</div>
    </div>
  </div>
</div><h2 id="deepseek-v3-生成标题热词摘要">Deepseek V3 生成标题、热词、摘要</h2>
<p>现在我们可以在 coze 工作流中配置 LLM，为我的文章内容生成标题、热词和摘要了。</p>
<p><img src="https://images.ygria.site/2025/04/74c55171d4f77989f97cf09c4746cd7a.jpg" alt="53161a038f0b0f30f2b859c0a8d8829.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>生成的内容可以直接用于下一步调用 Worker 接口的入参。生成图片后，调用微信 API 获得 MediaID。</p>
<h2 id="向微信草稿箱写入封面裁剪参数">向微信草稿箱写入封面、裁剪参数</h2>
<p>写入微信草稿箱，将上一步获取到的 MediaId 传入，另外需要计算裁剪比例，左侧作为消息列表中 2.35:1的文章封面，右边为转发和历史消息里的 1:1 封面。</p>



<blockquote>
    <p>先看一下微信官方文档给出的参数解释：</p>
</blockquote>
<table>
  <thead>
      <tr>
          <th>pic_crop_235_1</th>
          <th>否</th>
          <th>封面裁剪为2.35:1规格的坐标字段。以原始图片（thumb_media_id）左上角（0,0），右下角（1,1）建立平面坐标系，经过裁剪后的图片，其左上角所在的坐标即为（X1,Y1）,右下角所在的坐标则为（X2,Y2），用分隔符_拼接为X1_Y1_X2_Y2，每个坐标值的精度为不超过小数点后6位数字。示例见下图，图中(X1,Y1) 等于（0.1945,0）,(X2,Y2)等于（1,0.5236），所以请求参数值为0.1945_0_1_0.5236。</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>pic_crop_1_1</td>
          <td>否</td>
          <td>封面裁剪为1:1规格的坐标字段，裁剪原理同pic_crop_235_1，裁剪后的图片必须符合规格要求。</td>
      </tr>
  </tbody>
</table>
<p>同样地，让 <code>GPT</code> 老师帮我们算一下。</p>
<p><img src="https://images.ygria.site/2025/04/cc39bc13ec1ba50f8f0a96f1a583d9c0.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
<img src="https://images.ygria.site/2025/04/803b9046d1b53027e005823705476686.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> typescript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="typescript" data-theme="breeze" id="code-id-7">let body = {

        articles: [

            {

                title: data.title,
                content: data.content,
                digest: data.digest,
                thumb_media_id: data.thumb_media_id,
                pic_crop_235_1: &#39;0_0_0.7015_1&#39;,
                pic_crop_1_1: &#39;0.7015_0_1_1&#39;,
                // ... 其他参数

            }

        ],

    };</div>
    </div>
  </div>
</div><p>Ok，这样就完成啦！看下效果~
第一张图是消息列表里的图。</p>
<p><img src="https://images.ygria.site/2025/04/49e6f6d81dcc97f2a76a4154a5a52d95.jpg" alt="9da521a62f54a8fbbd85817009cb46f.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>第二张是转发时看到的。</p>
<p><img src="https://images.ygria.site/2025/04/f3674b90c5f11ef44f31fb4725a320e3.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>仅为测试效果，后续还会继续做样式改进~<strong>请朋友们尽情发挥自己的审美吧！</strong></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>ChatGPT支持MCP！什么是MCP，怎么用？</title>
      <link>https://ygria.site/model-context-protocol/</link>
      <pubDate>Thu, 27 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/model-context-protocol/</guid>
      <description>&lt;p&gt;今天Sam Altman（OpenAI CEO）宣布ChatGPT即将支持MCP。有趣的是，推特热评是：什么是MCP？为什么现在很多人都在谈论MCP？
&lt;img src=&#34;https://images.ygria.site/2025/03/667032a731114d8a60879c4a1822a2f3.png&#34; alt=&#34;image.png&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="ChatGPT支持MCP！什么是MCP，怎么用？" /><p>今天Sam Altman（OpenAI CEO）宣布ChatGPT即将支持MCP。有趣的是，推特热评是：什么是MCP？为什么现在很多人都在谈论MCP？
<img src="https://images.ygria.site/2025/03/667032a731114d8a60879c4a1822a2f3.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2025/03/e7b193f133e2cebb718178ff761d72a6.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>



<blockquote class="callout abstract" data-callout="abstract">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            简述
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>MCP，即Model Context Protocol：模型上下文协议，为AI如何查找、连接和使用外部工具制定明确的规则，无论是查询数据库、执行命令等。MCP让信息孤岛有了联结的可能。
MCP具有动态发现的特性：AI代理自动检测可用的MCP服务和功能，无需硬编码集成，这和Coze、Dify等编排工具相比，更为解耦和便捷。</p>
        </p>
    </div>
</blockquote>
<h1 id="从天气查询开始">从天气查询开始</h1>
<p>让我们通过一个实际例子来举例MCP怎么用，和有什么用：</p>



<blockquote>
    <p>当我向大模型提问，合肥天气如何？我明天该穿什么衣服？</p>
</blockquote>
<p><img src="https://images.ygria.site/2025/03/41830b3b36c677f96f389d88fa3d0e85.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>可以看到，AI无法直接访问实时天气数据，只能给出操作指导，实际的操作需要人来参与完成。</p>
<h2 id="配置mcp-server">配置MCP Server</h2>
<p>最新版本的Cherry Studio （<code>v1.1.10</code> ）已支持MCP能力。我们可以配置一个高德地图的MCP Server。</p>
<p>打开<code>https://mcp.so/server/amap-maps/amap</code> , 复制右上角框中的url。</p>
<p><img src="https://images.ygria.site/2025/03/d82d5f93f54b3749cb3254ffdd6abfb8.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>可以点击编辑图标，填入自己的高德开放平台的Key。</p>
<p><img src="https://images.ygria.site/2025/03/b49ecf57b6d70dc31c31627146be654d.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>在Cherry Studio 设置-MCP 服务器中添加。</p>
<p><img src="https://images.ygria.site/2025/03/a9611e4e7f3f784738a91c650c55da6a.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>选择SSE，填入url。也可以直接编辑并粘贴进JSON。</p>
<p><img src="https://images.ygria.site/2025/03/2ced4b622d6648f33cc9bf4ed62e9d7b.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>如果能启用成功，则说明MCP服务已成功添加！之后就可以使用了。如果报错，请检查网络，或在mcp.so网页上先测试高德地图的API key是否能正常使用。</p>
<h2 id="对话时使用">对话时使用</h2>
<p>注意，需要选择带有工具调用功能的模型，带着橙色小扳手的就是。如果模型本身支持，但没有显示，可以点击⚙️编辑模型，设置-更多设置打开。
<img src="https://images.ygria.site/2025/03/7b6a274a889b9d9c78f439383c2ba9bd.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2025/03/58d588e4aafc42bd56a37764444184c2.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>在对话框下方，点击MCP服务器图标，打开MCP服务。如果没有这个图标，切换带工具🔧功能的模型。</p>
<p><img src="https://images.ygria.site/2025/03/9adc33cc3e1c669d31d170d0c7c0017a.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="提问示例">提问示例</h2>
<p>这时再提问，可以看到模型会调用高德地图的借口，查询合肥的天气，并根据天气，给出穿衣建议。</p>
<p><img src="https://images.ygria.site/2025/03/1fda5eeaee717f3c8df6ce620668fbd4.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>有了这样的工具，大模型能做的事情一下就扩展了，我可以让它基于天气，来为我制定更精准、详细的旅行计划。</p>
<p>展开工具调用的详情，可以看到调用函数的返回。</p>
<p><img src="https://images.ygria.site/2025/03/92b27d3667b69ffdd6e756ec6b93f7fd.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="有趣的应用">有趣的应用</h2>
<h3 id="fetch">Fetch</h3>
<p>配置fetch MCP，遇到网页，就不用再截图或把内容复制给AI了，直接告诉它链接，它可以自己去试图获取链接的内容。</p>
<p><img src="https://images.ygria.site/2025/03/0e9c1ae1b8b4de170c1d44d4df4f0e33.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>看到有人直接把figma设计稿的公开链接发给AI，AI就可以直接生成对应的前端页面了。又节约了时间～
当然，也会翻车，不少网站有反爬机制。</p>
<p><img src="https://images.ygria.site/2025/03/ba3f0777d7b120b064089f7f5186afd0.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="postgresql-超简易chattodb">PostgreSQL： 超简易ChatToDB</h3>
<p>PostgreSQL是一个开源的关系型数据库，它也提供了官方的MCP服务。正好我有一个数据库专门用来存自己的账本，只需要填入连接字符串，就可以直接执行query，让大模型访问我的数据库并做数据分析了！</p>
<p><img src="https://images.ygria.site/2025/03/ad4a8c11a73070794abffdeb1dca674a.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>填入连接串，点击Connect。最好直接在页面上测试一下数据能不能正常拿到。</p>
<p><img src="https://images.ygria.site/2025/03/49e345007d32dad5f7bf7855f784a5a9.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>测试方法：切到Tools，展开query，填入SQL并尝试执行。</p>
<p><img src="https://images.ygria.site/2025/03/14c5d4c5ed60f23e02d63757dfe83fc0.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>现在，我可以和AI讨论，让它生成SQL，并直接执行，基于执行结果给出分析。如果要做数据分析，这节约了非常多的代码量，并能实时动态调整查询条件、查询范围，并直接根据结果输出分析报告、分析图表等。</p>



<blockquote class="callout question" data-callout="question">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            我遇到的问题
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>但在这里，我遇到了问题，几轮对话后，可能是上下文丢失，工具调用并不能稳定触发，我使用的Gemini2.5开始胡编乱造数据，并在我再三要求之下，也没有再去实际查询数据库。同样的话术、同样的模型，有时候也会不调用工具就直接回答。不知道未来会不会有强制触发工具调用的功能。
<img src="https://images.ygria.site/2025/03/1e43fc52535e34265ee39a46850beb43.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
        </p>
    </div>
</blockquote>
<h3 id="不仅能读也能写">不仅能读，也能写</h3>
<p>以上举例都是读，而MCP不仅能读，也能写！
当前最广为流传的通过MCP调用Blender，就是可以通过自然语言，直接控制Blender 3D建模软件进行建模。</p>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tL3ZpZGVvL0JWMXRoUVBZZkVZQy8%2fc3BtX2lkX2Zyb209MzMzLjMzNy5zZWFyY2gtY2FyZC5hbGwuY2xpY2smdmRfc291cmNlPTU2OThkZjE4OWQ0NjAwYTFhZjJmMmNlZGFkOTE4YWYx"
    
     target="_blank"
>
https://www.bilibili.com/video/BV1thQPYfEYC/?spm_id_from=333.337.search-card.all.click&vd_source=5698df189d4600a1af2f2cedad918af1</a></p>
<h1 id="总结">总结</h1>
<h2 id="mcp与编排引擎的区别">MCP与编排引擎的区别</h2>
<p>没有MCP，LLM只能通过人为指定的方式来访问数据，并且业务一旦变更或拓展，都需要重新编排流程，一百个新功能需要一百个新的流程。</p>



<blockquote class="callout cite" data-callout="cite">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            Cite
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>MCP 本身并不是一个“代理框架”，而是充当代理的标准化集成层。MCP 完全是关于操作部分，具体来说，它为代理提供了一种标准化的方式来执行涉及外部数据或工具的操作。它提供了以安全、结构化的方式将 AI 代理连接到外部世界的管道。如果没有 MCP（或类似的东西），每次代理需要在现实世界中执行某项操作时（无论是获取文件、查询数据库还是调用 API），开发人员都必须连接自定义集成或使用临时解决方案。这就像建造一个机器人，但必须定制每根手指来抓取不同的物体，这既繁琐又不可扩展。ref：[[https://huggingface.co/blog/Kseniase/mcp#so-what-is-mcp-and-how-does-it-work]]</p>
        </p>
    </div>
</blockquote>
<p>而有了MCP，相当于LLM有了多个访问外部的工具箱，大模型自己决定需要使用什么工具，而且完全是解耦的：可以任意更换需要使用的大模型，也可以不停迭代可以提供给大模型的工具箱。</p>
<p>除了软件层的数据接口，MCP还能与物联网设备、传感器、操作系统功能进行交互，也许未来人人都是钢铁侠，每个人都可以拥有一个贴心的AI管家，全方面地照顾人的起居和生活。</p>
<h2 id="mcp存在的疑问">MCP存在的疑问</h2>
<p>目前看来，MCP是大势所趋，越来越多的软件宣布支持，社区热度也越来越高，也许这就是AI落地应用的最终答案。</p>
<p>当然，MCP目前存在不稳定、可能有数据泄露隐患等风险。这也是未来我们需要面对的挑战。</p>
<p>可以使用Cloudflare提供的框架（注意必须是付费用户），封装并发布自己的MCP服务。我相信应该会有更多的框架出来，所有想拥抱AI的方向都存在着巨大的应用前景。</p>
<h2 id="我的展望">我的展望</h2>
<p>MCP有可能颠覆当前的客户端-服务端架构，未来也许前端（也就是客户端）只需要做自然语言的输入（也许加上多模态，比如语音输入等），后端只需要封装符合MCP的原子能力，二者通过大模型来作为中介。</p>
<p>想象一下，以后各类设计、工程软件都支持了MCP，以后修图、剪辑、编曲、建模、做实验都不再需要动手，而只需要动动嘴皮子，向AI表达意图，所有需要动手的工作都可以交给大模型和机器人来进行……</p>
<p>未来世界的工作模式将会是什么样呢？非常期待！</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Obsidian全面拥抱AI：插件Copilot和Text Generator使用</title>
      <link>https://ygria.site/obsidian-copilot-and-text-generator/</link>
      <pubDate>Sat, 08 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/obsidian-copilot-and-text-generator/</guid>
      <description>&lt;blockquote class=&#34;callout abstract&#34; data-callout=&#34;abstract&#34;&gt;
    &lt;div class=&#34;callout-title&#34;&gt;
     
        &lt;div class=&#34;callout-title-inner&#34;&gt;
           
            
            
            Abstract
            
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&#34;callout-content&#34;&gt;
        &lt;p&gt;
            &lt;p&gt;全面AI浪潮来了！你的Obsidian仓库还没有接入AI吗？推荐两个插件，轻松让你的笔记库全面接入大模型（Deepseek、ChatGPT、Gemini……你原先用什么，就可以接入什么），把自己本地的文件库变成个人知识库，让AI帮你生成笔记的一句话摘要，还有更多玩法等待探索～&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Obsidian全面拥抱AI：插件Copilot和Text Generator使用" />


<blockquote class="callout abstract" data-callout="abstract">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            Abstract
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>全面AI浪潮来了！你的Obsidian仓库还没有接入AI吗？推荐两个插件，轻松让你的笔记库全面接入大模型（Deepseek、ChatGPT、Gemini……你原先用什么，就可以接入什么），把自己本地的文件库变成个人知识库，让AI帮你生成笔记的一句话摘要，还有更多玩法等待探索～</p>
        </p>
    </div>
</blockquote>
<h1 id="copilot">Copilot</h1>
<p>当需要根据特定垂类内容进行深度分析时，经常会用到知识库，与单纯使用 AI 相比，使用自己选择的内容和材料，有如下好处：</p>
<ol>
<li>使用自己的知识库，可以让内容更符合个人认知，也能使用过程中的反馈帮助梳理自己的内容。</li>
<li>更贴合个人场景，更加垂直</li>
</ol>
<p>Obsidian 插件 Copilot ，可以直接基于你本地的内容仓库生成知识库，然后直接基于整个内容仓库（Vault）或者单文件做问答交互。</p>
<h2 id="1--obsidian-安装-copilot-插件">1 . Obsidian 安装 Copilot 插件</h2>
<p>设置-第三方插件（需先关闭安全模式）。</p>
<p><img src="https://images.ygria.site/2025/02/8eb0c94e5df230cbd22d762918f4ce7f.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="2-配置模型">2. 配置模型</h2>
<p>在设置-Model 中配置使用的模型（需要两个模型：Chat Models （对话模型）和 Embedding Models</p>
<p><img src="https://images.ygria.site/2025/03/a058836d4b1a65c17b77d51d4c3278cf.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<ul>
<li>在火山引擎/硅基流动/谷歌 AI Studio……等 AI 开放平台，配置推理节点，或使用免费开放的大模型，获取 API 地址和 Key</li>
<li>配置两个模型（==往下滚动配置 Embedding 模型，注意不要配错位置了！==）</li>
<li>配置时可以点击 <code>verify</code> 验证配置的地址是否有效
Chat Model
<img src="https://images.ygria.site/2025/03/0c1e7c193b12f8b1c83341d97c61200d.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>
<p>如果使用的模型在默认列表中，直接填上 key 就可以了。否则可以添加使用 OpenAI Format 协议的任意自定义模型。</p>
<p>Embedding Models：我使用的是 BAAI/bge-m3</p>
<p><img src="https://images.ygria.site/2025/03/f38385d448fd19545357a32fc6f77ef0.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<ul>
<li>在 Basic-general 中配置使用的模型
<img src="https://images.ygria.site/2025/03/72cb7cb9b2287b7e0b05ac6e8890c2a6.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>



<blockquote class="callout example" data-callout="example">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            Example
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>如果你使用了第三方模型接入平台，也可以配置到 Copilot 里。下面给出硅基流动和火山引擎的配置方法。</p>
        </p>
    </div>
</blockquote>
<h3 id="配置硅基流动中的模型">配置硅基流动中的模型</h3>
<p><strong>第一步</strong>：登入硅基流动</p>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9jbG91ZC5zaWxpY29uZmxvdy5jbi9tb2RlbHM%3d"
    
     target="_blank"
>
https://cloud.siliconflow.cn/models</a></p>
<p>模型广场-搜索模型名称</p>
<p><img src="https://images.ygria.site/2025/03/535e1f61f05c742ff397944d3526f446.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><strong>第二步</strong>：点击卡片，展开详情，点击复制按钮复制模型名称</p>
<p><img src="https://images.ygria.site/2025/03/b012152e6fa9e6f42e68eb7c7a699afd.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><strong>第三步</strong>：配置 API 调用的密钥</p>
<p><img src="https://images.ygria.site/2025/03/a3c06375922328c5f522ac188a4d8c55.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>如果已有密钥，直接复制。没有的话点击新建密钥。</p>
<p><strong>第四步</strong>：在 Copilot 插件的设置中配置</p>
<p>按如下图填写并配置。Model Name 就是第二步中复制的。Display Name 可以自己填，后续在其它选项中可以选择。 Provider 选 OpenAI Format，Base Url 请求基础地址： <code>https://api.siliconflow.cn/v1</code> ，API Key 是第三步中生成的。填写完成后，点击“Verify”，右上角弹出 Model Verify Successfu 表明验证成功。</p>
<p><img src="https://images.ygria.site/2025/03/acd27e8404bfabe594c307ba4cf932b5.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="配置火山引擎">配置火山引擎</h3>



<blockquote class="callout tip" data-callout="tip">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            💡
            
            Tip
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>火山引擎速度很快，而且提供了免费额度。</p>
        </p>
    </div>
</blockquote>
<p><strong>第一步</strong>：登入火山引擎-火山方舟</p>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9jb25zb2xlLnZvbGNlbmdpbmUuY29tL2Fyay8%3d"
    
     target="_blank"
>
https://console.volcengine.com/ark/</a></p>
<p><strong>第二步</strong>： 按照如图顺序，创建在线推理节点</p>
<p>【在线推理】- 【自定义推理接入点】-【创建推理接入点】</p>
<p><img src="https://images.ygria.site/2025/03/07600a661f832b8b0e9bc325f3fc6ac8.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><strong>第三步</strong>： 选择模型，点击确认接入</p>
<p><img src="https://images.ygria.site/2025/03/192514369ae8764ecbf520dc9ea25ecc.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><strong>第四步</strong>： 点击选择 API Key，创建或选择，并复制 API Key 的值</p>
<p><img src="https://images.ygria.site/2025/03/f86adf068d9f485f8d54bde5b67a16bc.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><strong>第五步</strong>：在 Obsidian Copilot 插件中配置</p>
<p>注意：Model Name 填写的是 <code>ep-</code> 开头，BASE URL 填写 <code>https://ark.cn-beijing.volces.com/api/v3/chat/completions</code> 。其余配置步骤与硅基流动相同。</p>
<p><img src="https://images.ygria.site/2025/03/49462ee68d9b4fea475670f4af665d47.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="3-使用">3. 使用</h2>
<p>点击左侧的图标开始使用</p>
<p><img src="https://images.ygria.site/2025/03/4e143503bc6e9c654c344cf2b91032d0.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>可以切换到 vault QA ，与全库对话</p>
<p><img src="https://images.ygria.site/2025/03/91055245f15e637cb1bf784425fff2d1.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2025/03/db51478199c500ed584f519bd4ac59eb.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2025/03/ed1835afceb5ccf459e3d52f9c924124.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>回答时，是根据本库内的文件内容，整合出了一份说明。可以根据这些说明去增补关联链接、合理化文件结构，或者用它来更好地检索材料，完成观点提炼和内容输出。</p>
<p>AI 提供了另一个视角去查看自己写下或收藏的内容，也能帮助个人思考的系统化、结构化！</p>
<h1 id="text-generator">Text Generator</h1>



<blockquote class="callout abstract" data-callout="abstract">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            Abstract
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>想要为我的文章快速生成简介、关键词，好用来 SEO 或填写文章简介。<strong>Obsidian Text Generator 插件</strong>（推荐 ✅，支持 OpenAI 兼容 API）。</p>
        </p>
    </div>
</blockquote>
<h2 id="步骤-1安装-obsidian-text-generator-插件"><strong>步骤 1：安装 Obsidian Text Generator 插件</strong></h2>
<ol>
<li>在 Obsidian <strong>Settings &gt; Community Plugins</strong> 里搜索 <code>Obsidian Text Generator</code>，安装。</li>
<li>（如需使用 Deepseek）进入 <strong>Settings &gt; Text Generator &gt; API Settings</strong>，选择 <strong>Use OpenAI Compatible API</strong>。 填入 API 地址和 Key</li>
<li>或者直接选择内置的模型，并填写 API key</li>
</ol>
<h2 id="步骤-2使用"><strong>步骤 2：使用</strong></h2>
<p>点击图标生成文本。</p>
<p><img src="https://images.ygria.site/2025/03/48d494ab291a17e6004468d83b2e0f43.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="自定义提示词">自定义提示词</h3>
<p>可在配置 <code>Custom Instruction</code> 中写自定义提示词，比如我给的提示词就是“一句话总结笔记”。AI 在概括信息方面表现优越。</p>
<p><img src="https://images.ygria.site/2025/03/4e4f5a1b51364fb1f0529bf89be3b07e.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="效果演示">效果演示</h2>
<p>小手一点，立刻生成了当前笔记的摘要。</p>
<p><img src="https://images.ygria.site/2025/03/4ff1efda7aacd8a3d810fa67cc7a691a.gif" alt="演示效果.gif" class="img-hide" loading="lazy" decoding="async" /></p>
<p>也支持配置兼容OpenAI协议的接口哟～注意，硅基流动的路径要配置成<code>https://api.siliconflow.cn/v1/chat/completions</code></p>
<p>推荐大家用Gemini，速度很快。但对网络环境要求较高。</p>
<p>还有什么玩法，欢迎大家分享～</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Automa详解：浏览器行为自动化和我的使用</title>
      <link>https://ygria.site/use-automa/</link>
      <pubDate>Sun, 05 Jan 2025 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/use-automa/</guid>
      <description>&lt;blockquote class=&#34;callout abstract&#34; data-callout=&#34;abstract&#34;&gt;
    &lt;div class=&#34;callout-title&#34;&gt;
     
        &lt;div class=&#34;callout-title-inner&#34;&gt;
           
            
            
            简述
            
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&#34;callout-content&#34;&gt;
        &lt;p&gt;
            &lt;p&gt;一个可以录制、自定义浏览器行为、通过可视化工具定义Workflow的浏览器插件。可以理解成浏览器的RPA机器人，也有点像直接对浏览器上的行为进行编程，可以&lt;strong&gt;打开Tab、切换Tab、输入/抓取文本、点击按钮、批量下载、批量操作等等。&lt;/strong&gt;
吸引我的点： UI优美*（一看就是有UI和UX打磨过的），功能强大，使用看上去很简便。&lt;strong&gt;对于流程（Workflow）、“块”（Block）、“线”（Line）的概念定义也值得学习，条件区块中的‘AND’和‘OR’设计也值得拆解&lt;/strong&gt;&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Automa详解：浏览器行为自动化和我的使用" />


<blockquote class="callout abstract" data-callout="abstract">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            简述
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>一个可以录制、自定义浏览器行为、通过可视化工具定义Workflow的浏览器插件。可以理解成浏览器的RPA机器人，也有点像直接对浏览器上的行为进行编程，可以<strong>打开Tab、切换Tab、输入/抓取文本、点击按钮、批量下载、批量操作等等。</strong>
吸引我的点： UI优美*（一看就是有UI和UX打磨过的），功能强大，使用看上去很简便。<strong>对于流程（Workflow）、“块”（Block）、“线”（Line）的概念定义也值得学习，条件区块中的‘AND’和‘OR’设计也值得拆解</strong></p>
<p>我用它做了一个把自己小红书无人点赞的笔记批量隐藏掉的功能。（官方没有提供，搜索了一下感觉有类似需求的人还蛮多的，之后还可以拓展条件为阅读量低的等等。）
除此之外，还做了一个复制微信读书Cookie到Github Action变量的功能。我使用的Weread2Notion项目会使用Github Action跑脚本，每天把我的阅读记录同步到Notion中，但有时候Cookie会失效。手动更新麻烦而且容易出错。使用Automa可以轻松地自动化这一系列操作。
用它还可以执行更多、更复杂的RPA功能，值得探索～</p>
        </p>
    </div>
</blockquote>
<p>使用的感受是<strong>非常强悍</strong>，设计好流程，完全可以使用自动化的工作流来代替琐碎、重复、<strong>跨越多个网站</strong>的工作。一切可重复性的操作交给机器来执行，既节约时间，又减少出错。看到不少人已经将它运用到运营工作中了。</p>
<h1 id="如何开始">如何开始</h1>
<h2 id="前往automa官网下载并安装">前往Automa官网，下载并安装</h2>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuYXV0b21hLnNpdGUv"
    
     target="_blank"
>
https://www.automa.site/</a></p>
<p><img src="https://images.ygria.site/2025/01/19edc312c4de99e823d1c2ed5d6cef6a.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="打开插件可能因为安全策略浏览器会自动关闭它记得打开">打开插件（可能因为安全策略，浏览器会自动关闭它。记得打开）</h3>
<p><img src="https://images.ygria.site/2025/01/348157b49894ec7825e41f1ea0858df9.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="从右上角点击插件图标开始使用">从右上角点击插件图标，开始使用</h3>
<p><img src="https://images.ygria.site/2025/01/e874028fa8fb5ef35f0adf9f83e3e0f3.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>值得注意的是原本的录制⏺️按钮无法再直接从插件弹窗中快速触发了，得从Dashboard中触发。</p>
<p>点击🏠图标，进入Dashboard，可以进行Workflow的编排。也可以直接使用Marketplace中别人已经编排好的流程。</p>
<h2 id="基础概念学习">基础概念学习</h2>
<p>每一个自动化行为是一个Workflow（工作流）。工作流由**块（Block）**和线（Line）构成。</p>



<blockquote class="callout quote" data-callout="quote">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            Automa 中有六类块
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <ul>
<li><strong>General</strong>: Perform a general action in the workflow, like making an HTTP request or executing another workflow.<br>
<strong>常规</strong>：在工作流中执行常规操作，例如发出 HTTP 请求或执行另一个工作流。</li>
<li><strong>Browser</strong>: To control the browser.<br>
<strong>浏览器</strong>：控制浏览器。</li>
<li><strong>Web Interaction</strong>: To interact with the active tab of the workflow. Before using blocks in this category, you need to use a 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb2NzLmF1dG9tYS5zaXRlL2Jsb2Nrcy9uZXctdGFiLmh0bWw%3d"
    
     target="_blank"
>
New Tab</a> or 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb2NzLmF1dG9tYS5zaXRlL2Jsb2Nrcy9hY3RpdmUtdGFiLmh0bWw%3d"
    
     target="_blank"
>
Active Tab</a> block.<br>
<strong>Web 交互</strong>：与工作流程的活动选项卡进行交互。在使用此类别中的块之前，您需要使用
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb2NzLmF1dG9tYS5zaXRlL2Jsb2Nrcy9uZXctdGFiLmh0bWw%3d"
    
     target="_blank"
>
新选项卡</a>或
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb2NzLmF1dG9tYS5zaXRlL2Jsb2Nrcy9hY3RpdmUtdGFiLmh0bWw%3d"
    
     target="_blank"
>
活动选项卡</a>块。</li>
<li><strong>Control Flow</strong>: Add logic to the workflow.<br>
<strong>控制流程</strong>：向工作流程添加逻辑。</li>
<li><strong>Online Services</strong>: Services that integrate with Automa.<br>
<strong>在线服务</strong>：与 Automa 集成的服务。</li>
<li><strong>Data</strong>: Modify or manipulate workflow variables or tables.<br>
<strong>数据</strong>：修改或操作工作流程变量或表格。</li>
</ul>
        </p>
    </div>
</blockquote>
<p>支持的配置项：
1⃣️ 执行的操作
2⃣️错误处理</p>



<blockquote class="callout quote" data-callout="quote">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            块执行支持如下错误处理：
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <ul>
<li><strong>1. Enable</strong>: Enable the error handler for the block<br>
<strong>Enable</strong> ：启用块的错误处理程序</li>
<li><strong>2. Retry action</strong>: retry the block execution if an error occurs on the block<br>
<strong>重试操作</strong>：如果块上发生错误，则重试块执行</li>
<li><strong>3. Throw error</strong>: if selected, the block will throw an error<br>
<strong>抛出错误</strong>：如果选择，该块将抛出错误</li>
<li><strong>4. Continue flow</strong>: if selected, the workflow execution will continue<br>
<strong>继续流程</strong>：如果选择，工作流程将继续执行</li>
<li><strong>5. Execute fallback</strong>: if selected, the workflow will continue to the block that connects to the fallback output<br>
<strong>执行回退</strong>：如果选择，工作流程将继续到连接到回退输出的块</li>
<li><strong>6.Insert data</strong>: insert data into the 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb2NzLmF1dG9tYS5zaXRlL3dvcmtmbG93L3RhYmxlLmh0bWw%3d"
    
     target="_blank"
>
table</a> or 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb2NzLmF1dG9tYS5zaXRlL3dvcmtmbG93L3ZhcmlhYmxlcy5odG1s"
    
     target="_blank"
>
variable</a>   (个人理解： 为人工操作或者后续其他操作留下空间)
<strong>插入数据</strong>：将数据插入
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb2NzLmF1dG9tYS5zaXRlL3dvcmtmbG93L3RhYmxlLmh0bWw%3d"
    
     target="_blank"
>
表</a>或
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb2NzLmF1dG9tYS5zaXRlL3dvcmtmbG93L3ZhcmlhYmxlcy5odG1s"
    
     target="_blank"
>
变量</a>中</li>
</ul>
        </p>
    </div>
</blockquote>



<blockquote class="callout quote" data-callout="quote">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            块之间通过Line来连接。
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>可以进行线的连接、删除、自定义外观等。</p>
        </p>
    </div>
</blockquote>



<blockquote class="callout note" data-callout="note">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            ℹ️
            
            我认为有趣的特性
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <ol>
<li>
<p>Trigger - ContextMenu -  selectionText
将Trigger配置为Context Menu类型，并支持将选中的文本作为变量，触发工作流。</p>
</li>
<li>
<p>后台运行
将“set active tab”取消，让其在后台运行</p>
</li>
<li>
<p><strong>节点支持写JavaScript代码！</strong>
通过内置 automa 函数，读取网页内容、读写变量</p>
</li>
<li>
<p><strong>条件与循环、各种条件对应的路径定义</strong></p>
</li>
<li>
<p>可以读取CSS元素的Attribute Value</p>
</li>
</ol>
        </p>
    </div>
</blockquote>
<h2 id="看视频学基础操作和应用场景官方示例">看视频，学基础操作和应用场景（官方示例）</h2>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuYXV0b21hLnNpdGUvdHV0b3JpYWxz"
    
     target="_blank"
>
https://www.automa.site/tutorials</a></p>
<p>官方给出的4个教程视频挺不错的，加起来不到一个小时，强烈推荐观看。下面是我总结的示例场景。</p>



<blockquote class="callout example" data-callout="example">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            右键选择内容，翻译并从页面alert
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>1⃣️触发器 Trigger 设置成Context Menu，支持将选中的文本作为变量输入到后续块中
2⃣️ New Tab 打开谷歌翻译页面
3⃣️ Form 块，输入内容，等待，获取结果
4⃣️ Javascript 块，alert结果</p>
        </p>
    </div>
</blockquote>
<p>有了“沉浸式翻译”插件，翻译选中内容不再是刚需，但举一反三，我们完全可以做到：选中内容-发送给AI-获取结果-回显到网页上，或者发送摘录的内容到自己的笔记中。</p>



<blockquote class="callout example" data-callout="example">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            谷歌搜索小猫咪，并逐个保存查询到的元素
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>1⃣️打开谷歌图像搜索页面
2⃣️ Form - 文本域输入，Press Key -  Search
3⃣️ Loop  - 根据CSS 选择元素来Loop （注意：条件区块的其他块，也应该连接到<strong>Loop Break点中！）</strong>
4⃣️通过“下载”区块，下载图片。</p>
        </p>
    </div>
</blockquote>
<p>快速出爬虫，这比Python代码还要简单不少，唯一要考虑的可能是别被反爬机制反制了。</p>



<blockquote class="callout example" data-callout="example">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            做一个WhatsUp 聊天机器人Bot
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>1⃣️打开聊天页面
2⃣️ Loop 聊天窗口
3⃣️  根据CSS属性做Condition，如果最后一条信息是hello，则进行回复“Hello Again”
4⃣️ Trigger 是定时触发，并使用fixed Delay</p>
        </p>
    </div>
</blockquote>



<blockquote class="callout example" data-callout="example">
    <div class="callout-title">
     
        <div class="callout-title-inner">
           
            
            
            读取谷歌Sheet中的内容，批量发送邮件
            
        </div>
    </div>
    <div class="callout-content">
        <p>
            <p>1⃣️将Google Sheet的权限分享给Automa账号
2⃣️ Loop Google Sheet数据（可以Preview）
3⃣️ 打开邮件页面，Form 块 - 填写收件人、内容、发件人信息，并点击发送。</p>
        </p>
    </div>
</blockquote>
<p>很适合运用到运营等工作领域中。由于工作流中支持接入HTTP请求和自定义的数据源，我们可以结合AIGC，做批量生成内容后的批量发布、批量运营。唯一风险点就是可能会被平台风控（很多时候不出批量功能不是不能，而是不想）。</p>
<h1 id="我的工作流">我的工作流</h1>
<h2 id="1批量隐藏小红书笔记">1.批量隐藏小红书笔记</h2>
<p>地址： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9hdXRvbWEuc2l0ZS93b3JrZmxvdy9QT0ktM1NPUlF3ckdSZEZYSUhzR3Q%3d"
    
     target="_blank"
>
https://automa.site/workflow/POI-3SORQwrGRdFXIHsGt</a></p>
<p><img src="https://images.ygria.site/2025/01/9af89736f10cbb9d1a713efdf8186027.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="思路讲解">思路讲解</h3>
<ol>
<li>打开小红书笔记管理页面</li>
</ol>
<p><img src="https://images.ygria.site/2025/01/15dc405436aa64dbc0ebf16a04aadf35.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<ol start="2">
<li>遍历元素，将上限设置得比较高，并设置Load More Elements（重要！页面默认懒加载，只加载前10条）</li>
</ol>
<p><img src="https://images.ygria.site/2025/01/32d5164e56498c8c3d87a81836807201.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<ol start="3">
<li>获取笔记的“喜欢”数据（通过CSS选择器读取）</li>
</ol>
<p>使用Automa提供的元素选择器进行拾取。值得注意的是，要将拾取到的路径中的父元素路径替换成 {{ loopData.notes }} ，这样每次读取的都是当前循环到的这条笔记的“喜欢”数量。同理也能拿到阅读量、评论、收藏、分享数据。</p>
<p><img src="https://images.ygria.site/2025/01/9d5afebe1d1556bfe3d7084fec625a6a.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2025/01/9f98a9e8da98bf496b7bf08dc67bfccd.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
4. 根据自定条件判断</p>
<p>这里的交互设计很优秀。我们可以看到它的AND 和OR条件是如何设计的：</p>
<p><img src="https://images.ygria.site/2025/01/2e694bb786efd9fbcee704c4b459507e.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>我这里只设置了一个条件，就是——没有人点赞。（可以自己调整）</p>
<p><img src="https://images.ygria.site/2025/01/66b4683c8f85c31c5aae4f3f81d28a58.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
条件块支持多个Path，可以理解成不是if else而是switch case。注意在循环内，每个path都应该链接到循环断点。</p>
<ol start="5">
<li>符合条件的编辑权限成“仅自己可见”
<img src="https://images.ygria.site/2025/01/2a9ad99594e1f88e3b3d41b0dac5c426.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></li>
</ol>
<p>没什么好说的，都是使用录制功能完成的。</p>
<ol start="6">
<li>下拉，强制列表刷新（不然读不到元素！）
每次遍历后都强制滚动页面，让页面元素加载</li>
</ol>
<p><img src="https://images.ygria.site/2025/01/bdb94428acbff2890ad519d4e2d152e6.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>递增是勾上的，不然是每次都滚动到同个位置。</p>
<ol start="7">
<li>循环结束</li>
</ol>
<p>亲测可用～</p>
<h2 id="2-复制微信读书cookie到github-action变量">2. 复制微信读书Cookie到Github Action变量</h2>
<p>地址： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9hdXRvbWEuc2l0ZS93b3JrZmxvdy8tcnVuQjVfbW9BWW1sbG5VZV83VnI%3d"
    
     target="_blank"
>
https://automa.site/workflow/-runB5_moAYmllnUe_7Vr</a></p>
<p><img src="https://images.ygria.site/2025/01/c91b7af1c037df803f6fe0186d3531f9.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>这个工作流相对来说更简单，唯一值得说的可能是需要配置一个全局数据，这样可以让别人也能用。</p>
<p><img src="https://images.ygria.site/2025/01/58f5e7293bd08e4cf35d74bc2a98a648.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<ol>
<li>打开微信读书（需要是登录好的）</li>
<li>Javascript代码块，读取Cookie，并放到变量里去</li>
</ol>
<p><img src="https://images.ygria.site/2025/01/f1d74749c442f5d0f355cde9f97bf3cd.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<ol start="3">
<li>打开Github Action Variable配置页面</li>
</ol>
<p>地址形如：</p>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL3t7Z2xvYmFsRGF0YS5naXRodWJOYW1lfX0vd2VyZWFkMm5vdGlvbi1wcm8vc2V0dGluZ3Mvc2VjcmV0cy9hY3Rpb25zL1dFUkVBRF9DT09LSUU%3d"
    
     target="_blank"
>
https://github.com/{{globalData.githubName}}/weread2notion-pro/settings/secrets/actions/WEREAD_COOKIE</a></p>
<p>这里就是globalData的作用了。</p>
<p><img src="https://images.ygria.site/2025/01/260a581857271926a80ad6619d0f34fc.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>页面是这样的</p>
<ol start="4">
<li>使用Form 填写变量值</li>
</ol>
<p><img src="https://images.ygria.site/2025/01/2eb04466055482eb62ad5f10c4a3a93c.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<ol start="6">
<li>点击提交</li>
</ol>
<p>我的工作流做到这里就停了，接下来会让你填写密码确认。当然可以也把密码填写一并做进去，看个人的偏好了～</p>
<h1 id="总结">总结</h1>
<p>Automa的设计思路和交互都很棒！虽然可能出于安全的考虑（这玩意让爬虫又好写了很多），有被封禁的风险，但它带来的效率提升的前景是毋庸置疑的。</p>
<p>自动化一向让我痴迷。一切可以抽象出来的操作，最后都可以交给自动化来执行～只要花一次配置的时间，就可以一直使用，并且减少出错的可能，何乐而不为呢？</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>2024年度总结</title>
      <link>https://ygria.site/2024-summary/</link>
      <pubDate>Sun, 29 Dec 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/2024-summary/</guid>
      <description>&lt;h1 id=&#34;done-list&#34;&gt;Done List&lt;/h1&gt;
&lt;p&gt;今年产生的想法是不列Todo List，而是Done List。不再设置目标，而是尽量建立系统。如下是我今年的成就清单。&lt;/p&gt;
&lt;h2 id=&#34;编程方面&#34;&gt;编程方面&lt;/h2&gt;
&lt;p&gt;今年开始跟着Youtube的一位博主（@codewithantonio）学习React课程。断断续续跟完了一两个教程，目前可以用React做独立项目开发了。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="2024年度总结" /><h1 id="done-list">Done List</h1>
<p>今年产生的想法是不列Todo List，而是Done List。不再设置目标，而是尽量建立系统。如下是我今年的成就清单。</p>
<h2 id="编程方面">编程方面</h2>
<p>今年开始跟着Youtube的一位博主（@codewithantonio）学习React课程。断断续续跟完了一两个教程，目前可以用React做独立项目开发了。</p>
<p>他的教程都是非常完整的小项目，主要使用NextJS做技术栈，不是只做前端，而是使用很多开源免费的组件，比如鉴权框架（Clerk、Next Auth），从0到1的数据库schema设计、orm（drizzle）、RPC API（hono）搭建和最后如何部署、上线都有涉及。</p>
<p>我学完的是：
<a 
    
        href="/tiaozhuan?target=W1todHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PUFESktidWF5dWJFXV0%3d"
    
    
>
# Build a Real-Time Miro Clone With Nextjs, React, Tailwind (2024) </a> ，学了一半的是 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g%2fdj1OX3VOS0F1czBJSSZ0PTM0NjAycw%3d%3d"
    
     target="_blank"
>
# Build a Finance SaaS Platform With Nextjs, React, Honojs with CSV Upload (2024)</a>。</p>
<p>仍然没有做出特别像样的东西。今年尝试了使用Cloudflare、Vercel等Serverless服务进行页面的部署，尝试了Github Action，搭建了自己的锻炼页面、书影音页面，重启了技术博客。</p>
<p>值得一提的是开发了几周Notion数据生成图表的项目，后来因为官方出了图表功能而作罢。</p>
<h2 id="设计和产品">设计和产品</h2>
<p>在社交平台上频繁刷到“XX小时借助Cursor完成APP开发并上线”、“0编程基础开发APP”的帖子，虽然很多是制造焦虑的，但也让我意识到编程经验可能不再是程序员的护城河，AI可以帮助很多人来完成简单APP的开发，甚至由于有产品、设计的优势，他们做出的东西更符合用户的需求，更容易受到关注和成功。</p>
<p>同样地，在AI的帮助下，设计、产品、运营的工作也并非无可替代的。我阅读了一些设计和产品相关的书籍，希望能借此提升自己的软实力。</p>
<p><strong>阅读：</strong>
Refactoring UI  （网页设计入门书籍  ）
UX for Beginners (Joel Marsh) （用户体验入门书籍）

<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuaXduYW0uY24vZG9jcy9Qcm9kdWN0"
    
     target="_blank"
>
https://www.iwnam.cn/docs/Product</a> 小红书 @白子阳的产品手册</p>
<p><strong>实践：</strong></p>
<p>暂无，下载了figma，但还没有开始使用。</p>
<h2 id="书法">书法</h2>
<p>进行了大约100天的硬笔书法练习，最大实践是写红包封面和贺卡。</p>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly95Z3JpYS5zaXRlL2hhbmR3cml0ZXMv"
    
     target="_blank"
>
我的书法练习记录</a></p>
<h2 id="烹饪">烹饪</h2>
<p>学做了大概一个月的菜，此前几乎没怎么下过厨房。</p>
<p><strong>实践：</strong></p>
<p>十一时和老公合作做了一桌菜，招待了爸妈和姐姐姐夫。</p>
<p>学做了地锅鸡给爸妈和老公吃。</p>
<h2 id="钓鱼">钓鱼</h2>
<p>跟着老公钓了几次鱼。今年是我第一次钓上来鱼，虽然都是特别小的鱼。</p>
<h2 id="博客和笔记">博客和笔记</h2>
<p>了解了卡片盒笔记法后，我将自己的阅读、观影、看剧等等均整理到了我的数字花园中。目前有大约15W字的摘录和1W字左右的笔记。</p>
<p>我非常喜欢卡片盒笔记的想法，通过不断地记录，像是在编织一个属于自己的知识网络，也许一开始稀稀拉拉，但会越来越紧密，而我的思想就会从基于这张网络而产生。</p>
<p><img src="https://images.ygria.site/2024/12/3854c033873d5cb81965e7b1fb1e017e.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="阅读娱乐和游玩">阅读、娱乐和游玩</h1>
<h2 id="旅游">旅游</h2>
<p><img src="https://images.ygria.site/5161735470081_.pic.jpg" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>今年旅游去了滁州琅琊山（1d）、成都（3d）、连云港（2d）、苏州（2d）、贵州（4d）。最远的应该就是贵阳啦，看到了黄果树大瀑布，也吃到了特别多的贵州美食。</p>
<h2 id="阅读和播客">阅读和播客</h2>
<p><img src="https://images.ygria.site/2024/12/aea139505150f7cc9eae64d3d9b91e90.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>今年总阅读时长大约300小时，听播客时长146小时。</p>
<p><img src="https://images.ygria.site/2024/12/24f65cf882e5e40b437ece3621599aa1.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>播客是非常好的高质量信息输入方式，我会随机打开一个之前不会特别关注的话题收听，比如东亚政治、俄乌战争、半岛局势等等。带来的负面影响是经常特别想键政，但又很少找到机会……</p>
<p><img src="https://images.ygria.site/5191735473843_.pic.jpg" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
<img src="https://images.ygria.site/5181735473577_.pic.jpg" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>今年从新开始看的时间最久的是《静静的顿河》（《笑傲江湖》以前看过），但因为太长了，读到后面兴趣缺缺，还是没能读完。</p>
<p>读了一些以前读过的书，整体看的还是小说偏多。通过播客了解到了项飙和袁凌老师，也找了一些他们的书看。读完了袁凌的《我的皮村兄弟》，最近在读的是项飙的《跨越边界的社区》。</p>
<p>除此之外看的女性主义的书偏多。</p>
<h2 id="演出">演出</h2>
<ul>
<li>《银河快递》Livehouse</li>
<li>凤凰传奇演唱会</li>
<li>告五人演唱会</li>
<li>牡丹亭昆曲演出</li>
</ul>
<h2 id="游戏">游戏</h2>
<p>今年主要玩《饥荒》《缺氧》《小丑牌》，都玩得特上头～</p>
<h2 id="剧综艺电影">剧/综艺/电影</h2>
<ol>
<li>
<p>之前受老公推荐，看了《小巷人家》，影视化后也看了剧。挺好看的年代剧，但到后面没看完。</p>
</li>
<li>
<p>综艺《再见爱人4》也是看到一半，因为太气人和感觉剧本痕迹太重没继续追了。</p>
</li>
<li>
<p>看完了《东京爱情故事》。挺好看的，莉香特别美特别可爱。丸子太垃圾了。</p>
</li>
<li>
<p>看了那不勒斯四部曲的第一部，顺便开始看《我的天才女友》，非常好看啊。最近在追～</p>
</li>
<li>
<p>和老公一起看了《鬼入侵》。特吓人，看得半夜不敢去上卫生间。</p>
</li>
</ol>
<p>今年院线上的电影看了不少，总体来说都挺喜欢的～</p>
<p><img src="https://images.ygria.site/5201735474161_.pic.jpg" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="总结">总结</h1>
<p>今年是我结婚后的第一年，总体生活上改变不大。</p>
<p>可能并没有很多显性的指标和成就，摄入的信息不少，但信息流的冲刷对我来说也未必是好事。我希望自己能在以后的日子里能将摄入的东西更好地留存转化，将“知道的”变成“懂得的”，行动起来。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Obsidian常用插件</title>
      <link>https://ygria.site/obsidian-plugin/</link>
      <pubDate>Sun, 29 Dec 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/obsidian-plugin/</guid>
      <description>&lt;h2 id=&#34;必选插件&#34;&gt;必选插件&lt;/h2&gt;
&lt;h3 id=&#34;1-上传图片插件-image-auto-upload-plugin-必装&#34;&gt;1. 上传图片插件 Image auto upload Plugin （必装）&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://images.ygria.site/2024/09/2dececc2f2545c955560da352e24a416.png&#34; alt=&#34;image.png&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;
&lt;p&gt;配合Picgo + Cloudflare 注册R2图床使用。&lt;/p&gt;
&lt;h3 id=&#34;2templater-增强模板功能&#34;&gt;2.Templater 增强模板功能&lt;/h3&gt;
&lt;p&gt;原生的模板功能较为简单，增强原生模板，支持函数引用、系统变量格式化等。好评。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Obsidian常用插件" /><h2 id="必选插件">必选插件</h2>
<h3 id="1-上传图片插件-image-auto-upload-plugin-必装">1. 上传图片插件 Image auto upload Plugin （必装）</h3>
<p><img src="https://images.ygria.site/2024/09/2dececc2f2545c955560da352e24a416.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>配合Picgo + Cloudflare 注册R2图床使用。</p>
<h3 id="2templater-增强模板功能">2.Templater 增强模板功能</h3>
<p>原生的模板功能较为简单，增强原生模板，支持函数引用、系统变量格式化等。好评。</p>
<h3 id="3-callout-manager">3. Callout Manager</h3>
<p>Callout样式不太好手打出来，这个插件专为便捷输入Callout块。可以方便地看样式，也支持自定义。使用简单。</p>
<p><img src="https://images.ygria.site/2024/12/61f68317fe079091d5772cccdf915de8.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="4-novel-word-count">4. Novel word count</h3>
<p>字数统计。用了几个觉得这个最符合我的需求。能快速看到文件夹的总字数和每个文件的字数。</p>
<p><img src="https://images.ygria.site/2024/12/896ab1b9deb19499684f8b0c4535dd8e.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="5-quick-explorer">5. Quick Explorer</h3>
<p>在文件头部增加面包屑，方便快速定位当前编辑文件在文件夹中的位置。</p>
<p><img src="https://images.ygria.site/2024/12/e75d4a94d6a62fee1696dce622587bf6.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="6-automatically-reveal-active-file">6. Automatically reveal active file</h3>
<p>左侧目录树太深不好翻动，用这个可以自动将滚轮定位到当前编辑文件相应位置并展开。非常需要。</p>
<h3 id="7--various-complement">7.  Various Complement</h3>
<p>自动像IDE一样提供文本补全功能。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>obsidian发布数字花园：沉淀个人知识库</title>
      <link>https://ygria.site/obsidian-digital-garden/</link>
      <pubDate>Fri, 23 Aug 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/obsidian-digital-garden/</guid>
      <description>&lt;h1 id=&#34;卡片盒和双链笔记及我所实践的知识库工作流&#34;&gt;“卡片盒”和双链笔记，及我所实践的知识库工作流&lt;/h1&gt;
&lt;h2 id=&#34;1卡片盒和第二大脑&#34;&gt;1.卡片盒和第二大脑&lt;/h2&gt;
&lt;p&gt;起因是看到这篇文章：
&lt;a 
    
        href=&#34;https://ygria.site/tiaozhuan?target=aHR0cHM6Ly93d3cubXJodWFuZ3RhbGsuY29tL3Bvc3RzL0J1bGlkWW91clNlY29uZEJyaWFuLw%3d%3d&#34;
    
     target=&#34;_blank&#34;
&gt;
# 把信息交给未来的自己——构建你的第二大脑 &lt;/a&gt;，文章的开头就切中了我的痛点：&lt;/p&gt;



&lt;blockquote&gt;
    &lt;p&gt;你有没有经历过：读了一本书，投入数小时的脑力劳动来理解它所呈现的想法。 读完这本书时，你会感觉自己获得了宝贵的知识体系。但是然后呢？你可能会尝试应用本书推荐的基于科学的方法，却发现它并不像你想象的那么清晰。 你可能会尝试改变饮食、锻炼、交流或工作的方式，相信习惯的力量。但随后生活的日常需求又回来了，你忘记了最初是什么激励你。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="https://images.ygria.site/2024/08/93b7b55b36fff0a7bd1f6b72e7286469.png" alt="obsidian发布数字花园：沉淀个人知识库" /><h1 id="卡片盒和双链笔记及我所实践的知识库工作流">“卡片盒”和双链笔记，及我所实践的知识库工作流</h1>
<h2 id="1卡片盒和第二大脑">1.卡片盒和第二大脑</h2>
<p>起因是看到这篇文章：
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cubXJodWFuZ3RhbGsuY29tL3Bvc3RzL0J1bGlkWW91clNlY29uZEJyaWFuLw%3d%3d"
    
     target="_blank"
>
# 把信息交给未来的自己——构建你的第二大脑 </a>，文章的开头就切中了我的痛点：</p>



<blockquote>
    <p>你有没有经历过：读了一本书，投入数小时的脑力劳动来理解它所呈现的想法。 读完这本书时，你会感觉自己获得了宝贵的知识体系。但是然后呢？你可能会尝试应用本书推荐的基于科学的方法，却发现它并不像你想象的那么清晰。 你可能会尝试改变饮食、锻炼、交流或工作的方式，相信习惯的力量。但随后生活的日常需求又回来了，你忘记了最初是什么激励你。</p>
<ul>
<li>之后你可能放弃了，将所有书籍都贴上浪费时间的标签。</li>
<li>也有可能你认为这只是没能记住阅读的所有内容的问题，之后就致力于研究各类花哨的记忆技术。</li>
<li>你还有可能变成了「信息狂」，强迫自己喂饱无数的书籍、文章和课程，希望能坚持下去。</li>
</ul>
<p>其实你之前做的很好，这些方法都非常有用，也非常重要，造成这些结果的原因，仅仅是你<strong>没有在正好需要的时候读到。</strong></p>
</blockquote>



<blockquote>
    <p>你现在正在阅读时间管理技巧，但它们可能只会在两年后有用。你现在正在和一个潜在客户谈论他的目标和挑战，但你真正可以使用这些信息的时间是明年他竞标一份新的巨额合同时。现在对学习知识的挑战不是获取它，在我们的数字世界中，你可以随时获取几乎任何知识。</p>
</blockquote>



<blockquote>
    <p>现在<strong>真正的挑战在于确认哪些知识值得获取，然后构建一个系统，通过时间将它们推送到未来最适用、最需要的时候。</strong> 在那时，你可以把这些知识直接应用于面临的困难和挑战。在那时，你不用再担心怎么去保存、记住、理解这些知识，你只需要直接应用它们，当你用它们解决一个真正的问题时，书本知识已经变成了经验知识。经验知识是你永远随身携带的东西。
所以，<strong>我们需要一个外部的、集成的数字存储库，用于存储你所学的东西，以及它们来自哪里。它是一个存储和检索系统，将知识点打包成离散的数据包，这些离散的数据包可以在未来任意时间点供我们使用。</strong></p>
<ul>
<li>这个储存和检索系统就是<strong>第二大脑</strong>，一套便捷高效的<strong>笔记系统</strong>。</li>
</ul>
</blockquote>
<p>我每天沉溺在过量的信息流中，阅读、观影、听播客、刷短视频，但似乎并没有留下些什么。知识引起我片刻的惊喜和瞩目，紧接着就被我抛到脑后，再次遇到也只是最熟悉的陌生人。</p>
<p>将知识拆解、碎片化，将它们按照自己的审美和习惯，整理成一套电子、可多设备同步、可存储、可检索的系统——知识库的模型并不总是树形结构，而更贴近网状。</p>
<p><strong>卡片盒笔记法</strong> 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly96ZXR0ZWxrYXN0ZW4uZGUvaW50cm9kdWN0aW9uL3poLw%3d%3d"
    
     target="_blank"
>
https://zettelkasten.de/introduction/zh/</a>：</p>



<blockquote>
    <ul>
<li><strong>卡片盒笔记系统</strong>：英文/德语 Zettelkasten，将笔记相互连接形成一个网状结构的超文本笔记系统，它是一个工具，是你的伙伴。</li>
<li><strong>卡片盒笔记法</strong>：英文 Zettelkasten Method，建立卢曼笔记系统用到的的一些具体方法</li>
<li><strong>笔记卡片</strong>：英文/德语 Zettel，原指一张用来记录笔记的小纸片，现指使用卢曼笔记法记录的一条单独的笔记</li>
<li><strong>想法</strong>：英文 Thought，卢曼笔记法要求<strong>每条笔记只记录一个 thought</strong>，thought 在中文中含义非常丰富，可以是一个术语、概念、理论、看法、点子，突然的灵感等等，这些在本文中统称为“想法”</li>
<li><strong>思想之网</strong>：英文 Web of Thought，由各种想法互相连接形成的网络，卢曼笔记系统就是一张思想之网</li>
<li><strong>链接</strong>：英文 Link，名词，指代每条笔记的 ID</li>
<li><strong>连接</strong>：英文 Link/Connect，动词，指将一条笔记与另一条笔记连接起来的动作/操作</li>
</ul>
</blockquote>
<h2 id="2我的实践">2.我的实践</h2>
<p>我决定立刻按照这篇文章给出的方法进行实践。在信息流的规整中，我使用了
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL21hbGlua2FuZw%3d%3d"
    
     target="_blank"
>
马林康大佬</a>提供的项目Podcast2Notion、Weread2Notion等，这些项目将会自动将我微信阅读记录及划线内容、播客收听及转写出的文本内容同步到我的Notion中：</p>
<p><img src="https://images.ygria.site/2024/08/7d50360f948f4415a13b504d1155282b.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>其他网页收藏内容，通过Save2Notion浏览器插件向Notion同步。</p>
<p>这里将留存大量我待整理的内容。</p>
<p>obsidian就是能做网状笔记的最佳利器，它打开速度极快，开源、免费、完全本地，可以通过丰富的插件拓展功能。
我采用Github作为同步方案：天生就适合文本同步的方法，也能追踪到之前编辑的版本。</p>
<p>我会将这些笔记逐步整理到我的obsidian知识库的Note文件夹，尽可能保留原文和原始链接，再在Summary文件夹下写下自己的感悟，两者通过双链进行关联。</p>
<p>我得承认这是一项非常考验耐心的工作，可能一开始做出的笔记很拙劣、并没什么创见、甚至有错误，也没有做到拆分粒度足够细、逻辑关联正确。但每次做一点，也许有一天能够构成我的思想之网。</p>
<p>例图：我为《情绪》一书做的笔记。</p>
<p><img src="https://images.ygria.site/2024/08/61e9e52ee78fbac72133aacdbad0ddd3.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="digital-garden发布我的数字花园">Digital Garden发布我的数字花园</h1>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kaWdpdGFsLWdhcmRlbi55Z3JpYS5zaXRlLw%3d%3d"
    
     target="_blank"
>
数字花园地址：Ygria‘s Digital Garden</a></p>
<p>效果预览：</p>
<p><img src="https://images.ygria.site/2024/08/93b7b55b36fff0a7bd1f6b72e7286469.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="1-安装digital-garden插件">1. 安装Digital Garden插件</h2>
<p>点击obsidian左下角设置⚙️，在设置页面打开Community Plugin，搜索Digital Garden插件安装并启用。</p>
<p><img src="https://images.ygria.site/2024/08/9ea0392b10c848a2dfe247f8137e26d2.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="2-配置和发布">2. 配置和发布</h2>
<h3 id="准备github仓库公有和私有均可">准备Github仓库（公有和私有均可）</h3>
<p>在github中新建私人仓库，并生成token</p>
<p>新建仓库（记住<strong>仓库名称①</strong>）<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417110652.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
Github 右上角-个人头像-setting<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417110804.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>点击”developer settings “-”Token（classic）“</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417110834.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
选择无过期时间，并给repo权限。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417111350.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
拉到最下，生成token，并复制<strong>token值②</strong></p>
<p>将①和②的值填写到插件配置中。</p>
<p><img src="https://images.ygria.site/2024/08/1f883e3f68d5e92bc4b21b6b143f0511.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="default-note-setting">Default Note Setting</h3>
<p>参考文档，可以全部打开。</p>
<p><img src="https://images.ygria.site/2024/08/84f010d04274cbec4b804bf8071e679f.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="样式主题设置"><strong>样式主题设置</strong></h3>
<p>可以选取社区中的主题</p>
<p><img src="https://images.ygria.site/2024/08/58e080cd91e8fe7dcaa766021bfde81f.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="首页和其他页面">首页和其他页面</h3>
<p>编写想要设置为首页的Markdown文件，在Front Matter中加入dg-home:true和dg-publish:true。
其他配置需要发布的页面配置dg-publish:true和dg-path，注意dg-path不能重复，否则构建会报错。</p>
<p><strong>发布首页：</strong></p>
<p><img src="https://images.ygria.site/2024/08/2b10398cbd66ce549508fce98f49fab5.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><strong>发布页面：</strong></p>
<p><img src="https://images.ygria.site/2024/08/59e0c75781f1e79ec6b334d72b1c78f7.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>点击Digital Garden插件提供的导航按钮，可以进行可视化的发布</p>
<p><img src="https://images.ygria.site/2024/08/87f0a7f54c7fe80b63f7603a2bd3b26c.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>发布之后，已将内容同步到创建好的Github仓库，配置Vercel和Cloudflare Pages构建均可。我使用的是Cloudflare  Pages构建。</p>
<h3 id="构建发布">构建发布</h3>
<p>在Cloudflare Dashboard新建一个Page，并配置Github仓库和构建指令即可。
<img src="https://images.ygria.site/2024/08/eb5e3376817eb5e2e5ab51dc8acf61db.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h4 id="建议关闭自动构建">建议关闭自动构建</h4>
<p>由于每次单文件的修改都会产生一次Commit，建议取消自动构建，使用Webhook触发构建。</p>
<p><img src="https://images.ygria.site/2024/08/87ad636c47e7ab48735975a4dc8d586b.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h4 id="使用webhook触发构建">使用Webhook触发构建</h4>
<p><img src="https://images.ygria.site/2024/08/fbb41a2f6eecbcb9aab820750d522047.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>可以本地通过curl或postman等客户端软件触发，也可以将webhook配置到其他工作流中。</p>
<p>至此完成数字花园的发布。</p>
<h2 id="3-样式自定义">3. 样式自定义</h2>
<p>可以将上文中所说的Github仓库Clone到本地进行样式调试和开发。使用<code>npm run start</code> 指令启动项目。
将自定义样式放<code>src/site/styles/user/custom-style.scss</code>中。</p>
<p>我因为本机网络环境问题，拉不到Github上的主题文件，将配置中THEME的地址改成可访问到的CDN地址即可。</p>
<p><img src="https://images.ygria.site/2024/08/f6903c2a84035767e5b7a7411f414347.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>贵州四日游</title>
      <link>https://ygria.site/4daysinguizhou/</link>
      <pubDate>Mon, 05 Aug 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/4daysinguizhou/</guid>
      <description>&lt;p&gt;老公放高温假，我极力主张去贵州玩，理由很简单：据说贵州凉快，又有很多好吃的，而且我们都没去过。当下就买了机票，开始了为期四天（7.31晚（周三）从合肥起飞、8.4（周日）下午从贵阳返回）完全没做攻略的冒险之旅。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="https://images.ygria.site/2024/08/5ef4c9dcec21bb32a4b00ef22fbeb7b1.png" alt="贵州四日游" /><p>老公放高温假，我极力主张去贵州玩，理由很简单：据说贵州凉快，又有很多好吃的，而且我们都没去过。当下就买了机票，开始了为期四天（7.31晚（周三）从合肥起飞、8.4（周日）下午从贵阳返回）完全没做攻略的冒险之旅。</p>
<h1 id="day-1黔灵山观猴">Day 1：黔灵山观猴</h1>
<h2 id="落地">落地</h2>
<h3 id="肠旺面">肠旺面</h3>
<p>周四凌晨，抵达贵阳龙洞堡机场。机场门口也有小摊贩摆摊卖小吃。我们定的酒店在贵阳北站附近的北大梦想城，坐上出租，从车窗吹进来微风夹着细细雨丝，非常凉爽。</p>
<p>抵达宾馆附近，已是凌晨1：30，宾馆楼下的街市依然繁华热闹。大部分饭店已关门，有一家<strong>南门口肠旺面</strong>24小时营业。</p>
<p><img src="https://images.ygria.site/2024/08/ac3bf95277261a5481a49d0f92be25bd.jpg" alt="664e67693ebb3edccd59906e702ccbe.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>肠旺面是肥肠+血旺+鸡蛋面条，面条偏硬，属于碱面，类似于泡得不太透的方便面的口感，加上红油，吃起来也会让人想起螺蛳粉。</p>
<p>面里还放了脆哨，也是贵阳特色的一种小吃，是肥肉炸出来再用酱油腌制，类似于猪油渣。一听做法就知道——这能不好吃吗？放在面里，提供油脂的香味和爽脆的口感。（后面几天发现贵阳几乎什么东西都能加脆哨……有点类似于我们这边儿用油炸花生米。）</p>
<p>浇头味道不错，面条我不是太吃得惯。</p>
<h3 id="蛋包洋芋">蛋包洋芋</h3>
<p>吃完了面，我又在小摊上买了一份蛋包洋芋，摆摊的阿姨为了给土豆泥包上完整的蛋皮打了两个蛋，最后做出的成品也是超级完美。</p>
<p><img src="https://images.ygria.site/2024/08/ac5418d7576d08e6a8b320fc85f31d6d.jpg" alt="ff56a556cdc2e442fe94b68175f8b85.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>厚厚的鸡蛋皮里裹着香糯的土豆泥，热乎乎的土豆泥里裹着洋葱、酸萝卜、脆哨等口感丰富的配菜颗粒，我又要重复——这能不好吃吗？用小勺子挖着吃，幸福感爆棚了。</p>
<h3 id="牛肉粉">牛肉粉</h3>
<p>当天行程轻松，我们睡到十点，在楼下吃了一碗花溪牛肉粉。牛肉粉也是贵阳的名小吃，价格不贵，一碗粉13元，加一份牛肉7元，汤鲜味美。</p>
<p><img src="https://images.ygria.site/2024/08/36443d82ef505401d7f5b7bccd08c92f.jpg" alt="b25859ca4591357ca8d4a9e3340187b.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/08/95290a74c613cfa1a0f71087c4e7da20.jpg" alt="1e7c8a6e45120f326495d6bf0634d7b.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>然后我们就前往此行游玩第一站：黔灵山公园。</p>
<h2 id="黔灵山">黔灵山</h2>
<h3 id="南门口的农贸市场">南门口的农贸市场</h3>
<p>我们打车到黔灵山南门，附近堵车了一公里左右，就提前下了车。正好可以穿过黔灵巷农贸市场。</p>
<p>我们都爱逛农贸市场，烟火十足，逛一下就可以了解本地的土产、物价和当地人的生活。菜市场人流密集，果蔬熟食各色齐备，有许多卖脆哨的摊位店铺，价格基本是39.9一斤。一家摊子招牌写：“哨王”，我心想口气真大，一靠近，店主拿铲子铲起一些招呼我尝，尝了一粒，果然又脆又香很好吃。</p>
<p>很喜欢这边水果的摆法，会在小篮子里垫上一圈绿色叶子，再放上葡萄、桃子、李子，衬得色彩明丽鲜艳。这里应季的西瓜、葡萄都卖得比合肥贵，蓝莓很便宜，十三一斤。本地特有的应该是刺梨，有许多卖刺梨汁的摊。巷口有人拉着一钓箱草鱼卖，不知是不是现钓起的。</p>
<p>老年人很多，听当地口音，和成都那边蛮像。</p>
<h3 id="徒步上山">徒步上山</h3>
<p>从南门口进入公园，人已经很多了。我们本来准备乘坐索道，结果被告知需要排队两小时。于是就徒步上山。行不多时，就看到一只蹲在栏杆上的猴子，啃着苞谷。它一点也不怕人，凑近拍也可以。</p>
<p><img src="https://images.ygria.site/2024/08/1659ab68415f6f4ceaca4c8bb4efdd84.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>有个小孩子去吓它，猴子就朝他扑了下。还好，没有伤人。</p>
<p>接着一路走到弘福寺，没有进去，继续上山。</p>
<p><img src="https://images.ygria.site/2024/08/6bc2591276ca5e13a966a3709e4ab06f.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>沿路语音播报提醒和猴子保持距离、不要投喂，但游客们出于好奇，还是会去引逗猴子。特别是小孩子们，都积极贡献着手里的AD钙奶和养乐多，还有小饼干、花生香蕉等等。小猴子们从山上蹿下来，从铁丝网的洞里钻出，伸爪拿走小孩子们手里的食物，就抱着食物，远远地躲到山上去吃。</p>
<p><img src="https://images.ygria.site/2024/08/82969303656459d7870a30d8aa988737.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>猴子很灵巧，会把香蕉皮剥开，也会啃开饮料的封皮。我跟对象一致认为受了工业糖精的荼毒，这些猴子可能上瘾再也吃不下山中的野果了。</p>
<p>又走了一阵，遇到满树的猴子。它们在树上或骑或坐，攀缘上下，蹬跳得树枝簌簌作响。</p>
<p><img src="https://images.ygria.site/2024/08/34a0e9a8f6e931538338941ad8ac3813.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>这群猴比遇到的第一只猴体型小很多，应当不是同一品种。</p>
<p><img src="https://images.ygria.site/2024/08/8df783e1c01c5946ebcb1b152f0e5c0e.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>遇到一只母猴带着小猴。</p>
<p><img src="https://images.ygria.site/2024/08/000aa6ccfbe8ccb0a434f5a3fe6cbd21.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>一只小猴倒着拿养乐多，把底部咬漏了，从底部啜吸，正下开口的部分漏了许多到地上。另一小猴就趴着舔舐地上的甜饮料。看着还怪搞笑的。</p>
<p>后来这小猴蹿到树上去了，舔饮料的猴子也跟着蹿到树上，用嘴接它漏下来的，想来一多半都进了它肚子里。</p>
<p>路边的猴子也有人伸手投喂，手里有花生，它们自然会从你手上掏摸。</p>
<p>在景区花6块买了瓶饮料，被一只猴子拦路抢走了。</p>
<h3 id="山脚动物园">山脚动物园</h3>
<p>山脚是个免费的动物园，动物品类还蛮多，许多被关到玻璃房子里，看着行动非常受局促。</p>
<p>有个百鸟园，鸟类很多。</p>
<p><img src="https://images.ygria.site/2024/08/3d2ebd82c44cbc52329cc9881bf84734.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/08/16b17156b3a1b28c69428f944e6be522.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>蓑羽鹤，看着仙气飘飘，行止也优雅，头部垂下来的白羽让人想到寿星的白眉。但叫起来声音颇像蛤蟆。</p>
<p><img src="https://images.ygria.site/2024/08/0e4353b6de5b0667eb4f93058ea5f2b7.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>西非冠鹤，一头黄毛，特征极显著。</p>
<p><img src="https://images.ygria.site/2024/08/678b5d177a2b6aaa03615a6b16de17d3.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>一只开屏开到一半的孔雀。它抖动着羽毛开了一小半，游客都呼朋引伴喊着来看，结果虚晃一枪，又收起来了。</p>
<p><img src="https://images.ygria.site/2024/08/9243bd4eadf3b69c9032d8e313a3e0d9.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>蹲在茅草屋顶上的一只孔雀。听到一个小男孩惊呼：它好胖啊！</p>
<p><img src="https://images.ygria.site/2024/08/39e9fe259f16631a71f74b2b4d6ad33a.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>长得很像狗的黑熊，看了让人很想质问是不是就是你偷的唐僧的袈裟。</p>
<p>还有个展区围了许多人我们就没看，后来得知那应该是很火的一只会摆pose的网红猴。</p>
<p>同为灵长科，有的猴在山上收着游客投喂的好吃好喝，有的却被关进玻璃房，同猴不同命。</p>
<h3 id="麒麟洞">麒麟洞</h3>
<p>临出园时看到个麒麟洞，据说是关押张学良的地方，结果刚进洞内，在潮湿的石头台阶上，我就滑了一跤……</p>
<h3 id="但家香酥鸭豆腐圆子和贵厨牛肉">但家香酥鸭、豆腐圆子和贵厨牛肉</h3>
<p>逛完出园，已是下午两点左右。门口就有一家但家香酥鸭。我买了个双人套餐。</p>
<p>的确是如推荐所说很好吃。串串非常便宜，肉是炸得很干的鸭肉，连肉皮也被炸至酥脆类似于油渣的状态，就连我向来不吃鸭皮也觉得可以接受。闻起来略带鸭味，吃起来干酥脆香，吃完整个嘴唇都是麻的，很爽。刚出锅热乎的最好吃。</p>
<p>没多时开始下雨，我们回酒店休息了一会儿，就又打车出去觅食。本打算去吃夺夺粉，结果定位定错了没定到据说最好吃的那一家，就去了附近好评极高的贵厨牛肉。排队高达一百桌，取了号，我们去了附近的民生路逛了逛。</p>
<p>整条路上全是小摊。</p>
<p><img src="https://images.ygria.site/2024/08/bacc251c30e3c6201dca8c78564882f9.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>豆腐圆子，从中间破开两半，撒上折耳根、辣椒、酸萝卜等调料，外脆内嫩，超出预期的好吃。
还吃了豆腐卷，也是撒上许多折耳根。我感觉单独吃折耳根味道很奇特，撒在小吃上意外地很能接受。</p>
<p>排贵厨的号一直排到了快11点才吃上。酸汤锅底吃起来有些像番茄锅，味道很酸，让服务员帮忙调了两碟蘸水，牛肉品质相当不错。</p>
<p><img src="https://images.ygria.site/2024/08/167e63879fa96ea4a5937f6f3b0b98b9.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>能理解为啥这么多人了，89的双人套餐肉量就挺足，还有很甜的西瓜，素菜、米饭也都是免费。我们又加了两碟肉，加起来也只要一百多，性价比惊人，吃了个肚儿圆。</p>
<p>第二天准备去黄果树瀑布，我们订了单日团。</p>
<h1 id="day-2黄果树暴雨观瀑">Day 2：黄果树暴雨观瀑</h1>
<p>黄果树瀑布景区在安顺，从贵阳可以坐高铁过去，也可以拼车或坐大巴。不得不吐槽下预约票的小程序设计得超级复杂，不熟悉的人都看不明白该买哪种票，而且还有各种免票策略：属龙、护士、教师、军人、广东人、江苏人……可惜我们哪个也不符合。为了方便，我们还是选择了报精品6人内的小团，包门票并且有车到酒店接送，人少时间灵活些。</p>
<p>第二天7:30，老公接到电话，司机已经在楼下等了。上车了我才知道，原来报团就等于是拼车+门票，没有导游。这个团就四个人，除了我俩，还有两位是来自广东的母子俩。</p>
<p>一路上我和司机姐姐聊着天，了解了不少关于贵阳的事情。车行驶了两小时左右，到了安顺附近，已下起了瓢泼大雨。景区附近也堵起了长长的车队。下了车，我们先穿上了雨衣，刷身份证进了景区。和我们拼车的母子因为没有预约，就去免票窗口排队。</p>
<p>我们坐上观光大巴车时，看到广东女士在群里说景区限流了不卖票了，他们进不去……我们不免暗道侥幸。</p>
<p>后来得知许多人都被拦到门口了，景区八点半发了通知说暂停售票了，但那个时候应该很多人都住在附近或到路上了。</p>
<h2 id="大瀑布">大瀑布</h2>
<p>下了观光车，冒着暴雨，我们开始了向黄果树瀑布的徒步。人多不易打伞，我们就只穿着雨衣向前，水流漫漶脚面，大雨击打身躯，不多时裤腿和衬衫前襟就已全湿。大概走了一两公里（其中有许多是下石台阶的路），终于到了观瀑台，浑黄水流激起大片的水雾，我眼镜上全是水雾和水珠，头发也湿了大半。机智的老公还带了手帕，我撑伞，临时用手帕擦一擦眼镜、抹一把脸，然后就咔咔对着瀑布一顿拍。</p>
<p><img src="https://images.ygria.site/2024/08/e89a53d8db8f6578aa58c921f11910f8.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/08/5ef4c9dcec21bb32a4b00ef22fbeb7b1.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/08/08d313ee795d4c78e6d3a5a605bcfed1.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>雨极大，人极多，假如撑伞，得把手举得很高才能挤出人群，不打伞又被瓢泼大雨浇得抬不起头、睁不开眼。我感到这人人暴雨之下变成落汤鸡、还在景区打卡拍照的行为十分滑稽，不由狂笑。虽然很狼狈但感觉也是非常新鲜的人生体验。</p>
<p>从黄果树瀑布出来，我们乘坐了付费的扶梯，得以不用再冒雨攀楼梯。
<img src="https://images.ygria.site/2024/08/8d00bfd0473f333f0e0594c4c0325059.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>如网上的吐槽所说，下了扶梯后，还需要经过一段商业街才能走到观光车的停车点，骤然走到从风雨飘摇的大自然中走到商业街上，有种突然回到人类文明社会的感觉。</p>
<h2 id="天星桥">天星桥</h2>
<p>我们去的第二处小景点是天星桥，到达时雨势渐收，天空放晴，我只觉得不下雨太好了。
天星桥的上半程，没什么特别的，就是一些石头山洞，游客也少多了。走起来倒也清幽。</p>
<p><img src="https://images.ygria.site/2024/08/4978f9b6b898c81ce8f55071c04a9c19.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/08/28dbcee70f9bcf9d9a5d1b904b2ffbcf.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>有山石间的流瀑。</p>
<p><img src="https://images.ygria.site/2024/08/7e845b40b0a42cbf7f4cd387ca0856fc.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>一棵很大的榕树。</p>
<p><img src="https://images.ygria.site/2024/08/36bcb2b22802e891ef5bb35e0397a2ef.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>许多仙人掌，没见过这么长的，挺新奇。</p>
<p>走完上半程，是个蛮大的商业区，我俩逛了会、买了根烤肠吃，还买了个冰箱贴。正准备进军下半程时才知道，因为汛期涨水，下半程关闭了……
没办法，我俩就从上半程出口出去了。令人无语的是观光车的站点还是设在入口处，得走挺长一段路，当时感觉身上衣服湿湿的贴在身体上很不舒服，据说精华一些的景色都在下半程，有各种瀑布，没看到挺可惜的，不过看告示牌写，下半程需要步行2小时，又莫名有种松了口气的感觉~</p>
<p>路边看到非常高大的芭蕉树，感觉和诗中所写“一点芭蕉一点愁”的几丛柔弱的芭蕉景色大为不同。</p>
<h2 id="陡坡塘">陡坡塘</h2>
<p>坐观光车到最后一个景区，在门口就听到导游说这个景区比较轻松。</p>
<p><img src="https://images.ygria.site/2024/08/b885fd7bfd4c6a793b0481c8585c2ad9.jpg" alt="56ea6eed018fa230b34e863378424bf.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/08/a9d22e2ecc60799977611a587552661f.jpg" alt="d1dddf66e3f55f596f24ad465ec5016.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>这里主要是有一个西游记片尾曲取景地，所以出名。水流相当湍急，跟着人群簇拥到最里面，拍了张照片，我们就往出走了。里面道路狭窄，不少地方只能单人通行，就得挤着走。</p>
<p>这时司机姐姐说接到了另外两名游客（早上的母子已经回市区了，新的两名游客只看了大瀑布就出来了），可以一起回贵阳了。我们出了景点，走到观光车处坐了车，本以为会把我们送出景区，没想到大巴没开多久，就把我们放下来。</p>
<p>原来又是景区的妙计，又把我们放到了一个商业区，要穿过商业区走几百米再换乘摆渡车才能真正出去……此时我们都感觉到很无语且有些厌烦了，毕竟已经很累，不由得骂骂咧咧。</p>
<p>回贵阳路上天又开始转阴，先要送走另外一组客人，所以等把我们送到宾馆已是下午六点多。淋雨+暴走感觉人很虚弱，一到宾馆赶紧洗头洗澡休息了。</p>
<p>老公下楼吃了黑米粥和灌汤包，我晚上就在宾馆点了份外卖没出门。</p>
<h1 id="day-3-甲秀楼青云市集和民族博物馆">Day 3 甲秀楼、青云市集和民族博物馆</h1>
<h2 id="住宿危机">住宿危机</h2>
<p>前一天太累，第二天我们就打算在市区休闲玩一玩。没提前订好宾馆，又恰逢旺季，我们遭遇了住宿危机：临时在高德平台定了一家酒店，结果酒店迟迟不确认，打电话给酒店前台也打不通。取消需要手续费，打电话找高德客服也是没回应。后来大概一个小时左右终于回复了：酒店没房了，给我们取消了。</p>
<p>这时候再在飞猪上看住宿，发现酒店价格涨得离谱，许多地方售罄，全季、丽枫等连锁都要七八百甚至上千，价格稍微低一些四五百的点进去价格就咔咔往上蹦，几分钟眼睁睁看着它们涨一两百……我俩一直在APP反复检索试图找到物美价廉的房间，都被平台判定成爬虫需要输入验证码了。</p>
<p>我搜了一下我们当前住的房间（有异味，很普通，前一天住四百多一晚），发现都涨到一千多了，简直离谱。</p>
<p>在闲鱼看到有人卖当晚贵阳饭店的酒店，只要388，但是需要房卡面交。交流了一下不知道是不是真的，但价格和酒店位置都让人心动。老公说搜到贵阳有一种茅台房，就是定了房可以获得买茅台的资格，可能有人要买茅台但不住，就会把房间低价出售掉。我一听觉得很有道理啊！又看交易记录里全是好评，就下了单。</p>
<p>拖着行李箱，我们先去吃早有耳闻的笔记安顺夺夺粉。</p>
<h2 id="笔记安顺夺夺粉">笔记安顺夺夺粉</h2>
<p>又是一家非常出名的店，我11点卡点去公众号取了号，已经排到第五十多桌，等了快两小时才吃上。</p>
<p><img src="https://images.ygria.site/2024/08/a3ff98642ca7db29d47139e3680badd9.jpg" alt="f9eb9165cf2dafc12cf0d2c60f89572.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>滋味不负等待，非常好吃~汤底里放了很多香料，往酸汤锅里下入肉末，吃起来酸辣开胃，不像贵厨只是酸，而是丰富清爽的鲜香，之前没吃过。
<img src="https://images.ygria.site/2024/08/23e9743dea163cd9d4dddba9ef7c5414.jpg" alt="a775a28bdb7234b4dac1b8bd4ba713f.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>锅底好吃，下什么菜都好吃。夺夺粉就是米豆腐，吃起来也蛮不错，很糯很弹的口感，米香浓郁。肉丸下进去最好吃。</p>
<p><img src="https://images.ygria.site/2024/08/ab9c6098c98f901ea6d36567577956e7.jpg" alt="5f6e4c38a19bfd5a1cbb14e18c36a04.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>点了一杯黄瓜味的冰浆，喝起来也很新鲜。</p>
<h2 id="南明区和甲秀楼">南明区和甲秀楼</h2>
<p>吃完饭，前往甲秀楼。老公在手机上查了一下说预约已经满了，我们就打算在外围看一看。到地方发现根本不用预约，工作人员在小桌子上摆了六张小卡片，只需掏出手机拍一张就行了……
很是无语，纯纯客户端校验啊。</p>
<p><img src="https://images.ygria.site/2024/08/054094c2ae7e9653d40daf650c3f9a73.jpg" alt="99c9ea30dcc6a540c52a998f17f404e.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>老公老实，还在那问工作人员：一个人拍是不是就行了……</p>
<p>甲秀楼是明朝建的，平平无甚出奇之处。据说夜景好看。</p>
<p><img src="https://images.ygria.site/2024/08/e5bd8b2328d841ceb770741d66a2a292.jpg" alt="f3df3736218a7f2f942bdbc72d0e392.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/08/e3563f106d0d298d35a664a2c1ca5f31.jpg" alt="9e90314fe1b55551dc8977bb8275c8f.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>正对河水和孔桥。</p>
<p>到翠微园也是一样，桌上摆了六张卡片，如果在甲秀楼拍过就可以不用拍了。</p>
<p><img src="https://images.ygria.site/2024/08/c3903fa8f53b32979b64a96c480cf63b.jpg" alt="4c1c8e0bf3acb92567a49f84fd7cd05.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>拱南阁，南明时的建筑。应当是拱固南朝的意思。贵阳这个区也叫南明区，感觉很是反清复明……在这么西南的地方想要反攻中原感觉也不太容易啊。</p>
<h2 id="民族博物馆">民族博物馆</h2>
<p>就近有民族博物馆，我们也进去参观了一番。</p>
<p><img src="https://images.ygria.site/2024/08/90d0ac0ae348d04916e12c5e7084859d.jpg" alt="0eefb8ebe245fd4d41e4f8fdacf6348.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>一楼展出许多木刻版画。</p>
<p><img src="https://images.ygria.site/2024/08/12490ab083d175f949da1e16bb162d72.jpg" alt="de8f9a7a7979d4e7b10251d4fabbbe6.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>奢香夫人。才去看凤凰传奇演唱会现场听到的《奢香夫人》，乌蒙山就是贵州的。看了简介感觉奢香夫人是个带路党，把明军带回自己家了……</p>
<p><img src="https://images.ygria.site/2024/08/0946d906292620b77903c162303128c0.jpg" alt="ec4ef8bb916d094f3c257567602b52d.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>黔绣。这边的绣品远不如苏绣、蜀绣精致，较为朴素，很有民族特色。
<img src="https://images.ygria.site/2024/08/3b981a2f76f9a68cae280771c80205df.jpg" alt="71c721d70051ab06bde818bda4a357f.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>和尚面具。感觉在红色的底板上就像眼睛发红光一样，挺吓人。
<img src="https://images.ygria.site/2024/08/ff50b02c117f57c800b56da586b339af.jpg" alt="26aea508b4b47d5b289f4bafb409f3d.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/08/3ab125db930477d574f8e97a30fd1ed1.jpg" alt="17b03b751d85728d9e845176d8f382f.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>苗银展览。苗族人穿戴的银器非常繁复精致漂亮，应该是把全身的家当都穿身上了。</p>
<p><img src="https://images.ygria.site/2024/08/6a646544ccc29e2c27a2a5c73f663c05.jpg" alt="436f416ea618e7ab433994e0afca570.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>嵌上宝石的银饰更漂亮了。</p>
<h2 id="突然的打击">突然的打击</h2>
<p>正在博物馆闲逛时，突然手机收到了闲鱼退款的消息，我们定的“茅台房”订单被关闭了。
赶紧打开手机准备询问卖家，一个问号发过去，收到一个红色感叹号，对方已经预防性拉黑了……有种悬着的心终于死了的感觉。</p>
<p>被拉黑了，自然也无法评价交易，难怪对方全是好评。上当了。</p>
<p>没办法，我俩就只能又上APP搜酒店，最后定了一个离青云市集很近的七天，虽然也贵而且看着就很破，但也只能如此将就了。</p>
<h2 id="青云市集">青云市集</h2>
<p>先去入住，然后就近去逛青云市集。夜晚的霓虹灯和灯牌很漂亮。</p>
<p><img src="https://images.ygria.site/2024/08/9c3539752ba36a6150fffc0568f94b97.jpg" alt="1eed795e7ffe1ff367d6c1ffeee1a69.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/08/a849d0012c456fc363ac10e91a5b4512.jpg" alt="d79a9dedf33925296117edc25c672bd.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>建筑上有梵高画的霓虹灯。
有很多网红店，人超多。</p>
<p><img src="https://images.ygria.site/2024/08/a680ccf53bfd32e2411a3b9661a0c991.jpg" alt="c71e0f8a694b2b7f22aee774177911a.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>布置得很不错的小店。</p>
<p>我们买了本盖章本，到各个文创店闲逛、购物、盖章，有很多漂亮的套色章可以盖，足足盖了小半本，还蛮开心的。</p>
<p>逛到深夜，回去路上随便吃了一家烙锅。应该是本地人开的店，感觉偏油，味道也一般般。</p>
<h1 id="day-4-医院拍片--贵州省博--回家">Day 4 医院拍片 、 贵州省博 、 回家</h1>
<p>因为我第一天摔了跤，走路没事，坐下也没事，坐下或站起就一阵剧痛……咳嗽也会痛，已经三天了还有点加剧的感觉。老公一直主张让我去医院看看。离我们宾馆很近就有个医院可以拍CT，我们就去挂号了。拍片的结果是骨头没事，应该就是肌肉拉伤。检查之后还是放心多了。</p>
<h2 id="贵州省博">贵州省博</h2>
<p>检查完已经不早了，我们就打算就去省博溜一圈。</p>
<p>省博说是要预约，实际上可以直接进。</p>
<p><img src="https://images.ygria.site/2024/08/fdf8f85dcac4886765056a27c2aa0eb8.jpg" alt="30fb961d4d89fe4d5c19f7b78209888.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>不锈钢的千户苗寨雕塑，相当漂亮。</p>
<p><img src="https://images.ygria.site/2024/08/58940eb6763271e2fb9d46fc65a4dad1.jpg" alt="4db5325bdb2593cd0528451c92568e3.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>谁不喜欢黄金呢。</p>
<p>人太多+时间紧，我们就没仔细逛，坐地铁去了机场。</p>
<p><img src="https://images.ygria.site/2024/08/cfd1d25f8af0c70556f86df78f171940.jpg" alt="8d202f073b499c58b8d429dfb016ab2.jpg" class="img-hide" loading="lazy" decoding="async" /></p>
<p>候机厅的风景还真不错。</p>
<h2 id="感想">感想</h2>
<p>坐飞机回家，结束了为期四天的旅行。合肥虽然热，但家还是让人感到无比亲切。</p>
<p>下次旅游还是得多做攻略。我们也是不赶巧，8.3、8.4 贵阳举办一个十几万人的音乐节，所以才会住宿那么难定，8.5就看到贵阳的市监局发文控价，我们属于正好遇到了。又遇到黄果树瀑布下大暴雨和涨水，别人看到的都是清水瀑布，我们看到的通黄。</p>
<p>我走路也是不看路，所以摔跤，所幸没有骨折，问题不大。</p>
<p>不过也是很难忘的体验了，和当地的司机姐姐、观光车旁边的游客交流，被科普了许多值得去的贵州景点，小七孔、马岭河、织金洞等等，就留到以后再来玩吧~</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>基于Hadge和苹果健康搭建锻炼页面</title>
      <link>https://ygria.site/build-workout-page/</link>
      <pubDate>Tue, 30 Jul 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/build-workout-page/</guid>
      <description>希望可以通过这个页面，督促自己好好运动起来，不当懒惰虫</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="基于Hadge和苹果健康搭建锻炼页面" /><h1 id="页面效果">页面效果</h1>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9zcG9ydHMueWdyaWEuc2l0ZS8%3d"
    
     target="_blank"
>
独立页面效果</a></p>
<p><img src="https://images.ygria.site/2024/07/3c6340d5a54764e368a4fa054aa6a56c.gif" alt="GIF 2024-7-30 10-38-48.gif" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/07/980543e229bdb929e8fa6b9026e9b33d.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/07/0efa363434e427ecdac223a978046ea9.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>嵌入博客效果： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly95Z3JpYS5zaXRlL3Nwb3J0cy8%3d"
    
     target="_blank"
>
嵌入博客效果: Ygria&rsquo;s Blog 锻炼页面</a></p>
<p><img src="https://images.ygria.site/2024/07/bc2f51d496a825f9ac3afb5747e5f746.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>基于苹果健康数据，搭建了个人锻炼信息页面，会显示每日的锻炼圆环，并根据目标完成情况渲染热力图（100%达成显示绿色，60%及以上显示橙色，否则为红色。）</p>
<p>表格中是每次锻炼的记录，会记录锻炼的类型、耗时、消耗热量等信息。</p>
<p>下面将介绍页面的搭建过程。</p>
<h1 id="搭建过程">搭建过程</h1>
<h2 id="工作流">工作流</h2>
<p>通过搭建如下工作流，实现在手机上点击一下，就能同步构建一个页面。</p>
<p><img src="https://images.ygria.site/2024/07/09085aca8e02f4f27455e29ea11e5af2.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="数据来源-hadge">数据来源： Hadge</h2>
<p>Hadge 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL2FzaHRvbS9oYWRnZQ%3d%3d"
    
     target="_blank"
>
GitHub - ashtom/hadge: 💪 Export workout data from Health.app on iOS to a GitHub repo</a> 是一款可以安装在苹果手机上，实现健康数据导出到Github仓库的APP。你可以从TestFlight安装它。</p>
<p><img src="https://images.ygria.site/2024/07/cb0d0193a29e194fb3701e52239e2978.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>安装、授权Github和健康数据读取，它就会自动为你创建一个名字叫health的Github私人仓库，里面是csv格式的健康数据，包括每日活动量、步数、锻炼等等。</p>
<p><img src="https://images.ygria.site/2024/07/0c2a15d09045d4ca9b4b74abdb14a63e.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="页面">页面</h2>
<p>新建Github仓库下React项目（我是基于开源项目 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL3lpaG9uZzA2MTgvcnVubmluZ19wYWdl"
    
     target="_blank"
>
Running Page</a>开发，想法是后续如果有GPX数据可以集成地图，所以是直接Fork的Running Page。）</p>
<h3 id="csv文件读取">CSV文件读取</h3>
<p>将health中的文件拷贝到public目录下，就可以在项目中使用这些数据了。</p>
<p>使用<code>papaparse</code>读取csv文件，定义一个读取文件的hook：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> typescript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="typescript" data-theme="breeze" id="code-id-1">
import { useState, useEffect } from &#39;react&#39;;
import { readString } from &#39;react-papaparse&#39;;
const useCSVParserFromURL = (fileURL) =&gt; {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  useEffect(() =&gt; {
    if (!fileURL) {
      setLoading(false);
      return;
    }
    const fetchCSV = async () =&gt; {
      setLoading(true);
      try {
        const response = await fetch(fileURL);
        if (!response.ok) {
          throw new Error(&#39;Network response was not ok&#39;);
        }
        const csvString = await response.text();
        readString(csvString, {
          header: true, // optional: if your CSV string has a header row
          skipEmptyLines: true, // 忽略空行
          complete: (result) =&gt; {
            setData(result.data.reverse());
            setLoading(false);
          },
          error: (err) =&gt; {
            setError(err);
            setLoading(false);
          },
        });
      } catch (err) {
        setError(err);
        setLoading(false);
      }
    };
    fetchCSV();
  }, [fileURL]);
  return { data, loading, error };
};
export default useCSVParserFromURL;</div>
    </div>
  </div>
</div><p>hook使用：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> typescript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="typescript" data-theme="breeze" id="code-id-2">const { data } = useCSVParserFromURL(&#39;/distances/2024.csv&#39;);
const { data: activityData } = useCSVParserFromURL(&#39;/activity/2024.csv&#39;);</div>
    </div>
  </div>
</div><p>将读到的内容根据日期归并成一个数组，在点击“前一天”“后一天”时切换读取数组的下标即可。</p>
<h3 id="图表绘制">图表绘制</h3>
<p>使用
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kM2pzLm9yZy8%3d"
    
     target="_blank"
>
d3</a>和
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cubnBtanMuY29tL3BhY2thZ2UvQHJpaXNoYWJoL3JlYWN0LWNhbGVuZGVyLWhlYXRtYXA%3d"
    
     target="_blank"
>
react-calender-heatmap</a>绘制图表。</p>
<p>d3可以灵活地绘制svg图形，我用它来绘制三层圆环。</p>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cubnBtanMuY29tL3BhY2thZ2UvQHJpaXNoYWJoL3JlYWN0LWNhbGVuZGVyLWhlYXRtYXA%3d"
    
     target="_blank"
>
react-calender-heatmap</a>提供了react组件，直接把数据传进去就行了。
我定义了锻炼目标完成显示为绿色，完成60%及以上显示为橙色，否则为红色。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> typescript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="typescript" data-theme="breeze" id="code-id-3">import { useEffect, useRef, useState } from &#39;react&#39;;
import useCSVParserFromURL from &#39;@/hooks/useWorkouts&#39;;
import { TileChart } from &#34;@riishabh/react-calender-heatmap&#34;;
const Heatmap = () =&gt; {
  const { data: activityData } = useCSVParserFromURL(&#39;/activity/2024.csv&#39;);
  const [dummydata,setDummyData] = useState([]);
  useEffect(() =&gt; {
    if (activityData.length === 0) return;
    // 解析数据
    const parsedData = activityData.map(row =&gt; {
      const date = row[&#34;Date&#34;];
      const calories = &#43;row[&#34;Move Actual&#34;];
      const caloriesGoal = &#43;row[&#34;Move Goal&#34;]
      const status = calories &gt; caloriesGoal ? &#39;success&#39; : calories &gt; caloriesGoal * 0.6 ? &#39;warning&#39; : &#39;alert&#39;
      return {
        date: new Date(date),
        status: status
      };
    });
    setDummyData(parsedData)
  }, [activityData]);
  return (
    &lt;&gt;
    &lt;TileChart data={dummydata} range={6} /&gt;
    &lt;/&gt;
  );
};
export default Heatmap;</div>
    </div>
  </div>
</div><p><img src="https://images.ygria.site/2024/07/2c020fbf1054e40972ff7570f32fb946.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>看我的热力图就知道我有多懒了……我的目标是每天400千卡，没有很高。得努力动起来把格子都填充成绿色了~</p>
<p>表格的渲染沿用了running page项目中的run table，遍历activity中的内容并渲染。</p>
<h3 id="样式">样式</h3>
<p>背景和文字样式使用了 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9hbmltYXRhLmRlc2lnbi9kb2NzL2JhY2tncm91bmQvYmx1cnJ5LSoqYmxvYg%3d%3d"
    
     target="_blank"
>
animata.design : # Blurry blob</a>和  
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9hbmltYXRhLmRlc2lnbi9kb2NzL3RleHQvdGlja2Vy"
    
     target="_blank"
>
animata.design : # Ticker</a>。</p>
<p>这个网站提供了很多使用TailwindCSS实现的动效组件，并支持直接复制粘贴到项目中，无需再安装依赖，侵入性低，使用简便。使用时也可以学习学习，非常不错~</p>
<p>悬停和选中表格某列的文字效果来自于 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9wcmltZXJlYWN0Lm9yZy8%3d"
    
     target="_blank"
>
https://primereact.org/</a> 首页，通过<code>webkit-background-clip: text;</code> 文字蒙版 + 渐变背景 + 动画，让文字有了彩色渐变的效果。</p>
<h3 id="部署">部署</h3>
<p>使用CloudFlare Pages 托管部署，监听到push事件时触发构建。</p>
<h2 id="workout-page-中拉取health内容">Workout Page 中拉取health内容</h2>
<p>上一步开发中，我们是将health仓库下csv文件拷贝到了workout pages的public目录下。那么怎么实现自动同步呢？
我们可以使用Github的Action，在构建时checkout health中的内容，并commit到workouts page中。
首先需要在Github中新建一个具有health仓库访问权限的token
前往 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL3NldHRpbmdzL3Rva2Vucw%3d%3d"
    
     target="_blank"
>
Github Setting</a>, 新建token</p>
<p><img src="https://images.ygria.site/2024/07/558b819a1a3c547fb680c6f3c0c6515d.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
<img src="https://images.ygria.site/2024/07/9771a669e1f957380b45494fb4f52d65.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>将这个token配置到Action的Secret中：
<img src="https://images.ygria.site/2024/07/340e68c656e95cf5c5acda21fad8a968.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>配置Action，先克隆当前库（workouts page），再签出health （指定path为<code>/public</code>），再提交。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> yaml</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="yaml" data-theme="breeze" id="code-id-4">name: Health Data Sync
on:
  push:
    branches:
      - master
  workflow_dispatch:    
jobs:
  sync-repo:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Checkout other repository
        uses: actions/checkout@v3
        with:
          repository: Ygria/health
          path: public
          token: ${{ secrets.HEALTH_TOKEN}} # 使用自定义的 token   
      - name: Commit changes
        uses: EndBug/add-and-commit@v8
        with:
          author_name: Apple Health Sync  # 提交者的GitHub用户名
          author_email: Apple Health Sync  # 提交者的电子邮件
          message: &#39;Automatically commit changes&#39;  # 提交信息
          add: &#39;.&#39;  # 添加当前目录下的所有变更</div>
    </div>
  </div>
</div><p>这样我们就实现了每次构建时，用的都是最新的健康数据了。</p>
<h2 id="health更新触发workout-pages">health更新触发workout pages</h2>
<p>问题来了，health内容变化了，该如何判断什么时候构建workout pages呢？</p>
<p>目前触发health更新是在手机上去hadge app点击，如果使用手机快捷指令触发Github Action，有可能有时序问题。（定时任务可能也是个好主意。）</p>
<p>我选择了在health中配置一个webhook，通过api触发workouts page中Health Data Sync的执行。Github支持通过REST接口操作Github Action，可参考： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb2NzLmdpdGh1Yi5jb20vZW4vcmVzdC9hY3Rpb25zP2FwaVZlcnNpb249MjAyMi0xMS0yOA%3d%3d"
    
     target="_blank"
>
Github Docs: REST Actions</a></p>
<p>在配置webhook时，我发现不支持自定义请求头和请求体，所以又去Cloudflare配置了一个worker做代理。安全起见，Github请求端使用secret加密，worker侧做了密钥验证。</p>
<h3 id="cloudflare-worker代理">Cloudflare worker代理</h3>
<h4 id="worker-使用">Worker 使用</h4>
<p>Worker的使用可以参考官方教程 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g%2fdj1IN1FlOTZmcWcxTQ%3d%3d"
    
     target="_blank"
>
Learn Cloudflare Workers - Full Course for Beginners</a>，是个很长的视频，看前十分钟就差不多够用了。</p>
<p>以更易于本地调试的方式使用Cloudflare Worker：</p>
<ol>
<li>在控制台使用<code>npx wrangler</code>，(mac os需要加上sudo)，第一次使用会自动安装wrangler最新版本</li>
<li> 使用<code>wranger init</code>创建一个新项目
<img src="https://images.ygria.site/2024/07/8b250a1e7c7ebfd70995b2a62cce44b9.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></li>
<li>在在线编辑页面上，也可以点击<code>Develop with Wrangler CLI</code>页签，将配置过的Worker 克隆到本地进行调试。
<img src="https://images.ygria.site/2024/07/2e57617b5007e624f2dbe5600c980056.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></li>
</ol>
<h4 id="参数准备和脚本">参数准备和脚本</h4>
<ol>
<li>先通过REST请求，拿到需要触发的workflow id</li>
<li>在Github中新建一个具有workflow权限的token，调用Github api时要加到header中</li>
</ol>
<p><img src="https://images.ygria.site/2024/07/25a369f0e661a0ac92c4e3bfd49d07c1.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/07/6afd4a0168132b7f341766803227e482.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>脚本内容</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-5">
addEventListener(&#39;fetch&#39;, event =&gt; {
  event.respondWith(handleRequest(event.request))
})

async function computeHMAC(secret, message) {
  const encoder = new TextEncoder()
  const key = await crypto.subtle.importKey(
    &#39;raw&#39;,
    encoder.encode(secret),
   { name: &#39;HMAC&#39;, hash: &#39;SHA-256&#39; },
    false,
    [&#39;sign&#39;]
  )
  const signature = await crypto.subtle.sign(
    &#39;HMAC&#39;,
    key,
    encoder.encode(message)
  )
  return hex(signature)
}

function hex(buffer) {
  const byteArray = new Uint8Array(buffer)
  const hexCodes = [...byteArray].map(value =&gt; {
    return value.toString(16).padStart(2, &#39;0&#39;)
  })
  return hexCodes.join(&#39;&#39;)
}

function secureCompare(a, b) {
  if (a.length !== b.length) {
    return false
  }
  let result = 0
  for (let i = 0; i &lt; a.length; i&#43;&#43;) {
    result |= a.charCodeAt(i) ^ b.charCodeAt(i)
  }
  return result === 0
}

  
async function handleRequest(request) {
  const url = new URL(request.url)
  const signature = request.headers.get(&#39;x-hub-signature-256&#39;)
  if (!signature) {
   return new Response(&#39;Missing signature&#39;, { status: 400 })
  }
  // 预期的 token
  const expectedToken = &#39;your token&#39;
  const body = await request.text()
  const expectedSignature = `sha256=${await computeHMAC(expectedToken, body)}`
  if (!secureCompare(signature, expectedSignature)) {
    return new Response(&#39;Unauthorized&#39;, { status: 401 })
  }


  const targetUrl = &#39;https://api.github.com/repos/name/repo/actions/workflows/your flow id/dispatches&#39;
  // 创建新的请求头
  const newHeaders = new Headers({})
  newHeaders.set(&#39;Content-Type&#39;, &#39;application/json&#39;)
  newHeaders.set(&#39;Authorization&#39;, &#39;your token&#39;)
  newHeaders.set(&#39;Accept&#39;, &#39;application/vnd.github.v3&#43;json&#39;)
  newHeaders.set(&#39;User-Agent&#39;, &#39;application/vnd.github.v3&#43;json&#39;)
  // 创建新的请求体
  const newBody = JSON.stringify({
    &#34;ref&#34;:&#34;master&#34;
  })
  // 转发请求到目标服务器
  const response = await fetch(targetUrl, {
    method: &#39;POST&#39;,
    headers: newHeaders,
    body: newBody
  })

  // 返回目标服务器的响应
  const responseBody = await response.text()
  return new Response(responseBody, {
    status: response.status,
    headers: response.headers
  })
}</div>
    </div>
  </div>
</div><p>部署worker后，将worker的访问url配置到health的webhook中即可。</p>
<p><img src="https://images.ygria.site/2024/07/1adf02784f781aa46c924830141a4e5c.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="嵌入博客">嵌入博客</h2>
<h3 id="嵌入时样式">嵌入时样式</h3>
<p>想在我的Hugo静态博客中也能看到这个页面，可以使用iframe嵌入。嵌入的页面样式略有不同，可以在workout pages中，增加一个hook来判断当前是否是嵌入的页面：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> typescript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="typescript" data-theme="breeze" id="code-id-6">import { useEffect, useState } from &#39;react&#39;;
const useIsEmbedded = () =&gt; {
  const [isEmbedded, setIsEmbedded] = useState(false);
  useEffect(() =&gt; {
    // 检测当前页面是否被嵌入到 iframe 中
    if (window.self !== window.top) {
      setIsEmbedded(true);
    }
  }, []);
  return isEmbedded;
};
export default useIsEmbedded;</div>
    </div>
  </div>
</div><p>使用：控制嵌入时不显示header</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> tsx</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="tsx" data-theme="breeze" id="code-id-7">import useIsEmbedded from &#39;@/hooks/useIsEmbedded&#39;;

const isEmbedded = useIsEmbedded();

{!isEmbedded &amp;&amp; (&lt;&gt;
	&lt;Header /&gt;&lt;/&gt;)}</div>
    </div>
  </div>
</div><p>通过一些样式调整，可以让页面嵌入显得更融合。</p>
<h3 id="hugo中iframe">hugo中iframe</h3>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> go</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="go" data-theme="breeze" id="code-id-8">{{ define &#34;body_classes&#34; }}page-workouts{{ end }} {{ define &#34;main&#34; }
{{ $src := .Params.src }}
{{ $width := .Params.width | default &#34;100%&#34; }}
{{ $tryautoheight := .Params.tryautoheight | default true }}
{{ $style := .Params.style | default &#34;min-height:98vh; border:none;&#34; }}
{{ $sandbox := .Params.sandbox | default false }}
{{ $name := .Params.name | default &#34;iframe-name&#34; }}
{{ $id := .Params.id | default &#34;iframe-id&#34; }}
{{ $class := .Params.class }}
{{ $sub := .Params.sub | default &#34;Your browser can not display embedded frames. You can access the embedded page via the following link:&#34; }}
{{ with $src }}
{{ if $tryautoheight }}
  &lt;script type=&#34;text/javascript&#34;&gt;
    function resizeIframe(iframe) {
      iframe.height = iframe.contentWindow.document.body.scrollHeight &#43; &#34;px&#34;;
    }
  &lt;/script&gt;  
{{ end }}
&lt;div className=&#34;container&#34;&gt;
    &lt;div className=&#34;blob-container&#34;&gt;
      &lt;div
        class =&#34;blob blob-blue&#34; &gt;&lt;/div&gt;
      &lt;div
       class =
          &#34;blob blob-purple&#34;
      &gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;iframe id=&#34;{{ $id }}&#34;{{ with $class }} class=&#34;{{ $class }}&#34;{{ end }} src=&#34;{{ $src }}&#34; width=&#34;{{ $width }}&#34; name=&#34;{{ $name }}&#34;{{ with $style }} style=&#34;{{ $style | safeCSS }}&#34;{{ end }}{{ if $tryautoheight }} onload=&#34;resizeIframe(this)&#34;{{ end }} referrerpolicy=&#34;no-referrer&#34;{{ if (eq $sandbox false)}}{{ else if (eq $sandbox true) }} sandbox{{ else }} sandbox=&#34;{{ $sandbox }}&#34;{{ end }}&gt;
  &lt;p&gt;{{ $sub }} &lt;a href=&#34;{{ $src }}&#34;&gt;{{ $src }}&lt;/a&gt;&lt;/p&gt;
&lt;/iframe&gt;
{{ end }}

{{end}}</div>
    </div>
  </div>
</div><p>在锻炼 markdown文件的front matter中，声明需要嵌入的页面url即可。
<img src="https://images.ygria.site/2024/07/4015baa8338327ff1db9395109de866e.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="小结">小结</h1>
<p>锻炼页面完工~希望可以通过这个页面，督促自己好好运动起来，不当懒惰虫。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Hugo博客美化：更美观代码块生成，支持复制和导出图片</title>
      <link>https://ygria.site/prettier-codeblock/</link>
      <pubDate>Sun, 21 Jul 2024 22:13:33 +0800</pubDate>
      
      <guid>https://ygria.site/prettier-codeblock/</guid>
      <description>&lt;p&gt;实现效果： 
&lt;a 
    
        href=&#34;https://ygria.site/tiaozhuan?target=aHR0cHM6Ly95Z3JpYS5zaXRlL3ByZXR0aWVyLWNvZGVibG9jay1kZW1vLw%3d%3d&#34;
    
     target=&#34;_blank&#34;
&gt;
博客美化：代码块美化示例 - Ygria&amp;rsquo;s Blog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://images.ygria.site/2024/07/adb5bf3140f5cbdc6bd90fb48f1a6996.png&#34; alt=&#34;my-image (14).png&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://images.ygria.site/2024/07/cf3233e35929ac0bd8d3f1d22031986e.png&#34; alt=&#34;my-image (24).png&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;
&lt;p&gt;偶然看到ray.so的开源工具网站：
&lt;a 
    
        href=&#34;https://ygria.site/tiaozhuan?target=aHR0cHM6Ly93d3cucmF5LnNvLw%3d%3d&#34;
    
     target=&#34;_blank&#34;
&gt;
Create beautiful images of your code&lt;/a&gt;，提供了非常漂亮的代码图片生成功能，支持一系列主题，并能对代码块进行高亮、添加背景并导出成图片。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Hugo博客美化：更美观代码块生成，支持复制和导出图片" /><p>实现效果： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly95Z3JpYS5zaXRlL3ByZXR0aWVyLWNvZGVibG9jay1kZW1vLw%3d%3d"
    
     target="_blank"
>
博客美化：代码块美化示例 - Ygria&rsquo;s Blog</a></p>
<p><img src="https://images.ygria.site/2024/07/adb5bf3140f5cbdc6bd90fb48f1a6996.png" alt="my-image (14).png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/07/cf3233e35929ac0bd8d3f1d22031986e.png" alt="my-image (24).png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>偶然看到ray.so的开源工具网站：
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cucmF5LnNvLw%3d%3d"
    
     target="_blank"
>
Create beautiful images of your code</a>，提供了非常漂亮的代码图片生成功能，支持一系列主题，并能对代码块进行高亮、添加背景并导出成图片。</p>
<p><img src="https://images.ygria.site/2024/07/e081fa9eb92ffce80c80d17a175ea1b6.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>正好最近在折腾我的Hugo静态博客，索性就把这个功能集成了进来。配合Hugo官方提供的render-codeblock hook，可以无痛兼容之前的博客文章。</p>
<h1 id="hugo-render-code-hook">Hugo render code hook</h1>
<p>参考Hugo博客官方给出的文档： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9nb2h1Z28uaW8vcmVuZGVyLWhvb2tzL2NvZGUtYmxvY2tzLw%3d%3d"
    
     target="_blank"
>
Code block render hooks | Hugo</a>，我们可以劫持Markdown文件生成html的过程，用自己的代码段代替。</p>
<p>在主题文件夹下（如果没有主题，则在根目录下），新建文件<code>render-codeblock.html</code>(注意文件名一定不能弄错，否则会无法解析到)</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-1">
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> render-codeblock</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-1">your-hugo-site/
├── themes/
   └── themename/
       ├── layouts/
          └── _default/
                  └── _markup/
                      └── render-codeblock.html</div>
    </div>
  </div>
</div><p>在配置文件中，添加：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> toml</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="toml" data-theme="breeze" id="code-id-2"># Hugo 解析文档的配置
[markup]
  # 语法高亮设置 (https://gohugo.io/content-management/syntax-highlighting)
  [markup.highlight]
    noClasses = false</div>
    </div>
  </div>
</div><p>这样我们就可以编写自己的渲染逻辑了。</p>
<h1 id="render-codeblock模版结构">render-codeblock模版结构</h1>
<p>通过检查元素，容易得到页面结构为：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-3">
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> render-code-template</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-3">wrapper 最外层）
├── background（根据传入参数，判断是否显示）
├──  window（代码窗口）
       ├──  header 
        │   └── controls 仿apple窗口的三个圆点
        │   └──  fileName 文件名
        │  └── 操作button，支持复制代码和导出图片
        └──   code（代码块） </div>
    </div>
  </div>
</div><p>在这个页面可以通过<code>.Type</code>拿到传入的语言（即写markdown语法时，```后紧邻的声明）,并能通过类似于```{title=&ldquo;rust demo&rdquo; theme=&ldquo;candy&rdquo; mode=&ldquo;dark&rdquo; padding=&ldquo;16&rdquo;}语法，传入attribute map并在模版代码中解析。通过这种方法，我们能够更灵活地应用代码块并渲染不同的样式。</p>
<h1 id="自定义高亮">自定义高亮</h1>
<h2 id="主题声明">主题声明</h2>
<p>通过查看ray.so源码得知使用了
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9zaGlraS5zdHlsZS8%3d"
    
     target="_blank"
>
shiki</a> 进行高亮，并自定义了若干主题。在assets/js下新建一个名为hightlighter.js的文件
并导入shiki：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-4">import { getHighlighter } from &#39;https://esm.sh/shiki@1.0.0&#39;</div>
    </div>
  </div>
</div><p>需要将这个js文件引入到Hugo模版代码中。由于使用了import语法，需要声明type = &ldquo;module&rdquo;</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> go</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="go" data-theme="breeze" id="code-id-5">{{ $hightlightJS := resources.Get &#34;js/hightlight.js&#34; | resources.Minify | resources.Fingerprint }}
&lt;script src=&#34;{{ $hightlightJS.Permalink }}&#34; defer type = &#34;module&#34;&gt;&lt;/script&gt;</div>
    </div>
  </div>
</div><p>将ray.so中声明的主题列表THEMES拷贝过来(在<code>app/(navigation)/(code)/store/themes.ts</code>中：)</p>
<p><img src="https://images.ygria.site/2024/07/2f30abdcad1324e0bcb5a60232a07b02.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>通过阅读源码，得知是通过动态替换CSS Variable的值来渲染主题的，因此我们只需要声明一个主题，即css-variables主题。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-6">const shikiTheme = createCssVariablesTheme({
  name: &#34;css-variables&#34;,
  variablePrefix: &#34;--ray-&#34;,
  variableDefaults: {},
  fontStyle: true,
});</div>
    </div>
  </div>
</div><p>该主题通过读取当前作用域内的CSS Variables的值进行代码高亮。</p>
<h2 id="声明用于高亮的hightlighter">声明用于高亮的hightlighter</h2>
<p>使用单例，声明hightlighter。这里我做了单例声明，这样如果页面上有很多代码块，可以避免重复创建多次，并预先将页面上所有代码块使用的语言遍历出来，获取hightlighter。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-7">const ShikiHightlighter = (function () {
  let highlight = null;
  async function init(languages) {
    // Singleton 初始化代码
    highlight = await getHighlighter({
      langs: languages,
      themes: [shikiTheme]
    })
    return highlight
  }

  return {
    getInstance: function (languages) {
      if (!highlight) {
        highlight = init(languages);
      }
      return highlight;
    }
  };
})();

// after dom loaded
document.querySelectorAll(&#39;[id^=&#34;code-id-&#34;]&#39;).forEach(element =&gt; {
    console.log(element.dataset.language); 
    languages.add(element.dataset.language)
});
var highlighter = await ShikiHightlighter.getInstance(Array.from(languages));</div>
    </div>
  </div>
</div><h2 id="页面加载后执行高亮逻辑">页面加载后，执行高亮逻辑</h2>
<p>在render-codeblock.html中，将读入的属性放到html元素的CSS属性内，再通过CSS选择器和属性进行获取。格式为<code>data-*</code>的属性，可以直接通过<code>element.dataset.*</code>的方式来获取，很方便。
根据传入的theme和mode（light或dark），可以从THEMES对象里拿到CSS Varible属性，将这个属性赋到code wrapper上，这样高亮出的效果就和变量一致了。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 16px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-8">document.addEventListener(&#34;DOMContentLoaded&#34;, async function () {
  const codeblocks = document.querySelectorAll(&#34;.code-wrapper&#34;);
  // 如果有，则调用hightlight
  if (codeblocks) {
    const languages= new Set();
    document.querySelectorAll(&#39;[id^=&#34;code-id-&#34;]&#39;).forEach(element =&gt; {
      console.log(element.dataset.language); 
      languages.add(element.dataset.language)
    });
    var highlighter = await ShikiHightlighter.getInstance(Array.from(languages));
    codeblocks.forEach(async codeblock =&gt; {
      try {
        const codeElement = codeblock.querySelector(&#39;[id^=&#34;code-&#34;]&#39;)
        const code = codeElement.textContent;
        const language = codeElement.dataset.language;
        const variables = THEMES[codeElement.dataset.theme][&#39;syntax&#39;][codeblock.dataset.theme]
        const styleVariables = Object.keys(variables).map(key =&gt; `${key}: ${variables[key]};`).join(&#39; &#39;);
        codeblock.style = styleVariables;
        console.log(&#34;data-language :&#34; &#43; language); // 输出: &#34;python&#34;
         // 使用shiki进行代码高亮
        const highlightedCode = await highlighter.codeToHtml(code, {
          lang: language,
          theme: &#34;css-variables&#34;
        })
        codeElement.innerHTML = highlightedCode;
      } catch (error) {
        console.error(&#34;hightlight failed...&#34;, error)
      }
    })
  }
}
)</div>
    </div>
  </div>
</div><h2 id="headerbgpadding及样式背景图片">header、bg、padding及样式背景图片</h2>
<p>均使用Attribute传入，通过CSS实现。
值得一提的是darkmode的写法,通过变量的形式，变化变量颜色。这样样式就不用写两遍了～</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-9">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> scss</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="scss" data-theme="breeze" id="code-id-9">:root {
    --ray-highlight-hover: rgba(0, 0, 0, 0.05);
    --ray-highlight: rgba(0, 0, 0, 0.1);
    --ray-highlight-border: transparent;
    --line-number: rgba(0, 0, 0, 0.2);
  }
  
  [data-theme=&#34;dark&#34;] {
    --frame-highlight-border: rgba(255, 255, 255, 0.3);
    --ray-highlight-hover: rgba(255, 255, 255, 0.05);
    --ray-highlight: rgba(255, 255, 255, 0.1);
    --line-number: rgba(255, 255, 255, 0.2);
  }
  .window {
        display: flex;
        box-shadow: 0 0 0 1px var(--frame-highlight-border), 0 0 0 1.5px var(--frame-shadow-border), 0 2.8px 2.2px rgba(0, 0, 0, 0.034), 0 6.7px 5.3px rgba(0, 0, 0, 0.048), 0 12.5px 10px rgba(0, 0, 0, 0.06), 0 22.3px 17.9px rgba(0, 0, 0, 0.072), 0 41.8px 33.4px rgba(0, 0, 0, 0.086), 0 100px 80px rgba(0, 0, 0, 0.12);
        background: var(--frame-background);       
    }</div>
    </div>
  </div>
</div><h1 id="其他功能">其他功能</h1>
<h2 id="复制代码">复制代码</h2>
<p>逻辑：在header中加入两个元素，一个图标为复制，一个为复制成功(隐藏)。监听按钮点击事件，点击后，向剪贴板写入文本，并将复制按钮隐藏，显示复制成功按钮。
三秒后，重新显示复制按钮。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-10">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-10">
          <button id="copyButton-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-10" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-10">&lt;button id=&#34;copyButton-{{ $id }}&#34;&gt;
    &lt;svg class=&#34;lucide lucide-copy&#34; /&gt;
&lt;/button&gt;
&lt;button id=&#34;copySuccess-{{ $id }}&#34; style=&#34;display: none;&#34; &gt;
    &lt;svg class=&#34;lucide lucide-clipboard-check&#34; /&gt;
&lt;/button&gt;</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-11">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-11">
          <button id="copyButton-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-11" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-11">const copyButton = codeblock.querySelector(&#39;[id^=&#34;copyButton-&#34;]&#39;);
const copySuccess = document.querySelector(&#39;[id^=&#34;copySuccess-&#34;]&#39;)
//  复制代码
copyButton.addEventListener(&#34;click&#34;, () =&gt; {
    navigator.clipboard.writeText(code)
    copyButton.style.display = &#39;none&#39;;

    copySuccess.style.display = &#34;block&#34;
    setTimeout(() =&gt; {
    copyButton.style.display = &#39;block&#39;;
    copySuccess.style.display = &#34;none&#34;
    }, 3000)
})</div>
    </div>
  </div>
</div><h2 id="导出图片">导出图片</h2>
<p>使用html2canvas库。需在baseof.html中引入：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-12">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-12">
          <button id="copyButton-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-12" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-12">&lt;script src=&#34;https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js&#34;&gt;&lt;/script&gt;</div>
    </div>
  </div>
</div><p>为了美观，导出时将复制、导出等按钮隐藏了，等下载好了再显示。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-13">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-13">
          <button id="copyButton-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-13" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-13">// 导出成图片
const controlButtons = codeblock.querySelector(&#39;[id^=&#34;controls-button-&#34;]&#39;);
const exportImageButton = codeblock.querySelector(&#39;[id^=&#34;exportImage-&#34;]&#39;);
exportImageButton.addEventListener(&#34;click&#34;, async () =&gt; {
    { {/*  const wrapper = document.querySelector()  */ } }
    controlButtons.style.visibility = &#39;hidden&#39;;
    html2canvas(codeblock).then(canvas =&gt; {
    // 添加 canvas 到 body，可选
    { {/*  document.body.appendChild(canvas);  */ } }

    // 保存为图片
    var img = canvas.toDataURL(&#34;image/png&#34;);

    // 创建一个链接元素用于下载
    var link = document.createElement(&#39;a&#39;);
    link.download = &#39;my-image.png&#39;;
    link.href = img;
    link.click();
    controlButtons.style.visibility = &#39;visible&#39;;
    });
})</div>
    </div>
  </div>
</div><h1 id="总结">总结</h1>
<p>通过看源码，学习到了很多关于CSS变量的知识。原生HTML、JS、CSS也是很强大的～框架能做，原生基本也能做。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>博客美化：代码块美化示例</title>
      <link>https://ygria.site/prettier-codeblock-demo/</link>
      <pubDate>Fri, 19 Jul 2024 11:13:33 +0800</pubDate>
      
      <guid>https://ygria.site/prettier-codeblock-demo/</guid>
      <description>&lt;ol&gt;
&lt;li&gt;默认（可自定义）
什么也不传：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block  is-closed show-line-numbers  tw-group tw-my-2&#34;&gt;
  &lt;div class=&#34;code-wrapper light&#34; data-theme=&#34;light&#34; id=&#34;code-block-id-1&#34;&gt;
    
    &lt;div class=&#34;withBackground breeze&#34; style = &#34;padding: 64px&#34;/&gt;
    
    &lt;div class=&#34;window&#34;&gt;
      &lt;div id=&#34;codeblock-header&#34; class=&#34;header&#34;&gt;
        &lt;div class=&#34;controls&#34;&gt;
          &lt;div class=&#34;control&#34;&gt;&lt;/div&gt;
          &lt;div class=&#34;control&#34;&gt;&lt;/div&gt;
          &lt;div class=&#34;control&#34;&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;div className={classNames(styles.fileName, styles.supabaseFileName)} id=&#34;codeblock-title&#34; class=&#34;fileName&#34;
          data-value={fileName}&gt;
          &lt;span&gt; text&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&#34;controls-buttons&#34; id=&#34;controls-button-id-1&#34;&gt;
          &lt;button id=&#34;copyButton-id-1&#34;&gt;
            &lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;#000&#34;
              stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; class=&#34;lucide lucide-copy&#34;&gt;
              &lt;rect width=&#34;14&#34; height=&#34;14&#34; x=&#34;8&#34; y=&#34;8&#34; rx=&#34;2&#34; ry=&#34;2&#34; /&gt;
              &lt;path d=&#34;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&#34; /&gt;
            &lt;/svg&gt;
          &lt;/button&gt;
          &lt;button id=&#34;copySuccess-id-1&#34; style=&#34;display: none;&#34; class=&#34;not-allowed&#34; &gt;
            &lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34;
              stroke=&#34;green&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;
              class=&#34;lucide lucide-clipboard-check&#34;&gt;
              &lt;rect width=&#34;8&#34; height=&#34;4&#34; x=&#34;8&#34; y=&#34;2&#34; rx=&#34;1&#34; ry=&#34;1&#34; /&gt;
              &lt;path d=&#34;M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2&#34; /&gt;
              &lt;path d=&#34;m9 14 2 2 4-4&#34; /&gt;
            &lt;/svg&gt;
          &lt;/button&gt;

          &lt;button id=&#34;exportImage-id-1&#34;&gt;
            &lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34;
              stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;
              class=&#34;lucide lucide-download&#34;&gt;
              &lt;path d=&#34;M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4&#34; /&gt;
              &lt;polyline points=&#34;7 10 12 15 17 10&#34; /&gt;
              &lt;line x1=&#34;12&#34; x2=&#34;12&#34; y1=&#34;15&#34; y2=&#34;3&#34; /&gt;
            &lt;/svg&gt;
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div data-language=&#34;text&#34; data-theme=&#34;breeze&#34; id=&#34;code-id-1&#34;&gt;declare a=1
echo &amp;#34;$a&amp;#34;
exit&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;传入标题、语言等&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="博客美化：代码块美化示例" /><ol>
<li>默认（可自定义）
什么也不传：</li>
</ol>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-1">declare a=1
echo &#34;$a&#34;
exit</div>
    </div>
  </div>
</div><p>传入标题、语言等</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground candy" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="rust" data-theme="candy" id="code-id-2">declare a=1
echo &#34;$a&#34;
exit</div>
    </div>
  </div>
</div><p>黑暗模式：需要传递 mode = &ldquo;dark&rdquo;</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-3">
    
    <div class="withBackground candy" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> rust demo</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="rust" data-theme="candy" id="code-id-3">declare a=1
echo &#34;$a&#34;
exit</div>
    </div>
  </div>
</div><p>不加背景模式:需要传递 nobackground = &ldquo;true&rdquo;</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> rust demo</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="rust" data-theme="candy" id="code-id-4">declare a=1
echo &#34;$a&#34;
exit</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-5">
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> rust demo</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="rust" data-theme="candy" id="code-id-5">declare a=1
echo &#34;$a&#34;
exit</div>
    </div>
  </div>
</div><p>自定义代码块边距（仅在有背景条件下生效,默认64 建议：16/32/64）</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-6">
    
    <div class="withBackground candy" style = "padding: 16px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> rust demo</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="rust" data-theme="candy" id="code-id-6">declare a=1
echo &#34;$a&#34;
exit</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-7">
    
    <div class="withBackground candy" style = "padding: 32px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> rust demo</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="rust" data-theme="candy" id="code-id-7">declare a=1
echo &#34;$a&#34;
exit</div>
    </div>
  </div>
</div><ol start="2">
<li>更换主题</li>
</ol>
<h2 id="1-ice">1. ice</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground ice" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="ice" id="code-id-8">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-9">
    
    <div class="withBackground ice" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="ice" id="code-id-9">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="2-breeze">2. breeze</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-10">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-10">
          <button id="copyButton-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-10" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-10">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-11">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-11">
          <button id="copyButton-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-11" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-11">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="3sand">3.Sand</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-12">
    
    <div class="withBackground sand" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-12">
          <button id="copyButton-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-12" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="sand" id="code-id-12">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-13">
    
    <div class="withBackground sand" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-13">
          <button id="copyButton-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-13" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="sand" id="code-id-13">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="4forest">4.forest</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-14">
    
    <div class="withBackground forest" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-14">
          <button id="copyButton-id-14">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-14" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-14">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="forest" id="code-id-14">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-15">
    
    <div class="withBackground forest" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-15">
          <button id="copyButton-id-15">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-15" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-15">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="forest" id="code-id-15">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="5mono">5.mono</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-16">
    
    <div class="withBackground mono" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-16">
          <button id="copyButton-id-16">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-16" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-16">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="mono" id="code-id-16">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-17">
    
    <div class="withBackground mono" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-17">
          <button id="copyButton-id-17">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-17" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-17">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="mono" id="code-id-17">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="6midnight">6.midnight</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-18">
    
    <div class="withBackground midnight" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-18">
          <button id="copyButton-id-18">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-18" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-18">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="midnight" id="code-id-18">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-19">
    
    <div class="withBackground midnight" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-19">
          <button id="copyButton-id-19">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-19" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-19">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="midnight" id="code-id-19">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="7raindrop">7.raindrop</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-20">
    
    <div class="withBackground raindrop" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-20">
          <button id="copyButton-id-20">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-20" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-20">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="raindrop" id="code-id-20">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-21">
    
    <div class="withBackground raindrop" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> layouts/_default/render-codeblock.html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-21">
          <button id="copyButton-id-21">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-21" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-21">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="raindrop" id="code-id-21">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="8meadow">8.meadow</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-22">
    
    <div class="withBackground meadow" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> Untitled</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-22">
          <button id="copyButton-id-22">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-22" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-22">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="meadow" id="code-id-22">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-23">
    
    <div class="withBackground meadow" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> Untitled</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-23">
          <button id="copyButton-id-23">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-23" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-23">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="meadow" id="code-id-23">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="9falcon">9.falcon</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-24">
    
    <div class="withBackground falcon" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> Untitled</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-24">
          <button id="copyButton-id-24">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-24" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-24">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="falcon" id="code-id-24">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-25">
    
    <div class="withBackground falcon" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> Untitled</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-25">
          <button id="copyButton-id-25">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-25" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-25">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="falcon" id="code-id-25">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="10crimson">10.crimson</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-26">
    
    <div class="withBackground crimson" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> Untitled</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-26">
          <button id="copyButton-id-26">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-26" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-26">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="crimson" id="code-id-26">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-27">
    
    <div class="withBackground crimson" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> Untitled</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-27">
          <button id="copyButton-id-27">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-27" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-27">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="crimson" id="code-id-27">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="11noir">11.noir</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-28">
    
    <div class="withBackground noir" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> Untitled</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-28">
          <button id="copyButton-id-28">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-28" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-28">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="noir" id="code-id-28">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-29">
    
    <div class="withBackground noir" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> Untitled</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-29">
          <button id="copyButton-id-29">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-29" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-29">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="noir" id="code-id-29">import { Detail } from &#34;@raycast/api&#34;;

export default function Command() {
  return &lt;Detail markdown=&#34;Hello World&#34; /&gt;;
}</div>
    </div>
  </div>
</div><h2 id="12sunset">12.sunset</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-30">
    
    <div class="withBackground sunset" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> Untitled</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-30">
          <button id="copyButton-id-30">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-30" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-30">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="sunset" id="code-id-30">module.exports = leftpad;

function leftpad(str, len, ch) {
  str = String(str);
  var i = -1;

  if (!ch &amp;&amp; ch !== 0) ch = &#39; &#39;;

  len = len - str.length;

  while (i&#43;&#43; &lt; len) {
    str = ch &#43; str;
  }
  return str;
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper dark" data-theme="dark" id="code-block-id-31">
    
    <div class="withBackground sunset" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> Untitled</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-31">
          <button id="copyButton-id-31">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-31" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-31">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="sunset" id="code-id-31">module.exports = leftpad;

function leftpad(str, len, ch) {
  str = String(str);
  var i = -1;

  if (!ch &amp;&amp; ch !== 0) ch = &#39; &#39;;

  len = len - str.length;

  while (i&#43;&#43; &lt; len) {
    str = ch &#43; str;
  }
  return str;
}</div>
    </div>
  </div>
</div>]]></content:encoded>
    </item>
    
    <item>
      <title>Hexo-&gt;Hugo，豆瓣书影墙及Hugo短代码</title>
      <link>https://ygria.site/hexo2hugo/</link>
      <pubDate>Sat, 13 Jul 2024 17:43:33 +0800</pubDate>
      
      <guid>https://ygria.site/hexo2hugo/</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;最新博客：https://ygria.site/&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;项目地址： 
&lt;a 
    
        href=&#34;https://ygria.site/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL1lncmlhL2h1Z28tamluZ3poZS10ZW1wbGF0ZQ%3d%3d&#34;
    
     target=&#34;_blank&#34;
&gt;
https://github.com/Ygria/hugo-jingzhe-template&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;blockquote&gt;
    &lt;p&gt;看到一个很棒的博客： 
&lt;a 
    
        href=&#34;https://koobai.com/&#34;
    
     target=&#34;_blank&#34;
&gt;
https://koobai.com/&lt;/a&gt; ，博客原作者也很大方地将源码公开放到了Github上。对电影页面非常眼馋，征得作者同意后，我对他的模版进行了一些小改造，发布到了Github上，也将自己的博客也从原Hexo引擎迁移到了Hugo。
&lt;img src=&#34;https://images.ygria.site/2024/07/86278778bb34f4482f2f280480775f8d.png&#34; alt=&#34;image.png&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Hexo->Hugo，豆瓣书影墙及Hugo短代码" /><ul>
<li>
<p>最新博客：https://ygria.site/</p>
</li>
<li>
<p>项目地址： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL1lncmlhL2h1Z28tamluZ3poZS10ZW1wbGF0ZQ%3d%3d"
    
     target="_blank"
>
https://github.com/Ygria/hugo-jingzhe-template</a></p>
</li>
</ul>



<blockquote>
    <p>看到一个很棒的博客： 
<a 
    
        href="https://koobai.com/"
    
     target="_blank"
>
https://koobai.com/</a> ，博客原作者也很大方地将源码公开放到了Github上。对电影页面非常眼馋，征得作者同意后，我对他的模版进行了一些小改造，发布到了Github上，也将自己的博客也从原Hexo引擎迁移到了Hugo。
<img src="https://images.ygria.site/2024/07/86278778bb34f4482f2f280480775f8d.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
</blockquote>
<h1 id="迁移和改造">迁移和改造</h1>
<h2 id="文章迁移">文章迁移</h2>
<p>Hexo与Hugo都基于MD文件的生成，所以除了修改了Front Matter中date时间格式外，迁移成本几乎为0，把之前的文章(.md)都挪到hugo的博客文件夹下就行了。</p>
<p>原： source/_post
现： content/posts</p>
<p>复制Koobai Github项目中theme/jingzhe_2,并在博客配置文件<code>hugo.toml</code>中指定即可。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-1">theme = &#34;jingzhe_2&#34;
themesDir = &#34;themes&#34;</div>
    </div>
  </div>
</div><h2 id="电影页面的生成">电影页面的生成</h2>
<h3 id="github-action配置">Github Action配置</h3>
<p>看了一下电影页面，是使用了Github Action自动化执行功能，从自己的豆瓣账号去拉取数据存在本地。有现成的轮子可以用，自己配置一个也很简单。
在项目的根目录下新建文件夹<code>.github/workflows</code>（注意拼写），新建<code>douban.yaml</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> yaml</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="yaml" data-theme="breeze" id="code-id-2">name: douban
on:
  push:
    branches:
      - main
  workflow_dispatch:
    inputs:
      douban-name:
        description: &#39;DouBan Id&#39;
        required: false  
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
jobs:
  sync:
    name: Douban Sync
    runs-on: ubuntu-latest
    env:
      DOUBAN_NAME: ${{ github.event.inputs.douban-name || secrets.DOUBAN_NAME  }}
      REF: ${{ github.ref }}
      REPOSITORY: ${{ github.repository }}
      YEAR: ${{ vars.YEAR }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: 3.11
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r scripts/douban-requirements.txt
      - name: Douban Movie sync
        run: |
          python -u scripts/douban.py  
      - name: Commit changes
        uses: EndBug/add-and-commit@v8
        with:
          author_name: DOUBAN Sync  # 提交者的GitHub用户名
          author_email: DOUBAN Sync  # 提交者的电子邮件
          message: &#39;Automatically commit changes&#39;  # 提交信息
          add: &#39;.&#39;  # 添加当前目录下的所有变更</div>
    </div>
  </div>
</div><p>为了方便调试，我增加了workflow_dispatch的触发方法（可从Github页面手动触发），并支持在手动触发输入豆瓣账号ID。非手动触发时，应将DOUBAN_NAME配置到secrets中。</p>
<p>工作流的默认工作目录是项目的根目录，我们在根目录下新建文件夹scripts,并添加执行脚本douban.py(省略部分内容)：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> python</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="python" data-theme="breeze" id="code-id-3">image_save_folder = &#39;static/images/douban/&#39;
json_movie_path = &#39;data/douban/movie.json&#39;
json_book_path = &#39;data/douban/book.json&#39;
movie_status = {
    &#34;mark&#34;: &#34;想看&#34;,
    &#34;doing&#34;: &#34;在看&#34;,
    &#34;done&#34;: &#34;看过&#34;,
}
@retry(stop_max_attempt_number=3, wait_fixed=5000)
def fetch_subjects(user, type_, status):
    offset = 0
    page = 0
    url = f&#34;https://{DOUBAN_API_HOST}/api/v2/user/{user}/interests&#34;
    total = 0
    results = []
    /** 调用豆瓣接口获取数据 **/


def downloadImgs(image_url, id):
    # 确保文件夹路径存在
    os.makedirs(image_save_folder, exist_ok=True)
    file_name = &#34;{id}.jpg&#34;.format(id=id)
    save_path = os.path.join(image_save_folder, file_name)

def insert_movie():
    results = []
    for i in movie_status.keys():
        results.extend(fetch_subjects(douban_name, &#34;movie&#34;, i))
    # 确保文件的父目录存在
    os.makedirs(os.path.dirname(json_movie_path), exist_ok=True)
    # 检查文件是否存在
    if not os.path.exists(json_movie_path):
        # 文件不存在时，创建文件
        open(json_movie_path, &#39;w&#39;).close()  # 创建一个空文件
        print(&#34;File created.&#34;)
    json_data = []
    for item in results:
        # 下载图片文件
        downloadImgs(item[&#34;subject&#34;][&#34;pic&#34;][&#34;large&#34;], item[&#34;id&#34;])
        # 准备要追加的数据
        new_data = {}
        json_data.append(new_data)

    with open(json_movie_path, mode=&#39;w&#39;, newline=&#39;&#39;, encoding=&#39;utf-8&#39;) as file:
        json.dump(json_data, file, indent=4, ensure_ascii=False)

if __name__ == &#34;__main__&#34;:
    douban_name = os.getenv(&#39;DOUBAN_NAME&#39;)
    if not douban_name:
        print(&#39;Douban name is not set&#39;)
        sys.exit(1)
    else:
        print(f&#34;DOUBAN_NAME = {douban_name}&#34;)    
    insert_movie()</div>
    </div>
  </div>
</div><p>爬取豆瓣数据，并以json格式存储。这里为了生成海报，将封面海报下载到了静态仓库内。这里也可以不下，渲染时使用一些三方API或用自己图床/代理worker，由于我豆瓣标记也不是很多，所以就选择静态文件了。</p>
<h3 id="页面渲染">页面渲染</h3>
<p>原页面基于csv渲染，改成从json文件渲染需要一些小改动,将getCSV改成getJSON，从下标读改为从属性即可。</p>
<p>增加了基于css选择器的过滤。</p>
<h2 id="照葫芦画瓢的读书页">照葫芦画瓢的读书页</h2>
<p>本着对豆瓣爬都爬了都心理，顺便也生成了读书页。基本是对电影页面的复制粘贴。</p>
<h2 id="制作归档页">制作归档页</h2>
<p>在pages/ 下新增了归档.md</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> markdown</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="markdown" data-theme="breeze" id="code-id-4">---
title: &#39;归档&#39;
url: &#34;archive&#34;
date: 2023-01-30

layout: archive
menu:
  main:
    name: &#34;归档&#34;
    weight: 12
---</div>
    </div>
  </div>
</div><p>在主题文件夹下layouts/_default中，新增archive.html,基于标签和年份进行归档。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-5">&lt;div class=&#34;content_zhengwen&#34;&gt;
&lt;div class=&#34;tabs&#34;&gt;
  &lt;button id = &#34;tagsButton&#34; class=&#34;tab-button&#34; onclick=&#34;openTab(event, &#39;Tags&#39;)&#34;&gt;标签&lt;/button&gt;
  &lt;button id = &#34;yearsButton&#34; class=&#34;tab-button&#34; onclick=&#34;openTab(event, &#39;Years&#39;)&#34;&gt;年份&lt;/button&gt;
&lt;/div&gt;

&lt;div class=&#34;content&#34;&gt;
  &lt;div id=&#34;Years&#34; class=&#34;tab-content&#34;&gt;
      {{ range (.Site.RegularPages.GroupByDate &#34;2006&#34;) }}
      &lt;h3&gt;{{ .Key }}&lt;/h3&gt;
      &lt;ul class=&#34;archive-list&#34;&gt;
          {{ range (where .Pages &#34;Type&#34; &#34;posts&#34;) }}
          &lt;li&gt;
              {{ .PublishDate.Format &#34;2006-01-02&#34; }}

              &lt;a href=&#34;{{ .RelPermalink }}&#34;&gt;{{ .Title }}&lt;/a&gt;
          &lt;/li&gt;
          {{ end }}
      &lt;/ul&gt;
      {{ end }}
  &lt;/div&gt;

  &lt;div id=&#34;Tags&#34; class=&#34;tab-content&#34;&gt;
      &lt;!-- 标签词云放这里 --&gt;
      &lt;!-- 标签归档开始 --&gt;
      {{ $allPages := .Site.RegularPages }}
      {{ range $index, $tag := .Site.Taxonomies.tags }}
      &lt;h3&gt;{{ $index }}&lt;/h3&gt;
      &lt;ul class=&#34;tag-archive-list&#34;&gt;
          {{ range $allPages }}
          {{ if in .Params.tags $index }}
          &lt;li&gt;
              {{ .PublishDate.Format &#34;2006-01-02&#34; }}
              &lt;a href=&#34;{{ .RelPermalink }}&#34;&gt;{{ .Title }}&lt;/a&gt;
          &lt;/li&gt;
          {{ end }}
          {{ end }}
      &lt;/ul&gt;
      {{ end }}
      &lt;!-- 标签归档结束 --&gt;
  &lt;/div&gt;</div>
    </div>
  </div>
</div><h1 id="hugo的优势">Hugo的优势</h1>
<h2 id="启动更快">启动更快</h2>
<p>构建速度的确更快。</p>
<h2 id="令人惊喜的短代码">令人惊喜的短代码</h2>
<p>在hexo中使用插件不是一件容易的事情，需要编写复杂的正则。生态也不是很好。</p>
<p>Hugo的短代码相对来就简单很多，也很像现在前端框架里的组件概念。</p>
<p>编写和使用都很简单，以douban卡片举例：
在主题文件夹中，/layout/shortcodes/下，新建一个douban.html 。接受一个url，通过正则，获取url中的subjectId，从本地的book.json中遍历找到对应书的信息，然后渲染到模版代码里。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-6">{{ $dbUrl := .Get 0 }}
{{ $dbType := replaceRE `https://(movie|book).douban.com/subject/.*` &#34;$1&#34; $dbUrl }}
{{ $dbID := replaceRE `.*douban.com/subject/([0-9]&#43;)/.*` &#34;$1&#34; $dbUrl }}
{{/*  {{ printf &#34;Page Params: %#v\n&#34; $dbID }}  */}}
{{ if eq $dbType &#34;book&#34; }}
    {{$items := resources.Get &#34;data/douban/book.json&#34; }}
    {{ $json := $data | transform.Unmarshal }}
    {{range $item := $json}}
    {{ $subjectId := string $item.subject_id}}
    {{if eq ($subjectId) $dbID }}
        {{ $rating := float ($item.douban_score) }}
          &lt;!--封面地址替换 --&gt;
        {{ $imagePath := printf &#34;images/douban/%s.jpg&#34; (path.Base ($subjectId)) }}
        {{ $defaultImg := &#34;images/default/default_poster.jpg&#34;}}
        &lt;div class=&#34;db-card&#34;&gt;
            &lt;div class=&#34;db-card-subject&#34;&gt;
                &lt;div class=&#34;db-card-post&#34;&gt;&lt;img loading=&#34;lazy&#34; decoding=&#34;async&#34; referrerpolicy=&#34;no-referrer&#34; src=&#34;{{ $imagePath | absURL }}&#34; &gt;&lt;/div&gt;
                &lt;div class=&#34;db-card-content&#34;&gt;
                    &lt;div class=&#34;db-card-title&#34;&gt;&lt;a href=&#34;{{  $dbUrl }}&#34; class=&#34;cute&#34; target=&#34;_blank&#34; rel=&#34;noreferrer&#34;&gt;{{ $item.name }}&lt;/a&gt;&lt;/div&gt;
                    &lt;div class=&#34;rating&#34;&gt;&lt;span class=&#34;allstardark&#34;&gt;&lt;span class=&#34;allstarlight&#34; style=&#34;width:{{mul 10 $rating }}%&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;rating_nums&#34;&gt;{{$rating}}&lt;/span&gt;&lt;/div&gt;
                    &lt;div class=&#34;db-card-abstract&#34;&gt;{{  $item.card_subtitle }}&lt;/div&gt;
                    &lt;div class=&#34;db-card-comment&#34;&gt;{{ $item.intro}}&lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    {{end}}
    {{end}}
{{ end }}</div>
    </div>
  </div>
</div><h2 id="做一个短代码来支持livephoto吧">做一个短代码来支持LivePhoto吧！</h2>
<p>知道了原理就可以很容易开发短代码了。我做了一个LivePhoto的。
Apple的LivePhoto本质就是一张图  +一小段视频 (.mov)，我们可以从icloud将图和视频下载下来。
<img src="https://images.ygria.site/2024/07/b3031a53f00b883a244fc0915ffdb30b.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>注意要选择兼容性最好的选项，不然可能显示不出来。</p>
<p><img src="https://images.ygria.site/2024/07/97833dfb4761c878f840de1a70dad6d7.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>下载下来后是一个压缩包zip，里面包含一个jpeg文件和一个mov文件。解压后传到自己图床上。</p>
<p>短代码中需要引入apple的cdn文件。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-7">&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;title&gt;Live Photo Example&lt;/title&gt;
    &lt;script src=&#34;https://cdn.apple-livephotoskit.com/lpk/1/livephotoskit.js&#34;&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;!-- Live Photo Element with dynamic ID --&gt;
    &lt;live-photo id=&#34;{{ .Get &#34;id&#34; | default &#34;default-live-photo-id&#34; }}&#34; 
                photo-src=&#34;{{ .Get &#34;photo&#34; }}&#34; 
                video-src=&#34;{{ .Get &#34;video&#34; }}&#34; 
                style=&#34;width: {{ .Get &#34;width&#34; | default &#34;300&#34; }}px; height: {{ .Get &#34;height&#34; | default &#34;400&#34; }}px;&#34;&gt;
    &lt;/live-photo&gt;

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            // Using the dynamic ID to create the LivePhotosKit player
            const livePhotoId = &#34;{{ .Get &#34;id&#34; | default &#34;default-live-photo-id&#34; }}&#34;;
            const player = LivePhotosKit.Player(document.getElementById(livePhotoId));

            // Set photo and video sources dynamically
            player.photoSrc = &#34;{{ .Get &#34;photo&#34; }}&#34;;
            player.videoSrc = &#34;{{ .Get &#34;video&#34; }}&#34;;

            // Event listeners for player states
            player.addEventListener(&#39;canplay&#39;, evt =&gt; console.log(&#39;Player is ready&#39;, evt));
            player.addEventListener(&#39;error&#39;, evt =&gt; console.log(&#39;Player load error&#39;, evt));
            player.addEventListener(&#39;ended&#39;, evt =&gt; console.log(&#39;Player finished playing through&#39;, evt));

            // Playback controls and styles
            player.playbackStyle = LivePhotosKit.PlaybackStyle.FULL;
            player.play();

            // Error handling specific to live photo elements
            player.addEventListener(&#39;error&#39;, (ev) =&gt; {
                if (typeof ev.detail.errorCode === &#39;number&#39;) {
                    switch (ev.detail.errorCode) {
                    case LivePhotosKit.Errors.IMAGE_FAILED_TO_LOAD:
                        console.error(&#39;Image failed to load&#39;);
                        break;
                    case LivePhotosKit.Errors.VIDEO_FAILED_TO_LOAD:
                        console.error(&#39;Video failed to load&#39;);
                        break;
                    }
                } else {
                    console.error(&#39;Unexpected error:&#39;, ev.detail.error);
                }
            });
        });
    &lt;/script&gt;
&lt;/body&gt;</div>
    </div>
  </div>
</div><p>使用时传入刚刚的mov和jpeg图片地址即可。</p>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Live Photo Example</title>
    <script src="https://cdn.apple-livephotoskit.com/lpk/1/livephotoskit.js"></script>
</head>
<body>
    
    <live-photo id="uniqueID" 
                photo-src="https://images.ygria.site/2024/07/52945bc1810927c121234ea83652e28b.JPEG" 
                video-src="https://images.ygria.site/2024/07/c564c859f561bc98cf36c9d051ec58f6.MOV" 
                style="width: 400px; height: 600px;">
    </live-photo>
    <p class  = "livephoto-description">
        路边一只被抛弃的可怜的小狗
    </p>

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            
            const livePhotoId = "uniqueID";
            const player = LivePhotosKit.Player(document.getElementById(livePhotoId));

            
            player.photoSrc = "https:\/\/images.ygria.site\/2024\/07\/52945bc1810927c121234ea83652e28b.JPEG";
            player.videoSrc = "https:\/\/images.ygria.site\/2024\/07\/c564c859f561bc98cf36c9d051ec58f6.MOV";

            
            player.addEventListener('canplay', evt => console.log('Player is ready', evt));
            player.addEventListener('error', evt => console.log('Player load error', evt));
            player.addEventListener('ended', evt => console.log('Player finished playing through', evt));

            
            player.playbackStyle = LivePhotosKit.PlaybackStyle.FULL;
            player.play();

            
            player.addEventListener('error', (ev) => {
                if (typeof ev.detail.errorCode === 'number') {
                    switch (ev.detail.errorCode) {
                    case LivePhotosKit.Errors.IMAGE_FAILED_TO_LOAD:
                        console.error('Image failed to load');
                        break;
                    case LivePhotosKit.Errors.VIDEO_FAILED_TO_LOAD:
                        console.error('Video failed to load');
                        break;
                    }
                } else {
                    console.error('Unexpected error:', ev.detail.error);
                }
            });
        });
    </script>
    <style>
        .livephoto-description {
            color: #a2afb9
        }

    </style>
</body>
</html>

<p>在请求自己的图床时，发生跨域错误，需要到Cloudflare上配置一个Worker和Worker Rule，解决跨域问题。
新建worker，并配置图床绑定的子域名下/*的woker rule
<img src="https://images.ygria.site/2024/07/95498c73003f1c1695728af8c90ccd79.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>允许本地开发ip和部署后的ip即可。（要注意地址末尾没有斜杠）</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-8">addEventListener(&#39;fetch&#39;, event =&gt; {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const origin = request.headers.get(&#34;Origin&#34;);
  let response = await fetch(request);

  // Create a new response by cloning the original response
  response = new Response(response.body, response);

  // Check if the origin is one of the allowed origins
  const allowedOrigins = [&#34;https://ygria.site&#34;, &#34;http://127.0.0.1:5500&#34;,&#34;http://127.0.0.1:1313&#34;];
  if (allowedOrigins.includes(origin)) {
    // Set CORS headers
    response.headers.set(&#34;Access-Control-Allow-Origin&#34;, origin);
    response.headers.set(&#34;Access-Control-Allow-Methods&#34;, &#34;GET, POST, OPTIONS&#34;);
    response.headers.set(&#34;Access-Control-Allow-Headers&#34;, &#34;Content-Type&#34;);
  }

  // Handle OPTIONS preflight request
  if (request.method === &#34;OPTIONS&#34;) {
    response = new Response(null, {
      status: 204,
      headers: response.headers
    });
  }

  return response;
}</div>
    </div>
  </div>
</div><h1 id="其他折腾">其他折腾</h1>
<h2 id="notion的快速建站">Notion的快速建站</h2>
<p>Notion页面支持直接发布成网页，我将自己的读书和播客页面都进行了发布，并绑定了子域名。
<img src="https://images.ygria.site/2024/07/f3c6209b4dfbe2da75dbdb24cbd54be3.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>在Cloudflare控制台增加一条CNAME，指向notion.so
<img src="https://images.ygria.site/2024/07/6a3d3e6fc1ea1d227a13ab88d2f9ac3c.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>新增一个worker，同样配置worker rule，将所有访问notion.ygria.site/*的请求都定向到worker。worker内容可以访问 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9mcnVpdGlvbi1zdGVwaGVub3UudmVyY2VsLmFwcC8%3d"
    
     target="_blank"
>
生成worker</a>。</p>
<p>如下图，可以定义多个二级路由和notion页面的关系。</p>
<p><img src="https://images.ygria.site/2024/07/74ecfe1bb25375a06a70416a9436aa30.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>现在，通过notion.ygria.site/podcast 和 notion.ygria.site/weread 就可以访问到我的两个notion公开页面了。并且都是实时更新的，真不错～</p>
<h2 id="emoji-页头">Emoji 页头</h2>
<p>参考  
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9ibG9nLmdpbW8ubWUvcG9zdHMvdXNpbmctZW1vamktYXMtZmF2aWNvbi8%3d"
    
     target="_blank"
>
把 emoji 当作 favicon</a> ，加入了这一小小的功能彩蛋。</p>
<h1 id="总结">总结</h1>
<ol>
<li>
<p>搜索教程过程中，看到了很多做得很棒的个人博客。不过对于我来说，坚持写博客才是最重要的～</p>
</li>
<li>
<p>评论和memos等等都暂时还没做，对我来说也不是核心功能。</p>
</li>
<li>
<p>chatgpt、github和cloudflare，帮助折腾的利器。自动化流、workspace、函数计算、页面托管、S3存储，都是免费的，还都很好用。</p>
</li>
</ol>
]]></content:encoded>
    </item>
    
    <item>
      <title>书法练习记录（D5-D80）</title>
      <link>https://ygria.site/handwrites/</link>
      <pubDate>Thu, 04 Jul 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/handwrites/</guid>
      <description>&lt;h2 id=&#34;day-802024-06-18&#34;&gt;Day 80（2024-06-18）&lt;/h2&gt;







&lt;div class=&#34;vertical-cn-note primary&#34; style=&#34;height:full;&#34;&gt;
    
    &lt;h2&gt;蓦山溪·梅 曹组&lt;/h2&gt;
    

    
洗妆真态，不作铅花御。竹外一枝斜，想佳人，天寒日暮。黄昏院落，无处著清香，风细细，雪垂垂，何况江头路。
月边疏影，梦到消魂处。结子欲黄时，又须作，廉纤细雨。孤芳一世，供断有情愁，消瘦损，东阳也，试问花知否？


    
    &lt;div class=&#34;footnote&#34;&gt;曹组&lt;/div&gt;
    
&lt;/div&gt;








&lt;div class=&#34;vertical-cn-note primary&#34; style=&#34;height:auto;&#34;&gt;
    
    &lt;h2&gt;贺新郎·春情 李玉&lt;/h2&gt;
    

    
篆缕消金鼎，醉沉沉。庭阴转午，画堂人静。芳草王孙知何处？惟有杨花糁径。渐玉枕、腾腾春醒。帘外残红春已透，镇无聊、殢酒厌厌病。云鬓乱，未忺整。
江南旧事休重省。遍天涯、寻消问息，断鸿难倩。月满西楼凭阑久，依旧归期未定。又只恐、瓶沉金井。嘶骑不来银烛暗，枉教人，立尽梧桐影。谁伴我，对鸾镜。


    
&lt;/div&gt;

&lt;p&gt;&lt;img src=&#34;https://images.ygria.site/2024/07/11d4099f89f228e844d2df5e9243ff61.png&#34; alt=&#34;image.png&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="书法练习记录（D5-D80）" /><h2 id="day-802024-06-18">Day 80（2024-06-18）</h2>







<div class="vertical-cn-note primary" style="height:full;">
    
    <h2>蓦山溪·梅 曹组</h2>
    

    
洗妆真态，不作铅花御。竹外一枝斜，想佳人，天寒日暮。黄昏院落，无处著清香，风细细，雪垂垂，何况江头路。
月边疏影，梦到消魂处。结子欲黄时，又须作，廉纤细雨。孤芳一世，供断有情愁，消瘦损，东阳也，试问花知否？


    
    <div class="footnote">曹组</div>
    
</div>








<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>贺新郎·春情 李玉</h2>
    

    
篆缕消金鼎，醉沉沉。庭阴转午，画堂人静。芳草王孙知何处？惟有杨花糁径。渐玉枕、腾腾春醒。帘外残红春已透，镇无聊、殢酒厌厌病。云鬓乱，未忺整。
江南旧事休重省。遍天涯、寻消问息，断鸿难倩。月满西楼凭阑久，依旧归期未定。又只恐、瓶沉金井。嘶骑不来银烛暗，枉教人，立尽梧桐影。谁伴我，对鸾镜。


    
</div>

<p><img src="https://images.ygria.site/2024/07/11d4099f89f228e844d2df5e9243ff61.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="day-752024-06-11">Day 75（2024-06-11）</h2>







<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>月夜 杜甫</h2>
    

    
今夜鄜州月，闺中只独看。遥怜小儿女，未解忆长安。香雾云鬟湿，清辉玉臂寒。何时倚虚幌，双照泪痕干。


    
</div>








<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>春宿左省 杜甫</h2>
    

    
花隐掖垣暮，啾啾棲鳥過。星臨萬戶動，月傍九霄多。不寢聽金鑰，因風想玉珂。明朝有封事，數問夜如何。


    
</div>








<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>至德二載甫自京金光門出 間道歸鳳翔 乾元初從左拾遺移華州掾 與親故別 因出此門 有悲往事 杜甫</h2>
    

    
此道昔歸順 西郊胡正繁 至今猶破膽 應有未招魂 近侍歸京邑 移官豈至尊 無才日衰老 駐馬望千門


    
</div>

<p><img src="https://images.ygria.site/2024/07/df482a475e31bc36e8dbad5ff43c2a9d.jpeg" alt="D5A03DB8-734F-4586-934F-55B574332857_1_105_c.jpeg" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="day-742024-06-08">Day 74（2024-06-08）</h2>







<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>澡兰香 淮安重午 吴文英</h2>
    

    
盘丝系腕，巧篆垂簪，玉隐绀纱睡觉。银瓶露井，彩箑云窗，往事少年依约。为当时曾写榴裙，伤心红绡褪萼。黍梦光阴，渐老汀洲烟蒻。
莫唱江南古调，怨抑难招，楚江沉魄。薰风燕乳，暗雨梅黄，午镜澡兰帘幕。念秦楼也拟人归，应剪菖蒲自酌。但怅望、一缕新蟾，随人天角。


    
</div>








<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>点绛唇·试灯夜初晴 吴文英</h2>
    

    
卷尽愁云，素娥临夜新梳洗。暗尘不起。酥润凌波地。
辇路重来，仿佛灯前事。情如水。小楼熏被。春梦笙歌里。


    
</div>

<p><img src="https://images.ygria.site/2024/07/0f2d5cc8b0352baf9f6a6e7fce1944c5.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="day-72-73-06-04">Day 72 73 （06-04）</h2>







<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>牡丹亭 汤显祖</h2>
    

    
梦回莺啭，乱煞年光遍。人立小庭深院。炷尽沉烟，抛残绣线，恁今春关情似去年。袅晴丝吹来闲庭院，摇漾春如线。停半饷、整花钿。没揣菱花，偷人半面，迤逗的彩云偏。步香闺，怎便把全身现！
原来姹紫嫣红开遍，似这般都付与断井颓垣。良辰美景奈何天，赏心乐事谁家院！朝飞暮卷，云霞翠轩；雨丝风片，烟波画船，锦屏人忒看的这韶光贱！遍青山啼红了杜鹃，荼靡外烟丝醉软。闲凝眄，生生燕语明如剪，呖呖莺歌溜的圆。
没乱里春情难遣，蓦地里怀人幽怨。则为俺生小婵娟，拣名门、一例里神仙眷。甚良缘，把青春抛的远！俺的睡情谁见？则索因循腼腆。想幽梦谁边，和春光暗流转？迁延，这衷怀那处言！淹煎，泼残生，除问天！


    
</div>

<p><img src="https://images.ygria.site/2024/07/6889fc7bac5fce4ba8b386212e1fd9cd.jpeg" alt="435CF810-392E-4AEA-9792-31799E1FA038_1_102_o.jpeg" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://images.ygria.site/2024/07/11f216cf331c2b90621d71abd0838668.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="day-6605-25">Day 66（05-25）</h2>







<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>轮台歌奉送封大夫出师西征 唐 岑参</h2>
    

    
轮台城头夜吹角，轮台城北旄头落。羽书昨夜过渠黎，单于已在金山西。戍楼西望烟尘黑，汉兵屯在轮台北。上将拥旄西出征，平明吹笛大军行。四边伐鼓雪海涌，三军大呼阴山动。虏塞兵气连云屯，战场白骨缠草根。剑河风急雪片阔，沙口石冻马蹄脱。亚相勤王甘苦辛，誓将报主静边尘。古来青史谁不见，今见功名胜古人。


    
</div>








<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>游子吟 唐 孟郊</h2>
    

    
慈母手中线，游子身上衣。临行密密缝，意恐迟迟归。谁言寸草心，报得三春晖。


    
</div>

<p><img src="https://images.ygria.site/2024/07/2c850945c00f6ca127c6e7228aa33838.jpeg" alt="8DAF83E3-EECB-44CE-968E-9F4D72CB4D2F_1_105_c.jpeg" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="day-6005-15">Day 60（05-15）</h2>







<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>长相思 唐 李白</h2>
    

    
其一
长相思，在长安。络纬秋啼金井阑，微霜凄凄簟色寒。孤灯不明思欲绝，卷帷望月空长叹。美人如花隔云端！上有青冥之长天，下有渌水之波澜。天长路远魂飞苦，梦魂不到关山难。长相思，摧心肝！
其二
日色欲尽花含烟，月明如素愁不眠。赵瑟初停凤凰柱，蜀琴欲奏鸳鸯弦。此曲有意无人传，愿随春风寄燕然。忆君迢迢隔青天，昔日横波目，今作流泪泉。不信妾断肠，归来看取明镜前。


    
</div>








<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>塞上曲 王昌龄</h2>
    

    
蝉鸣空桑林，八月萧关道。出塞入塞寒，处处黄芦草。从来幽并客，皆共沙尘老。莫学游侠儿，矜夸紫骝好。


    
</div>

<p><img src="https://images.ygria.site/2024/07/eb648ec3ee8ef3d41719a181b85bf095.jpeg" alt="9C0629F3-A128-4CD2-A8F8-E02D10DECF21_1_105_c.jpeg" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="day-5105-06">Day 51（05-06）</h2>







<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>石鱼湖上醉歌 元结</h2>
    

    
石鱼湖，似洞庭，夏水欲满君山青。山为樽，水为沼，酒徒历历坐洲岛。长风连日作大浪，不能废人运酒舫。我持长瓢坐巴丘，酌饮四坐以散愁。


    
</div>








<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>山石 韩愈</h2>
    

    
山石荦确行径微，黄昏到寺蝙蝠飞。升堂坐阶新雨足，芭蕉叶大栀子肥。僧言古壁佛画好，以火来照所见稀。铺床拂席置羹饭，疏粝亦足饱我饥。夜深静卧百虫绝，清月出岭光入扉。天明独去无道路，出入高下穷烟霏。山红涧碧纷烂漫，时见松枥皆十围。当流赤足踏涧石，水声激激风吹衣。人生如此自可乐，岂必局束为人鞿。嗟哉吾党二三子，安得至老不更归。


    
</div>

<p><img src="https://images.ygria.site/2024/07/06713a2585c2bcfc0eba5a02f1d1fdc2.jpeg" alt="50DD3A1D-457F-44E8-8BF2-09E4D4C39229_1_105_c.jpeg" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="day-4004-23">Day 40（04-23）</h2>







<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>锦瑟 李商隐</h2>
    

    
锦瑟无端五十弦，一弦一柱思华年。庄生晓梦迷蝴蝶，望帝春心托杜鹃。沧海月明珠有泪，蓝田日暖玉生烟。此情可待成追忆？只是当时已惘然。


    
</div>








<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>无题 李商隐</h2>
    

    
其一
昨夜星辰昨夜风，画楼西畔桂堂东。身无彩凤双飞翼，心有灵犀一点通。隔座送钩春酒暖，分曹射覆蜡灯红。嗟余听鼓应官去，走马兰台类转蓬。

其二
相见时难别亦难，东风无力百花残。春蚕到死丝方尽，蜡炬成灰泪始干。晓镜但愁云鬓改，夜吟应觉月光寒。蓬山此去无多路，青鸟殷勤为探看。


    
</div>

<p><img src="https://images.ygria.site/2024/07/680a0befcad82bbcad1c266226cbd579.jpeg" alt="EA30266E-38B8-4C82-A976-F1B64A9FDD86_1_105_c.jpeg" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="day-3004-12">Day 30（04-12）</h2>







<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>念奴娇·书东流村壁 辛弃疾</h2>
    

    
野棠花落，又匆匆、过了清明时节。刬地东风欺客梦，一夜云屏寒怯。曲岸持觞，垂杨系马，此地曾轻别。楼空人去，旧游飞燕能说。
闻道绮陌东头，行人长见，帘底纤纤月。旧恨春江流不尽，新恨云山千叠。料得明朝，尊前重见，镜里花难折。也应惊问，近来多少华发？


    
</div>

<p><img src="https://images.ygria.site/2024/07/2bb2ae2144299f092a3fd8c07191e612.jpeg" alt="400D979D-DC15-47E0-B761-95690168FF68_1_105_c.jpeg" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="day-503-16">Day 5（03-16）</h2>







<div class="vertical-cn-note primary" style="height:auto;">
    
    <h2>虞美人 李煜</h2>
    

    
春花秋月何时了？往事知多少。小楼昨夜又东风，故国不堪回首月明中。
雕栏玉砌应犹在，只是朱颜改。问君能有几多愁？恰似一江春水向东流。


    
</div>

<p><img src="https://images.ygria.site/2024/07/4b9f3e44c9be74ca11a5ddff6ced8b98.jpeg" alt="7934C9E1-1240-4423-A0B0-2FA5F8C1FACD_1_105_c.jpeg" class="img-hide" loading="lazy" decoding="async" /></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>《权力与人-思悼世子之死与朝鲜王室》读书及观影笔记</title>
      <link>https://ygria.site/readnote-2024-01/</link>
      <pubDate>Wed, 03 Jul 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/readnote-2024-01/</guid>
      <description>Data type: &amp;lt;nil&amp;gt;
    
    &lt;p&gt;Error unmarshalling JSON data&lt;/p&gt;
    



&lt;p&gt;朝鲜王朝宫廷史。皇帝父亲把太子儿子折磨成精神病后、将其关入木柜处死的故事。这本书能看到权力对人的扭曲，以及在东亚传统的君臣父子叙事框架下，君父的威权能专横到何种地步。引用《恨中录》（朝鲜英祖次子思悼世子嫔惠庆宫所著），阐述分析了英祖性格成因、表现，着重分析思悼世子的死亡前后的“木柜事件”，以及正祖（ 思悼世子之子）即位后的洗草、消除相关记录从而主张自己王位合法性和正统性等。探讨了权力对人和关系的影响。
附录中批判了偶像化、民粹化的历史研究方向，主张讲逻辑、讲事实，严谨地进行历史研究。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="《权力与人-思悼世子之死与朝鲜王室》读书及观影笔记" />




    
    
    Data type: &lt;nil&gt;
    
    <p>Error unmarshalling JSON data</p>
    



<p>朝鲜王朝宫廷史。皇帝父亲把太子儿子折磨成精神病后、将其关入木柜处死的故事。这本书能看到权力对人的扭曲，以及在东亚传统的君臣父子叙事框架下，君父的威权能专横到何种地步。引用《恨中录》（朝鲜英祖次子思悼世子嫔惠庆宫所著），阐述分析了英祖性格成因、表现，着重分析思悼世子的死亡前后的“木柜事件”，以及正祖（ 思悼世子之子）即位后的洗草、消除相关记录从而主张自己王位合法性和正统性等。探讨了权力对人和关系的影响。
附录中批判了偶像化、民粹化的历史研究方向，主张讲逻辑、讲事实，严谨地进行历史研究。</p>
<h2 id="书摘">书摘</h2>
<h3 id="英祖的偏执和对思悼世子的打压">英祖的偏执和对思悼世子的打压</h3>







<div class="cn-note primary" style="height:auto;">
    

    
   <p>
    英祖极度恐惧死亡，因此几乎不使用任何与死亡相关的词语。如果在外听到杀人或与之相关的不祥之语，必须刷牙或清洗耳朵祛除不祥后再进入寝殿。英祖非常严格地区分生死与内外的界限。不仅如此，他病态地区分好事与坏事，宠爱的人与憎恶的人。好事或坏事发生时，英祖的出入之门都不同。后来惠庆宫仅凭英祖处死世子前从景华门而入的消息，就判断会发生不祥之事。因为英祖平时进入昌德宫璿源殿时，若是经过万安门，则无事发生；若是经过景华门，则必有变故。璿源殿是供奉英祖父亲肃宗的御真即肖像之处。在行善事前向父亲肖像禀告时，便经过意为“万事平安”的万安门；禀告祸事时，则经过景华门。参考英祖忌讳“死”字的性格，可知他这么做是因为景华门的“华”字，“华”与意为“灾殃”的“祸”是同音。
    </p>
    <p>

    
    英祖根据不同的事情出入不同的门，对待不同的人更是如此。英祖不允许自己宠爱的人与憎恶的人共处一室，不允许自己憎恶的人走宠爱的人所经过的道路。某次思悼世子前去英祖宠爱的女儿和缓翁主的住处附近，英祖知道后大发雷霆，命令他即刻返回。父王发怒后，世子惊慌失措，翻过高窗后逃走。（《恨中录》，第62页）虽然不知思悼世子踩着什么东西翻过窗户，但这是有多恐惧才会让一国的王世子、代理国事的掌权人翻窗逃走？
    </p>
    <p>

     <strong>英祖不仅严格区分生死、内外、好恶和爱憎，而且彻底执行。这样彻底的二分法思考本身就是一种病。某心理学论文曾指出，偏执症的人认识自我的根本方式就是二分法。 </strong>

    </p>


    
    <div class="footnote">英祖严格区分好恶、吉凶，并在生活中有很多刻板表现，比如洗耳、根据所要行之事决定经过出入之门等。最后宣布处死世子命令时也是经过景华门，惠庆宫也因此绝望地知道了世子的命运。</div>
    
</div>








<div class="cn-note primary" style="height:auto;">
    

    
   <p>
 《英祖实录》称十五岁代理听政后，世子开始生病。《恨中录》虽没有具体说明是什么时候，但也说代理听政后世子开始出现“病患之萌”。惠庆宫这样解释原因：世子开始代理听政的时候，发生了很多复杂的事情，对于强调党论问题等敏感或重要的事情，世子几乎都会禀告父王。“大朝则以为若此小事，不能决断，频烦于我，有何代理之本意？由是叱责小朝。小朝若不禀，则以为若此之事，何为不禀于我而自断，又叱责之。”（《恨中录》，第45页）思悼世子的处境变得非常困难。

    </p>


    
    <div class="footnote">英祖的偏执和精神控制：世子临政后，遇事无论禀告与否，都会遭到父亲的斥责</div>
    
</div>

<h3 id="绝对君主时代中的君主品德">绝对君主时代中的君主品德</h3>







<div class="cn-note primary" style="height:auto;">
    

    
   <p>
 权力的无情正是来源于此。英祖平时对思悼世子冷漠而严格，都到了要杀子的地步，这就更不用说了。英祖声称为了宗庙与社稷，要求思悼世子赴死。但从本质上看，他所说的四百年宗社不是其他，正是他自己的权力。对权力的核心，也就是对他本人的挑战，一丝一毫都不会得到原谅，子女也概莫能外。对于平凡的父亲来说，这是完全无法理解的事情，但根据权力的一般逻辑，这并非无法理解。正祖亦是一样的无情。正祖登极后，立即打压外祖家，在此过程中还欺骗了母亲，后来又把像朋友一样亲近的洪国荣逼入死亡。对掌权人来说，没有朋友、家人、父母、子女。

    </p>
       <p>
   
    现实就是现实。虽然普通人看起来觉得有些扭曲，但这样的国王们打造了朝鲜的全盛期，即以愤怒与恐怖为长技的英祖，以欺骗为长技的正祖成就了朝鲜的“文艺复兴”。<strong>马基雅维利在《君主论》中提出了“作为君主，受人畏惧和受人爱戴哪个更好”的问题，回答是“如果一个人对两者必须有所取舍，那么受人畏惧比受人爱戴安全得多”。</strong>  马基雅维利还忠告道：“在不可避免出现流血事态的情况下，一定要树立强有力的、明确的逻辑与大义名分，然后推进事情。”他还说：“君主必须做一个伟大的伪装者和假好人。”可以说，他把恐怖与欺骗说成是君主的品德。英祖与正祖正是具有马基雅维利式君主品德的国王。 可以说因英祖与正祖忠于马基雅维利式君主的品德，所以创造了最好的时代吗？这两人作为统治者所具备的资质里，可以高度评价的是他们不仅对周围人，对自己也非常严格。他们欣然放弃了作为帝王可以享有的奢侈生活，比任何人都彻底、诚实地实践了俭约这一品德，此外他们在孝道等其他儒教品德的实践上也比任何人都彻底。虽然他们有时用愤怒来威胁大臣，有时用欺骗来安抚大臣，但两位国王的权威从根本上来说，是来自彻底的自我管理。
    </p>


    
    <div class="footnote">马基雅维利式君主：在绝对君主的时代，令人恐惧的国王胜过好人，令人畏惧胜过受人爱戴。此类君主的矛盾之处：最无情的国王，同时也能打造全盛的时代。</div>
    
</div>








<div class="cn-note primary" style="height:auto;">
    

    
   <p>
 现在，绝对君主的时代已经过去了。但在政治领域之外，其他领域还留有类似的权力。最具代表性的就是经济权力。有些人拥有与人类生存息息相关的经济财富，不受任何干涉，行使着强大的权力。但是仔细分析就会发现，就像朝鲜不是国王的所有物一样，经济财富也不是资本家的所有物。即便自己努力积累财富，但也没有权力把它世世代代传下去，应该将此视为暂时委任的权力。但是有人利用这样的权力，甚至影响了他人的生存。<strong>不分享的权力是孤独而危险。</strong>

    </p>



    
    <div class="footnote">推衍到其他领域的绝对权力的危险之处</div>
    
</div>

<h3 id="作者的历史观点和对其他看法者的驳斥">作者的历史观点和对其他看法者的驳斥</h3>
<p>本书附录也很精彩，作者非常坦然地讲述了自己的观点，并非常直白地批判了先行其他历史作者的作品及观点。</p>
<ol>
<li>关于正祖是否存在歪曲历史的举动</li>
</ol>







<div class="cn-note primary" style="height:auto;">
    

    

<p>
    关于证据提供者的信赖度问题，白敏贞（音译）曾提出过反驳。针对我对正祖的怀疑，她表示：“如果把正祖的历史歪曲之举视为严重的历史欺诈行为，那么作者如何对正祖的治世赋予意义呢？”意思是我在详细论述正祖歪曲思悼世子相关事实的同时，如何给他的治世赋予一定的意义？世间对正祖的评价极为不同，有些人认为他是改革的圣君或启蒙君主，但也有人认为他是导致朝鲜走向灭亡的暴君。旅德历史学家卞媛琳认为答案是后者。我认为正祖的政治统治在朝鲜历代国王中相对优秀，但不能视正祖时代为太平盛世，也不能视他为圣君。当然，更不能认为他是圣君，所以就不会歪曲事实，即我不认为政治与道德具有必然或密切的关联性。另外，我认为正祖歪曲事实，是为追崇父亲而歪曲父亲传记，可能并不违背当时的道德准则。
    </p>

    <p>
要警惕偶像化研究对象。白敏贞（音译）认为正祖是优秀的统治者，不可能说谎。崔诚桓则辩护道：“即使正祖编造了思悼世子的传记，也应将其视为统治行为的一环。”我怀疑两位学者是把正祖偶像化成优秀的君主。优秀的统治者也会说谎，即使把编造视为统治行为，编造这一事实本身也不会改变。不应该说某某不是那样的人，而应承认这样的事实。如果学者们不具备严格的客观与冷静，那普通人就会偶像化历史人物。
    </p>
    

    
    <div class="footnote">「政治与道德不存在必然联系」，警惕偶像化研究对象</div>
    
</div>

<ol start="2">
<li>对于畅销书《思悼太子的告白》的批判</li>
</ol>







<div class="cn-note primary" style="height:auto;">
    

    

<p>
  我认为，所有人都只能通过自己凿出的孔洞(peephole)来窥视世界。观察历史也一样，学识渊博的人拥有更多的大型孔洞，而学识浅薄的人拥有的孔洞非少即小，有眼光的人会占据视野更好的孔洞，差异无非这些。归根到底，无论多么优秀的人，在观察世界与历史时，都只能从自己凿出的几个孔中窥视世界。而历史只不过是用适当的理论或方法论，有逻辑地对这些透过孔洞看到的情形进行编织的东西。因此， <strong>所有历史在本质上都具有局限性。我们之所以承认某段历史属实，不是因为历史本身属实，而是因为我们认为可以接受构建该段历史时所使用的证据与逻辑。</strong> 所以任何历史如果存在论据或逻辑上的问题，都不能被称作历史。
    </p>

    <p>
思悼世子的告白》取得了大众化的胜利，但无法成为历史。历史大众化不是迎合大众的历史关注点，这是不行的。在“历史”大众化中，绝对不能丢弃的是历史。只有成为历史，大众化才有意义。大众化历史不能是迎合大众感情的曲解的历史，而必须是提高大众历史意识的历史。所谓韩国史， <strong>不应该是公然只助长民族感情、煽动国粹主义的历史，而应该是能让人正视民族现实的历史。</strong>提高韩国人的自豪感固然重要，但不能因为自豪感便歪曲历史。我们需要基于真实的历史大众化。
    </p>
    

    
    <div class="footnote">孔中窥世界：历史研究的局限性</div>
    
</div>

<h1 id="电影思悼">电影《思悼》</h1>











<p>先从新闻看到了刘亚仁吸毒的事情，后面看的电影。可惜了这么好演技的演员。
是先读了《权力与人 思悼世子之死与朝鲜王室》这本书再看的电影。电影拍得很精彩，主线是思悼世子被关入木柜惨死，穿插回忆，每一个转场都或由扇子等小物件，或由一个片段或话语串起来，能看到一个从小被寄予厚望的孩子怎么在父亲一次次的打压下变得任诞放荡乃至疯狂。皇室残酷的规训下也存在的稀薄可怜的亲情，在祖母死、世子在河边敲鼓祭奠那段情绪张力最强。豆瓣短评说只有东亚拍得出这么扭曲的关系，诚然如此。
另：历史上世子肥胖到坐不进小轿，电影里可一点也不胖啊，哈哈。对于太子擅绘画也有一定的体现（画扇面），历史上世子临死前还在给西游记等绘本作序。也是中华文明影响到朝鲜的一个佐证。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>建站小记：迁移域名DNS到CloudFlare</title>
      <link>https://ygria.site/move-2-cloudflare/</link>
      <pubDate>Sun, 30 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/move-2-cloudflare/</guid>
      <description>CloudFlare使用和建站配置</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="建站小记：迁移域名DNS到CloudFlare" /><p>CloudFlare一直有赛博菩萨之称，据说用它做DNS解析服务又快又好又免费，还能防DDOS攻击，并且可以提供页面访问统计功能。
正好我博客网页打开略卡顿，所以决定将自己的DNS解析迁移到CloudFlare。</p>
<h1 id="1登录cf控制台添加自己的域名">1.登录CF控制台，添加自己的域名</h1>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240630232345.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>如上图所示，这里的截图是我已经配置好了的。</p>
<h1 id="2-登录原域名服务商账号并添加cf-dns解析服务">2. 登录原域名服务商账号，并添加CF DNS解析服务</h1>
<p>一般来说，在哪里买的域名，就会用哪个服务商提供的DNS解析服务。
我的域名是从西部数据购买的。登录该网站，并进入域名管理。
将DNS解析改为自定义解析，并输入CF提供的两个解析服务器。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240630233346.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
注意如果有DNSSEC设置，需要关闭。
配置完成后保存即可。</p>
<h1 id="3-解决重定向问题">3. 解决重定向问题</h1>
<p>保存DNS配置后，等待2-24H DNS刷新。
如出现网页访问反复重定向问题，需要到SSL/TLS标签下，将模式选为Strict即可解决。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240630233944.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="4-完成">4. 完成</h1>
<p>现在可以到CF的控制台看到博客的访问情况啦，是不是很不错呢～
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240630234128.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>详解Next Auth：自定义邮箱密码登录注册、Github、Notion授权 &amp; Convex集成</title>
      <link>https://ygria.site/learn-next-auth/</link>
      <pubDate>Wed, 12 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/learn-next-auth/</guid>
      <description>&lt;p&gt;最近用NextJS框架做全栈项目做的很顺手，现在想给项目加上&lt;strong&gt;登录、注册、鉴权拦截、分角色路由控制&lt;/strong&gt;等功能，并接入Github、Notion等第三方登录。
可以使用NextJS官方提供的Auth框架实现。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="https://next-auth.js.org/img/logo/logo-sm.png" alt="详解Next Auth：自定义邮箱密码登录注册、Github、Notion授权 & Convex集成" /><p>最近用NextJS框架做全栈项目做的很顺手，现在想给项目加上<strong>登录、注册、鉴权拦截、分角色路由控制</strong>等功能，并接入Github、Notion等第三方登录。
可以使用NextJS官方提供的Auth框架实现。</p>
<h1 id="intro">Intro</h1>
<p>阅读本篇，你将学会：
1、登录、注册等逻辑，和如何接入第三方（以Github、Notion为例）
2、建立用户、角色等数据模型，存储用户数据
3、公开、私有路由守卫</p>
<h2 id="技术栈">技术栈</h2>
<ul>
<li>NextJs（前端框架）  v14.2.3</li>
<li>React（前端框架）  18</li>
<li>NextAuth（鉴权框架） v5.0.0-beta.18</li>
<li>Convex （后端接口 + ORM）</li>
</ul>
<h2 id="背景知识学习">背景知识学习</h2>
<p>在开始实现之前，需要知道NextJS中服务端组件和客户端组件的概念。
NextJS中使用”use client“和”use server“标识服务端和客户端组件，客户端运行在浏览器中，服务端运行在服务器端。<strong>不标识时，默认为服务端组件。</strong>
服务端组件用于异步请求等，负责与服务端交互、请求数据等，客户端组件主要用于和用户交互。React的钩子也有明确的区分，比如useEffect等钩子只能在客户端组件中使用。</p>
<h1 id="实现步骤">实现步骤</h1>
<h2 id="代码框架搭建">代码框架搭建</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> node</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="node" data-theme="breeze" id="code-id-1">npx create-next-app@latest  </div>
    </div>
  </div>
</div><p>使用<strong>NextAuth（v5版本）</strong></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> node</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="node" data-theme="breeze" id="code-id-2">npm install next-auth@beta  </div>
    </div>
  </div>
</div><p>开始之前，需要在环境变量文件<code>.env.local</code>中配置变量</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> json</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="json" data-theme="breeze" id="code-id-3">AUTH_SECRET=**********************</div>
    </div>
  </div>
</div><h1 id="credentials">Credentials</h1>
<p>我们首先实现一个简单的账号密码注册、登录、登出。
参考： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9hdXRoanMuZGV2L2dldHRpbmctc3RhcnRlZC9hdXRoZW50aWNhdGlvbi9jcmVkZW50aWFscyNjcmVkZW50aWFscy1wcm92aWRlcg%3d%3d"
    
     target="_blank"
>
Credentials</a></p>
<h2 id="1基础配置">1.基础配置</h2>
<p>在项目根目录下，新建<code>auth.js</code>文件，并写入以下内容：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-4">import NextAuth from &#34;next-auth&#34;
import Credentials from &#34;next-auth/providers/credentials&#34;
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from &#34;@/utils/password&#34;
 
export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Credentials({
      authorize: async (credentials) =&gt; {
        let user = null
 
       // logic to salt and hash password
        const pwHash = saltAndHashPassword(credentials.password)
 
        // logic to verify if user exists
        user = await getUserFromDb(credentials.email, pwHash)
 
        if (!user) {
          // No user found, so this is their first attempt to login
          // meaning this is also the place you could do registration
          throw new Error(&#34;User not found.&#34;)
        }
 
        // return user object with the their profile data
        return user
      },
    }),
  ],
})</div>
    </div>
  </div>
</div><p>在根目录下，新建文件<code>middleware.ts</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-5">import NextAuth from &#39;next-auth&#39;;

import {
DEFAULT_LOGIN_REDIRECT,
apiAuthPrefix,
authRoutes,
publicRoutes,
} from &#34;@/routes&#34;

import { auth } from &#39;./auth&#39;;
export default auth((req) =&gt; {

const { nextUrl } = req;
// console.log(&#34;NEXT URL&#34; &#43; nextUrl.pathname)
const isLoggedIn = !!req.auth;
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix);
const isPublicRoutes = publicRoutes.includes(nextUrl.pathname);
const isAuthRoute = authRoutes.includes(nextUrl.pathname);
if (isApiAuthRoute) {
// DO NOTHING!
return null;

}
if (isAuthRoute) {
if (isLoggedIn) {
return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl))
} else {
return null;
}
}

if (!isLoggedIn &amp;&amp; !isPublicRoutes) {

return Response.redirect(new URL(&#34;/auth/login&#34;, nextUrl))

}

})
// invoke the middle ware!

export const config = {

matcher: [&#34;/((?!.&#43;\\.[\\w]&#43;$|_next).*)&#34;, &#34;/&#34;, &#34;/(api|trpc)(.*)&#34;],

};</div>
    </div>
  </div>
</div><p>routes.ts</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-6">// Public Routes
export const publicRoutes = [
&#34;/&#34;,
]
// redirect logged in users to /settings
export const authRoutes = [
&#34;/auth/login&#34;,
&#34;/auth/register&#34;,
]
export const apiAuthPrefix = &#34;/api/auth&#34;
export const DEFAULT_LOGIN_REDIRECT = &#34;/dashboard&#34;</div>
    </div>
  </div>
</div><p><code>middleware.ts</code>为保留文件名，其中<code>config</code>变量定义了触发中间件方法的匹配规则。该文件中，定义了<code>auth</code>方法的过滤器。
在<code>route.ts</code>中定义公开路径、用于鉴权的路径、鉴权接口前缀及默认重定向地址。
在过滤方法中，返回null说明无需执行权限检查。对于公开路径及鉴权接口，无需登录即可访问。登录后，再访问注册和登录页面，会自动重定向到<code>DEFAULT_LOGIN_REDIRECT</code>定义的<code>/dashboard</code>路由中。
配置NextAuth路由：
<code>api/auth/[...nextauth]/route.ts</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-7">import { handlers } from &#34;@/auth&#34;
export const { GET, POST } = handlers</div>
    </div>
  </div>
</div><h2 id="2注册页面">2.注册页面</h2>
<p>实现形如下图的注册页面，核心为可提交的表单，包含name、email、password等字段。</p>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d13f0c305c004e24bea0da279ee4ce37~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=388%5c&amp;h=561%5c&amp;s=25327%5c&amp;e=png%5c&amp;b=ffffff" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>使用zod进行字段的合法性校验。在<code>schemas/index.ts</code>中，定义注册使用的schema：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-8">import * as z from &#34;zod&#34;
export const RegisterSchema = z.object({
    email: z.string().email({
        message: &#34;Email is Required.&#34;
    }),
    password: z.string().min(6,{
        message: &#34;Minimum 6 characters required&#34;,
    }),
    name: z.string().min(1,{
        message: &#34;Name is Required&#34;
    })
})</div>
    </div>
  </div>
</div><p>注册页面代码（部分）：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-9">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-9">&#34;use client&#34;
import { useState, useTransition } from &#34;react&#34;
import { cn } from &#34;@/lib/utils&#34;
import * as z from &#34;zod&#34;
import { zodResolver } from &#34;@hookform/resolvers/zod&#34;
import { register } from &#34;@/actions/register&#34;
interface RegisterFormProps extends React.HTMLAttributes&lt;HTMLDivElement&gt; { }
export function RegisterForm({ className, ...props }: RegisterFormProps) {
  const [isPending, startTransition] = useTransition();
  const [error, setError] = useState&lt;string | undefined&gt;(&#34;&#34;);
  const [success, setSuccess] = useState&lt;string | undefined&gt;(&#34;&#34;);
  const form = useForm&lt;z.infer&lt;typeof RegisterSchema&gt;&gt;({
    resolver: zodResolver(RegisterSchema),
    defaultValues: {
      name: &#34;&#34;,
      email: &#34;&#34;,
      password: &#34;&#34;
    }
  });
  async function onSubmit(values: z.infer&lt;typeof RegisterSchema&gt;) {
    setError(&#34;&#34;)
    setSuccess(&#34;&#34;)
    startTransition(() =&gt; {
      register(values).then((data) =&gt; {
        setError(data.error)
        setSuccess(data.success)
      })
    })
  }
  return (
    &lt;div className={cn(&#34;grid gap-6&#34;, className)} {...props}&gt;  
          &lt;form onSubmit={form.handleSubmit(onSubmit)} className=&#34;space-y-6&#34;&gt;
          // form field inputs
            &lt;Button type=&#34;submit&#34; className=&#34;w-full&#34; disabled={isPending}&gt;Create an account&lt;/Button&gt;
          &lt;/form&gt;
    &lt;/div&gt;
  )
}</div>
    </div>
  </div>
</div><p><code>actions/register.ts</code> 处理注册用户入库：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-10">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-10">
          <button id="copyButton-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-10" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-10">&#34;use server&#34;;

import * as z from &#34;zod&#34;
import { RegisterSchema } from &#34;@/schemas&#34;;
import  bcrypt  from &#34;bcryptjs&#34;
import { api } from &#34;@/convex/_generated/api&#34;;
import { fetchMutation, fetchQuery } from &#34;convex/nextjs&#34;;
import { getUserByEmail } from &#34;@/data/user&#34;;

export const register = async (values: z.infer&lt;typeof RegisterSchema&gt;) =&gt; {
    const validatedFields = RegisterSchema.safeParse(values);
    if (!validatedFields.success) {
        return {
            error: &#34;Invalid fields!&#34;
        }
    }
    const { email, password, name } = validatedFields.data;
    const hasedPassword = await bcrypt.hash(password, 10)
    const existingUser = await getUserByEmail(email)
    if (existingUser) {
        ``
        return {
            error: &#34;Email already in use!&#34;
        }
    }
    await fetchMutation(api.user.create, {
        name,
        email,
        password: hasedPassword
    })
    // TODO : Send verification token email
    return {
        // error: &#34;Invalid fields!&#34;,
        success: &#34;User Created&#34;
    }
}</div>
    </div>
  </div>
</div><p>用户在注册页面填写名称、邮箱、密码后，点击submit按钮，客户端组件调用了服务组件方法，先查询邮箱是否被占用，未被占用，将明文密码使用<code>bcryptjs</code>加密后，存入数据库中。</p>
<h2 id="3用户登录">3.用户登录</h2>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e1a207eee5b14f279f6e7c469af4c482~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=430&amp;h=519&amp;s=23590&amp;e=png&amp;b=ffffff" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
同样使用zod进行登录表单的字段的合法性校验。在<code>schemas/index.ts</code>中，定义登录使用的schema：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-11">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-11">
          <button id="copyButton-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-11" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-11">import * as z from &#34;zod&#34;
export const LoginSchema = z.object({
    email: z.string().email(),
    password: z.string().min(1)
})</div>
    </div>
  </div>
</div>


<blockquote>
    <p>注意：不应限制用户填入密码规则。虽然注册时限定了用户填写的密码至少6位，但系统的密码规则有可能变更。</p>
</blockquote>
<p>登录页面代码（部分）：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-12">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-12">
          <button id="copyButton-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-12" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-12">&#34;use client&#34;
import { useState, useTransition } from &#34;react&#34;
import { cn } from &#34;@/lib/utils&#34;
import { useForm } from &#34;react-hook-form&#34;
import { LoginSchema } from &#34;@/schemas&#34;
import * as z from &#34;zod&#34;
import { zodResolver } from &#34;@hookform/resolvers/zod&#34;
import { login } from &#34;@/actions/login&#34;
interface LoginFormProps extends React.HTMLAttributes&lt;HTMLDivElement&gt; { }

export function LoginForm({ className, ...props }: LoginFormProps) {
  const [isPending, startTransition] = useTransition();
  const [error, setError] = useState&lt;string | undefined&gt;(&#34;&#34;);
  const [success, setSuccess] = useState&lt;string | undefined&gt;(&#34;&#34;);
  const form = useForm&lt;z.infer&lt;typeof LoginSchema&gt;&gt;({
    resolver: zodResolver(LoginSchema),
    defaultValues: {
      email: &#34;&#34;,
      password: &#34;&#34;
    }
  });

  async function onSubmit(values: z.infer&lt;typeof LoginSchema&gt;) {
    setError(&#34;&#34;)
    setSuccess(&#34;&#34;)
    startTransition(() =&gt; {
      login(values).then((data) =&gt; {
        if (data &amp;&amp; data.error) {
          setError(data.error)
        }
      })
    })
  }
 return (
    &lt;div className={cn(&#34;grid gap-6&#34;, className)} {...props}&gt;
          &lt;form onSubmit={form.handleSubmit(onSubmit)} className=&#34;space-y-6&#34;&gt;
            &lt;Button type=&#34;submit&#34; className=&#34;w-full&#34; disabled={isPending}&gt;Login&lt;/Button&gt;
          &lt;/form&gt;
    &lt;/div&gt;
  )
}</div>
    </div>
  </div>
</div><p><code>actions/login.ts</code> 登录操作：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-13">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-13">
          <button id="copyButton-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-13" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-13">&#34;use server&#34;;
import { signIn } from &#34;@/auth&#34;;
import * as z from &#34;zod&#34;
import { LoginSchema } from &#34;@/schemas&#34;;
import { DEFAULT_LOGIN_REDIRECT } from &#34;@/routes&#34;;
import { AuthError } from &#34;next-auth&#34;;
export const login = async (values: z.infer&lt;typeof LoginSchema&gt;) =&gt; {
    const validatedFields = LoginSchema.safeParse(values);
    if (!validatedFields.success) {
        return {
            error: &#34;Invalid fields!&#34;
        }
    }
    const { email, password } = validatedFields.data;
    console.log(&#34;EMAIL!&#34; &#43; email &#43; &#34;PASSWORD!&#34; &#43; password)

    try {
        await signIn(&#34;credentials&#34;, {
            email, password,
            // redirect: true,
            redirectTo: DEFAULT_LOGIN_REDIRECT
        },)
    } catch (error) {
        if (error instanceof AuthError) {
            switch (error.type) {
                case &#34;CredentialsSignin&#34;:
                    return { error: &#34;Invalid Credentials!&#34; }
                default:
                    return { error: &#34;Something went wrong!&#34; }
            }
        }
        throw error;
    }
}</div>
    </div>
  </div>
</div><p>可以看到，该操作从<code>@/auth</code>中导入了SignIn方法，第一个参数指定了Provider，实际的授权在Provider定义的<code>authorize</code>方法中完成：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-14">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-14">
          <button id="copyButton-id-14">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-14" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-14">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-14">import bcrypt from &#34;bcryptjs&#34;;
import NextAuth, { type DefaultSession } from &#34;next-auth&#34;;
import Credentials from &#34;next-auth/providers/credentials&#34;;
import { LoginSchema } from &#34;./schemas&#34;;

export const { auth, handlers, signIn, signOut } = NextAuth({
    providers: [
        Credentials({
            async authorize(credentials) {
                const validatedFields = LoginSchema.safeParse(credentials);
                if (validatedFields.success) {
                    const { email, password } = validatedFields.data;
                    const user = await getUserByEmailFromDB(email);
                    if (!user || !user.password) return null;

                    const passwordsMatch = await bcrypt.compare(password, user.password);
                    if (passwordsMatch) {
                        return {
                            ...user,
                            id: user._id
                        }
                    }
                }
                return null;
            },

        }),]

}</div>
    </div>
  </div>
</div><p>其中，由于密码在注册时使用了<code>bcryptjs</code>加密，所以比较时也要使用<code>bcryptjs</code>提供的match方法。至此，使用邮箱和密码登录注册的简单逻辑已完成。</p>
<h1 id="github-provider">Github Provider</h1>
<h2 id="1-新建github-oauth-app">1. 新建Github Oauth App</h2>
<p>在<code>https://github.com/settings/developers</code>下，新建Oauth Apps</p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0fe1d1952c894a0887147f2c2a1cfffb~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1518&amp;h=993&amp;s=139826&amp;e=png&amp;b=ffffff" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>



<blockquote>
    <p>Callback url很重要，一定是你的站点host+port，后面配置为Next Auth默认回跳地址<code>/api/auth/callback/github</code>。（我当前配置为开发用，生产时需要改为线上地址。）</p>
</blockquote>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6f6072a4ba2443cb684b1939776a5d4~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=670&amp;h=467&amp;s=58414&amp;e=png&amp;b=ffffff" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
配置完成后，将Client ID 和Client Secret粘贴到配置文件中。
<code>.env.local</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-15">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> env</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-15">
          <button id="copyButton-id-15">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-15" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-15">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="env" data-theme="breeze" id="code-id-15">GITHUB_CLIENT_ID=****
GITHUB_CLIENT_SECRET=****</div>
    </div>
  </div>
</div><h2 id="2-配置github-provider">2. 配置Github Provider</h2>
<p>在<code>auth.ts</code>的Providers数组中，加入：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-16">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-16">
          <button id="copyButton-id-16">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-16" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-16">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-16">GitHub({
  clientId: process.env.GITHUB_CLIENT_ID,
  clientSecret: process.env.GITHUB_CLIENT_SECRET,
  allowDangerousEmailAccountLinking: true
}),</div>
    </div>
  </div>
</div><p><code>allowDangerousEmailAccountLinking</code>是允许使用同一邮箱的Github、Notion账号互联。若不配置该属性，使用同一邮箱注册的Notion和Github（或其他第三方）登录将被拦截。当使用的Providers来自于不可靠的OAuth App厂商，须谨慎使用该属性。</p>
<h2 id="3页面触发github登录">3.页面触发Github登录</h2>
<p><code>social.tsx</code>
从<code>@/auth.ts</code>中导入登录方法，并在点击按钮时触发即可。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-17">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-17">
          <button id="copyButton-id-17">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-17" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-17">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-17">&#34;use client&#34;
import { FaGithub } from &#34;react-icons/fa&#34;
import { SiNotion } from &#34;react-icons/si&#34;;
import { Button } from &#34;@/components/ui/button&#34;
import { signIn } from &#34;next-auth/react&#34;
import { DEFAULT_LOGIN_REDIRECT } from &#34;@/routes&#34;

export const Social = () =&gt; {
    const onClick = (provider: &#34;github&#34; | &#34;notion&#34;) =&gt; {
        signIn(provider, {
            callbackUrl: DEFAULT_LOGIN_REDIRECT
        })
    }

    return (
        &lt;div className=&#34;flex items-center w-full gap-x-2&#34;&gt;
            &lt;Button size=&#34;lg&#34; className=&#34;w-full&#34; variant=&#34;outline&#34; onClick={() =&gt; onClick(&#34;github&#34;)}&gt;
                &lt;FaGithub className=&#34;h-5 w-5&#34;&gt;&lt;/FaGithub&gt;
            &lt;/Button&gt;
            &lt;Button size=&#34;lg&#34; className=&#34;w-full&#34; variant=&#34;outline&#34; onClick={() =&gt; onClick(&#34;notion&#34;)}&gt;
                &lt;SiNotion className=&#34;h-5 w-5&#34; /&gt;
            &lt;/Button&gt;


        &lt;/div&gt;
    )
}</div>
    </div>
  </div>
</div><p>首次点击时，会跳转到Github询问是否授权。之后会直接点击后跳转鉴权并登录。</p>
<h1 id="notion-provider">Notion Provider</h1>
<p>Notion登录的配置方法与Github非常类似。</p>
<h2 id="1-配置public-ingegration">1. 配置Public Ingegration</h2>
<p>前往https://www.notion.so/my-integrations ，新建一个Integration，并设置为公有</p>
<p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ddb316800dbd49e8adf78d60df2ea3e3~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1127&amp;h=648&amp;s=103126&amp;e=png&amp;b=fefdfb" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
需将回调地址配置为<code>/api/auth/callback/notion</code></p>
<p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1fbdfd00f8094b7bb6f2d522f73c15b1~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=601&amp;h=620&amp;s=71973&amp;e=png&amp;b=fdfcfa" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>生成并复制Secrets：</p>
<p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00b68d512f5246f381d18b667779e902~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=988&amp;h=407&amp;s=40786&amp;e=png&amp;b=fefdfb" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>将Client ID和Secret复制到配置文件中。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-18">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> json</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-18">
          <button id="copyButton-id-18">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-18" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-18">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="json" data-theme="breeze" id="code-id-18">AUTH_NOTION_ID=&#34;&#34;
AUTH_NOTION_SECRET=&#34;&#34;
AUTH_NOTION_REDIRECT_URI=&#34;http://localhost:3001/api/auth/callback/notion&#34;</div>
    </div>
  </div>
</div><h2 id="2-配置notion-provider">2. 配置Notion Provider</h2>
<p>在<code>auth.ts</code>的Providers数组中，加入：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-19">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-19">
          <button id="copyButton-id-19">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-19" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-19">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-19"> Notion({
      clientId: process.env.AUTH_NOTION_ID,
      clientSecret: process.env.AUTH_NOTION_SECRET,
      redirectUri: process.env.AUTH_NOTION_REDIRECT_URI,
      allowDangerousEmailAccountLinking: true
    }),</div>
    </div>
  </div>
</div><p>需传入redirectUri（该uri必须配置到上一步的Ingegration中）</p>
<h2 id="使用notion登录">使用Notion登录</h2>
<p>登录的触发方法与Github相同，不赘述。点击登录时，会多出一步，可以选择授权访问的page范围。</p>
<p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c9b1c6b82d9f4dbe9ab8c1f64750fe8b~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=663&amp;h=733&amp;s=70654&amp;e=png&amp;b=fdfcfc" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="convex-adapter">Convex Adapter</h1>
<p>参考文档：</p>
<ol>
<li>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9hdXRoanMuZGV2L2d1aWRlcy9jcmVhdGluZy1hLWRhdGFiYXNlLWFkYXB0ZXI%3d"
    
     target="_blank"
>
Auth.js | Creating A Database Adapter</a></li>
<li>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9zdGFjay5jb252ZXguZGV2L25leHRhdXRoLWFkYXB0ZXI%3d"
    
     target="_blank"
>
https://stack.convex.dev/nextauth-adapter</a></li>
</ol>
<p>本应用使用了Convex作为后台API提供，所以需要实现Convex Adapter。按https://stack.convex.dev/nextauth-adapter 步骤操作即可。</p>
<p><code>auth.ts</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-20">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-20">
          <button id="copyButton-id-20">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-20" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-20">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-20">import NextAuth from &#34;next-auth&#34;
import { ConvexAdapter } from &#34;@/app/ConvexAdapter&#34;;
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [],
  adapter: ConvexAdapter,
})</div>
    </div>
  </div>
</div>


<blockquote>
    <p><strong>注意避坑！！！！</strong></p>
<p>这里遇到了一个坑，配置了Convex Adapter后，使用邮箱密码再也无法登陆。经过调试发现是通过Credentials登录时，没有调用createSession方法，session没有创建。在Github上搜索后，发现有人遇到类似问题：https://github.com/nextauthjs/next-auth/issues/3970</p>
</blockquote>
<p><strong>在auth.ts中增加如下配置：</strong></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-21">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-21">
          <button id="copyButton-id-21">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-21" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-21">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-21"> session: {
    // Set to jwt in order to CredentialsProvider works properly
    strategy: &#39;jwt&#39;
  }</div>
    </div>
  </div>
</div><p>即可解决问题！！（卡了好久，真的有点坑……教训就是遇到问题先上github搜搜issue，我一直试图手动自己往数据库塞入一个session未果……）</p>
<h1 id="其他功能">其他功能</h1>
<h2 id="1回调-callbacks">1.回调 callbacks</h2>
<p>在回调中，可以向session加入自定义属性</p>
<p><code>auth.ts</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-22">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-22">
          <button id="copyButton-id-22">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-22" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-22">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-22">  callbacks: {
    async signIn({ user }) {
      // console.log(&#34;AFTER SIGN IN !&#34; &#43; JSON.stringify(user));
      return true;
    },

    async jwt({ token, user, account, profile, isNewUser, session }) {
      if (user) {
        token.id = user.id
      }
      const notionAccount = await getNotionAccount(token.id!! as Id&lt;&#34;users&#34;&gt;)
      if (notionAccount) {
        token.notion_token = notionAccount.access_token;
      
      }
      return token;
    },

    async session({ token, session }) {
      if (token.sub &amp;&amp; session.user) {
        session.user.id = token.sub;
      }
      if (token.notion_token &amp;&amp; session.user) {
        session.user.notion_token = token.notion_token;
      }

      return session;
    },
  },</div>
    </div>
  </div>
</div><p>执行顺序： signIn =&gt; jwt =&gt; session</p>
<p>可在session中读到在jwt方法返回的token值，可将需要的属性放到session中，如角色、权限等。此处我将Notion的secret放到session中，以便业务代码中取用。</p>
<h2 id="2自定义session类型">2.自定义Session类型</h2>
<p>在auth.ts中加入如下代码，可解决自定义session中的属性报ts错误问题。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-23">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-23">
          <button id="copyButton-id-23">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-23" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-23">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-23">declare module &#34;next-auth&#34; {
  /**
   * Returned by `auth`, `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
   */
  interface Session {
    user: {
      role: string;
      notion_token: string;
    } &amp; DefaultSession[&#34;user&#34;];
  }
}</div>
    </div>
  </div>
</div><h2 id="3事件-events">3.事件 events</h2>
<p><code>auth.ts</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-24">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-24">
          <button id="copyButton-id-24">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-24" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-24">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-24">  events: {
    async linkAccount({ user }) {
      if (user.id) {
        await setEmailVerified(user.id)
      }
    },
  },</div>
    </div>
  </div>
</div><p>一个用户（user）可连接多个account（由github、notion等提供的第三方OAuth账号），可设置当连接用户时，将用户设置为邮箱已验证（通过github、notion等可靠app登录的用户无需二次验证邮箱。）</p>
<h2 id="4页面">4.页面</h2>
<p><code>auth.ts</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-25">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-25">
          <button id="copyButton-id-25">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-25" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-25">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-25">  pages: {
    signIn: &#34;/auth/login&#34;,
    error: &#34;/auth/error&#34;
  },</div>
    </div>
  </div>
</div><p>指定登录页、自定义鉴权出错页。</p>
<h2 id="5登出">5.登出</h2>
<p><code>action/logout.ts</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-26">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-26">
          <button id="copyButton-id-26">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-26" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-26">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-26">&#34;use server&#34;
import { signOut } from &#34;@/auth&#34;

export const logout = async () =&gt; {
    //  Some server stuff
    await signOut();
}</div>
    </div>
  </div>
</div><p>在客户端组件中使用：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-27">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-27">
          <button id="copyButton-id-27">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-27" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-27">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-27">import { logout } from &#34;@/actions/logout&#34;;
function onClickSignOut() {
    logout();
}</div>
    </div>
  </div>
</div><h2 id="6使用session获取用户信息">6.使用Session获取用户信息</h2>
<p>在客户端调用useSession(),需要在SessionProvider的包裹下使用。</p>
<p><code>(protected)/layout.ts</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-28">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-28">
          <button id="copyButton-id-28">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-28" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-28">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-28">import { Suspense } from &#34;react&#34;;

import { SessionProvider } from &#34;next-auth/react&#34;;

export default async function DashboardLayout({
  children, // will be a page or nested layout
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;SessionProvider&gt;
        &lt;div&gt;{children}&lt;/div&gt;     
    &lt;/SessionProvider&gt;
  );
}</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-29">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> ts</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-29">
          <button id="copyButton-id-29">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-29" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-29">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="ts" data-theme="breeze" id="code-id-29">import { useSession } from &#34;next-auth/react&#34;;
const session = useSession();</div>
    </div>
  </div>
</div><h1 id="总结">总结</h1>
<p>NextAuth能帮助你快速集成第三方登录，也支持灵活的自定义账号、密码登录。</p>
<p>借助ORM框架和一些中间件，一个NextJS项目已可以完成所有业务功能，不需要再有独立的后端服务。对于小而美的项目，是一个快速落地的理想选择。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>琅琊山一日游</title>
      <link>https://ygria.site/travel-2-langya/</link>
      <pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/travel-2-langya/</guid>
      <description>&lt;p&gt;和对象一起尝试的合肥公交APP上的旅游线路。原本准备去淮南的焦岗湖（据说风景不错，买车票还送船票、影视城门票），前一天夜里才发现路线比较冷门，大巴没凑够30人，所以强制退票了……所以临时选了滁州的琅琊山。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240521212120.png" alt="琅琊山一日游" /><p>和对象一起尝试的合肥公交APP上的旅游线路。原本准备去淮南的焦岗湖（据说风景不错，买车票还送船票、影视城门票），前一天夜里才发现路线比较冷门，大巴没凑够30人，所以强制退票了……所以临时选了滁州的琅琊山。</p>
<p>早晨六点多起床，开车去大巴停车地点杏花公园。许久没有早起过了，很吃惊原来六点多天就这么亮了啊。经常睡懒觉的我有些惭愧，也不知辜负了多少好辰光。
大巴是新的，空调也开得足，开得也蛮快。7:30准时发车，到和平广场停了一站，然后就直接开到景区。9:50左右下车，就看到一块刻着“琅琊山”的大石，琅琊山就到啦。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240521212120.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
琅琊山本身是不收费的，从美团购买的景区联票包含的四个小景点是分开收费，即醉翁亭、同乐园、琅琊古寺、南天门。我们游览也按照这个顺序，由北门进入，到南天门，然后折返。
刚一入山，树影参差，东侧有小溪，有挺多大人带着孩子在溪边戏水，用网捕捞小鱼。溪流不深，流速也很缓。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240521213236.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>首先到的是醉翁亭。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/DBA7A0C3-1705-48CA-B851-AC9ED787DC60.JPG" alt="DBA7A0C3-1705-48CA-B851-AC9ED787DC60.JPG" class="img-hide" loading="lazy" decoding="async" /></p>
<p>亭本身不大，有苏轼题字匾额。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/CE37009E-3145-4AD9-AC9A-0C59B795F014_1_102_o.jpeg" alt="CE37009E-3145-4AD9-AC9A-0C59B795F014_1_102_o.jpeg" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240521214524.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>亭后有二贤内堂，有欧阳修、王禹偁两位曾当过滁州太守的塑像。
还有菱溪石，并有石刻碑文《菱溪》，字很娟秀。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/089145A9-D667-4562-8520-513D584C9110.JPG" alt="089145A9-D667-4562-8520-513D584C9110.JPG" class="img-hide" loading="lazy" decoding="async" />
最近对书法颇感兴趣，遇到此类碑文都会再多看几眼。</p>
<p>园内有一亭，名为“影香亭”，有民乐团演奏，叮叮咚咚的声音悦耳动听，为景色又添了一分情致。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/BCE2BDB2-05F1-4AD5-B192-5B3CCDCF5258.JPG" alt="BCE2BDB2-05F1-4AD5-B192-5B3CCDCF5258.JPG" class="img-hide" loading="lazy" decoding="async" />
出门后看到大石上有一只枯叶蝶。看它合起翅膀还真像一片枯叶呢，偶尔展开才能看到漂亮的花纹。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240629233959.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />
叠翠亭。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/B9516408-9BB1-4D4B-B1BD-52DBA8F56506.JPG" alt="B9516408-9BB1-4D4B-B1BD-52DBA8F56506.JPG" class="img-hide" loading="lazy" decoding="async" />
深秀湖。树多垂到水面上。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/6580613B-6366-43E9-A3B0-F2F73137922A.JPG" alt="6580613B-6366-43E9-A3B0-F2F73137922A.JPG" class="img-hide" loading="lazy" decoding="async" />
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/A2711970-282F-42B4-98BA-B24615BF1A77.JPG" alt="A2711970-282F-42B4-98BA-B24615BF1A77.JPG" class="img-hide" loading="lazy" decoding="async" />
南天门的入口，过了这道口就是要爬山。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/5FA55008-424A-417A-BC81-B0C3E97CFEE9.JPG" alt="5FA55008-424A-417A-BC81-B0C3E97CFEE9.JPG" class="img-hide" loading="lazy" decoding="async" />
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/675FFA51-17D4-47E7-B526-481640AB4336.JPG" alt="675FFA51-17D4-47E7-B526-481640AB4336.JPG" class="img-hide" loading="lazy" decoding="async" />
路边一只慵懒的小猫咪。
到了琅琊寺。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/6299B24F-6838-4562-9031-228F166E76B3.JPG" alt="6299B24F-6838-4562-9031-228F166E76B3.JPG" class="img-hide" loading="lazy" decoding="async" />
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/0B4B3E82-5E5C-408C-96CF-AE8F2649575A.JPG" alt="0B4B3E82-5E5C-408C-96CF-AE8F2649575A.JPG" class="img-hide" loading="lazy" decoding="async" /></p>
<p>这里专门有工作人员摆了摊让你可以换钱，可以花五块换成50个一角的硬币，往鱼嘴和龟身上投，我和老公换了两次，就为了投中。哈哈，还是挺好玩儿的。
寺内的石壁上也有不少铭刻。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/BC71F4B9-A910-4FBB-B278-94DF6E87A7B9.JPG" alt="BC71F4B9-A910-4FBB-B278-94DF6E87A7B9.JPG" class="img-hide" loading="lazy" decoding="async" /></p>
<p>午餐就是吃了寺内的素斋，20一个菜，两人吃了40。不便宜，但菜是大铁锅现炒出来的，味道还不错。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/B9B61DC4-93AA-48C6-A01A-9A06D51AD98F.JPG" alt="B9B61DC4-93AA-48C6-A01A-9A06D51AD98F.JPG" class="img-hide" loading="lazy" decoding="async" /></p>
<p>吃完午饭，坐着休息了一会儿，我们又上南天门，在顶上观赏了一下风景，就下山啦。
两点四十差不多人就到齐了，回去也很快，两个小时就到合肥啦。
好久没锻炼，一共是徒步了11.5公里左右。愉快的短途旅行～！</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/7E5E6D38-C79A-4D32-B881-085BFBFCA5F0.JPG" alt="7E5E6D38-C79A-4D32-B881-085BFBFCA5F0.JPG" class="img-hide" loading="lazy" decoding="async" /></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>全栈从0到1 3D旅游地图标记和轨迹生成</title>
      <link>https://ygria.site/build-3d-travel-trace/</link>
      <pubDate>Thu, 25 Apr 2024 22:46:00 +0000</pubDate>
      
      <guid>https://ygria.site/build-3d-travel-trace/</guid>
      <description>&lt;h1 id=&#34;功能演示&#34;&gt;功能演示&lt;/h1&gt;
&lt;p&gt;
&lt;a 
    
        href=&#34;https://ygria.site/tiaozhuan?target=aHR0cHM6Ly93d3cuaXhpZ3VhLmNvbS83MzYxNzk5MjM2NzU0NzY4Mzg0P3V0bV9zb3VyY2U9eGlndWFzdHVkaW8%3d&#34;
    
     target=&#34;_blank&#34;
&gt;
演示视频&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425231245.png&#34; alt=&#34;image.png&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425231108.png&#34; alt=&#34;image.png&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;
&lt;h1 id=&#34;体验地址&#34;&gt;体验地址&lt;/h1&gt;
&lt;p&gt;
&lt;a 
    
        href=&#34;https://ygria.site/tiaozhuan?target=aHR0cHM6Ly90cmF2ZWwtdHJhY2UudmVyY2VsLmFwcC8%3d&#34;
    
     target=&#34;_blank&#34;
&gt;
Vercel App&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;开发技术栈：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NextJs（前端框架）&lt;/li&gt;
&lt;li&gt;React（前端框架）&lt;/li&gt;
&lt;li&gt;TailwindCSS （CSS样式）&lt;/li&gt;
&lt;li&gt;echart + echart gl （地图生成）&lt;/li&gt;
&lt;li&gt;shadui（UI组件库）&lt;/li&gt;
&lt;li&gt;Zustand&lt;/li&gt;
&lt;li&gt;lucide-react （图标）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第三方：&lt;/strong&gt;&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="全栈从0到1 3D旅游地图标记和轨迹生成" /><h1 id="功能演示">功能演示</h1>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuaXhpZ3VhLmNvbS83MzYxNzk5MjM2NzU0NzY4Mzg0P3V0bV9zb3VyY2U9eGlndWFzdHVkaW8%3d"
    
     target="_blank"
>
演示视频</a></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425231245.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425231108.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="体验地址">体验地址</h1>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly90cmF2ZWwtdHJhY2UudmVyY2VsLmFwcC8%3d"
    
     target="_blank"
>
Vercel App</a></p>
<p><strong>开发技术栈：</strong></p>
<ul>
<li>NextJs（前端框架）</li>
<li>React（前端框架）</li>
<li>TailwindCSS （CSS样式）</li>
<li>echart + echart gl （地图生成）</li>
<li>shadui（UI组件库）</li>
<li>Zustand</li>
<li>lucide-react （图标）</li>
</ul>
<p><strong>第三方：</strong></p>
<ul>
<li>Convex（数据存储+接口）</li>
<li>Vercel（项目托管）</li>
<li>高德开放平台（提供地图编码、逆编码等WEB API）</li>
</ul>
<h1 id="开发流程">开发流程</h1>
<p>下面给出关键步骤及部分代码。</p>
<h2 id="1-setup">1. Setup</h2>
<h3 id="11-初始化nextjs项目">1.1 初始化NextJS项目</h3>
<p>系统要求：Nodejs 18.17+<br>
打开终端，在控制台执行：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> bash</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="bash" data-theme="breeze" id="code-id-1">npx create-next-app@latest```  
  
全部选择默认选项即可。  
![image.png](https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425232407.png)  
  
初始化完成后，进入项目并运行。  
  
```bash  
cd travel-tracel  
npm run dev  
  </div>
    </div>
  </div>
</div><p>打开localhost:3000，看到如下页面，项目初始化成功。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425232418.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="12-安装npm依赖">1.2 安装npm依赖</h3>
<p>安装一些主要的依赖。后续需要用到的依赖可以边开发边安装。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> bash</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="bash" data-theme="breeze" id="code-id-2"># 安装echarts依赖  
npm install echarts-for-react  
npm install echarts-gl  
npm install require # 图标组件  
npm install lucide-react  </div>
    </div>
  </div>
</div><h2 id="2-定义标记点">2 定义标记点</h2>
<p>实现输入地点或地点关键字，查询经纬度，从而完成标记点的定义。<br>
实现思路：</p>
<ol>
<li>首先查询Convex数据库，提供10个最匹配的候选项</li>
<li>若没有找到对应候选项，用户可以点击搜索按钮，会调用高德API进行查询。</li>
<li>如果仍然没有查询到，可以点击“经纬度”按钮，进行经纬度的自行填写。</li>
<li>已经添加了的标记点，支持编辑和删除。</li>
<li>使用Zustand，进行地点增、删、改等状态管理。</li>
</ol>
<h3 id="21-定义uselocationpoints-store">2.1 定义useLocationPoints store</h3>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-3">import {create} from &#34;zustand&#34;;  import {nanoid} from &#34;nanoid&#34;;  interface ILocationPoints {    
    locations: LocationPoint[],    
    addLocation: (loc: LocationPoint) =&gt; void,    
    editLocation: (loc: LocationPoint) =&gt; void,    
removeLocation: (id: string) =&gt; void  }  interface LocationPoint{    
    id: string,    
    name: string,    
    lng: string,    
lat: string  }    
    
export const useLocationPoints = create&lt;ILocationPoints&gt;(set =&gt; ({    
    locations: [] as LocationPoint[],    
    addLocation: (loc: LocationPoint) =&gt; {    
        const _id  = nanoid()    
        loc[&#34;id&#34;] = _id    
        set(state =&gt; ({    
            locations: [...state.locations, loc]    
        }));    
    },    
    
    editLocation: (loc: LocationPoint)=&gt;{    
        set(state =&gt; ({    
            locations: state.locations.map(item=&gt;{    
                if(item.id === loc.id){    
                    return {    
                        ...loc,    
                        id: item.id,    
                    }    
                }else {    
                    return item    
                }    
            })    
        }));    
    
    },    
// 移除location    
    removeLocation:(id:string)=&gt;{    
        set(state =&gt; ({    
            locations: state.locations.filter(item=&gt;item.id !== id)    
        }));    
}  }));  </div>
    </div>
  </div>
</div><p>对于location的增删改，均依赖于以上store实现。单页面定义React自带的useState当然也可以，但是为了便利于组件拆分，所以使用store，不用再跨组件管理状态的提交、更新、监听等，方便了很多。<br>
考虑到location的名称、经纬度均有可能更改，所以使用nanoid生成唯一id进行location的索引。</p>
<h3 id="22-引入convex">2.2 引入Convex</h3>
<p>使用convex作为平台后台。convex可以提供数据库存储、RESTful接口及接口调试的功能。</p>
<h4 id="221-convex-项目配置">2.2.1 Convex 项目配置</h4>
<ol>
<li>
<p>访问
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuY29udmV4LmRldi8%3d"
    
     target="_blank"
>
Convex</a></p>
</li>
<li>
<p>创建app<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425232444.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
<li>
<p>在nextjs项目中，进行相应的配置。<br>
参考Convex官方文档：<br>

<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb2NzLmNvbnZleC5kZXYvcXVpY2tzdGFydC9uZXh0anM%3d"
    
     target="_blank"
>
Next.js Quickstart | Convex Developer Hub</a></p>
</li>
</ol>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> bash</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="bash" data-theme="breeze" id="code-id-4"># 进入项目目录后，安装  
cd my-app &amp;&amp; npm install convex  
npx convex dev  </div>
    </div>
  </div>
</div><p>因为我已经在Convex控制台中创建过app了，所以选择已存在的project</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425232503.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h4 id="222-table-schema定义和数据初始化">2.2.2 Table Schema定义和数据初始化</h4>
<p>在Convex目录下，新建·文件<code>schema.ts</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-5">import { v } from &#34;convex/values&#34;;  
import {defineSchema, defineTable} from &#34;convex/server&#34;;  
  
  
export default defineSchema({  
    locations: defineTable({        // 经纬度  
        name: v.string(),        //  经度  
        lng: v.number(),        // 纬度  
        lat:v.number(),  
    })        .index(&#34;by_name&#34;,[&#34;name&#34;])        .searchIndex(&#34;search_by_name&#34;,{            searchField: &#34;name&#34;,            filterFields: [&#34;name&#34;]        }),});  </div>
    </div>
  </div>
</div><p>如上所示，定义了一个名称为“locations”的数据表，有name、lng、lat三个字段，并定义了查询的规则（by_name）<br>
运行<code>npx convex dev</code>后，会发现Convex控制台中已经生成了该表。<br>
我从网上找到了一些世界范围内的经纬度数据，是csv格式。通过python处理成出初始化数据。<br>
小技巧：对于不同格式的csv，在第一行定义与字段相匹配的表头即可快速处理。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> python</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="python" data-theme="breeze" id="code-id-6">import json    
    
import pandas as pd    
    
data = []  csv_data = pd.read_csv(&#39;globalcities.csv&#39;,header=0,encoding=&#34;utf-8&#34;)    
    
# {&#34;name&#34;: &#34;上海&#34;, &#34;lng&#34;:  121.47,&#34;lat&#34;:31.23}  with open(&#34;global.csv&#34;,&#34;w&#34;,encoding=&#39;utf-8&#39;) as file:    
      
    for index, row in csv_data.iterrows():    
        d = {    
            &#34;name&#34;: row[&#34;城市名中文&#34;] ,    
            &#34;lng&#34;:row[&#34;经度&#34;],    
            &#34;lat&#34;:row[&#34;纬度&#34;]    
        }    
        file.write(json.dumps(d,ensure_ascii=False) )    
        print(json)  
  </div>
    </div>
  </div>
</div><p>执行导入：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> shell</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="shell" data-theme="breeze" id="code-id-7">npx convex import --table locations convex/init.jsonl```  
  
如下图所示，数据导入完成！  
  
![image.png](https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425220013.png)  
  
#### 2.2.3 查询接口定义和使用  
  
在convex文件夹下，新建文件location.ts，定义一个get查询接口  
```javascript  
import { v } from &#34;convex/values&#34;;  import { query } from &#34;./_generated/server&#34;    
    
export const get = query({    
    args:{    
        // orgId: v.string(),    
        // search: v.optional(v.string()),        // favorites: v.optional(v.string())        name: v.string()    
    },    
    handler: async (ctx,args) =&gt; {    
    
        let locations = [];    
    
        locations = await ctx.db.query(&#34;locations&#34;)    
            .withSearchIndex(&#34;search_by_name&#34;, (q) =&gt;    
            q.search(&#34;name&#34;, args.name)).take(10);    
    
    
        return locations;    
}  })  </div>
    </div>
  </div>
</div><p>运行<code>npx convex dev</code>进行接口的生成。<br>
接口使用：（value为useState定义的动态值，绑定地点input输入框）</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-8">const queryResult =  useQuery(api.locations.get, {name: value});  </div>
    </div>
  </div>
</div><p>在模版代码中遍历查询结果：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-9">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-9">{    
    queryResult?.map(res =&gt; (    
        &lt;Badge variant=&#34;outline&#34; key = {res._id} onClick={event =&gt; handleClick(event, res)}&gt;    
            {res.name } [&lt;span className = &#34;text-red-300&#34;&gt;{res.lng}&lt;/span&gt;,&lt;span className = &#34;text-green-800&#34;&gt;{res.lat}&lt;/span&gt;]    
        &lt;/Badge&gt;    
))  }  </div>
    </div>
  </div>
</div><p>实现效果如下图所示，输入内容后value更新，就会触发接口调用，出现候选地点供用户选择。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425221747.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="23-引入高德api">2.3 引入高德API</h3>
<p>由于上一步的地点不一定全，也由于限定了仅显示前10条，故而又引入高德api进行查询。（有局限：无法查询国外地名）</p>
<h4 id="231-高德开放平台">2.3.1 高德开放平台</h4>
<ol>
<li>新建应用</li>
</ol>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425232532.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<ol start="2">
<li>创建key（web端使用的key）</li>
</ol>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425232548.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<pre><code>使用该配置好的key就可以调用高德的接口了，每天有免费五千次的额度，对于一个小demo完全够用了。  
</code></pre>
<h4 id="232-调用高德接口">2.3.2 调用高德接口</h4>
<p>gaode.ts</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-10">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-10">
          <button id="copyButton-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-10" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-10">// 调用接口    
   
interface GaodeRes {    
   formatted_address: string,    
location: string  }    
     
export const getGeoCode = async (address: string) : Promise&lt;GaodeRes[]&gt; =&gt; {    
   let result = await fetch(`https://restapi.amap.com/v3/geocode/geo?address=${address}&amp;key={}`,{    
       headers: {    
           Accept: &#39;application/vnd.dpexpo.v1&#43;json&#39; //设置请求头    
       },    
       method: &#39;get&#39;,    
   })    
   let res = await result.json() //必须通过此方法才可返回数据    
return res.geocodes;  }  </div>
    </div>
  </div>
</div><p>该接口设定为点击查询按钮时才触发（节约次数）。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-11">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-11">
          <button id="copyButton-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-11" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-11">const [gaodeQueryResult,setGaodeQueryResult] = useState([]);  
  
const searchGeoCode = () =&gt;{    
    let queryResult = getGeoCode(value);    
    queryResult.then(res=&gt;{    
        if(res &amp;&amp; res.length &gt; 0){    
            let data  = res.map(item=&gt;{    
                return {    
                    &#34;name&#34;: item.formatted_address,    
                    &#34;lng&#34;: item.location.split(&#34;,&#34;)[0],    
                    &#34;lat&#34;: item.location.split(&#34;,&#34;)[1],    
                }    
            })    
    
            setGaodeQueryResult(data)    
        }else{    
            setGaodeQueryResult([])    
            toast.error(&#34;未能查询到该地点！您可以通过经纬度进行查询。&#34;)    
        }    
    })    
    
}  </div>
    </div>
  </div>
</div><p>同样将结果在模版代码中遍历展示即可。</p>
<h3 id="24-通过经纬度增加">2.4 通过经纬度增加</h3>
<p>如果查询不到，实现了点击“经纬度”展开，自行输入经纬度定义标记点的功能。值得一提的是使用了ShadUI的InputOTP组件，可以规定输入的位数和正则，我规定了只可以输入负号、小数点和数字。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425222652.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="25-标记点的删除和编辑">2.5 标记点的删除和编辑</h3>
<ol>
<li>悬停状态才显示编辑和删除按钮。</li>
</ol>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-12">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-12">
          <button id="copyButton-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-12" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-12">&lt;div className = &#34;flex gap-x-2 m-2 relative group&#34; ref={drag}    
      style={{    
          opacity: isDragging ? 0.5 : 1,    
      }}&gt;&lt;MapPin /&gt;{name}    
    &lt;button className = &#34;opacity-0 group-hover:opacity-100&#34; onClick={()=&gt;onOpen(id,name,lng,lat)} &gt;&lt;Pencil size = &#34;16&#34;&gt;&lt;/Pencil&gt; &lt;/button&gt;    
&lt;button className = &#34;opacity-0 group-hover:opacity-100&#34; onClick={onRemove} &gt;&lt;X size = &#34;16&#34;&gt;&lt;/X&gt; &lt;/button&gt;  &lt;/div&gt;  </div>
    </div>
  </div>
</div><ol start="2">
<li>定义useEditModal，控制编辑Modal的显示和方法。</li>
</ol>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-13">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-13">
          <button id="copyButton-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-13" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-13">import {create} from &#34;zustand&#34;;  const defaultValues = {    
    name: &#34;&#34;,    
    lng: &#34;&#34;,    
    lat: &#34;&#34;,    
id: &#34;&#34;  };    
    
    
interface IEditModal {    
    isOpen: boolean;    
    initialValues: typeof defaultValues;    
    onOpen: (id:string,name:string,lng: string,lat: string) =&gt;void;    
onClose: () =&gt; void;  }    
    
    
export const useEditModal = create&lt;IEditModal&gt;((set) =&gt;({    
    isOpen: false,    
    onOpen:(id:string,name,lng,lat)=&gt;set({    
        isOpen:true,    
        initialValues: {id,name,lng,lat}    
    }),    
    onClose: ()=&gt;set({    
        isOpen: false,    
        initialValues: defaultValues    
    }),    
    initialValues: defaultValues    
    
}))  </div>
    </div>
  </div>
</div><p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425223400.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="22-定义路线react-dnd">2.2 定义路线（react dnd）</h2>
<p>用户可以拖拽地点到虚线框内，形成路线。路线图的增删改同样适用zustand实现，不加赘述。拖动点到路线框中形成路线，使用了react drag and drop库完成。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-14">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> shell</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-14">
          <button id="copyButton-id-14">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-14" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-14">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="shell" data-theme="breeze" id="code-id-14">npm install react-dnd```  
引入react dnd后，将使用到拖拽的部分使用如下provider包裹。  
```html  
&lt;DndProvider backend={HTML5Backend}&gt;&lt;/DndProvider&gt;  </div>
    </div>
  </div>
</div><p>参考官方示例写法，<strong>拖</strong>部分：(location.tsx)</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-15">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-15">
          <button id="copyButton-id-15">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-15" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-15">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-15">import { useDrag } from &#39;react-dnd&#39;  
const [{ isDragging, }, drag, preview] = useDrag(    
    () =&gt; ({    
        type: ItemTypes.Location,    
        item: { name: name,id:id } ,    
    
        collect: (monitor) =&gt; ({    
            isDragging: !!monitor.isDragging(),    
        }),    
    }),    
[],  )  </div>
    </div>
  </div>
</div><p><strong>放</strong>部分：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-16">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-16">
          <button id="copyButton-id-16">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-16" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-16">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-16">import {Overlay, OverlayType} from &#34;@/app/components/Overlay&#34;;  
import { useDrop} from &#39;react-dnd&#39;  
const [{ isOver,canDrop }, drop] = useDrop(    
    () =&gt; ({    
        accept: ItemTypes.Location,    
        canDrop: (item:{name: string,id:string}) =&gt; {    
            if(!lineData || lineData.length == 0){    
                return true    
            }else{    
                return lineData[lineData.length - 1]?.id !== item.id    
            }    
        },    
        drop: (item:{name: string,id:string}) =&gt; {    
    
            dropLocation(id, item)    
    
        },    
    
        collect: (monitor) =&gt; ({    
            isOver: !!monitor.isOver(),    
            canDrop: !!monitor.canDrop(),    
        }),    
    }),    
[lineData],  )  </div>
    </div>
  </div>
</div><p>在模版代码中，增加了Overlay并根据是否可以放的状态，给不同的颜色。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-17">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-17">
          <button id="copyButton-id-17">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-17" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-17">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-17">{isOver &amp;&amp; !canDrop &amp;&amp; &lt;Overlay type={OverlayType.IllegalMoveHover} /&gt;}  {!isOver &amp;&amp; canDrop &amp;&amp; &lt;Overlay type={OverlayType.PossibleMove} /&gt;}  {isOver &amp;&amp; canDrop &amp;&amp; &lt;Overlay type={OverlayType.LegalMoveHover} /&gt;}  </div>
    </div>
  </div>
</div><p>Overlay.js</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-18">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-18">
          <button id="copyButton-id-18">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-18" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-18">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-18">export var OverlayType  ;(function (OverlayType) {    
    OverlayType[&#39;IllegalMoveHover&#39;] = &#39;Illegal&#39;    
    OverlayType[&#39;LegalMoveHover&#39;] = &#39;Legal&#39;    
OverlayType[&#39;PossibleMove&#39;] = &#39;Possible&#39;  })(OverlayType || (OverlayType = {}))  export const Overlay = ({ type }) =&gt; {    
    const color = getOverlayColor(type)    
    return (    
        &lt;div    
            className=&#34;overlay&#34;    
            role={type}    
            style={{    
                position: &#39;absolute&#39;,    
                top: 0,    
                left: 0,    
                height: &#39;100%&#39;,    
                width: &#39;100%&#39;,    
                zIndex: 1,    
                opacity: 0.5,    
                backgroundColor: color,    
            }}    
        /&gt;    
)  }  function getOverlayColor(type) {    
    switch (type) {    
        case OverlayType.IllegalMoveHover:    
            return &#39;red&#39;    
        case OverlayType.LegalMoveHover:    
            return &#39;green&#39;    
        case OverlayType.PossibleMove:    
            return &#39;#66CC66&#39;    
}  }  </div>
    </div>
  </div>
</div><p>当所拖拽的地点与路线合集中最后一个地点一样时，不允许拖拽进入。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425224705.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="3-地图渲染echartgl">3 地图渲染（echartgl）</h2>
<p>使用react-echart，并导入echart-gl，实现3D地图渲染。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-19">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-19">
          <button id="copyButton-id-19">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-19" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-19">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-19">&lt;ReactEcharts    
    option={options}    
    style={{ width: &#34;900px&#34;, height: &#34;800px&#34; }}    
    
&gt;&lt;/ReactEcharts&gt;  </div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-20">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-20">
          <button id="copyButton-id-20">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-20" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-20">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-20">  const [options,setOptions] = useState({    
    backgroundColor: &#34;#000&#34;,    
    globe: {    
        baseTexture:&#34;/earth1.jpg&#34;,    
        shading: &#34;lambert&#34;,    
        atmosphere: {    
// 不需要大气光圈去掉即    
            show: false,    
offset: 4, // 大气层光圈宽度    
        },    
        viewControl: {    
distance: 200, // 默认视角距离地球表面距离    
        },    
        light: {    
            ambient: {    
intensity: 1, // 全局的环境光设置    
            },    
            main: {    
intensity: 1, // 场景主光源设置    
            },    
        },    
    },    
    
})  </div>
    </div>
  </div>
</div><p>使用useState，根据lineCollection、location数据，动态地增、减地图options。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-21">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-21">
          <button id="copyButton-id-21">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-21" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-21">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-21">useEffect(() =&gt; {    
   let series = initSeries;    
    series[0].data = normalData(lines);    
    series[1].data = activeData(lines);    
    locations.forEach((item) =&gt; {    
        series[2].data.push({    
            name: item.name,    
            value: [item.lng,item.lat]    
        });    
    });    
    setOptions({    
        ...options,    
        ...customTheme,    
        series: series    
    })    
    
}, [locations,lineCollections,customTheme]);  </div>
    </div>
  </div>
</div><h3 id="31-地球换皮肤">3.1 地球换皮肤</h3>
<p>更换贴图，即可实现地球换皮肤。<br>
从网上搜罗一些地球贴图，放入public目录即可。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425225608.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-22">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-22">
          <button id="copyButton-id-22">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-22" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-22">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-22">const themeTopics  = [{    
    globe: {    
        baseTexture: &#34;/earth1.jpg&#34;,    
      
},  },    
    {    
      
        globe: {    
            baseTexture: &#34;/earth2.jpg&#34;,    
            
        },    
    },    
    {        
        globe: {    
            baseTexture: &#34;/earth3.jpg&#34;,         
        },    
    },    
    {         
        globe: {    
            baseTexture: &#34;/earth4.jpg&#34;,     
        },    
}  ]  </div>
    </div>
  </div>
</div><h1 id="部署">部署</h1>
<p>使用vercel作为部署托管。进入vercel并授权github项目，配置NextJS项目的构建命令。<br>
由于我在github的项目源码没有放在根目录，所以还需要设置root-directory。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425225806.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
将所使用到的环境变量放在environment-variables中。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425225959.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425225918.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
需要注意的是，Convex需要生成部署生产使用的URL和KEY，并配到环境变量中。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240425230255.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>这样就完成啦～</p>
<h1 id="源码地址">源码地址</h1>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL1lncmlhL3RyYXZlbC10cmFjZQ%3d%3d"
    
     target="_blank"
>
https://github.com/Ygria/travel-trace</a></p>
<h1 id="小结">小结</h1>
<p>写的第一个相对完整的react小项目，麻雀虽小五脏俱全。使用合理的开源组件让全栈变得非常容易。<br>
只使用到了react useState和useEffect两个hooks，已经感觉到了一定的理解门槛，与vue的将许多状态处理都放在内部封装好相比，react很多时候需要你自己来理解状态的依赖关系然后处理。react的tsx函数式写法的确很方便（比vue的defineComponents好多了……）。期待随着学习深入，了解到更多有趣的东西。</p>
<h1 id="参考">参考</h1>
<ol>
<li>echart assets<br>

<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL2Vjb21mZS9lY2hhcnRzLWdsL3RyZWUvbWFzdGVyL3Rlc3QvYXNzZXQ%3d"
    
     target="_blank"
>
https://github.com/ecomfe/echarts-gl/tree/master/test/asset</a></li>
<li>全流程开发参考：<br>

<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuY29kZXdpdGhhbnRvbmlvLmNvbS9jb3Vyc2VzLzg4ZWUzY2NjLWFmZDctNDE0Yi1hNjI2LWU1OWM5Mzg0N2Y2NS9jaGFwdGVycy9iMmZiMzE0My05NjgzLTQ2NWQtYWQ0OS0wNGY5MjAxMWExMDc%3d"
    
     target="_blank"
>
https://www.codewithantonio.com/courses/88ee3ccc-afd7-414b-a626-e59c93847f65/chapters/b2fb3143-9683-465d-ad49-04f92011a107</a></li>
<li>echarts+echarts-gl实现带有散点、路径的3d地球<br>

<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kb3dubG9hZC5jc2RuLm5ldC9kb3dubG9hZC93ZWl4aW5fNDU2NjkxNTYvODYyNDg1NDA%2feWRyZWZlcmVyPWFIUjBjSE02THk5aWJHOW5MbU56Wkc0dWJtVjBMM2RsYVhocGJsODBOVFkyT1RFMU5pOWhjblJwWTJ4bEwyUmxkR0ZwYkhNdk1USTFPVE15TmpBeA%3d%3d"
    
     target="_blank"
>
https://download.csdn.net/download/weixin_45669156/86248540?ydreferer=aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTY2OTE1Ni9hcnRpY2xlL2RldGFpbHMvMTI1OTMyNjAx</a></li>
</ol>
]]></content:encoded>
    </item>
    
    <item>
      <title>obsidian图床配置（Markdown格式笔记均适用）</title>
      <link>https://ygria.site/obsidian-picbed/</link>
      <pubDate>Wed, 17 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/obsidian-picbed/</guid>
      <description>&lt;p&gt;参考： 
&lt;a 
    
        href=&#34;https://ygria.site/tiaozhuan?target=aHR0cHM6Ly9ibG9nLm1pbmdvOTkudG9wL2FydGljbGUvcGljYmVkL3BpY2dv&#34;
    
     target=&#34;_blank&#34;
&gt;
https://blog.mingo99.top/article/picbed/picgo&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;1--在github中新建私人仓库并生成token&#34;&gt;1.  在github中新建私人仓库，并生成token&lt;/h2&gt;
&lt;p&gt;新建仓库（记住&lt;strong&gt;仓库名称①&lt;/strong&gt;）&lt;br&gt;
&lt;img src=&#34;https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417110652.png&#34; alt=&#34;image.png&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;br&gt;
Github 右上角-个人头像-setting&lt;br&gt;
&lt;img src=&#34;https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417110804.png&#34; alt=&#34;image.png&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;
&lt;p&gt;点击”developer settings “-”Token（classic）“&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417110652.png" alt="obsidian图床配置（Markdown格式笔记均适用）" /><p>参考： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9ibG9nLm1pbmdvOTkudG9wL2FydGljbGUvcGljYmVkL3BpY2dv"
    
     target="_blank"
>
https://blog.mingo99.top/article/picbed/picgo</a></p>
<h2 id="1--在github中新建私人仓库并生成token">1.  在github中新建私人仓库，并生成token</h2>
<p>新建仓库（记住<strong>仓库名称①</strong>）<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417110652.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
Github 右上角-个人头像-setting<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417110804.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>点击”developer settings “-”Token（classic）“</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417110834.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
选择无过期时间，并给repo权限。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417111350.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
拉到最下，生成token，并复制<strong>token值②</strong></p>
<h2 id="2-下载picgo并配置">2. 下载Picgo并配置</h2>
<p>访问项目Github网址，在<strong>下载安装</strong>中找到对应的下载地址，安装系统安装包。<br>

<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL01vbHVuZXJmaW5uL1BpY0dvP3RhYj1yZWFkbWUtb3YtZmlsZQ%3d%3d"
    
     target="_blank"
>
GitHub - Molunerfinn/PicGo: :rocket:A simple &amp; beautiful tool for pictures uploading built by vue-cli-electron-builder</a></p>
<p>仓库名：github个人账户名 + 仓库名（上一步中的①）<br>
分支名：仓库中存在的分支名，默认为main<br>
token：上一步中的②</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417103934.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="3-obsidian插件配置---下载社区插件-image-auto-upload-plugin-社区插件">3. Obsidian插件配置 - 下载社区插件 Image auto upload Plugin （社区插件）</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417104408.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>下载后无需配置，打开插件。</p>
<h2 id="4使用">4.使用</h2>
<p>使用起来很简单，直接向obsidian中粘贴文件，会自动上传到图床，并插入图片链接。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>黄山两日游</title>
      <link>https://ygria.site/travel-to-huangshan/</link>
      <pubDate>Mon, 01 Aug 2022 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/travel-to-huangshan/</guid>
      <description>&lt;p&gt;续前面的千岛湖游记，结束了30、31日千岛湖的两日行程，准备转战黄山。计划行程是一日宏村，一日黄山。&lt;/p&gt;
&lt;h1 id=&#34;第一日-宏村&#34;&gt;第一日 ：宏村&lt;/h1&gt;
&lt;p&gt;先从千岛湖坐穿梭大巴至千岛湖车站，再坐半个多小时高铁，12:45到达黄山北站。  从 黄山北站可以坐旅游大巴去宏村景区，可以去窗口买票，25r一人。每十分钟都有班次，会提前半个小时左右检票，建议预留一些时间。我们坐的是13:55的班次。  到达宏村需要一个半小时车程，沿路是盘山公路，到了已经是下午三点半了，稍事休整之后就进入景区～  宏村的门票三天都有效，52r/人，还是很实惠的。门口和景区内都有很多小贩卖一种叫“黑莓”的水果，吃起来和葡萄一模一样……到现在不知道是什么水果。门口买二十一斤，宏村内有十五的……（后注：应该是骗人的水果，就是打蜡的葡萄）&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="黄山两日游" /><p>续前面的千岛湖游记，结束了30、31日千岛湖的两日行程，准备转战黄山。计划行程是一日宏村，一日黄山。</p>
<h1 id="第一日-宏村">第一日 ：宏村</h1>
<p>先从千岛湖坐穿梭大巴至千岛湖车站，再坐半个多小时高铁，12:45到达黄山北站。  从 黄山北站可以坐旅游大巴去宏村景区，可以去窗口买票，25r一人。每十分钟都有班次，会提前半个小时左右检票，建议预留一些时间。我们坐的是13:55的班次。  到达宏村需要一个半小时车程，沿路是盘山公路，到了已经是下午三点半了，稍事休整之后就进入景区～  宏村的门票三天都有效，52r/人，还是很实惠的。门口和景区内都有很多小贩卖一种叫“黑莓”的水果，吃起来和葡萄一模一样……到现在不知道是什么水果。门口买二十一斤，宏村内有十五的……（后注：应该是骗人的水果，就是打蜡的葡萄）</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417212302.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  村内都是徽派建筑小村落，走走逛逛，很悠闲快乐，也有很多地方特色小吃可以品尝，乌米糕、 黄山烧饼、毛豆腐等，价格都挺低的，不妨一试。 <br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417213118.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  宏村中间有一棵 大树，还有一片湖叫“月沼”，里面有很多荷花。正值盛夏，荷花盛开，有许多美术生在岸边取景绘画，从他们的画布上看景色也挺有趣。  景区内人有点多，景色行来看来觉得平常的地方，拍照特别出片，很神奇。 <br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417213259.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417213347.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417213406.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  我们在景区内吃了饭菜，这边有很多徽菜馆，可以随意选一家评价高的，菜色挺地道。吃到了很好吃的臭鳜鱼、土鸡汤和鲜笋～ <br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417213610.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>在宏村内的文创店还留下了明信片，内容就不放了哈哈。来来往往的客人都留下了字迹，看一看也很有意思～ <br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417213553.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  离开的时候已是暮色四合，宏村内亮起了灯笼，夜景挺好看。第二天要去 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cubWFmZW5nd28uY24vdHJhdmVsLXNjZW5pYy1zcG90L21hZmVuZ3dvLzEwNDQwLmh0bWw%3d"
    
     target="_blank"
>
黄山</a> ，得很早起床，所以就早点回酒店休息了～</p>
<h1 id="第二日黄山">第二日：黄山</h1>
<p>从宏村坐6:50的大巴，四十分钟左右即可到达黄山南面的汤口镇，这里离黄山景区的南大门很近。因为忘了要提前一天订票，所以选择散客拼团，比自己买稍微贵一些，两个人446r（不含索道）</p>
<p>我们的路线是：玉屏索道上-迎客松-一线天-天海-光明顶-飞来石-太平索道下。</p>
<p>全程耗时五六个小时，一共步行约八公里，没有爬莲花峰，总体不算太累 <br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417213944.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>坐索道可以直接从网上买票，刷身份证进，很方便。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417213956.png" alt="索道" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417214119.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417214126.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  上山索道挺刺激的，恐高的话可能不太敢坐，哈哈。  从索道下来爬一段路就到了迎客松，作为 安徽人很是熟悉～（ 安徽卫视的logo） <br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417214205.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  沿路很多导游，可以听一听他们的解说，从不同的角度去观赏奇石，形状各异，别有意趣。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417214232.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417214544.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  向着光明顶进发，中间一线天这段道路陡峭，耗费体力较多。在天海吃了辛辛苦苦背上来的午餐，很快乐。（因为是男朋友背的，所以我更快乐。） <br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417214600.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  光明顶：一个大球。 <br>
一路上都听到一些很累的游客问还有多远还有多远 <br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417214746.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  <img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417214752.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  <img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417214756.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  ⚠️upload failed, check dev console  <img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417214805.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" />  风景还是很好看的～  两三点就下山啦，五点多到达黄山北站，坐车回合肥 。  愉快的四天旅行到此结束～</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>千岛湖两日游</title>
      <link>https://ygria.site/travel-2-qiandaohu/</link>
      <pubDate>Sat, 30 Jul 2022 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/travel-2-qiandaohu/</guid>
      <description>&lt;p&gt;趁着今年的高温假，和女友第一次正式的外出游玩。大致行程是两日千岛湖、两日黄山。&lt;/p&gt;
&lt;h1 id=&#34;第一日到达千岛湖天屿山夕阳&#34;&gt;第一日：到达千岛湖、天屿山夕阳&lt;/h1&gt;
&lt;p&gt;早上十一点从合肥出发，高铁达到千岛湖高铁站。&lt;/p&gt;
&lt;p&gt;可以选择从高铁站做酒店穿梭巴士，¥15一位，可以直达至千岛湖中心码头的沿途酒店，还是蛮方便的。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="千岛湖两日游" /><p>趁着今年的高温假，和女友第一次正式的外出游玩。大致行程是两日千岛湖、两日黄山。</p>
<h1 id="第一日到达千岛湖天屿山夕阳">第一日：到达千岛湖、天屿山夕阳</h1>
<p>早上十一点从合肥出发，高铁达到千岛湖高铁站。</p>
<p>可以选择从高铁站做酒店穿梭巴士，¥15一位，可以直达至千岛湖中心码头的沿途酒店，还是蛮方便的。</p>
<p>三点整至酒店休息了会，一下子睡到了四点多…考虑再三，决定先去旁边的天屿山看夕阳。</p>
<p>这里面有两点比较坑：一是上山的你可以选择买车票，否则你得沿着汽车的柏油路爬上去，大概是3.3km单程，没有登山路，所以走起来会感觉很累。二是车票17:30之前就停售了……没办法，17:35我们才到山脚下知道这个消息，结果，一上一下，累死<br>
山上的风景还是很美的，约六点半的时候日落，能看到中心湖区这边大部分的景观。夕阳很好看！<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215435.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215440.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215444.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="第二日中心湖区西南湖区文渊狮城">第二日：中心湖区、西南湖区、文渊狮城</h1>
<p>早上八点起床，在酒店吃完早餐。 <br>
今日是三个目标：必坐船、必吃鱼头、必做核酸！ <br>
为了第一个必做船，报了个坐船观光一日团，流程如下： <br>
10:30夜游码头发船，航行约17min至月光岛，岛上游览约四十分钟。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215505.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215525.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215520.png" alt="" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215632.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
11:40月光岛第二码头出发，航行约25min至梅峰岛，岛上游览约30分钟。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215646.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215654.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215701.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
游览完成后可以选择船上用餐，也可以选择在梅峰岛上用餐，或者吃点自带的餐点。 <br>
约13:40从梅峰岛出发，一直航行两个小时，中途会有个水下狮城的介绍，到达文渊狮城。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215714.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215720.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215726.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215737.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
狮城游览40min。最后乘坐大巴返回千岛湖夜游码头。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215753.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215804.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215808.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215812.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
做核酸，吃鱼头。<br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215822.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417215832.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /><br>
最后总结一下费用（单人）： <br>
游船加门票费用¥350  升舱（从一楼到二楼，二楼可以在甲板上转悠，多杯绿茶）¥60【没有必要】 <br>
月光岛上面有自费项目两个：一个滑索和一个走状元桥【没有新意…】 <br>
梅峰岛上有自费项目两个：缆车和滑草【都不是很有必要，除非膝盖不好，大约能节省8-10分钟的山路】 <br>
船上吃饭是有40的快餐或者单点，单点一个鱼头¥280 <br>
说下行程，本着坐船游湖来的，爬两个山嘛总体也不是很累，在船上的话喝点茶水，吃点零食，和对象两人很惬意。 <br>
整体感观还是很不错的，不过这些繁繁杂杂的二次收费项目很是让人不爽。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>ElasticSearch简单入门和SpringBoot集成</title>
      <link>https://ygria.site/elasticsearch%E7%AE%80%E5%8D%95%E5%85%A5%E9%97%A8%E5%92%8Cspringboot%E9%9B%86%E6%88%90/</link>
      <pubDate>Sat, 07 May 2022 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/elasticsearch%E7%AE%80%E5%8D%95%E5%85%A5%E9%97%A8%E5%92%8Cspringboot%E9%9B%86%E6%88%90/</guid>
      <description>&lt;h1 id=&#34;背景&#34;&gt;背景&lt;/h1&gt;
&lt;p&gt;项目中使用第三方网关系统，该网关使用&lt;code&gt;ElasticSearch&lt;/code&gt;进行服务访问日志记录。
为充分利用该网关功能，并在数据基础上实现计数、计费功能，需对&lt;code&gt;ElasticSearch&lt;/code&gt;进行快速学习，并使用Java代码集成，从而实现项目所需要的运营功能。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="ElasticSearch简单入门和SpringBoot集成" /><h1 id="背景">背景</h1>
<p>项目中使用第三方网关系统，该网关使用<code>ElasticSearch</code>进行服务访问日志记录。
为充分利用该网关功能，并在数据基础上实现计数、计费功能，需对<code>ElasticSearch</code>进行快速学习，并使用Java代码集成，从而实现项目所需要的运营功能。</p>
<h1 id="elasticsearch基础知识学习">ElasticSearch基础知识学习</h1>
<p>为快速建立起对ES印象，可按下表进行概念映射：
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417143109.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="kibana搭建">Kibana搭建</h1>
<p>为对ES中数据可视化，在服务器上进行<code>Kibana</code>搭建。</p>
<h3 id="1下载指定压缩包">1、下载指定压缩包</h3>
<p>地址： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuZWxhc3RpYy5jby9jbi9kb3dubG9hZHMva2liYW5h"
    
     target="_blank"
>
https://www.elastic.co/cn/downloads/kibana</a>
传至服务器上，解压即可。注意要与ES版本相对应。
<code>kibana-6.8.23-linux-x86_64.tar.gz</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> bash</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="bash" data-theme="breeze" id="code-id-1"># 解压缩
tar -zxvf kibana-6.8.23-linux-x86_64.tar.gz
# 进入配置文件，进行配置的编写
vi /config/kibana.yml</div>
    </div>
  </div>
</div><h3 id="2修改配置并启动">2、修改配置并启动</h3>
<p>编辑配置文件如下：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> yaml</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="yaml" data-theme="breeze" id="code-id-2"># 指定ElasticSearch实例地址
elasticsearch.hosts: [&#34;http://localhost:9200&#34;]
# 指定允许访问服务地址，规定为0.0.0.0，为允许所有ip访问
server.host: &#34;0.0.0.0&#34;
 # 指定es的用户名
elasticsearch.username: &#34;username&#34;
 # 指定es的密码
elasticsearch.password: &#34;password&#34;
# 指定kibana的语言为中文
i18n.locale: &#34;zh-CN&#34;</div>
    </div>
  </div>
</div><p>进入bin目录并启动：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> shell</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="shell" data-theme="breeze" id="code-id-3">nohup ./kibana &amp;
tailf -n nohup.out</div>
    </div>
  </div>
</div><p>访问<code>ip:5601</code>端口即可打开页面访问:</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417143159.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="3页面设置索引规则">3、页面设置索引规则</h3>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417143214.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>使用索引模式，建立通配索引后，可以在discover tab下查看到内容。</p>
<h3 id="4使用开发工具进行查询语句调试">4、使用开发工具，进行查询语句调试</h3>
<p>对于需要调试的DSL语句，可以使用开发工具进行请求的调试。</p>
<h1 id="springboot集成">SpringBoot集成</h1>
<h2 id="客户端集成带账号密码">客户端集成（带账号、密码）</h2>
<p>1、将密码、地址等配到SpringBoot项目的配置文件中;
2、重写restHighLevelClient
代码清单如下：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-4">@Configuration
@Log4j2
public class ElasticSearchConfiguration {


    @Value(&#34;${elasticsearch.host}&#34;)
    private String elasticSearchHost;

    @Value(&#34;${elasticsearch.port}&#34;)
    private String elasticSearchPort;

    @Value(&#34;${elasticsearch.username}&#34;)
    private String elasticSearchUser;

    @Value(&#34;${elasticsearch.password}&#34;)
    private String elasticSearchPass;

    @Bean(name = &#34;restHighLevelClient&#34;)
    public RestHighLevelClient restHighLevelClient() {
        List&lt;HttpHost&gt; hostLists = new ArrayList&lt;&gt;();
        hostLists.add(new HttpHost(elasticSearchHost, Integer.parseInt(elasticSearchPort), &#34;http&#34;));
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(elasticSearchUser, elasticSearchPass));

        // 转换成 HttpHost 数组
        HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
        // 构建连接对象
        RestClientBuilder builder = RestClient.builder(httpHost);
        // 异步连接延时配置
        builder.setRequestConfigCallback(requestConfigBuilder -&gt; {
            requestConfigBuilder.setConnectTimeout(5000);
            requestConfigBuilder.setSocketTimeout(5000);
            requestConfigBuilder.setConnectionRequestTimeout(5000);
            return requestConfigBuilder;
        });
        // 异步连接数配置
        builder.setHttpClientConfigCallback(httpClientBuilder -&gt; {
            httpClientBuilder.setMaxConnTotal(100);
            httpClientBuilder.setMaxConnPerRoute(100);
            return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
        });
        return new RestHighLevelClient(builder);
    }
}</div>
    </div>
  </div>
</div><h1 id="聚合查询示例">聚合查询示例</h1>
<p>业务需求：
1、按照查询条件，查询某服务的访问次数
2、查询该服务的请求体总长度，用于计量计费
给出单元测试代码：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-5">    @Resource
    RestHighLevelClient client;
    @Test
    public void testQuery() {
        SearchRequest searchRequest = new SearchRequest(&#34;sg-access-*&#34;);
//构建查询
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        //按时间聚合，求TX的和
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchPhraseQuery(&#34;paasid&#34;, &#34;111&#34;))
                .must(QueryBuilders.matchQuery(&#34;srvid&#34;, &#34;111&#34;));

        AggregationBuilder aggregation = AggregationBuilders.filter(&#34;term&#34;, queryBuilder);
        aggregation.subAggregation(AggregationBuilders.sum(&#34;reqLengthSum&#34;).field(&#34;reqLength&#34;));

        sourceBuilder.aggregation(aggregation);


        searchRequest.source(sourceBuilder);
        //发送请求
        SearchResponse searchResponse = null;
        try {
            searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            Aggregations aggFilter = searchResponse.getAggregations();
            if (aggFilter != null &amp;&amp; aggFilter.get(&#34;term&#34;) != null) {
                ParsedFilter parsedFilter = aggFilter.get(&#34;term&#34;);
                System.out.println(&#34;查询出请求的次数：&#34; &#43; parsedFilter.getDocCount());
                ParsedSum sum = (ParsedSum) parsedFilter.getAggregations().getAsMap().get(&#34;reqLengthSum&#34;);
                System.out.println(&#34;请求体的总大小 ：&#34; &#43;  sum.getValue());
            }

        } catch (IOException e) {
            System.out.println(searchResponse);
        }
    }</div>
    </div>
  </div>
</div><p>运行结果如下：
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417143241.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>可以看出，我们可以通过Java代码，对ES的数据进行条件查询，并对索引进行通配匹配查询，并使用聚合方法进行聚合运算。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>橘子说</title>
      <link>https://ygria.site/orange-said/</link>
      <pubDate>Fri, 08 Apr 2022 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/orange-said/</guid>
      <description>胡言乱语</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="橘子说" /><p>一直以来我都喜欢简单而又可以稳定供应的食物。
比如橘子这样一整个冬天所有水果店都有卖的水果，气味强烈、酸甜，颜色鲜艳得像一簇簇小火苗，可以迅速刺激到我的味蕾，让我感到欣快。
或者是随处可见的包子铺卖的肉包子，热气腾腾地蒸出来，五块钱以内就可以买到，放在手里、烘热手掌，只吃一个就是一顿很好的早餐。
它们是如此安全，不要求太高的付出，又是如此触手可及。在我忍耐和等待的时候，它们就像是安慰剂一样让我放松。
所以就算怕冷，我也会喜欢冬天。
一直以来我都讨厌计算不出期望的随机。
没办法做出合适的期望让我感到焦虑。不希望有不切实际的期望，因为很害怕失望。我想要的是可控范围内的变化，可以预估出的损失，
就算已经给自己留了足够多的退路，我也会害怕充满了随机的未来。
可是未来正是一个个现在的瞬间积累而成的，不是吗？
给我的美好的现在瞬间够多了，那么就算害怕，我也会期待着未来。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Java | 记录基于CAS登录模块的几个安全问题的解决</title>
      <link>https://ygria.site/cas-problem/</link>
      <pubDate>Sun, 12 Dec 2021 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/cas-problem/</guid>
      <description>&lt;p&gt;手头的项目的登录模块，基本都是集成了部门内封装出的基于CAS的中心鉴权组件，在安全扫描中暴露了一些问题，有些是因为没有合理的使用这一开源框架导致的，有的是通用的问题，在此记录问题和解决方案。
1、密码明文传输问题
2、页面无验证码、无登录防抖，易被暴力破解问题
3、开放重定向问题&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Java | 记录基于CAS登录模块的几个安全问题的解决" /><p>手头的项目的登录模块，基本都是集成了部门内封装出的基于CAS的中心鉴权组件，在安全扫描中暴露了一些问题，有些是因为没有合理的使用这一开源框架导致的，有的是通用的问题，在此记录问题和解决方案。
1、密码明文传输问题
2、页面无验证码、无登录防抖，易被暴力破解问题
3、开放重定向问题</p>
<h1 id="密码明文传输">密码明文传输</h1>
<h2 id="问题描述">问题描述</h2>
<p>用户输入的密码，虽然在页面的输入框中显示为“*****”，却在接口层面通过明文传输，易被抓包工具捕获。</p>
<h2 id="解决思路">解决思路</h2>
<p>使用<code>RSA</code>非对称加密，前端对密码进行加密，后端解密后，再与数据库存储的凭证进行比对。</p>
<h2 id="代码实现">代码实现</h2>
<h3 id="前端">前端</h3>
<p>前端是在CAS项目中的casLoginView中进行改造，使用JavaScript (JQuery)  + HTML + CSS；
1、 改造登录结构代码 - 将原有的登录表单中的按钮进行隐藏，增加一个用于点击的登录按钮；</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-1">  &lt;form id = &#34;fm1&#34;&gt;
    &lt;input id=&#34;username&#34; name=&#34;username&#34; class=&#34;input_user_name&#34;
                           tabindex=&#34;1&#34; placeholder=&#34;请输入用户名称&#34; accesskey=&#34;n&#34; type=&#34;text&#34; value=&#34;&#34;
                           maxlength=&#34;30&#34; autocomplete=&#34;false&#34;&gt;
    &lt;input id=&#34;password&#34; name=&#34;&#34; class=&#34;input_password&#34; tabindex=&#34;2&#34;
                           placeholder=&#34;请输入登录密码&#34; accesskey=&#34;p&#34; type=&#34;password&#34; value=&#34;&#34;
                           maxlength=&#34;28&#34; autocomplete=&#34;off&#34;&gt;
     &lt;input id=&#34;login_normal1&#34; class=&#34;login-button&#34; name=&#34;submit&#34;
               accesskey=&#34;l&#34;
               value=&#34;登 录&#34; tabindex=&#34;3&#34; type=&#34;button&#34;&gt;
     &lt;input id=&#34;login_normal&#34; style=&#34;display: none&#34;
               name=&#34;submit&#34; accesskey=&#34;l&#34;
               value=&#34;登 录&#34; tabindex=&#34;3&#34; type=&#34;submit&#34;&gt;
&lt;/form&gt;</div>
    </div>
  </div>
</div><p>注意，需要将原有的密码输入框input的name属性置为空字符串，或删去该属性，否则提交时会提交一个密文和一个明文。
2、引入用于加密的JS
下载JS，放在common/js目录下，并在页面引入。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-2">&lt;script src=&#34;common/js/jsencrypt.min.js&#34; type=&#34;text/javascript&#34;&gt;&lt;/script&gt;</div>
    </div>
  </div>
</div><p>3、登录逻辑改造
原先登录是触发了表单提交后，浏览器自带的<code>post</code>事件，将原有按钮进行隐藏，监听显示出来的登录按钮的点击事件。
可以使用回车监听方法，禁用原有回车登录方法，或也调用加密密码后提交的逻辑。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-3">&lt;script type=&#34;text/javascript&#34;&gt;
    $(document).ready(function(){
        if (window.top.location !== self.location) {
            top.location.replace(self.location);
        }
        $(&#34;#login_normal1&#34;).click( function() {
            if(!checkSubmit()){
                return
            }
            // 登陆验证之前，对密码进行加密处理
            const password = encrypt($(&#39;#password&#39;).val())
            $(&#39;#login_normal&#39;)
                .attr(&#39;name&#39;, &#34;password&#34;)
                .attr(&#39;value&#39;, password)
            $(&#39;#login_normal&#39;).click()

        });
    });
    function encrypt(password) {
        var encrypt = new JSEncrypt()
          // 此处需要填入自己生成的密钥。
        encrypt.setPublicKey(``);
        return encrypt.encrypt(password);
    }
    function checkSubmit() {
        var username = $(&#34;#username&#34;).val().trim();
        var password = $(&#34;#password&#34;).val().trim();

        if (username == &#39;&#39;||username==null) {
            $(&#39;#username&#39;).focus();
            $(&#39;#msg1&#39;).html(&#39;请输入用户名！&#39;);
            return false;
        }
        if (password == &#39;&#39;||password==null) {
            $(&#39;#password&#39;).focus();
            $(&#39;#msg1&#39;).html(&#39;请输入密码！&#39;);
            return false;
        }
        return true;

    }
}
&lt;/script&gt;</div>
    </div>
  </div>
</div><h3 id="后端">后端</h3>
<p>后端仅需要在验证密码之前，对加密后的密码进行解密即可。
下面给出解密方法示例：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-4">private String decrypt(String password) throws Exception {
 BASE64Decoder base64Decoder = new BASE64Decoder();
    byte[] keyByte = base64Decoder.decodeBuffer(&#34;);
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyByte);
    KeyFactory keyFactory = KeyFactory.getInstance(&#34;RSA&#34;);
    RSAPrivateKey privateKey = (RSAPrivateKey)keyFactory.generatePrivate(keySpec);
    byte[] dataByte = base64Decoder.decodeBuffer(password);
    Cipher cipher = Cipher.getInstance(&#34;RSA&#34;);
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] result = cipher.doFinal(dataByte);
    return new String(result);
}</div>
    </div>
  </div>
</div><h1 id="添加验证码">添加验证码</h1>
<h2 id="后端改造">后端改造</h2>
<p>集成验证码，对于后端来说没什么难度。引入<code>easy-captcha</code>或其他依赖；</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> xml</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="xml" data-theme="breeze" id="code-id-5">&lt;dependency&gt;
   &lt;groupId&gt;com.github.whvcse&lt;/groupId&gt;
    &lt;artifactId&gt;easy-captcha&lt;/artifactId&gt;
    &lt;version&gt;1.6.2&lt;/version&gt;
&lt;/dependency&gt;</div>
    </div>
  </div>
</div><p>接口暴露：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-6">import com.wf.captcha.utils.CaptchaUtil;

@GetMapping(&#34;/capcha/code&#34;)
public void captchaCode(HttpServletRequest request,HttpServletResponse response) throws Exception {
    CaptchaUtil.out(request, response);
}

@GetMapping(&#34;/captcha/check&#34;)
public ResponseEntity&lt;String&gt; captchaCode(@RequestParam String code, HttpServletRequest request) throws Exception {
  boolean success = false;
  if (CaptchaUtil.ver(code, request)) {
      success = true;
  }
  CaptchaUtil.clear(request);
  String successStr =  success ? &#34;ok&#34; : &#34;error&#34;;
  System.out.println(&#34;验证码验证结果 = &#34;  &#43; successStr);
  return ResponseEntity.ok(successStr);
}</div>
    </div>
  </div>
</div><h2 id="前端改造">前端改造</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417143825.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>1、对前端登录页面稍加改造；可以进行样式的自定义适配。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-7"> &lt;div class=&#34;input-p captcha&#34;&gt;
   &lt;div class=&#34;input__prepend captcha&#34;&gt;&lt;/div&gt;
    &lt;input id=&#34;captcha&#34; name=&#34;&#34;
           class=&#34;captcha&#34; tabindex=&#34;3&#34;
           placeholder=&#34;请输入验证码&#34; accesskey=&#34;p&#34;
           maxlength=&#34;4&#34; autocomplete=&#34;off&#34;&gt;
    &lt;img id=&#34;cimg&#34;
         src=&#34;&#34;
         title=&#34;看不清？点击更换另一个。&#34; /&gt;
&lt;/div&gt;</div>
    </div>
  </div>
</div><p>2、增加进入页面后，请求验证码、校验验证码、点击更换验证码等交互逻辑</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-8">&lt;script type=&#34;text/javascript&#34;&gt;
    $(document).ready(function(){
        if (window.top.location !== self.location) {
            top.location.replace(self.location);
        }
        $(&#34;#login_normal1&#34;).click( function() {
            if(!checkSubmit()){
                return
            }
            // 验证码验证失败
            if(!validateCaptcha()){
                return;
            }
            // 登陆验证之前，对密码进行加密处理
            const password = encrypt($(&#39;#password&#39;).val())
            $(&#39;#login_normal&#39;)
                .attr(&#39;name&#39;, &#34;password&#34;)
                .attr(&#39;value&#39;, password)
            $(&#39;#login_normal&#39;).click()

        });

        $(&#34;#cimg&#34;).click(function(){
            initCaptcha()
        })
        initCaptcha();
    });

    //
    function initCaptcha(){
        var _codeImage = $(&#39;#cimg&#39;);
        var rand = Math.random();
        var url = &#39;/captcha/code?rand=&#39; &#43; rand;
        _codeImage.attr(&#34;src&#34;, url);
    }
    // 对验证码进行验证
    function validateCaptcha(){
        var isValid = false
        $.ajax({
            url: &#39;/captcha/check?code=&#39; &#43; $(&#39;#captcha&#39;).val(),
            type: &#39;GET&#39;,
            async:false,
            success: function(data) {
                if (data) {
                    if(data === &#39;ok&#39;){
                        isValid =  true
                    }else {
                        $(&#39;#msg1&#39;).html(&#39;验证码输入错误，请重新输入！&#39;);
                        //密码验证失败后，重新请求验证码
                        initCaptcha()
                        isValid =  false
                    }
                }
            }
        })
        return isValid
    }
    function checkSubmit() {
        var username = $(&#34;#username&#34;).val().trim();
        var password = $(&#34;#password&#34;).val().trim();
        var captcha = $(&#34;#captcha&#34;).val().trim();
        if (username &gt; &#39;&#39; &amp;&amp; password &gt; &#39;&#39; &amp;&amp; captcha &gt; &#39;&#39;) {
            $(&#39;#msg1&#39;).html(&#34;&#34;);
            return true;
        }
        else {
            if(!username || !password){
                $(&#39;#msg1&#39;).html(&#39;请输入您的用户名和密码&#39;);
            }else {
                $(&#39;#msg1&#39;).html(&#39;请输入验证码&#39;);
            }

            return false;
        }
    }
&lt;/script&gt;</div>
    </div>
  </div>
</div><p>可以看到，在用户触发登录动作时，先校验了验证码是否合法，再去调用后台登录接口，这样可以一定程度上避免被暴力破解。</p>
<h1 id="开放重定向问题">开放重定向问题</h1>



<blockquote>
    <p>开放重定向问题的定义：https://www.wangan.com/articles/1132</p>
</blockquote>
<p>简而言之，就是在我们服务的登录、登出地址中，将原本的服务地址${MY_SERVICE}替换成其他，也可以被CAS后端转发跳转。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-9">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> properties</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="properties" data-theme="breeze" id="code-id-9">http://${CAS}/cas/login?service=http://${MY_SERVICE}
http://${CAS}/cas/logout?service=http://${MY_SERVICE}</div>
    </div>
  </div>
</div><p>而经过排除和阅读CAS文档，发现是在我们配置认证客户端定义JSON时，将所有的serviceId都配成可以通配所有网址导致的！</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-10">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> json</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-10">
          <button id="copyButton-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-10" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="json" data-theme="breeze" id="code-id-10">{
  &#34;@class&#34; : &#34;org.apereo.cas.services.RegexRegisteredService&#34;, 
  &#34;serviceId&#34; : &#34;^(https|imaps|http)://.*&#34;,
  &#34;name&#34; : &#34;&#34;,
  &#34;id&#34; : 1000,
  &#34;description&#34; : &#34;&#34;,
  &#34;evaluationOrder&#34; : 1,
  &#34;theme&#34;: &#34;&#34;
}</div>
    </div>
  </div>
</div><p>容易得出，<code>serviceId</code>的值是一个正则表达式，仅当能匹配到正则时，才会进行跳转，不然会显示出：
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417143906.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>根据官网的建议，应该将<code>serviceId</code>配置得越精确越好，配置成具体的网址，就能避免重定向到其他网站的问题了。
那么问题又来了，在进行部署之前，我们可能并不知道这个网址。如果已经进行了代码打包，就改不了这个配好的网址了，有什么办法从外部数据源或配置文件中读取呢？这样更改了其他服务的部署地址，CAS不需要重新打包，如果可以读取到动态的数据源，CAS组件甚至不用重启。
查阅官网：https://apereo.github.io/cas/5.3.x/planning/Getting-Started.html
关于Service的管理中，我们可以看到多种存储方案：
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417143925.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>借助配置 + 内存管理方案，可以实现服务的动态配置。
给出我的实现代码：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-11">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-11">
          <button id="copyButton-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-11" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-11">    @Value(&#34;${supportServiceId}&#34;)
    private String supportServiceId;


    @Bean
    public List inMemoryRegisteredServices() {
        final List services = new ArrayList&lt;&gt;();
        final RegexRegisteredService service = new RegexRegisteredService();
        service.setServiceId(supportServiceId);
        service.setName(&#34;moss&#34;);
        service.setId(1L);
        service.setTheme(&#34;moss&#34;);
        service.setDescription(&#34;MOSS2.0语义化系统&#34;);
        service.setEvaluationOrder(1);
        services.add(service);
        return services;
    }</div>
    </div>
  </div>
</div><p>这样就可以从CAS的服务配置中读取，当然也可以配置一个服务列表。需要将原有的<code>JSON</code>配置删去。</p>
<h1 id="小结">小结</h1>
<p>分享了几个改造的方法，需要在现有的框架下进行尽量小的改动，后续可以考虑提取成通用的JS代码，降低其他服务的改造成本。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>CICD Jenkins Shared Libraries &amp; Http-request插件使用</title>
      <link>https://ygria.site/how-to-use-jenkins2/</link>
      <pubDate>Tue, 23 Nov 2021 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/how-to-use-jenkins2/</guid>
      <description>&lt;blockquote&gt;
    &lt;p&gt;前篇：
1、Jenkins的搭建和简介：https://www.jianshu.com/p/ca4886e11720
2、Jenkins Gitlab集成，使用WebHook触发构建：https://www.jianshu.com/p/ca4886e11720&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="CICD Jenkins Shared Libraries & Http-request插件使用" />


<blockquote>
    <p>前篇：
1、Jenkins的搭建和简介：https://www.jianshu.com/p/ca4886e11720
2、Jenkins Gitlab集成，使用WebHook触发构建：https://www.jianshu.com/p/ca4886e11720</p>
</blockquote>
<p>之前我搭建的开发环境的Jenkins，经过一年多时间的积累和组内使用，已经为二十多个项目提供了部署运维环境。在需要快速迭代部署的时候，Jenkins的规范化和自动化执行节约了大量的时间成本。</p>
<h1 id="目前存在问题">目前存在问题</h1>
<p>1、搭建流水线时，大部分步骤和代码都是可以复用的，但没有复用的方法，不得不进行大段代码的复制粘贴。
2、代码部署到托管平台逻辑未能解耦，如果托管平台变更，目前所有存量脚本都需要变更。
3、调用HTTP接口的脚本都使用<code>shell</code>中的<code>curl</code>指令实现，存在较多的转义字符和参数拼接，代码可读性较低，不容易维护，并且很容易出错，接口的请求结果也需要自己处理。</p>
<p>经过调研，使用了公用共享库（ <code>Shared Libraries</code> ）和<code>http-request</code>插件，完美解决了这些问题。</p>
<h1 id="shared-libraries-配置和使用">Shared Libraries 配置和使用</h1>
<h2 id="配置">配置</h2>
<p>进入Jenkins首页后，点击左侧【系统管理】；
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152626.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>搜索“Global pipeline Libraries”，找到共享仓库配置。</p>
<p>配置的地址是gitlab上的代码仓库，方便公用脚本的版本管理和维护。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152637.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="脚本编写">脚本编写</h2>
<p>编写规范和目录结构，参考：https://www.jenkins.io/zh/doc/book/pipeline/shared-libraries/
以下为简单的使用示范：
1、在脚本代码仓库中，添加：<code>src/deploy/DeployHelper.groovy</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-1">def hello() {
    echo &#34;Hello World!!!!&#34;
}</div>
    </div>
  </div>
</div><p>2、在流水线脚本中，头部增加引入：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-2">@Library(&#39;SharedLibraries&#39;)
import deploy.DeployHelper</div>
    </div>
  </div>
</div><p>在流水线脚本中使用：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-3">script {
    DeployHelper deployHelper = new DeployHelper()
    deployHelper.hello()   
}</div>
    </div>
  </div>
</div><p>优点：
1、可以灵活使用Jenkins中已经安装的插件，不需要另外的依赖。
2、不需要另外给脚本授权（原本脚本在sandbox中执行，使用部分groovy公共类库时需要另外的授权。）</p>
<h1 id="http-request">http-request</h1>
<p>在脚本内部声明式地调用HTTP接口。

<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuamVua2lucy5pby9kb2MvcGlwZWxpbmUvc3RlcHMvaHR0cF9yZXF1ZXN0Lw%3d%3d"
    
     target="_blank"
>
https://www.jenkins.io/doc/pipeline/steps/http_request/</a>
使用该插件要求的Jenkins版本较高，进行了升级。由于之前配置了清华镜像，无法自动升级，选择去官网下载了安装包后，替换Jenkins内安装包，之后重启即可。
http-request使用较为简单，下面给出两个比较特别的范例：
1、上传文件（注意：multipartName 为文件参数的名称）</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-4"> def uploadBuildFile = httpRequest contentType: &#39;APPLICATION_OCTETSTREAM&#39;,
            httpMode: &#39;POST&#39;,
            consoleLogResponseBody: true,
            customHeaders: [[name: &#39;Authorization&#39;, value: &#34;basic ${token}&#34;]],
            url: &#34;http://${ip}/upload/${repoName}&#34;,
            uploadFile: &#34;${filepath}&#34;,
            multipartName: &#34;files&#34;</div>
    </div>
  </div>
</div><p>2、参数payload为<code>JSONArray</code>：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-5">import groovy.json.JsonOutput
def restartResponse = httpRequest contentType: &#39;APPLICATION_JSON&#39;,
            httpMode: &#39;POST&#39;,
            consoleLogResponseBody: true,
            url: &#34;http://${ip}/status/reboot&#34;,
            customHeaders: [[name: &#39;Authorization&#39;, value: &#34;basic ${token}&#34;]],
            requestBody: JsonOutput.toJson([[a: &#34;${ip}&#34;, b: [&#34;${serverEndpoint}&#34;]]])</div>
    </div>
  </div>
</div><p>值得注意的是，requestBody对应的参数，如果是JSONObject对应的JSONString，插件内部会自动进行反序列化，而如果是JSONArray，需要使用JsonOutput进行反序列化，不然会报参数错误。</p>
<h1 id="小结">小结</h1>
<p>使用共享脚本库，进一步提高了运维效率，尽量避免重复的劳动，降低了脚本的维护成本。
Jenkins的灵活、易于拓展可以给我们的工作带来很多方便，使用起来也是非常的有意思~</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>微前端qiankun框架接入实战</title>
      <link>https://ygria.site/learn-qiankun/</link>
      <pubDate>Thu, 26 Aug 2021 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/learn-qiankun/</guid>
      <description>&lt;h1 id=&#34;背景&#34;&gt;背景&lt;/h1&gt;
&lt;p&gt;随着项目的演进，前端的业务架构也会变得更加庞大、复杂，并常常会出现需要模块复用的场景：
1、组件复用，例如统一的导航栏、侧边栏、路由权限处理逻辑等
2、模块级别复用，例如统一的用户管理模块、文档中心等
3、系统级别复用，总的系统由多个系统组合而成，不同的系统可能由不同的开发团队维护、使用不同的技术栈开发。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="微前端qiankun框架接入实战" /><h1 id="背景">背景</h1>
<p>随着项目的演进，前端的业务架构也会变得更加庞大、复杂，并常常会出现需要模块复用的场景：
1、组件复用，例如统一的导航栏、侧边栏、路由权限处理逻辑等
2、模块级别复用，例如统一的用户管理模块、文档中心等
3、系统级别复用，总的系统由多个系统组合而成，不同的系统可能由不同的开发团队维护、使用不同的技术栈开发。</p>
<p>除了代码层面复用（复制粘贴），也需要更加完善的模块和系统复用方案。引入微前端，将代码根据业务逻辑划分至不同的项目之中进行维护，能够有效的降低维护难度，每个系统既可以独立运行、独立部署，也可以组合起来构成一个完整的系统，能够更快速地响应客户的需求。</p>
<hr>
<p>之前页面嵌入都使用<code>iframe</code>，简捷易用，两行代码就可以搞定，但在加载速度方面略有些不尽人意。可以看这篇➡
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cueXVxdWUuY29tL2t1aXRvcy9na3k3eXcvZ2VzZXh2"
    
     target="_blank"
>
Why Not Iframe</a>
听说隔壁项目组都已经用<code>qiankun</code>用的飞起了，我们必不能落后于人~于是在赶鸭子上架下，使用了<code>qiankun</code>进行了一次完整的实践。</p>
<h1 id="实战步骤">实战步骤</h1>
<h2 id="什么是qiankun">什么是<code>qiankun</code></h2>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9xaWFua3VuLnVtaWpzLm9yZy96aC9ndWlkZQ%3d%3d"
    
     target="_blank"
>
qiankun官方文档</a></p>



<blockquote>
    <p><code>qiankun</code>是基于<code>single-spa</code>的封装，可以参考：https://single-spa.js.org/ .
<code>single-spa</code>是一个将多个单页面应用聚合为一个整体应用的 <code>JavaScript</code> 微前端框架。
我们每个前端项目最终都会被打包成一个单页应用，该应用以index.html为入口，在其中引入打包后的js和css文件。</p>
</blockquote>
<h2 id="前端代码改造">前端代码改造</h2>
<p><code>qiankun</code>在逻辑上，将前端应用划分为主应用（又称为基座应用）和微应用，主应用拉取微应用打包后的js，并设置一定的规则来控制微应用的生命周期（装载、卸载等），微应用则要暴露出生命周期钩子函数，供主函数调用。</p>
<p>在主应用下安装依赖:</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-1">npm i qiankun --save-dev</div>
    </div>
  </div>
</div><h3 id="主应用改造">主应用改造</h3>
<h4 id="1改造入口文件">1、改造入口文件</h4>
<p><code>mainApp/main.js</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-2">import { registerMicroApps, start } from &#34;qiankun&#34;
let msg = {
   // 传入子应用的内容
};

// 注册子应用
registerMicroApps(
    [
        {
            name: &#34;turing-permission&#34;,
            entry: &#34;//127.0.0.1:9527&#34;,
            //  指定子应用的挂载容器
            container: &#39;#subApp&#39;,
            activeRule: &#34;#/turing-permission&#34;,
            props: msg
        },
        {
            name: &#34;turing-moss&#34;,
            entry: &#34;//127.0.0.1:9528&#34;,
            //  指定子应用的挂载容器
            container: &#39;#subApp&#39;,
            activeRule: &#34;#/turing-moss&#34;,
            props: msg
        },
    ],

);

const request = url =&gt; { return fetch(url, { referrerPolicy: &#39;origin-when-cross-origin&#39; }) };
start({ prefetch: true, sandbox: { experimentalStyleIsolation: true }, fetch: request });</div>
    </div>
  </div>
</div><p>说明：
参看
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9xaWFua3VuLnVtaWpzLm9yZy96aC9hcGk%3d"
    
     target="_blank"
>
qiankunAPI说明文档</a></p>
<ul>
<li>
<p><code>registerMicroApps</code>方法接收子应用列表。 参数解释：
1、name - 子应用名称
2、entry - 主应用使用fetch请求，从该入口获取子应用的js、css等资源，注意该地址需要去掉协议（http/https）。部署到线上时，该地址可填写为部署地址IP + 端口 + /subApp的形式，使用nginx代理，后面说到部署时会给出示例。此处为本地开发时的地址。
3、container - 子应用挂载的DOM根节点。需要注意在子应用加载时，该DOM节点必须存在，否则会报子应用挂载失败错误
4、activeRule - 触发子应用挂载的条件。如果子应用使用的路由为hash模式，则需要加<code>#</code>，如果使用的是history 模式，则不需要加<code>#</code>。本次实战使用的路由模式均为hash模式（也是默认的模式）
该方法可以自定义方法实现
5、props： 可以定义主应用传入到子应用的值。可以将主应用的store和router都传过去。</p>
</li>
<li>
<p><code>start</code>方法
参考API文档进行配置。这里踩了一个坑，如果沙箱隔离配置为 <code>sandbox: { strictStyleIsolation: true }</code>,可能会导致element-UI组件样式被影响（下拉框挂到左上角）。</p>
</li>
</ul>
<h4 id="2通信">2、通信</h4>
<p>应用在鉴权中，用于同步登录状态和传递token（若使用同一个域下的Cookie来鉴权，此处可忽略）：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-3">import qiankunActions from &#39;@/store/qiankun&#39;
//   登录成功后，获取到访问令牌
const { permissionList, accessToken } = await store.dispatch(&#39;user/getInfo&#39;)
qiankunActions.setGlobalState({ token: accessToken });</div>
    </div>
  </div>
</div><h4 id="3提供子应用挂载的根节点">3、提供子应用挂载的根节点</h4>
<p><code>App.vue</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> vue</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="vue" data-theme="breeze" id="code-id-4">&lt;template&gt;
    &lt;div id=&#34;container&#34; class=&#34;container&#34;&gt;
        &lt;head-top v-if=&#34;$route.name != &#39;Home&#39;&#34; /&gt;
        &lt;router-view /&gt;
        &lt;div  id=&#34;subApp&#34; /&gt;
    &lt;/div&gt;
&lt;/template&gt;</div>
    </div>
  </div>
</div><p>注意：如果使用了<!-- raw HTML omitted -->，该节点需要与最高层级的<!-- raw HTML omitted -->同级！</p>
<h3 id="子应用改造">子应用改造</h3>
<p>子应用无需安装qiankun依赖</p>
<h4 id="1入口文件改造">1、入口文件改造</h4>
<p><code>subApp/main.js</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-5">let instance;
function render(props) {
  let container = props ? props.container : undefined;
  instance = new Vue({
    router,
    store,
    render: h =&gt; h(App),
  }).$mount(container ? container.querySelector(&#39;#app&#39;) : &#39;#app&#39;);
}
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}
export async function bootstrap() { 
}

export async function mount(props) {
  props.onGlobalStateChange((state, prevState) =&gt; {
    store.commit(&#39;user/SET_TOKEN&#39;, prevState.token)
  }, true);
  render(props);
}
export async function unmount() {
  console.log(&#39;[turing-permission] unmounted&#39;);
  instance.$destroy();
  instance = null;
}</div>
    </div>
  </div>
</div><p>从代码逻辑易得，在子应用中暴露出的mount钩子方法中执行了Vue的render方法。该方法根据传入的props（会将container传入），找到对应的dom节点，在该dom节点下插入子应用的模板代码，再执行Vue的mount方法。需要区分两个mount：一个是子应用的挂载，一个是Vue应用的挂载。
通过<code>window.__POWERED_BY_QIANKUN__</code>，可以判断是否是被嵌入在主应用中运行。
<code>props.onGlobalStateChange((state, prevState) =&gt; { store.commit('user/SET_TOKEN', prevState.token) }, true);</code>第二个参数是必须的，用于从主应用中获取到token。</p>
<h4 id="2router改造">2、router改造</h4>
<p>若使用history模式，<code>mode: 'history'</code>,需要增加<code>base: '/sub-app'</code>
<code>router/index.js</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-6">const createRouter = () =&gt;{
    // 微应用运用的路由是只读的，需要先进行全量的定义
    let actualRoutes = window.__POWERED_BY_QIANKUN__ ?constantRoutes.concat(asyncRoutes) : constantRoutes;
    let prefix = &#34;/sub-app&#34;;
    //  若需要
    if(window.__POWERED_BY_QIANKUN__){
        actualRoutes.forEach(item =&gt; {
            item.path = prefix &#43; item.path;
            item.redirect = prefix &#43; item.redirect;
        })
    }

    return new Router({
        mode: &#39;hash&#39;,
        routes: actualRoutes
    });
}
const router = createRouter();
export default router</div>
    </div>
  </div>
</div><h4 id="3打包地址改造">3、打包地址改造</h4>
<p><code>vue.config.js</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-7">const { name } =require(`./package`);
...
 configureWebpack: {
    name: name,
    resolve: {
      alias: {
        &#39;@&#39;: resolve(&#39;src&#39;)
      }
    },
    output: {
      // 把子应用打包成 umd 库格式
      library: `${name}-[name]`,
      libraryTarget: &#39;umd&#39;,
      jsonpFunction: `webpackJsonp_${name}`,
    }
  },</div>
    </div>
  </div>
</div><p>新增<code>public-path.js</code>，并引入到<code>main.js</code>中
<code>src/public-path.js</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-8">if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}</div>
    </div>
  </div>
</div><h4 id="4请求改造">4、请求改造</h4>
<p>1）baseUrl改造（方便之后的代理）</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-9">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-9">let baseURL = window.__POWERED_BY_QIANKUN__ ? &#39;/turing-moss&#39; &#43; process.env.VUE_APP_BASE_API : process.env.VUE_APP_BASE_API
// 创建axios实例
const service = axios.create({
  baseURL: baseURL, // api的base_url
  timeout: 300000 // 请求超时时间
})</div>
    </div>
  </div>
</div><ol start="2">
<li>鉴权请求头改造，适配改造后的鉴权方案</li>
</ol>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-10">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-10">
          <button id="copyButton-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-10" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-10">function createHeader(token, isformdata) {
  var contentType = isformdata ? &#39;multipart/form-data&#39; : &#39;application/json&#39;
  let headers = {
    &#39;Content-Type&#39;: contentType,
    &#39;time&#39;: new Date().getTime(),
    &#39;salt&#39;: rdNum(6),
  }
  if (window.__POWERED_BY_QIANKUN__) {
    headers.Authorization = store.getters.token
    headers.useToken = true
  }
  return headers;
}</div>
    </div>
  </div>
</div><h2 id="后端代码改造">后端代码改造</h2>
<p>主要是鉴权改造。
鉴权顺序：
用户在主应用登录➡主应用后端生成令牌传递给前端➡前端微应用共享该令牌，在请求微应用后端时携带➡微应用后端拿到令牌后，请求主应用接口，判断是否合法，并获取用户信息</p>
<p>原先的鉴权方案都是CAS鉴权。</p>
<h3 id="主应用后端">主应用后端</h3>
<h4 id="1生成token并在请求用户信息接口中返回给前端使用oauth2">1、生成token，并在请求用户信息接口中返回给前端（使用OAuth2）</h4>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-11">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-11">
          <button id="copyButton-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-11" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-11">  UserDetails userDetails = domainUserDetailsService.createSpringSecurityUser(userInfoDTO);
                Authentication userAuth = new 
PreAuthenticatedAuthenticationToken(userDetails,userDetails.getPassword(),userDetails.getAuthorities());
String token = tokenProvider.createToken(userAuth,true);</div>
    </div>
  </div>
</div><h4 id="2提供内部鉴权接口">2、提供内部鉴权接口</h4>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-12">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-12">
          <button id="copyButton-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-12" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-12">   @GetMapping(&#34;/inner/tokenValid&#34;)
    AuthResp validToken(String token){
        System.out.println(token);
        if(tokenProvider.validateToken(token)){
            Authentication authentication = tokenProvider.getAuthentication(token);
            String accountName = authentication.getName();
            System.out.println(authentication);
            return AuthResp.builder().accountName(accountName)
                    .isAuth(true).build();
        }else{
            //  权限校验失败
            return AuthResp.builder().accountName(&#34;&#34;)
                    .isAuth(false).build();
        }</div>
    </div>
  </div>
</div><h3 id="微应用后端">微应用后端</h3>
<h4 id="定义优先级高于casfilter的自定义过滤器进行鉴权">定义优先级高于CasFilter的自定义过滤器进行鉴权</h4>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-13">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-13">
          <button id="copyButton-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-13" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-13">@Order(0)
@Slf4j
@Component
public class TokenAuthorFilter implements Filter {


    @Resource
    UserService userService;
    @Resource
    AuthClient authClient



    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        String accessToken = ((RequestFacade) servletRequest).getHeader(&#34;Authorization&#34;);
        HttpSession session = ((RequestFacade) servletRequest).getSession();
        if(((RequestFacade) servletRequest).getHeader(&#34;useToken&#34;)!=null &amp;&amp; ((RequestFacade) servletRequest).getHeader(&#34;useToken&#34;).equals(&#34;true&#34;)){
            log.info(&#34;进入微前端鉴权逻辑&#34;);
            if(StringUtils.isNotBlank(accessToken) &amp;&amp; session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) == null) {
                // 能够获取到token
               AuthResp authResp = authClient.authToken(accessToken);
                if (authResp.getIsAuth()) {
                    String accountName = authResp.getAccountName();
                    Assertion assertion = new AssertionImpl(accountName);
                    session.setAttribute(AbstractCasFilter.CONST_CAS_ASSERTION, assertion);
                    // assertion 非空：从assertion中获取数据
                    log.debug(&#34;从permission获取当前用户信息，用户名称 = {}&#34;, accountName);
                    session.setAttribute(Constants.SESSION_KEY,userService.getUserFullyByAccountName(accountName));
                }else{
                    throw new RuntimeException(&#34;qiankun主应用鉴权失败！&#34;);
                }

            }
        }
        filterChain.doFilter(servletRequest,servletResponse);
    }

}</div>
    </div>
  </div>
</div><p>此处逻辑：识别到携带<code>useToken</code>头的请求（此处头的名称支持自定义），请求主应用的后台，进行token的合法性校验，通过则执行后续逻辑，不通过抛出异常，前端跳转主应用的登录页。</p>
<h3 id="部署">部署</h3>
<p>将main.js中微应用的地址改为//${IP}/subApp的形式，使用nginx进行部署。
配置（配置到server 80或 443下（https）），将subAppIp设置为微服务的地址。
<img src="https://img-blog.csdnimg.cn/img_convert/a79582d6e748fdd54a9ccc637682f4ae.png" alt="nginx配置参考" class="img-hide" loading="lazy" decoding="async" /></p>
<p>本地进行联调时，需要将proxyTable代理至微服务地址。
至此，接入完成。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>JavaScript ES6  使用展开运算符完成全平台校验重名逻辑封装</title>
      <link>https://ygria.site/js-operate/</link>
      <pubDate>Thu, 08 Jul 2021 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/js-operate/</guid>
      <description>&lt;h1 id=&#34;背景&#34;&gt;背景&lt;/h1&gt;
&lt;p&gt;在应用系统中创建业务对象时，需要填写表单，对于对象的名称、标识等，全平台往往有统一的功能规范。
例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;名称：统一为中文、不超过50字符、不能为空、不能与现有平台重复&lt;/li&gt;
&lt;li&gt;标识：统一为英文，不超过50字符、不能为空、不能与现有平台重复
交互逻辑一致（填写名称/标识后，调用后台接口进行判断，后台查询数据库后，返回是否存在重名数据（&lt;code&gt;true/false&lt;/code&gt;）,存在重复则报表单校验错误，不允许表单提交），前端使用的组件也一致（使用&lt;code&gt;element-UI&lt;/code&gt;的&lt;code&gt;Form&lt;/code&gt;组件），公共逻辑清晰，于是我尝试进行统一的&lt;strong&gt;规则校验逻辑&lt;/strong&gt;方法封装，简化了大量重复代码，在使用时用**展开运算符&lt;code&gt;spread(...)&lt;/code&gt;**进行引入，保证了代码的优雅和简洁。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;展开spread运算符&#34;&gt;展开（spread）运算符&lt;/h1&gt;
&lt;p&gt;展开运算符是&lt;code&gt;JavaScript ES6&lt;/code&gt;的特性，可以用于数组、字符串、对象的解构赋值。
具体使用逻辑请参考：
&lt;a 
    
        href=&#34;https://ygria.site/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL3J1YW55Zi9lczZ0dXRvcmlhbC9ibG9iLzM5MjlmNGYyMTE0OGRjZDJhMTBkMmViYzcyMjMyM2E1ZGJkNDczZjQvZG9jcy9hcnJheS5tZA%3d%3d&#34;
    
     target=&#34;_blank&#34;
&gt;
扩展运算符&lt;/a&gt;&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="JavaScript ES6  使用展开运算符完成全平台校验重名逻辑封装" /><h1 id="背景">背景</h1>
<p>在应用系统中创建业务对象时，需要填写表单，对于对象的名称、标识等，全平台往往有统一的功能规范。
例如：</p>
<ul>
<li>名称：统一为中文、不超过50字符、不能为空、不能与现有平台重复</li>
<li>标识：统一为英文，不超过50字符、不能为空、不能与现有平台重复
交互逻辑一致（填写名称/标识后，调用后台接口进行判断，后台查询数据库后，返回是否存在重名数据（<code>true/false</code>）,存在重复则报表单校验错误，不允许表单提交），前端使用的组件也一致（使用<code>element-UI</code>的<code>Form</code>组件），公共逻辑清晰，于是我尝试进行统一的<strong>规则校验逻辑</strong>方法封装，简化了大量重复代码，在使用时用**展开运算符<code>spread(...)</code>**进行引入，保证了代码的优雅和简洁。</li>
</ul>
<h1 id="展开spread运算符">展开（spread）运算符</h1>
<p>展开运算符是<code>JavaScript ES6</code>的特性，可以用于数组、字符串、对象的解构赋值。
具体使用逻辑请参考：
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL3J1YW55Zi9lczZ0dXRvcmlhbC9ibG9iLzM5MjlmNGYyMTE0OGRjZDJhMTBkMmViYzcyMjMyM2E1ZGJkNDczZjQvZG9jcy9hcnJheS5tZA%3d%3d"
    
     target="_blank"
>
扩展运算符</a></p>
<h1 id="校验逻辑方法定义">校验逻辑方法定义</h1>
<h2 id="校验函数封装">校验函数封装</h2>
<p>定义方法入参：
1、资源英文名称
用于拼接调用后端<code>RESTful</code>接口，例如：校验应用重复，后端定义接口URI为：
<code>/api/applications/nameOrKeyExisted</code>，此时资源英文名称为<code>applications</code>
2、资源中文名
用于页面提示回显，例如：表单中应用名称没有填写，提示：“请填写应用名称”，应用名称已存在，提示：“应用名称重复”，此时资源中文名为“应用”
3、附加参数</p>
<ul>
<li>有些资源限定为某类型下不能重复，或某个领域内不能重复，在调用判重接口时需要传递给后端。</li>
<li>prop参数，用于表单绑定的<code>prop</code>定义，如果不传，默认为name和key，允许传入自定义值。
方法前端源码（定义在通用的<code>util.js</code>中，在Vue工程中可以在<code>main.js</code>引入到全局中。）</li>
</ul>
<p>定义方法返回：
返回对象，对象中，键对应表单 <code>prop</code>属性，值是一个数组，包括多种规则（特殊字符校验、非空校验、重名校验等）
utils.js：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-1">/**
 * @entity 校验实体资源
 * @entityName 校验实体中文名
 * @options  调用校验接口，额外参数传递
 */
function getRules(entity, entityName,options) {
  let name = options?.nameProp || &#39;name&#39;
  let key = options?.keyProp || &#39;key&#39;
  let rules = {};
  rules[name] = validateRules(
    &#39;name&#39;,
    entity,
    entityName,
    options?.params
  );
  rules[key] = validateRules(
    &#39;key&#39;,
    entity,
    entityName,
    options?.params
  );
  return rules;

}
function validateRules(field, entity, entityName,extraParams) {
  let rules = [];

  let requiredRule = {
    required: true,
    message: `请输入${entityName}${field === &#39;key&#39; ? &#39;标识&#39; : &#39;名称&#39;}`,
    trigger: &#34;blur&#34;,
  };
  if (field == &#39;key&#39;) {
    let maxLengthRule = { max: 40, message: &#34;不得超过40个字符&#34;, trigger: &#34;blur&#34; };
    rules.push(maxLengthRule);
    rules.push({ validator: keyValidator, trigger: [&#39;blur&#39;, &#39;change&#39;] })
  } else {
    let maxLengthRule = { max: 16, message: &#34;不得超过16个字符&#34;, trigger: &#34;blur&#34; };
    rules.push(maxLengthRule);
    rules.push({ validator: nameValidator, trigger: [&#39;blur&#39;, &#39;change&#39;] })
  }

  rules.push(requiredRule);
  rules.push(
    {
      validator:
        nameOrKeyExistedValidator,
      entity: entity,
      extraParams: extraParams,
      entityName: entityName,
      trigger: &#34;blur&#34;,
    }
  );
  return rules;
}
const keyValidator = (rule, value, callback) =&gt; {
  const reg = /^[a-zA-Z0-9_]&#43;$/
  if (!reg.test(value)) {
    callback(new Error(&#39;仅支持英文、数字和下划线&#39;))
  } else {
    callback();
  }
}
const nameValidator = (rule, value, callback) =&gt; {
  const reg = /^[a-zA-Z0-9_\u4e00-\u9fa5]&#43;$/

  if (!reg.test(value)) {
    callback(new Error(&#39;仅支持中文、英文、数字和下划线&#39;))
  } else {
    callback();
  }
}
const nameOrKeyExistedValidator = (rule, value, callback) =&gt; {
  if (rule.oldVal &amp;&amp; rule.oldVal === value) {
    callback();
  } else {
    nameOrKeyExisted(rule.entity, rule.field, value,rule.extraParams).then(res =&gt; {
      if (res) {
        if (rule.field === &#34;name&#34;) {
          callback(`${rule.entityName}名称重复`)
        } else {
          callback(`${rule.entityName}标识重复`)
        }
      } else {
        callback();
      }
    })
  }
}</div>
    </div>
  </div>
</div><ul>
<li>编辑实体时的判断逻辑（传入oldVal，避免错误的报错）
这里的逻辑暂时没想到比较好的解决方法，所以写的比较恶心，因为原先的值可能是异步拿到的，所以需要手动赋值。传入oldVal后，当表单输入值与原先的值一致时，就不会调用后端判重接口了。</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-2">  created() {
    if (this.isEdit) {
      this.rules.name[3].oldVal = this.ruleForm.name
      this.rules.key[3].oldVal = this.ruleForm.key
    }
  }</div>
    </div>
  </div>
</div><p>封装后端axios请求：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-3">export function nameOrKeyExisted(entityName, type, data, params) {
  let queryParams ={};
  if (params) {
     queryParams = {
      ...params,
      type: type,
      nameOrKey: data
     }
  } else {
     queryParams = {
      type: type,
      nameOrKey: data
    }
  }
  return $get(`/api/${entityName}/nameOrKeyExisted`, queryParams)
}</div>
    </div>
  </div>
</div><p>请求的封装需要后端的配合~（因为这个平台后端也是由我一手包办的，所以当然不在话下啦）</p>
<ul>
<li>后端定义接口时，只需要注意后端URI和返回值一致就可以了。</li>
</ul>
<h1 id="使用">使用</h1>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-4"> &lt;el-form :model=&#34;esseForm&#34; :rules=&#34;esseFormRules&#34; ref=&#34;baseInfoForm&#34;&gt;
...
&lt;/el-form&gt;</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-5"> esseFormRules: {
        type: [
          {
            required: true,
            message: &#39;请选择实体类型&#39;,
            trigger: &#39;blur&#39;
          }
        ],

        ...this.$utils.validate.getRules(&#39;sem-esses&#39;, &#39;实体&#39;,{params: {user: a}})
      },</div>
    </div>
  </div>
</div><p>如上，使用展开运算符，将返回的结果赋值到rules对象中，名称和标识的规则由通用的校验函数根据入参生成，该方法已经挂载到全局的$utils上，无需额外的引入成本，一次性生成了对于名称、标识的所有校验。</p>
<h1 id="小结">小结</h1>
<p>本方法适用于校验逻辑雷同、并且需要实体创建和校验的平台，如果功规调整，也能快速适应（例如长度从限制50字符改为限制100字符），节约时间。
缺陷：校验规则函数的灵活度往往与复杂度成正比，如果需要更多特殊的校验，需要考虑是否有必要修改校验函数，可能不太适用这种方法，如果创建表单的校验逻辑差异较大，就还是建议为每个表单定义自己的rules规则。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Vue 指令实现自动填充英文名功能</title>
      <link>https://ygria.site/vue-func/</link>
      <pubDate>Fri, 21 May 2021 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/vue-func/</guid>
      <description>&lt;ul&gt;
&lt;li&gt;背景：应用系统中存在多个创建实体表单，表单填写时，在填写中文名称后，要填写对应的英文名作为标识或数据库查询索引。&lt;/li&gt;
&lt;li&gt;需求：&lt;strong&gt;填写中文名的同时，系统自动生成英文名并填充到表单中，辅助用户操作，节约操作时间。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;实现效果&#34;&gt;实现效果&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/img_convert/28c3278af86760a4e9fa3a04588173b9.gif&#34; alt=&#34;转全拼&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Vue 指令实现自动填充英文名功能" /><ul>
<li>背景：应用系统中存在多个创建实体表单，表单填写时，在填写中文名称后，要填写对应的英文名作为标识或数据库查询索引。</li>
<li>需求：<strong>填写中文名的同时，系统自动生成英文名并填充到表单中，辅助用户操作，节约操作时间。</strong></li>
</ul>
<h1 id="实现效果">实现效果</h1>
<p><img src="https://img-blog.csdnimg.cn/img_convert/28c3278af86760a4e9fa3a04588173b9.gif" alt="转全拼" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://img-blog.csdnimg.cn/img_convert/85a002b82a346a8bd03fd6d9f3161673.gif" alt="转英文（伪）" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="方案调研">方案调研</h1>
<p>对需求进行分析后，对于如何将中文名翻译成英文字符串，调研以下方案：</p>
<ul>
<li>调用翻译引擎
优点：翻译准确，对于短句也能翻译
缺点：部署难度大，需要捆绑翻译引擎</li>
<li>调用开放API（谷歌翻译/百度翻译等）
优点：能完成翻译功能
缺点：可能需要付费/开发者帐号等，需要集成成本，需要私有化部署版本时（无法连接外网）可能无法实现</li>
<li>使用音译插件（参考：https://github.com/dzcpy/transliteration）
优点：轻量，集成简单，有一定可扩展性，可离线
缺点：无法翻译，只能音译（会将“你好”翻译成“ni_hao”而不是“hello&quot;），使标识的可读性和语义性下降。</li>
</ul>
<p>以上三种仅为中转英的方法不同，均可实现功能。本次方案暂使用第三种。</p>
<h1 id="实现方案">实现方案</h1>
<ul>
<li>分析：该功能需要增加到多个表单中，如果为每个需要添加的组件都增加相应逻辑，侵入性较强，也不好维护。</li>
<li>逻辑提炼：</li>
</ul>
<ol>
<li>为中文名的输入框绑定监听事件，监听输入，取得该input框输入的值</li>
<li>将第一步中获得的中文值转化成英文字符串</li>
<li>将英文字符串写入到英文名输入框中
思路： 为表单添加<code>vue</code>自定义指令，通过取子节点（根据虚拟节点层级，<code>vnode</code>的子级）的方法，获取到需要操作的dom元素，再在指令逻辑中进行逻辑处理。</li>
</ol>
<h1 id="代码实现">代码实现</h1>
<h2 id="指令定义">指令定义</h2>
<ul>
<li>定义<code>v-transliterate </code>指令（vue自定义指令的定义和使用可参考官方文档，此处不做赘述）</li>
<li><code>transliterate.js</code></li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-1">import { transliterate as tr, slugify } from &#39;transliteration&#39;
export default {
  inserted(el, binding, vnode) {
    let sourceInputEl = vnode.componentInstance.$children.find(item =&gt; item.prop === &#39;name&#39;).$children[1].$el.children[0]
    let targetInputEl = vnode.componentInstance.$children.find(item =&gt; item.prop === &#39;key&#39;).$children[1].$el.children[0]

    let isFirstInput = true;

    sourceInputEl.addEventListener(&#39;keyup&#39;, () =&gt; {
      // 判断当前标识是否已填写，若已填写，则不再根据中文名称生成
      let isEmpty = !targetInputEl.value;

      if (isEmpty || !isFirstInput) {
    // 一定延迟处理，用户使用几乎无感知
        setTimeout(() =&gt; {
          let transValue = slugify(sourceInputEl.value, { separator: &#39;_&#39; });
          let inputEvt = new InputEvent(&#39;input&#39;, {
            inputType: &#39;insertText&#39;,
            data: transValue,
            dataTransfer: null,
            isComposing: false
          });
          targetInputEl.value = transValue;
          targetInputEl.dispatchEvent(inputEvt);
          isFirstInput = false;

        }, 500);
      }
    })

  }
}</div>
    </div>
  </div>
</div><h2 id="注意事项">注意事项</h2>
<p>1、transValue的生成可根据前面所说的不同方案，更改生成的方法。
2、两个input 元素是根据prop来筛选的，代码中硬编码为”name“ -中文名 和”key“ 英文名，可根据需求调整，也可以根据指令方法入参的binding赋值。由于本项目中所有表单prop都是固定的，所以没有写相应逻辑。
3、<code>keyup</code>事件可根据需求更改为<code>blur</code>事件，对于调用后台api获值，可考虑改为blur，降低频繁请求。
4、执行<code> targetInputEl.value = transValue;</code>  后，页面上显示已经改变，但点击保存表单时仍然会触发空值校验，怀疑是因为该赋值没有刷新到虚拟节点的model中，故而使用 <code>targetInputEl.dispatchEvent(inputEvt);</code>方法模拟输入事件，触发值的刷新。
5、<code>isEmpty</code>  空值校验，避免用户在填写表单时先填写了英文名，再填写中文名时，英文名被覆盖。逻辑一般限定标识生成后就不允许修改，该方法也规避了修改时的英文名跟着中文名修改的问题。
6、使用<code>transliterate </code>可定义配置字典，实现常用中-英单词的翻译，但仍然无法替代翻译引擎。配置逻辑参考github上的README即可。
<code>slugify.config({ replace: [['世界','world'],['你好','hello']] });</code></p>
<h2 id="指令使用">指令使用</h2>
<p>需要用该功能的地方，在表单元素增加该指令即可。
<img src="https://img-blog.csdnimg.cn/img_convert/fa9079117854ffa7932e6bde2dfd9665.png" alt="指令使用" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="总结">总结</h1>
<p>以上就是实现全过程，如果有更好的实现方法，请留言告诉我哦~</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Java | 使用OpenFeign管理多个第三方服务调用</title>
      <link>https://ygria.site/use-open-feign/</link>
      <pubDate>Wed, 28 Apr 2021 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/use-open-feign/</guid>
      <description>&lt;h1 id=&#34;背景&#34;&gt;背景&lt;/h1&gt;
&lt;p&gt;最近开发了一个统一调度类的项目，需要依赖多个第三方服务，这些服务都提供了&lt;code&gt;HTTP&lt;/code&gt;接口供我调用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/img_convert/1ff2d029d047f2573ccf599a473dea8d.png&#34; alt=&#34;组件架构&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;
&lt;p&gt;服务多、接口多，如何进行第三方服务管理和调用就成了问题。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Java | 使用OpenFeign管理多个第三方服务调用" /><h1 id="背景">背景</h1>
<p>最近开发了一个统一调度类的项目，需要依赖多个第三方服务，这些服务都提供了<code>HTTP</code>接口供我调用。</p>
<p><img src="https://img-blog.csdnimg.cn/img_convert/1ff2d029d047f2573ccf599a473dea8d.png" alt="组件架构" class="img-hide" loading="lazy" decoding="async" /></p>
<p>服务多、接口多，如何进行第三方服务管理和调用就成了问题。</p>
<p>常用的服务间调用往往采用<code>zk</code>、<code>Eureka</code>等注册中心进行服务管理（<code>SpringBoot</code>常使用<code>SpringCloud</code>）。<code>OpenFeign</code>也是<code>SpringCloud</code>的解决方案之一。我们单独使用<code>OpenFeign</code>， 无需对原有第三方服务进行改动，本服务开发时的引入也很轻量。</p>
<p>下面给出我的用法。</p>
<h1 id="应用">应用</h1>
<h2 id="maven依赖">maven依赖</h2>
<p>引入maven依赖：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-1">       &lt;dependency&gt;
           &lt;groupId&gt;io.github.openfeign&lt;/groupId&gt;
           &lt;artifactId&gt;feign-core&lt;/artifactId&gt;
           &lt;version&gt;10.2.3&lt;/version&gt;
       &lt;/dependency&gt;
       &lt;dependency&gt;
           &lt;groupId&gt;io.github.openfeign&lt;/groupId&gt;
           &lt;artifactId&gt;feign-gson&lt;/artifactId&gt;
           &lt;version&gt;10.2.3&lt;/version&gt;
       &lt;/dependency&gt;
       &lt;dependency&gt;
           &lt;groupId&gt;io.github.openfeign.form&lt;/groupId&gt;
           &lt;artifactId&gt;feign-form&lt;/artifactId&gt;
           &lt;version&gt;3.8.0&lt;/version&gt;
       &lt;/dependency&gt;
       &lt;dependency&gt;
           &lt;groupId&gt;io.github.openfeign.form&lt;/groupId&gt;
           &lt;artifactId&gt;feign-form-spring&lt;/artifactId&gt;
           &lt;version&gt;3.8.0&lt;/version&gt;
       &lt;/dependency&gt;</div>
    </div>
  </div>
</div><p>其中，form相关引入是为了解决<code>ContentType</code>为<code>application/x-www-form-urlencoded</code>和<code>multipart/form-data</code>的编码问题。</p>
<h2 id="配置和服务声明">配置和服务声明</h2>
<p>第三方服务的地址通过配置来注入。</p>
<h3 id="服务地址配置">服务地址配置</h3>
<p><code>ThirdpartServiceConfig.java</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-2">@Data
@Component
@ConfigurationProperties(prefix = &#34;thirdpart-service&#34;)
public class ThirdpartServiceConfig {
    private String serviceA;
    private String serviceB;
    private String serviceC;
}</div>
    </div>
  </div>
</div><p>服务配置（超时时间配置等也可以写在这里）
<code>application.yaml</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> yaml</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="yaml" data-theme="breeze" id="code-id-3">thirdpart-service:
  serviceA: http://****:***/
  serviceB:  http://****:***/
  serviceC:  http://****:***/</div>
    </div>
  </div>
</div><h3 id="第三方服务配置">第三方服务配置</h3>
<p>因为声明方法一致，所以省略了多个第三方声明。
<code>ThirdPartClientConfig.java</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-4">@Configuration
public class ThirdParttClientConfig {

    @Resource
    private ThirdpartServiceConfig thirdpartServiceConfig;

    @Bean
    public ServiceAClient serviceAClient() {
        return Feign.builder()
            .encoder(new FormEncoder(new GsonEncoder()))
            .decoder(new GsonDecoder())
            .target(ServiceAClient.class, thirdpartServiceConfig.getServiceA());
    }
}</div>
    </div>
  </div>
</div><h2 id="接口声明和使用">接口声明和使用</h2>
<p>完成了服务的声明和服务的配置之后，就可以进行服务接口的声明了。具体声明方法可以参看<code>OpenFeign</code>文档：[# 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9zZWdtZW50ZmF1bHQuY29tL2EvMTE5MDAwMDAxODMxMzI0Mw%3d%3d"
    
     target="_blank"
>
翻译: Spring Cloud Feign使用文档</a>
](
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9zZWdtZW50ZmF1bHQuY29tL2EvMTE5MDAwMDAxODMxMzI0Mz91dG1fc291cmNlPXRhZy1uZXdlc3Q%3d"
    
     target="_blank"
>
https://segmentfault.com/a/1190000018313243?utm_source=tag-newest</a>)
下面给出使用示例:</p>
<ul>
<li><code>GET</code>请求（<code>feign</code>可直接将返回的结果反序列化为本服务中定义的<code>POJO</code>）</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-5">@RequestLine(&#34;GET testGet?a={a}&amp;b={b}&#34;)
ServiceResp testGet(@Param(&#34;a&#34;) String a,@Param(&#34;b&#34;)String b);</div>
    </div>
  </div>
</div><ul>
<li><code>GET</code> 下载
使用<code>feign.Response</code>接收请求结果</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-6">@RequestLine(&#34;GET export?exportId={exportId}&#34;)
Response exportFromServiceA(@Param(&#34;exportId&#34;)String exportId);</div>
    </div>
  </div>
</div><div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-7">@Resource
private ServiceAClient serviceAClient ;

// 导出方法
public void export(exportId) {
    Response serviceResponse = serviceserviceAClient.exportFromServiceA(exportId);
    Response.Body body = serviceResponse.body();
    try(InputStream inputStream = body.asInputStream();
        // 处理获取到的inputStream
    } catch (IOException e) {
    log.error(&#34;导出发生异常&#34;,e);
}</div>
    </div>
  </div>
</div><ul>
<li><code>POST</code> application/json&quot;</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-8"> @RequestLine(&#34;POST /save&#34;)
 @Headers(&#34;Cofntent-Type: application/json&#34;)
  ServiceResp saveEntity(EntityPOJO entityPOJO);</div>
    </div>
  </div>
</div><ul>
<li>POST form</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-9">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-9"> @RequestLine(&#34;POST  uqa/repo/qa/batch&#34;)
 @Headers(&#34;Content-Type:multipart/form-data&#34;)
 ServiceResp uploadFile(@Param(&#34;id&#34;)String id, @Param(&#34;batch_file&#34;) File file);</div>
    </div>
  </div>
</div><ul>
<li>注意：除了file类型，其他参数会被序列化为String，所以若第三方接口参数的值为POJO（或Map），可能会出错。</li>
<li>对于POJO参数，若第三方参数名含有<code>Java</code>中不合法的属性字符（如 ”-“，”#“，”.“等），可使用注解进行序列化时的转化。由于声明<code>Feign Client</code>时使用的encoder是<code>Gson</code>，所以使用如下注解：</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-10">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-10">
          <button id="copyButton-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-10" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-10"> @SerializedName(value=&#34;aaa-bbb&#34;)
 private String aaaBbb;</div>
    </div>
  </div>
</div><p>如果使用的是其他序列化工具，改为对应的注解即可。</p>
<h1 id="小结">小结</h1>
<p>使用声明式的第三方和接口写法，基本覆盖了请求第三方接口的需求，也易于拓展和管理。
我计划在后续添加统一的鉴权、日志打印和异常捕获处理功能，使依赖组件引入的风险更为可控。<code>OpenFeign</code>帮我们实现了服务声明、接口声明、HTTP请求发送和结果处理等逻辑，在项目需要调用多个第三方服务时可以使用。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Vue &#43; WebRTC 实现音视频直播（附自定义播放器样式)</title>
      <link>https://ygria.site/vue-webrtc/</link>
      <pubDate>Tue, 17 Nov 2020 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/vue-webrtc/</guid>
      <description>&lt;h1 id=&#34;1-什么是webrtc&#34;&gt;1. 什么是WebRTC&lt;/h1&gt;
&lt;h2 id=&#34;11-webrtc简介&#34;&gt;1.1 WebRTC简介&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;WebRTC&lt;/strong&gt;，名称源自&lt;strong&gt;网页即时通信&lt;/strong&gt;（英语：Web Real-Time Communication）的缩写，是一个支持网页浏览器进行实时语音对话或视频对话的实时通信框架，提供了一系列页面可调用API。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Vue + WebRTC 实现音视频直播（附自定义播放器样式)" /><h1 id="1-什么是webrtc">1. 什么是WebRTC</h1>
<h2 id="11-webrtc简介">1.1 WebRTC简介</h2>
<p><strong>WebRTC</strong>，名称源自<strong>网页即时通信</strong>（英语：Web Real-Time Communication）的缩写，是一个支持网页浏览器进行实时语音对话或视频对话的实时通信框架，提供了一系列页面可调用API。</p>



<blockquote>
    <p>参考定义：
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cub3NjaGluYS5uZXQvcC93ZWJydGM%3d"
    
     target="_blank"
>
 谷歌开放实时通信框架</a></p>
</blockquote>



<blockquote>
    <p>在上一篇博客
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC81NTEwNzI5NWFiNmM%3d"
    
     target="_blank"
>
Vue +WebSocket + WaveSurferJS 实现H5聊天对话交互</a> 中，已经涉及到WebRTC接口的使用，使用到了<code>getUserMedia</code>方法，用于通过浏览器获取设备麦克风，从而采集音频。</p>
</blockquote>
<p>最近项目中的需求则是与服务端建立即时通信，实现低延迟音视频直播。</p>
<p>RTC的特征是（参考来源：https://www.zhihu.com/question/22301898）</p>
<ul>
<li><strong>复杂度较高</strong></li>
<li><strong>半可靠传输</strong>，对于特定情境（比如网络环境较差时）可以对音视频进行有损传输，降低延迟</li>
<li><strong>音视频友好</strong>：可针对音视频做定制化优化</li>
<li>提供<strong>端对端</strong>优化方案。 对于传统连接模式，使用C/S架构，A=&gt;服务端=&gt;B，而WebRTC使用的是<code>peer-to-peer</code>模式，A=&gt;B，一旦点和点之间的连接形成，它们之间的数据传输是不经过服务端的，大大降低了服务端的压力。</li>
<li><strong>理论延迟较低</strong>，能应用在各种低延迟场景。</li>
</ul>
<h1 id="2-业务描述">2. 业务描述</h1>
<p><strong>功能描述</strong>：
实现对摄像设备的管理列表，在设备列表点击查看视频时，弹出页面浮窗，进行摄像机摄像的视频和音频实时转播。
视频弹窗下方有自己实现的控制条，实现播放/暂停控制，能显示播放时间、切换分辨率、是否全屏等。</p>
<p><strong>效果如图</strong>：</p>
<p><img src="https://img-blog.csdnimg.cn/img_convert/97ed5309b97abfa2b600af17bfd3d97f.png" alt="视频浮窗 - hover状态，显示控制条" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://img-blog.csdnimg.cn/img_convert/ddff8319f59f951d767aa74acbef9e63.png" alt="视频浮窗 - 非hover状态，隐藏控制条" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="3-代码实现">3. 代码实现</h1>
<h2 id="31-html模板代码">3.1 Html模板代码</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-1">&lt;el-dialog ref=&#34;videoDialog&#34; title=&#34;视频播放&#34; :visible.sync=&#34;dialogShowed&#34; :close-on-click-modal=&#34;false&#34;&gt;
        &lt;div id=&#34;dialog-wrap&#34;&gt;
            &lt;div id=&#34;video-wrap&#34; v-if=&#34;isSuccess&#34; v-loading=&#34;isLoading&#34; element-loading-text=&#34;视频加载中&#34; element-loading-spinner=&#34;el-icon-loading&#34;
                element-loading-background=&#34;rgba(0, 0, 0, 0.8)&#34; /&gt;
            &lt;div class=&#34;video-onloading&#34; v-else v-loading=&#34;isLoading&#34; element-loading-text=&#34;视频加载中&#34; element-loading-spinner=&#34;el-icon-loading&#34;
                element-loading-background=&#34;rgba(0, 0, 0, 0.8)&#34;&gt;
                &lt;span&gt;&lt;i class=&#34;el-icon-error&#34; v-if=&#34;!isLoading&#34; /&gt;{{errorMessage}}&lt;/span&gt;
            &lt;/div&gt;
            &lt;!-- 遮罩层 --&gt;
            &lt;div class=&#34;cover&#34; v-if=&#34;isSuccess&#34;&gt;
                &lt;div class=&#34;controls&#34;&gt;
                  
                    &lt;i class=&#34;el-icon-video-play&#34; v-if=&#34;!isPlaying&#34; @click=&#34;playOrPauseVideo&#34; /&gt;
                    &lt;i class=&#34;el-icon-video-pause&#34; v-else @click=&#34;playOrPauseVideo&#34; /&gt;
                    &lt;div id=&#34;currentTime&#34;&gt;播放时长:{{currentTime}}&lt;/div&gt;
                    &lt;div class=&#34;control-resolution&#34;&gt;
                        分辨率：
                        &lt;el-select v-model=&#34;selectResolution&#34; @change=&#34;changeResolution&#34;&gt;
                            &lt;el-option v-for=&#34;item in resolutions&#34; :key=&#34;item&#34; :value=&#34;item&#34;&gt;
                                {{item}}
                            &lt;/el-option&gt;
                        &lt;/el-select&gt;
                    &lt;/div&gt;
                    &lt;i class=&#34;el-icon-full-screen&#34; @click=&#34;onClickFullScreen&#34;&gt;&lt;/i&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/el-dialog&gt;</div>
    </div>
  </div>
</div><ul>
<li>
<p>使用了<code>Element-UI</code>框架提供的<code>v-loading</code>指令，该指令根据<code>isLoading</code>属性决定是否在区域内加载loading动画
<img src="https://img-blog.csdnimg.cn/img_convert/1294b0a9bb6fb97f911b2a115465aeed.png" alt="视频加载状态" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
<li>
<p>若视频加载失败，则显示错误信息
<img src="https://img-blog.csdnimg.cn/img_convert/4bc4e455db6c5cc09c575da7765c9477.png" alt="显示错误信息" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
<li>
<p>预留标签，用于挂载`video和audio DOM元素
<code>&lt;div id=&quot;video-wrap&quot; &gt;&lt;/div&gt;</code>
注意该标签内最好不要再加其他元素，这样后续判断比较简单。</p>
</li>
</ul>
<h2 id="32-建立连接接收音频">3.2 建立连接、接收音频</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-2">       getVideo() {
                let that = this;
                that.isLoading = true;
                that.pc = new RTCPeerConnection();
                that.pc.addTransceiver(&#34;video&#34;);
                that.pc.addTransceiver(&#34;audio&#34;);
                that.pc.ontrack = function (event) {
                    var el = document.createElement(event.track.kind);
                    el.srcObject = event.streams[0];
                    el.autoplay = true;
                    document.getElementById(&#34;video-wrap&#34;).appendChild(el);
                    if (el.nodeName === &#34;VIDEO&#34;) {
                        el.oncanplay = () =&gt; {
                            that.isLoading = false;
                            // 播放状态设置为true
                            that.isPlaying = true;
                            that.getVideoDuration();
                        };
                    } else if (el.nodeName === &#34;AUDIO&#34;) {
                        el.oncanplay = () =&gt; {
   
                        };
                    }
                };
                that.pc
                    .createOffer()
                    .then((offer) =&gt; {
                        that.pc.setLocalDescription(offer);
                        let req = {
                            webrtc: offer,
                        };
                        console.log(offer);
                        return that.$api.device.getSignaling(
                            that.deviceData.id,
                            that.origin,
                            that.selectResolution,
                            req
                        );
                    })
                    .then((res) =&gt; {
                        if (res.code === 0) {
                            that.isSuccess = true;
                            that.pc.setRemoteDescription(res.body.webrtc);
                            that.connId = res.body.connId;
                        } else {
                        
                            that.errorMessage = res.message || &#34;视频加载错误&#34;;
                        }
                    })
                    .catch(alert);
            }</div>
    </div>
  </div>
</div>


<blockquote>
    <p>参考https://www.jianshu.com/p/43957ee18f1a，查看<code>Peer Connection</code>建立连接的流程。
参考 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvemgtQ04vZG9jcy9XZWIvQVBJL1JUQ1BlZXJDb25uZWN0aW9u"
    
     target="_blank"
>
https://developer.mozilla.org/zh-CN/docs/Web/API/RTCPeerConnection</a> 查看<code>RTCPeerConnection</code> 支持的接口</p>
</blockquote>
<p><code>createOffer()</code> 方法： 主动与其他<code>peer</code>建立P2P连接，把自己的SDP信息整理好，通过<code>signaling server</code>转发给其他peer。
在上面的代码中，通过向后端发送POST请求，实现信令交换。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-3"> that.pc.addTransceiver(&#34;video&#34;);
 that.pc.addTransceiver(&#34;audio&#34;);</div>
    </div>
  </div>
</div><p>指明同时接收音频和视频。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-4"> that.pc.ontrack = function(event){
}</div>
    </div>
  </div>
</div><p>该方法进行音视频的接收，使用接收到的数据创建video和audio元素。
只对pc状态进行监听无法监听到实际视频可以播放的状态，因此需要对video添加监听方法：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-5">  el.oncanplay = () =&gt; {
     that.isLoading = false;
     // 播放状态设置为true
    that.isPlaying = true;
    that.getVideoDuration();
};</div>
    </div>
  </div>
</div><p>在video可以播放时，才将loading状态取消，并开始获取video时长。</p>
<h2 id="33-控制音视频的js代码">3.3 控制音视频的JS代码</h2>
<p>获取视频播放时长方法：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-6">getVideoDuration() {
    var video = document.getElementsByTagName(&#34;video&#34;)[0];
    //  如果没有获取到视频元素
    if (!video) {
        return;
    }
    let that = this;

    video.addEventListener(&#34;timeupdate&#34;, () =&gt; {
        that.currentTime = getTime(video.currentTime);
    });

    var getTime = function (time) {
        let hour =
            Math.floor(time / 3600) &lt; 10
                ? &#34;0&#34; &#43; Math.floor(time / 3600)
                : Math.floor(time / 3600);
        let min =
            Math.floor((time % 3600) / 60) &lt; 10
                ? &#34;0&#34; &#43; Math.floor((time % 3600) / 60)
                : Math.floor((time % 3600) / 60);
        var sec =
            Math.floor(time % 60) &lt; 10
                ? &#34;0&#34; &#43; Math.floor(time % 60)
                : Math.floor(time % 60);
        return hour &#43; &#34;:&#34; &#43; min &#43; &#34;:&#34; &#43; sec;
    };
}</div>
    </div>
  </div>
</div><p>控制音频/视频同步暂停的方法：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-7">  playOrPauseVideo() {
    var video = document.getElementsByTagName(&#34;video&#34;)[0];
    var audio = document.getElementsByTagName(&#34;audio&#34;)[0];
    if (this.isPlaying) {
        video.pause();
        audio.pause();
    } else {
        // audio
        video.play();
        audio.play();
    }
    this.isPlaying = !this.isPlaying;
}</div>
    </div>
  </div>
</div><p>全屏方法</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-8">onClickFullScreen() {
    let dialogElement = document.getElementById(&#34;dialog-wrap&#34;);
    dialogElement.webkitRequestFullScreen();
}</div>
    </div>
  </div>
</div><h2 id="34-样式表">3.4 样式表</h2>
<p>样式部分较为简单，值得注意的有以下几点：</p>
<ul>
<li>隐藏原有视频控制条，便于对控制条进行自定义</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-9">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> css</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="css" data-theme="breeze" id="code-id-9">video::-webkit-media-controls {
    /* 去掉全屏时显示的自带控制条 */
    display: none !important;
}</div>
    </div>
  </div>
</div><ul>
<li>扩大hover热区，视频下半部分（高度为400px部分）悬浮显示控制条
（不设置为全部部分是因为如果设置为全部部分，则全屏状态无法隐藏控制条）
以下完整样式表（<code>scss</code>）:</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-10">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> scss</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-10">
          <button id="copyButton-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-10" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="scss" data-theme="breeze" id="code-id-10">    $controlFontColor: rgb(136 141 150);
    $backgroundColor: rgba(0, 0, 0, 0.8);
    $height: 60px;

    .el-dialog .el-dialog__body {
        padding: 0 !important;
        margin-bottom: 0 !important;
        width: unset !important;
    }

    .video-onloading {
        min-height: 500px;
        background-color: $backgroundColor;

        span {
            width: 100%;
            display: block;

            line-height: 500px;
            text-align: center;
            color: $controlFontColor;
            i {
                margin-right: 5px;
            }

            i::before {
                font-size: 17px;
            }
        }
    }

  .cover {
        bottom: 0px;
        height: 300px;
        position: absolute;
        width: 100%;
        z-index: 2;
        &amp;:hover,
        &amp;:focus,
        &amp;:focus-within {
            .controls {
                display: flex;
            }
        }
    }
  .controls {
        width: 100%;
        height: $height;
        line-height: $height;
        font-size: 15px;
        display: none;
        z-index: 2;
        background-color: $backgroundColor;
        color: $controlFontColor;
        position: absolute;
        bottom: 0
        justify-content: space-between;

        &amp; &gt; [class^=&#34;el-icon-&#34;] {
            &amp;::before {
                font-size: 26px;
                line-height: $height;
                padding: 0 15px;
                cursor: pointer;
            }
        }

        .playStatus {
            width: 64px;
            height: $height;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
        #currentTime {
            width: 140px;
            height: $height;
            text-align: center;
        }

        .control-resolution {
            line-height: $height;
            .el-input__inner {
                background: $backgroundColor;
            }
            .el-input {
                width: 95px;
            }
            input {
                border: none;
                font-size: 15px !important;
                color: $controlFontColor;
                &amp;::-webkit-input-placeholder {
                    color: $controlFontColor;
                }
            }
        }
        #fullScreen {
            width: 32px;
            height: 32px;
            position: relative;
            top: 16px;
         
        }
    }</div>
    </div>
  </div>
</div><h1 id="总结">总结</h1>
<p>本次的前端业务<code>WebRTC</code>只做了浅显的了解和应用，只应用了接收流，还没有用到推流，<code>WebRTC</code>还有更多用法，比如实现实时视频通话、语音通话等，也许以后的业务中会用到，所以以这篇博客做一个入门记录~</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Vue &#43; WebSocket &#43; WaveSurferJS 实现H5聊天对话交互</title>
      <link>https://ygria.site/vue-chat/</link>
      <pubDate>Thu, 22 Oct 2020 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/vue-chat/</guid>
      <description>&lt;h1 id=&#34;引言&#34;&gt;引言&lt;/h1&gt;
&lt;p&gt;在与实现了语音合成、语义分析、机器翻译等算法的后端交互时，页面可以设计成更为人性化、亲切的方式。我们采用类似于聊天对话的实现，效果如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;智能客服&lt;/strong&gt;（输入文本，返回引擎处理后的文本结果）
&lt;img src=&#34;https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/85b8e7f52c9b329bfe65fc810f133a31.gif&#34; alt=&#34;85b8e7f52c9b329bfe65fc810f133a31.gif&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Vue + WebSocket + WaveSurferJS 实现H5聊天对话交互" /><h1 id="引言">引言</h1>
<p>在与实现了语音合成、语义分析、机器翻译等算法的后端交互时，页面可以设计成更为人性化、亲切的方式。我们采用类似于聊天对话的实现，效果如下：</p>
<ul>
<li>
<p><strong>智能客服</strong>（输入文本，返回引擎处理后的文本结果）
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/85b8e7f52c9b329bfe65fc810f133a31.gif" alt="85b8e7f52c9b329bfe65fc810f133a31.gif" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
<li>
<p><strong>语音合成</strong>（输入文本，返回文本以及合成的音频）
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/%E8%AF%AD%E9%9F%B3%E5%90%88%E6%88%90.gif" alt="语音合成.gif" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
</ul>
<p>如上图所示，返回文本后，再返回合成出的音频。
音频按钮嵌在对话气泡中，可以点击播放。</p>
<ul>
<li><strong>语音识别</strong>（在页面录制语音发送，页面实时展示识别出的文本结果）
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/%E8%AF%AD%E9%9F%B3%E8%AF%86%E5%88%AB.gif" alt="语音识别.gif" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>
<h1 id="实现功能及技术要点">实现功能及技术要点</h1>
<p><strong>1、基于WebSocket实现对话流</strong>
页面与后端的交互是实时互动的，所以采用WebSocket协议，而不是HTTP请求，这样后端推送回的消息可以实时显示在页面上。
WebSocket的返回是队列的、无序的，在后续处理中我们也需要注意这一点，在后文中会说到。
<strong>2、调用设备麦克风进行音频录制和转码加头，基于WebAudio、WaveSurferJS等实现音频处理和绘制</strong>
<strong>3、基于Vue的响应式页面实现</strong>
<strong>4、CSS3 + Canvas + JS 交互效果优化</strong></p>
<ul>
<li>录制音频CSS动画效果</li>
<li>聊天记录自动滚动
下面给出部分实现代码。</li>
</ul>
<h1 id="集成websocket">集成WebSocket</h1>
<p>我们的聊天组件是页面侧边打开的抽屉（<code>el-drawer</code>），Vue组件会在打开时创建，关闭时销毁。在组件中引入WebSocket，并管理它的开、关、消息接收和发送，使它的生命周期与组件一致（打开窗口时创建ws连接，关闭窗口时关闭连接，避免与后台连接过多。）</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-1">created(){
   if (typeof WebSocket === &#39;undefined&#39;) {
      alert(&#39;您的浏览器不支持socket&#39;)
    } else {
      // 实例化socket
      this.socket = new WebSocket(this.socketServerPath)
      // 监听socket连接
      this.socket.onopen = this.open
      // 监听socket错误信息
      this.socket.onerror = this.error
      // 监听socket消息
      this.socket.onmessage = this.onMessage
      this.socket.onclose = this.close
    }
}
destroyed(){
  this.socket.close()
}</div>
    </div>
  </div>
</div><p>如上，将WebSocket的事件绑定到JS方法中，可以在对应方法中实现对数据的接收和发送。
打开浏览器控制台，选中指定的标签，便于对<code>WebSocket</code>连接进行监控和查看。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417141841.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h1 id="音频录制采集">音频录制采集</h1>
<p>从浏览器端音频和视频采集基于网页即时通信（Web Real-Time
Communication，简称<code>WebRTC</code>） 的API。通过<code>WebRTC</code>的<code>getUserMedia</code>实现，获取一个<code>MediaStream</code>对象，将该对象关联到AudioContext即可获得音频。</p>



<blockquote>
    <p>可参考RecorderJS的实现： 
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL21hdHRkaWFtb25kL1JlY29yZGVyanMvYmxvYi9tYXN0ZXIvZXhhbXBsZXMvZXhhbXBsZV9zaW1wbGVfZXhwb3J0d2F2Lmh0bWw%3d"
    
     target="_blank"
>
https://github.com/mattdiamond/Recorderjs/blob/master/examples/example_simple_exportwav.html</a></p>
</blockquote>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-2">if (navigator.getUserMedia) {
      navigator.getUserMedia(
        { audio: true }, // 只启用音频
        function(stream) {
          var context = new(window.webkitAudioContext || window.AudioContext)()
          var audioInput = context.createMediaStreamSource(stream)
          var recorder = new Recorder(audioInput)

        },
        function(error) {
          switch (error.code || error.name) {
            case &#39;PERMISSION_DENIED&#39;:
            case &#39;PermissionDeniedError&#39;:
              throwError(&#39;用户拒绝提供信息。&#39;)
              break
            case &#39;NOT_SUPPORTED_ERROR&#39;:
            case &#39;NotSupportedError&#39;:
              throwError(&#39;浏览器不支持硬件设备。&#39;)
              break
            case &#39;MANDATORY_UNSATISFIED_ERROR&#39;:
            case &#39;MandatoryUnsatisfiedError&#39;:
              throwError(&#39;无法发现指定的硬件设备。&#39;)
              break
            default:
              throwError(&#39;无法打开麦克风。异常信息:&#39; &#43; (error.code || error.name))
              break
          }
        }
      )
    } else {
      throwError(&#39;当前浏览器不支持录音功能。&#39;)
    }</div>
    </div>
  </div>
</div>


<blockquote>
    <p>注意： 若navigator.getUserMedia获取到的是<code>undefined</code>，是Chrome浏览器的安全策略导致的，需要通过https请求或配置浏览器，配置地址： chrome://flags/#unsafely-treat-insecure-origin-as-secure</p>
</blockquote>



<blockquote>
    <p>浏览器采集到的音频为PCM格式(<code>PCM</code> （脉冲编码调制  <code>Pulse Code Modulation</code>）)，需要对音频加头才能在页面上进行播放。注意加头时采样率、采样频率、声道数量等必须与采样时相同，不然加完头后的音频无法解码。参考查看https://github.com/mattdiamond/Recorderjs/blob/master/src/recorder.js中<code>exportWav</code>方法。</p>
</blockquote>
<p>业务中对接的语音识别引擎为实时转写引擎，即：不是录制完成后再发送，而是一边录制一边进行编码并发送。
使用<code>onaudioprocess</code>方法监听语音的输入：
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417142734.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>参考这个实现，我们可以在每次监听到有数据写入时，从buffer中获取到录制到的数据，并进行编码、压缩，再通过WebSocket发送。</p>
<h1 id="vue组件设计和业务实现">Vue组件设计和业务实现</h1>
<p>分析页面业务逻辑，将代码拆分成两个组件：
<code>ChatDialog.vue</code> 聊天对话框页面，根据输入类型，分为文本输入、语音输入。
<code>ChatRecord.vue</code>聊天记录组件，根据发送方（自己或者系统）展示向左/向右的气泡，根据内容显示文本、音频等。<code>ChatDialog</code>是<code>ChatRecord</code>的父组件，遍历<code>ChatDialog</code>中的<code>chatList</code>对象（<code>Array</code>），将<code>chatList</code>中的项注入到<code>ChatRecord</code>中。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-3">&lt;div class=&#34;chat-list&#34;&gt;
            &lt;div v-for=&#34;(item,index) in chatList&#34; :key=&#34;index&#34; class=&#34;msg-wrapper&#34;&gt;
                &lt;chat-record ref=&#34;chatRecord&#34; :data=&#34;item&#34; @showJson=&#34;showJsonDialog&#34;&gt;&lt;/chat-record&gt;
            &lt;/div&gt;
            &lt;div id=&#34;msg_end&#34; style=&#34;height:0px; overflow:hidden&#34;&gt;&lt;/div&gt;
        &lt;/div&gt;
&lt;/div&gt;</div>
    </div>
  </div>
</div><p>对于聊天记录的气泡展示，与数据类型相关性很强，<code>ChatRecord</code>组件只关心对数据的处理和展示，我们可以完全不用关心消息的发送、接收、音频的录制、停止录制、接受音频等逻辑，只需要根据数据来展示不同的样式即可。
<strong>这样Vue的响应式就充分获得了用武之地</strong>：无需用代码对样式展示进行控制，只需要设计合理的数据格式和样式模板，然后注入不同的数据即可。
模板页面： 使用<code>v-if</code>控制，修改<code>chatList</code>里的对象内容即可改变页面展示。</p>
<p>根据业务需求，将<code>ChatRecord</code>可能接收到的数据分为以下几类：</p>
<p>发送方为自己：</p>
<ul>
<li>文本输入，显示文本
实现简单，不做赘述。</li>
<li>语音输入 Loading状态，显示波纹动画和计时
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/wave.gif" alt="wave.gif" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>
<p>计时器使用JS的<code>setInterval</code>方法，每100ms更新一次录制时长</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-4"> this.recordTimer = setInterval(() =&gt; {
        this.audioDuration = this.audioDuration &#43; 0.1
      }, 100)</div>
    </div>
  </div>
</div><p>停止后清空计时器：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-5"> clearInterval(this.recordTimer)</div>
    </div>
  </div>
</div><ul>
<li>语音输入完毕，根据录制的语音，绘制波纹
效果：
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417142409.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>
<p>使用<code>wavesurfer</code>插件:</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-6"> initWaveSurfer() {
      this.$nextTick(() =&gt; {
        this.wavesurfer = WaveSurfer.create({
          container: this.$refs.waveform,
          height: 20,
          waveColor: &#39;#3d6fff&#39;,
          progressColor: &#39;blue&#39;,
          backend: &#39;MediaElement&#39;,
          mediaControls: false,
          audioRate: &#39;1&#39;,
          fillParent: false,
          maxCanvasWidth: 500,
          barWidth: 1,
          barGap: 2,
          barHeight: 5,
          barMinHeight: 3,
          normalize: true,
          cursorColor: &#39;#409EFF&#39;
        })
        this.convertAudioToUrl(this.waveAudio).then((res) =&gt; {
          this.wavesurfer.load(res)

          setTimeout(() =&gt; {
            this.audioDuration = this.getAudioDuration()
          }, 100)
        })
      })
    },

   // 将音频转化成url地址
    convertAudioToUrl(audio) {
      let blobUrl = &#39;&#39;
      if (this.data.sendBy === &#39;self&#39;) {
        blobUrl = window.URL.createObjectURL(audio)
        return new Promise((resolve) =&gt; {
          resolve(blobUrl)
        })
      } else {
        return this.base64ToBlob({
          b64data: audio,
          contentType: &#39;audio/wav&#39;
        })
      }
    },

    base64ToBlob({ b64data = &#39;&#39;, contentType = &#39;&#39;, sliceSize = 512 } = {}) {
      return new Promise((resolve, reject) =&gt; {
        // 使用 atob() 方法将数据解码
        let byteCharacters = atob(b64data)
        let byteArrays = []
        for (
          let offset = 0;
          offset &lt; byteCharacters.length;
          offset &#43;= sliceSize
        ) {
          let slice = byteCharacters.slice(offset, offset &#43; sliceSize)
          let byteNumbers = []
          for (let i = 0; i &lt; slice.length; i&#43;&#43;) {
            byteNumbers.push(slice.charCodeAt(i))
          }
          // 8 位无符号整数值的类型化数组。内容将初始化为 0。
          // 如果无法分配请求数目的字节，则将引发异常。
          byteArrays.push(new Uint8Array(byteNumbers))
        }
        let result = new Blob(byteArrays, {
          type: contentType
        })
        result = Object.assign(result, {
          // 这里一定要处理一下 URL.createObjectURL
          preview: URL.createObjectURL(result),
          name: `XXX.wav`
        })
        resolve(window.URL.createObjectURL(result))
      })
    },</div>
    </div>
  </div>
</div><hr>
<p>发送方为系统：</p>
<ul>
<li>
<p>仅返回文本：显示文本</p>
</li>
<li>
<p>仅返回音频（参考发送方为自己的实现）
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417142600.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
<li>
<p>返回文本，随即返回文本对应的合成音频，显示文本和播放按钮
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417142626.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
</ul>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417142644.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>页面嵌入audio标签，将hidden设置为true使其不显示：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-7">&lt;div class=&#34;audio-player&#34;&gt;
          &lt;svg-icon v-if=&#34;!isPlaying&#34; icon-class=&#39;play&#39; @click=&#34;onClickAudioPlayer&#34; /&gt;
          &lt;svg-icon v-else icon-class=&#39;pause&#39; @click=&#34;onClickAudioPlayer&#34; /&gt;
          &lt;audio :src=&#34;playAudioUrl&#34; autostart=&#34;true&#34; hidden=&#34;true&#34; ref=&#34;audioPlayer&#34; /&gt;
        &lt;/div&gt;</div>
    </div>
  </div>
</div><p><code>playAudioUrl</code>的生成参考上面生成的<code>wavesurfer</code>的url。
使用<code>isPlaying</code>参数记录当前音频的播放状态，并使用<code>setTimeout</code>方法，当播放了音频时长后，将播放按钮自动置为<code>play</code>。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-8">  onClickAudioPlayer() {
      if (this.isPlaying) {
        this.$refs.audioPlayer.pause()
        this.isPlaying = false
      } else {
        // 每次点击时，开始播放，并在播放完毕将isPlaying置为false
        this.$refs.audioPlayer.currentTime = 0
        this.$refs.audioPlayer.play()
        this.isPlaying = true

        setTimeout(() =&gt; {
          // 将正在播放重置为false
          this.isPlaying = false
        }, Math.ceil(this.$refs.audioPlayer.duration) * 1000)
      }
    },</div>
    </div>
  </div>
</div><ul>
<li>聊天记录自动定位到最后一条：
使用<code>scrollIntoView()</code>方法</li>
<li>记录每次会话对应的记录ID（<code>recordId</code>）：
定义单次会话的id，并在返回的消息中回传，从而建立多条<code>websocket</code>返回的关联关系。</li>
</ul>
<hr>
<p>以上就是全部实现。难点主要是请求麦克风权限和对音频进行编码，<strong>在加wav头时必须保证和采样时的采样率、频率一致</strong> 。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Vue 定义指令和动态路由实现权限控制</title>
      <link>https://ygria.site/vue-permission-and-directive/</link>
      <pubDate>Thu, 27 Aug 2020 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/vue-permission-and-directive/</guid>
      <description>&lt;p&gt;功能概述：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据后端返回接口，实现路由动态显示&lt;/li&gt;
&lt;li&gt;实现按钮（HTML元素）级别权限控制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;涉及知识点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;路由守卫&lt;/li&gt;
&lt;li&gt;Vuex使用&lt;/li&gt;
&lt;li&gt;Vue自定义指令&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;导航守卫&#34;&gt;导航守卫&lt;/h1&gt;



&lt;blockquote&gt;
    &lt;p&gt;前端工程采用Github开源项目&lt;code&gt;Vue-element-admin&lt;/code&gt;作为模板，该项目地址：
&lt;a 
    
        href=&#34;https://ygria.site/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL1BhbkppYUNoZW4vdnVlLWVsZW1lbnQtYWRtaW4%3d&#34;
    
     target=&#34;_blank&#34;
&gt;
Github | Vue-element-admin&lt;/a&gt; 。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Vue 定义指令和动态路由实现权限控制" /><p>功能概述：</p>
<ul>
<li>根据后端返回接口，实现路由动态显示</li>
<li>实现按钮（HTML元素）级别权限控制</li>
</ul>
<p>涉及知识点：</p>
<ul>
<li>路由守卫</li>
<li>Vuex使用</li>
<li>Vue自定义指令</li>
</ul>
<h1 id="导航守卫">导航守卫</h1>



<blockquote>
    <p>前端工程采用Github开源项目<code>Vue-element-admin</code>作为模板，该项目地址：
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL1BhbkppYUNoZW4vdnVlLWVsZW1lbnQtYWRtaW4%3d"
    
     target="_blank"
>
Github | Vue-element-admin</a> 。</p>
</blockquote>
<p>在<code>Vue-element-admin</code>模板项目的
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9naXRodWIuY29tL1BhbkppYUNoZW4vdnVlLWVsZW1lbnQtYWRtaW4vYmxvYi9tYXN0ZXIvc3JjL3Blcm1pc3Npb24uanM%3d"
    
     target="_blank"
>
src/permission.js</a>文件中，给出了路由守卫、加载动态路由的实现方案，在实现了基于不同角色加载动态路由的功能。我们只需要稍作改动，就能将基于角色加载路由改造为基于权限加载路由。</p>



<blockquote>
    <p><strong>导航守卫</strong>：可以应用于在路由跳转时，对用户的登录状态或权限进行判断。项目中使用全局前置守卫。参考Vue官方文档：
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9yb3V0ZXIudnVlanMub3JnL3poL2d1aWRlL2FkdmFuY2VkL25hdmlnYXRpb24tZ3VhcmRzLmh0bWw%3d"
    
     target="_blank"
>
https://router.vuejs.org/zh/guide/advanced/navigation-guards.html</a></p>
</blockquote>
<h2 id="后台返回接口">后台返回接口</h2>
<p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLWNlODcwMTFmNDBkYjljNzIucG5n?x-oss-process=image/format,png" alt="getUserInfo接口返回用户信息和路由、操作权限等" class="img-hide" loading="lazy" decoding="async" />
权限系统后台采用基于角色的权限控制方案（<code>role-based access control</code>），如上图所示，
该用户信息接口将查询用户所具有的所有角色，再将这些角色的权限并集按照路由 - 操作整合在一起返回。在用户登录入系统后，我们从后台请求获得用户信息（个人信息 + 权限信息），作为全局属性储存在前端。不同权限的用户看到的页面不同，依赖于这些属性，它们决定了路由如何加载、页面如何渲染。</p>
<p>这种多个组件依赖一组属性的场景，Vue提供了<code>VueX</code>作为全局状态管理方案。</p>
<h2 id="使用vuex存储权限信息">使用VueX存储权限信息</h2>
<p>在<code>src/store/moudules</code>目录下定义<code>permission.js</code></p>
<h3 id="1定义异步方法方法内部包含http请求从后台拉取数据">1.定义异步方法，方法内部包含HTTP请求从后台拉取数据</h3>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-1">import http from &#39;../../axios&#39;;
async function getUserInfo() {
  const res = await http.getUserInfo();
  return res;
}</div>
    </div>
  </div>
</div><p>使用<code>await</code>关键字，保证执行顺序正确。这里是为了保证能拿到接口返回的内容，以便于下一步处理。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-2">const actions = {
  getPermissions({ commit }) {
    return new Promise(resolve =&gt; {
      getUserInfo().then(res =&gt; {
        if (res) {
          let permissionList = res.permissionList;
          commit(&#39;SET_PERMISSIONS&#39;, permissionList);
          // 根据后台返回的路由，生成实际可以访问的路由
          let accessRoutes = filterAsyncRoutesByPermissions(asyncRoutes, permissionList);
          commit(&#39;SET_ROUTES&#39;, accessRoutes);
          commit(&#39;SET_USER_INFO&#39;, { name: res.name, accountName: res.accountName })
          resolve(accessRoutes);
        } else {
          resolve([]);
        }
      }).catch(() =&gt; resolve([]));
    })
  }
}</div>
    </div>
  </div>
</div><p>VueX中action定义异步方法。</p>
<h3 id="2-定义静态动态路由">2. 定义静态、动态路由</h3>
<p><code>src/router/index.js</code>
静态路由：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-3">export const constantRoutes = [
    {
        path: &#39;/redirect&#39;,
        component: Layout,
        hidden: true,
        children: [
            {
                path: &#39;/redirect/:path(.*)&#39;,
                component: () =&gt; import(&#39;@/views/redirect/index&#39;),
            },
        ],
    ,
  ...
    {
        path: &#39;/404&#39;,
        component: () =&gt; import(&#39;@/views/error-page/404&#39;),
        hidden: true,
    }
];</div>
    </div>
  </div>
</div><p>动态路由：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-4">export const asyncRoutes = [
    {
        path: &#39;/system&#39;,
        component: Layout,
        name: &#39;系统管理&#39;,
        meta: { title: &#39;系统管理&#39;, icon: &#39;el-icon-user&#39;, affix: true },
        children: [
            {
                path: &#39;/system&#39;,
                component: () =&gt; import(&#39;@/views/management/system/Index&#39;),
                meta: { title: &#39;系统管理&#39;, icon: &#39;el-icon-setting&#39;, affix: true },
            },
        ],
    }
...
]</div>
    </div>
  </div>
</div><p>静态路由中定义了所有用户均可访问的路由，动态路由中定义了动态加载的路由。</p>
<h3 id="3根据权限过滤并排序路由">3.根据权限过滤并排序路由</h3>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-5">export function filterAsyncRoutesByPermissions(routes, menus) {
  const res = []
  routes.forEach(route =&gt; {
    const tmp = { ...route }
    let index = menus.map(menu =&gt; menu.url).indexOf(tmp.path);
    if (index != -1) {
      // 后端返回路由信息覆盖前端定义路由信息
      tmp.name = menus[index].name;
      // debugger;
      tmp.meta.title = menus[index].name;
      tmp.children.forEach(child =&gt; {
        if (child.path == tmp.path) {
          child.meta.title = tmp.meta.title;
        }
      })
      res.push(tmp)
    }
  });
  // 根据返回菜单顺序，确定路由顺序
  /**
   * TODO 子菜单排序
   */
  res.sort((routeA, routeB) =&gt; menus.map(menu =&gt; menu.url).indexOf(routeA.path) - menus.map(menu =&gt; menu.url).indexOf(routeB.path))
  return res
}</div>
    </div>
  </div>
</div><p>根据url匹配，匹配到url的路由则加入数组。最终用户可以访问的路由 = 允许访问的动态路由 + 不需要权限的静态路由。</p>
<h3 id="4srcpermissionjs中的处理逻辑">4.src/permission.js中的处理逻辑</h3>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-6">// 引入store
import store from &#39;./store&#39;;
const whiteList = [&#39;/login&#39;, &#39;/auth-redirect&#39;]; // no redirect whitelist

// 路由守卫
router.beforeEach(async (to, from, next) =&gt; {
    //start progress bar
    NProgress.start()
    if (hasToken) {
        if (to.path === &#39;/login&#39;) {
          // ... 省略登出逻辑
            NProgress.done();
        } else {    
            // 查看是否已缓存过动态路由
            const hasRoutes = store.getters.permission_routes &amp;&amp; store.getters.permission_routes.length &gt; 0;
            if (hasRoutes) {
                next();
            } else {
                try {
                    const accessRoutes = await store.dispatch(&#39;permission/getPermissions&#39;);
                    router.addRoutes(accessRoutes);
                    const toRoute = accessRoutes.filter((route) =&gt; route.path == to.path);
                    next({ path: toRoute.length &gt; 0 ? toRoute[0].path : accessRoutes[0].path, replace: true });
                } catch (error) {
                    next(`/login?redirect=${to.path}`);
                    NProgress.done();
                }
            }
        }
    } else {
        if (whiteList.indexOf(to.path) !== -1) {
            // in the free login whitelist, go directly
            next();
        } else {
            next(`/login?redirect=${to.path}`);
            NProgress.done();
        }
    }
});

router.afterEach(() =&gt; {
    // finish progress bar
    NProgress.done();
});</div>
    </div>
  </div>
</div><p>以上是动态路由实现方案。</p>
<hr>
<p><code>Vue</code>支持自定义指令，用法类似于Vue原生指令如<code>v-model</code>、<code>v-on</code>等，网上查阅到的大部分细粒度权限控制方案都使用这种方法。下面将给出我的实现。</p>
<h1 id="自定义指令">自定义指令</h1>
<p>自定义指令 <code>v-permission</code>：</p>
<p><code>src/directive/permission/index.js</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-7">import store from &#39;@/store&#39;
 export default {
  inserted(el, binding, vnode) {
    const { value } = binding
    const permissions = store.getters &amp;&amp; store.getters.permissions;
    if (value) {
      // 获取当前所挂载的vue所在的上下文节点url
      let url = vnode.context.$route.path;
      let permissionActions = permissions[url];
      // console.log(permissionActions)
      const hasPermission = permissionActions.some(action =&gt; {
        if (value.constructor === Array) {
          // 或判断： 只要存在任1，判定为有权限
          return value.includes(action);
        } else {
          return action === value;
        }
      })
      if (!hasPermission) {
        el.parentNode &amp;&amp; el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`need further permissions!`)
    }
  }
}</div>
    </div>
  </div>
</div><p>后端给出的权限数据是路由（url）与操作的对应Map，url可以通过将要挂载到的vnode属性拿到。这个方法有点类似于AOP，在虚拟元素挂载之后做判断，如果没有权限则从父元素上移除掉。
使用方法：</p>
<ul>
<li>举例一：单个按钮 （注意双引号套单引号的写法）</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-8">  &lt;el-button @click.native.prevent=&#34;editUser(scope.row)&#34; type=&#34;text&#34; size=&#34;small&#34; v-permission=&#34;&#39;op_edit&#39;&#34;&gt;
                                编辑
 &lt;/el-button&gt;</div>
    </div>
  </div>
</div><ul>
<li>举例二：或判断（传入数组），只要拥有数组中一个权限，则保留元素，所有权限都没有，则移除。
在上一篇博客
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC8wNjZjNGNlNGM3Njc%3d"
    
     target="_blank"
>
https://www.jianshu.com/p/066c4ce4c767</a>
下拉菜单上增加控制：
<img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLTJiNDNjZGFjNThmZmQzM2IucG5n?x-oss-process=image/format,png" alt="dot-dropdown" class="img-hide" loading="lazy" decoding="async" />
<img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLWExZTQyODhhOTAzODA4MGQucG5n?x-oss-process=image/format,png" alt="数据定义" class="img-hide" loading="lazy" decoding="async" />
相应数据定义中增加action属性。</li>
</ul>
<p>该方法无法覆盖所有场景，所以依然给出相应工具类：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-9">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-9">/**
 * 
 * @param {*当前页面路由} url 
 * @param {*操作code e.g op_add } value 
 * @return true/false 是否有该项权限
 */
function checkPermission(url, value) {

    const permissions = store.getters &amp;&amp; store.getters.permissions;
    let permissionActions = permissions[url];

    if (!permissionActions) {
        return false;
    }

    let hasPermission = permissionActions.some(action =&gt; {
        if (value.constructor === Array) {
            // 或判断： 只要存在任1，判定为有权限
            return value.includes(action);
        } else {
            return action === value;
        }
    });
    return hasPermission;

}</div>
    </div>
  </div>
</div><p>以上完成按钮粒度权限控制。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Vue&#43;Element UI 树形控件整合下拉功能菜单（tree &#43; dropdown &#43;input）</title>
      <link>https://ygria.site/vue-dropdown-menu/</link>
      <pubDate>Sat, 08 Aug 2020 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/vue-dropdown-menu/</guid>
      <description>&lt;p&gt;这篇博客主要介绍树形控件的两个小小的功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;下拉菜单&lt;/li&gt;
&lt;li&gt;输入过滤框&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以CSS样式为主，也会涉及到Vue组件和element组件的使用。&lt;/p&gt;
&lt;p&gt;对于没有层级的数据，我们可以使用表格或卡片来展示。要展示或建立层级关系，就一定会用到树形组件了。
使用Vue + Element UI，构建出最基本的树如下图所示：
&lt;img src=&#34;https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLTkyMGUwZTA5MDJmOTMyM2YucG5n?x-oss-process=image/format,png&#34; alt=&#34;最简单的树结构&#34; class=&#34;img-hide&#34; loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;
现在我们就要在这个基础上进行改造，使页面更加符合我们的交互场景。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Vue+Element UI 树形控件整合下拉功能菜单（tree + dropdown +input）" /><p>这篇博客主要介绍树形控件的两个小小的功能：</p>
<ul>
<li>下拉菜单</li>
<li>输入过滤框</li>
</ul>
<p>以CSS样式为主，也会涉及到Vue组件和element组件的使用。</p>
<p>对于没有层级的数据，我们可以使用表格或卡片来展示。要展示或建立层级关系，就一定会用到树形组件了。
使用Vue + Element UI，构建出最基本的树如下图所示：
<img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLTkyMGUwZTA5MDJmOTMyM2YucG5n?x-oss-process=image/format,png" alt="最简单的树结构" class="img-hide" loading="lazy" decoding="async" />
现在我们就要在这个基础上进行改造，使页面更加符合我们的交互场景。</p>
<h1 id="一下拉菜单">一、下拉菜单</h1>
<p>将下拉菜单嵌到树节点中，使操作更加简便、紧凑。</p>
<h2 id="效果演示">效果演示</h2>
<p>效果如图：</p>
<ul>
<li>
<p>图示1：悬浮在树节点状态
<img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLTE0NGI2ZDJmOGYzMWM0ODgucG5n?x-oss-process=image/format,png" alt="悬浮在树节点状态" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
<li>
<p>图示2：点击三个点图标状态
<img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLWMxMzFmMmExNDBjZTk4NTYucG5n?x-oss-process=image/format,png" alt="点击三个点图标状态" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
<li>
<p>图示3： 选中并选择菜单
<img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLTdmOGY5MzA5MGU5OGQ5ZWEucG5n?x-oss-process=image/format,png" alt="下拉菜单效果" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
</ul>
<p>如上，当鼠标悬浮在树节点上时，出现竖着的三个小点，点击时弹出下拉菜单，显示可以对树节点进行的操作。</p>
<h2 id="实现步骤">实现步骤</h2>
<h3 id="1使用插槽slot--子组件">1、使用插槽（<code>slot</code>） + 子组件</h3>
<ul>
<li>父组件（含有树形控件）模板代码</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-1"> &lt;el-tree :data=&#34;resourceTree&#34; :ref=&#34;tree&#34; node-key=&#34;id&#34;  size=&#34;small&#34;
                :highlight-current=&#34;true&#34; :check-on-click-node=&#34;true&#34; &gt;
                &lt;span class=&#34;custom-tree-node&#34; slot-scope=&#34;{ node, data }&#34;&gt;
                    &lt;div class=&#34;custom-tree-node-wrapper&#34;&gt;
                        &lt;span class=&#34;custom-tree-node-label&#34;&gt;
                            {{ node.label }}
                        &lt;/span&gt;
                        &lt;span class=&#34;operate-btns&#34;&gt;                
                            &lt;dot-dropdown  :events=&#34;dropMenuEvents&#34; :data=&#34;{node,data}&#34; @addNode=&#34;addNode&#34; /&gt;
                        &lt;/span&gt;
                    &lt;/div&gt;
                &lt;/span&gt;
            &lt;/el-tree&gt;</div>
    </div>
  </div>
</div><h3 id="2-dotdropdown-下拉框代码">2、 DotDropdown 下拉框代码</h3>
<p>很多树形结构都会使用该下拉框，所以定义组件，方便复用。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-2">&lt;template&gt;
    &lt;el-dropdown trigger=&#34;click&#34; class=&#34;custom-tree-menu&#34; size=&#34;small&#34;&gt;
        &lt;i class=&#34;el-icon-more rotate &#34; /&gt;
        &lt;el-dropdown-menu slot=&#34;dropdown&#34;&gt;
            &lt;el-dropdown-item v-for=&#39;(item,index) in events&#39; :key=&#34;index&#34; :divided=&#34;index &gt;0&#34; @click.native=&#34;clickMenu(item)&#34;&gt;
                {{item.label}}
            &lt;/el-dropdown-item&gt;
        &lt;/el-dropdown-menu&gt;
    &lt;/el-dropdown&gt;
&lt;/template&gt;
&lt;script&gt;
export default {
  props: {
    events: {
      type: Array,
      default: function() {
        return [
          {
            label: &#39;新建同级&#39;,
            funcName: &#39;addNode&#39;
          },
          {
            label: &#39;编辑&#39;,
            funcName: &#39;editNode&#39;
          },
          {
            label: &#39;删除&#39;,
            funcName: &#39;deleteNode&#39;
          }
        ]
      }
    },
    // 注入数据
    data: {
      type: Object
    }
  },
  methods: {
    clickMenu(item) {
      this.$emit(item.funcName, this.data)
    }
  }
}</div>
    </div>
  </div>
</div><p>模板代码很简单，是一个点击触发的下拉菜单组件（<code>trigger=&quot;click&quot;</code>），菜单循环props中传入的events属性，data为从父组件拿到的数据，定义了菜单和菜单的事件（方法名称），当点击菜单（<code>@click.native</code>）时，触发：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-3">this.$emit(item.funcName, this.data)</div>
    </div>
  </div>
</div><p>容易看出，数据和实现方法都是父组件的，该子组件只做了转发。</p>
<h3 id="3-父组件使用子组件">3、 父组件使用子组件</h3>
<p>引入和注册子组件，并定义好对应的方法即可。下面给出使用示例：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-4">&lt;span class=&#34;operate-btns&#34;&gt;
          &lt;dot-dropdown v-if=&#34;data.type === 1&#34; :events=&#34;dropMenuEvents&#34; :data=&#34;{node,data}&#34;/&gt;
          &lt;dot-dropdown v-if=&#34;data.type === 2&#34; :events=&#34;sysDropMenuEvents&#34; :data=&#34;{node,data}&#34; @addNode=&#34;addResource&#34; /&gt;
 &lt;/span&gt;</div>
    </div>
  </div>
</div><p>根据数据节点的类型，注入不同的<code>events</code>属性，显示不同的下拉框菜单。（常用场景：根节点不可删除、不可编辑，只能新增子级，叶子节点可以新增同级和子级）。
在父组件中的data中定义:</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-5">sysDropMenuEvents: [{ label: &#39;新增资源&#39;, funcName: &#39;addNode&#39; }],

dropMenuEvents: [
      { label: &#39;新建同级&#39;, funcName: &#39;addPeerNode&#39; },
      { label: &#39;新建子级&#39;, funcName: &#39;addNode&#39; },
      { label: &#39;分配操作&#39;, funcName: &#39;distributeAction&#39; },
      { label: &#39;编辑&#39;, funcName: &#39;editNode&#39; },
      { label: &#39;删除&#39;, funcName: &#39;removeNode&#39; }
 ]</div>
    </div>
  </div>
</div><p>父组件编写实际功能方法：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> js</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="js" data-theme="breeze" id="code-id-6">// 打开新增资源弹窗
    addResource({ node, data }) {
      ...
    }</div>
    </div>
  </div>
</div>


<blockquote>
    <p>父组件注入data时，将树节点插槽中的node和data都注入了进去（<code>:data=&quot;{node,data}&quot;</code>），在使用时也可以用过同样的大括号+属性名的方式拿到对应的属性，这里体现了ES6解构赋值的特性。</p>
</blockquote>
<h3 id="4父组件样式">4、父组件样式</h3>
<p>父组件中，树节点的样式：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> css3</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="css3" data-theme="breeze" id="code-id-7"> .el-tree-node__content {
      position: relative;
      .operate-btns {
          position: absolute;
          right: 2px;
          display: none;
      }
      // 鼠标悬停时，展示
      &amp;:hover,
      :focus-within {
          .operate-btns {
              display: inline;
          }
      }
  }
 }</div>
    </div>
  </div>
</div><ul>
<li>子绝对，父相对，使操作按钮靠贴边显示</li>
<li>无状态时不显示，hover或内部元素被激活时显示（<code>:hover :focus-within</code>）</li>
</ul>
<h3 id="5子组件样式">5、子组件样式</h3>
<p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLWQyMWRhNmE4ZjU0YzcxNDQucG5n?x-oss-process=image/format,png" alt="最终效果" class="img-hide" loading="lazy" decoding="async" /></p>
<ul>
<li>旋转图标
原本的图标使用的是element UI提供的 <code>&lt;i class=&quot;el-icon-more&quot; /&gt;</code>，是横着的点点点↓
<img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLTNhNzA5Njg1ZWI2OGY5OWQucG5n?x-oss-process=image/format,png" alt="原本的图标" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>
<p>图标有点小，颜色也不喜欢。改下字体让它变大一点。这里注意需要修改的是元素的before伪类:</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-8"> .el-icon-more:before {
      content: &#34;\E794&#34;;
      color: #c0c4cc;
      font-size: 20px;
}</div>
    </div>
  </div>
</div><p>加一个<code>transform</code>将它旋转90°，悬停时鼠标样式为<code>pointer</code>:</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-9">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-9">.rotate {
      cursor: pointer;
      margin-left: 5px;
      transform: rotate(90deg);
 }</div>
    </div>
  </div>
</div><p>点击时，增加圆形半透明的灰色背景：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-10">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-10">
          <button id="copyButton-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-10" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-10">.rotate:focus {
      width: 20px;
      height: 20px;
      border-radius: 4em;
      background-color: rgba(130, 132, 138, 0.2);
}</div>
    </div>
  </div>
</div><p>至此，下拉全部完成。
除了用在树节点中，也可以用在表格中。</p>
<h1 id="输入过滤框">输入过滤框</h1>
<p><code>el-tree</code>提供了过滤方法，使用<code>:filter-node-method=&quot;filterNode&quot;</code>属性即可。这里主要分享样式：
效果：
<img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLWU0NTIzMjFiODk0NGIzOWIucG5n?x-oss-process=image/format,png" alt="非激活状态" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLTQwNmFkNzZlY2Y5MGYyM2IucG5n?x-oss-process=image/format,png" alt="激活输入框状态" class="img-hide" loading="lazy" decoding="async" /></p>
<p>模板代码：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-11">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-11">
          <button id="copyButton-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-11" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-11">&lt;div class=&#34;filter-input&#34;&gt;
    &lt;el-input placeholder=&#34;输入资源名称进行过滤&#34; v-model=&#34;filterText&#34; size=&#34;small&#34; prefix-icon=&#34;el-icon-search&#34;&gt;
       &lt;/el-input&gt;
&lt;/div&gt;</div>
    </div>
  </div>
</div><ul>
<li>去掉输入框上、左右边框和圆角，并两侧留出10px边距</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-12">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-12">
          <button id="copyButton-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-12" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-12">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-12"> .el-input__inner,.el-input-group__prepend{
      width: calc(100% - 20px);
      margin:0 10px;
      height: 40px;
      border-top:none;
      border-width: 0 0 1px;
      border-radius:0;
    }</div>
    </div>
  </div>
</div><ul>
<li>调整搜索图标大小、颜色和粗细，并稍微调整位置：</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-13">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-13">
          <button id="copyButton-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-13" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-13">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-13">    .el-input__prefix{
      .el-input__icon{
        margin-right: 15px;
        display: inline-block;
      }
      font-size:18px;
    }</div>
    </div>
  </div>
</div>


<blockquote>
    <p>此时点击输入框，只有下边变蓝色，希望图标的样式也随之更改。
只有<code>input</code>被触发了<code>focus</code>事件，<code>icon</code>感知不到，<code>:focus</code>伪类不满足需求了。我们可以使用<code>:focus-within</code>伪类，加在<code>icon</code>和<code>input</code>共同的父类上。</p>
</blockquote>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-14">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-14">
          <button id="copyButton-id-14">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-14" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-14">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-14">.el-input:focus-within{
      .el-icon-search:before {
         color: #3c6eff;
         font-weight: bold;
      }
    }</div>
    </div>
  </div>
</div><p>至此完成。</p>
<h1 id="总结">总结</h1>
<p>没写前端之前以为前端只是展示从后端拿到的数据，但现在觉得，前端作为面向用户的直接门面，承担了绝大部分交互体验优化的任务。
合理的布局和样式能避免用户的无效操作，体验的优化是一个漫长而细致的过程，可能需要仔细打磨，才能做出好用的产品。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Linux虚拟网络：Docker网络知识之基础篇</title>
      <link>https://ygria.site/learn-linux-network/</link>
      <pubDate>Tue, 30 Jun 2020 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/learn-linux-network/</guid>
      <description>&lt;p&gt;我们在工作中应用了docker容器化技术，服务的部署、维护和扩展都方便了很多。然而，近期在私有化部署过程中，由于不同服务器环境的复杂多变，常常遇到网络方面的问题，现象为容器服务运行正常，但宿主机、容器之间网络不通。
本篇博客旨在总结：&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Linux虚拟网络：Docker网络知识之基础篇" /><p>我们在工作中应用了docker容器化技术，服务的部署、维护和扩展都方便了很多。然而，近期在私有化部署过程中，由于不同服务器环境的复杂多变，常常遇到网络方面的问题，现象为容器服务运行正常，但宿主机、容器之间网络不通。
本篇博客旨在总结：</p>
<ul>
<li>Linux虚拟网络及docker网络的基础知识</li>
<li>遇到网络问题时排查问题思路</li>
<li>常用指令和工具的使用</li>
</ul>
<p>以上三部分作为之后的参考，本篇文章也将会在日后实践过程中逐渐补充。本篇为第一篇，主要介绍基础知识</p>
<h1 id="linux网络虚拟化基础">Linux网络虚拟化基础</h1>
<h2 id="network-namespace">Network Namespace</h2>



<blockquote>
    <p>网络命名空间，是Linux 2.6.x内核版本之后提供的功能，主要用于资源的隔离。namespace是实现网络虚拟化的重要功能，使用它，一个Linux系统可以抽象出多个网络子系统，各个子系统都有自己独立的网卡、路由表、iptables、协议栈等网络资源。不管是虚拟机还是容器，运行时仿佛自己都在独立的网络中。</p>
</blockquote>
<p><code>ip netns</code>命令用于完成对ns的各种操作，<code>ip netns exec</code>子命令用于在namespace执行指令。</p>
<h2 id="veth-pairvirtual-ethernet-pair">Veth Pair（Virtual Ethernet Pair）</h2>



<blockquote>
    <p>成对虚拟设备端口。它总是成对出现，一端连着协议栈，一端彼此连着。从其中一个端口发出的数据包，可以直接出现在与它对应的另一个端口上，即使它们在不同的namespace中。</p>
</blockquote>
<p><img src="https://upload-images.jianshu.io/upload_images/6810620-8187694f1d4d7dd2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="Veth Pair功能：在不同的ns中通信" class="img-hide" loading="lazy" decoding="async" />
如上图，一对veth-pair直接将两个namespace连接在一起。</p>
<ul>
<li>使用如下图所示命令，测试veth pair功能
<img src="https://upload-images.jianshu.io/upload_images/6810620-88e4ef5d99f13359.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="测试使用veth pair连通两个namespace" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>
<h2 id="bridge-网桥">Bridge 网桥</h2>
<p>veth pair打破了Network Namespace的限制，实现了不同Network Namespace之间的通信。但是veth pair的局限性也很明显，只能实现两个网络接口的通信。
Linux中引入网桥来实现多个网络接口之间的通信，可以将一台机器上的若干接口连通起来。在OSI网络模型中，网桥属于数据链路层。</p>
<p><img src="https://upload-images.jianshu.io/upload_images/6810620-8b57f92a2c1941ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="网桥连通多个端口示意图" class="img-hide" loading="lazy" decoding="async" /></p>
<p>和网桥相关的操作使用命令<code>brctl</code>，需要先安装<code>bridge-utils</code>工具包。安装指令：
<code>yum install bridge-utils</code></p>
<h2 id="iptablesnetfilter">iptables/Netfilter</h2>
<p>请参考：
<a 
    
        href="/tiaozhuan?target=aHR0cDovL3d3dy56c3l0aGluay5uZXQvYXJjaGl2ZXMvMTE5OQ%3d%3d"
    
     target="_blank"
>
iptables详解（1）：iptables概念</a></p>
<h1 id="docker网络基础">Docker网络基础</h1>
<p>Docker支持四种网络模式：host模式，container模式，none模式和bridge模式。默认使用的是桥接模式。
使用<code>docker network ls</code>指令可以查看到宿主机上所有的Docker网络：
<img src="https://upload-images.jianshu.io/upload_images/6810620-1986bf9ba4a3a090.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="当前宿主机所有的docker网络" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="bridge-桥接模式">Bridge 桥接模式</h2>



<blockquote>
    <p>Docker在启动时，默认会自动创建网桥设备docker0，Docker在运行时，守护进程通过docker0为docker的容器提供网络通信服务。
当Docker启动容器时，会创建一对Veth Pair，并将其中一个veth网络设备附加到网桥docker0，另一个加入容器的network namespace中。</p>
</blockquote>
<p>根据上一节中关于网桥的定义，我们很容易画出示意图：</p>
<p><img src="https://upload-images.jianshu.io/upload_images/6810620-9d869758c007bf38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="Docker网桥模型" class="img-hide" loading="lazy" decoding="async" /></p>
<p>由上图可得，容器可以通过网桥互相通信。如果不想使用默认的网桥设备，也可以在启动docker daemon的时候使用<code> --bridge==BRIDGE</code>参数指定其他网桥。
然而这还不够，Docker容器还需要与外网进行相互通信。这里涉及到NAT相关知识。</p>



<blockquote>
    <ul>
<li>NAT
网络地址转换，就是替换IP报文头部的地址信息。NAT通常部署在一个组织的网络出口位置，通过将内部网络IP地址替换为出口的IP地址，提供公网可达性和上层协议的链接地址。（
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cuemhpaHUuY29tL3F1ZXN0aW9uLzMxMzMyNjk0"
    
     target="_blank"
>
请参考：NAT相关科普</a>）</li>
<li>SNAT<br>
源地址转换即内网地址向外访问时，发起访问的内网ip地址转换为指定的ip地址（可指定具体的服务以及相应的端口或端口范围），这可以使内网中使用保留ip地址的主机访问外部网络，即内网的多部主机可以通过一个有效的公网ip地址访问外部网络。</li>
</ul>
</blockquote>
<p>使用<code>iptables -t nat -vnL</code>指令查看宿主机NAT表。</p>
<p><img src="https://upload-images.jianshu.io/upload_images/6810620-2a4faa72585b3bb5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="宿主机iptables表部分截图" class="img-hide" loading="lazy" decoding="async" /></p>
<p>查看规则：
<code>2051  125K MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0 </code>
这条规则就关系着Docker容器与外界的通信，含义为将源地址为172.17.0.0/16的数据包（就是docker容器中发出的数据），如果不是从docker0网卡发出时，做SNAT转换，将IP包的源地址替换为相应网卡的地址。
对于外界来说，从docker容器内发出的请求，和宿主机发出的请求相同。</p>
<p>外界想要访问Docker容器的服务呢？
在启动docker容器时，我们使用 <code>-p</code>参数指定端口，这时其实是在iptables中添加了规则，如下图所示：</p>
<p><img src="https://upload-images.jianshu.io/upload_images/6810620-440166ee3f148b3a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="宿主机iptables表部分截图" class="img-hide" loading="lazy" decoding="async" /></p>
<p>DNAT规则，将发送到宿主机的流量转发到真正提供服务的容器IP端口上。</p>
<h2 id="host模式">host模式</h2>
<p>Docker容器与宿主机使用相同的网络环境，直接使用宿主机的IP和端口及其他网络设备。这样虽然避免了很多桥接带来的网络问题，但同时也容易造成网络环境的混淆和冲突，比如端口被占用等。不推荐。</p>
<h2 id="container">container</h2>
<p>指定与某一容器共享网络。</p>
<h2 id="none">none</h2>
<p>不配置任何网络。</p>
<h3 id="--link">&ndash;link</h3>
<p>docker容器之间还可以通过<code>--link</code>阐述进行通信，当提供服务的容器只希望个别容器能够访问时，我们可以使用该指令，提供更为高效、安全的连接方式。</p>
<h1 id="小结">小结</h1>
<p>对Linux虚拟网络基础知识的简单学习后，有助于理清楚下一步排查问题思路。
下一篇博客将介绍目前遇到问题时的排查思路和解决方案，并列举一些常用工具。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>CICD Jenkins &amp; Gitlab集成 WebHook触发构建</title>
      <link>https://ygria.site/how-to-use-jenkins3/</link>
      <pubDate>Sat, 06 Jun 2020 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/how-to-use-jenkins3/</guid>
      <description>&lt;p&gt;在上一篇博客中，我们学习了&lt;code&gt;Jenkins&lt;/code&gt;的搭建和插件+流水线的基本使用方法，&lt;code&gt;Jenkins&lt;/code&gt;极大地提升了部署效率。
最近想学习一下如何集成&lt;code&gt;GitLab webhook&lt;/code&gt;，实现进一步解放双手，目标：&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="CICD Jenkins & Gitlab集成 WebHook触发构建" /><p>在上一篇博客中，我们学习了<code>Jenkins</code>的搭建和插件+流水线的基本使用方法，<code>Jenkins</code>极大地提升了部署效率。
最近想学习一下如何集成<code>GitLab webhook</code>，实现进一步解放双手，目标：</p>
<ul>
<li>推送（<code>git push</code>）触发构建</li>
<li>推送到指定分支触发构建</li>
<li>根据<code>commit</code>的文件，结合<code>mvn -pl </code>指令，实现部分增量构建，并记录<code>commit</code>信息</li>
</ul>
<p>推送事件也可以换成<code>Tag push events</code>、<code>Merge request events</code>等其他触发条件，根据需要自由选择。</p>
<h1 id="基础实现">基础实现</h1>
<p>使用<code>Gitlab Hook Plugin</code>，并在Jenkins和GitLab中分别配置。</p>
<h2 id="下载并配置插件">下载并配置插件</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153252.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153238.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="在gitlab中配置">在<code>GitLab</code>中配置</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153303.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153312.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153324.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153333.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p><strong>至此，目标中的前两条，推送构建和推送到指定分支构建实现！</strong></p>
<h1 id="进阶实现">进阶实现</h1>
<p>从上述过程，我们也可以看出，<code>WebHook</code>的本质就是从<code>GitLab</code>发了一条请求，<code>Jenkins</code>配置了一个终端地址（<code>endpoint</code>）来接收，从而实现了两个步骤的串联。
这个请求实质上就是一条<code>HTTP POST</code>请求。
相信接触过服务互相调用的小伙伴们都不陌生。有了请求体，我们自然可以拿到自己想要的东西，进行进一步的处理了。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153344.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="jenkins插件generic-webhook-trigger-pugin">Jenkins插件:Generic WebHook Trigger Pugin</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153354.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>从插件简介来看，支持接收任何一个<code>HTTP</code>请求，当然也包括接收<code>GitLab</code>发送的请求。</p>
<h3 id="在jenkins-job中配置接收地址">在<code>Jenkins Job</code>中配置接收地址</h3>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153403.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h3 id="配置鉴权token">配置鉴权token</h3>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153413.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>我直接使用<code>admin</code>帐号创建，在发送请求时需要携带此token。</p>
<h3 id="gitlab配置">GitLab配置</h3>
<p>在Gitlab中的配置与上文相同，格式为：
<code>http://admin:${token}@${JENKINS_IP}:${PORT}/generic-webhook-trigger/invoke</code>
填上刚刚配置生成的<code>token</code>和自己的<code>Jenkins</code>地址和端口即可。
同样可以使用自带的测试来测试连接，返回200成功。</p>
<ul>
<li>如果返回<code>404</code>，看配置的地址是否有误</li>
<li>返回<code>403</code>，查看权限配置是否有误
<strong>至此，连接建立成功！</strong></li>
</ul>
<h1 id="编写流水线脚本">编写流水线脚本</h1>
<p>关于如何使用声明式流水线，上一次的博客已有所介绍。这里主要说明如何加入触发器语法。</p>
<h2 id="流水线触发器语法">流水线触发器语法</h2>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153423.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>要从请求体中拿到所需要的参数，可以通过配置获取JSONPath参数实现。</p>
<p>在流水线中加入下列语句，<strong>即可当作变量在流水线脚本中使用。</strong></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-1"> triggers {
        GenericTrigger(
            genericVariables: [
              [key: &#39;branch&#39;, value: &#39;$.ref&#39;],
              [key:&#39;commitText&#39;, value:&#39;$.commits&#39;]
            ],
            causeString: &#39;Triggered on $branch&#39; ,
            printContributedVariables: false,
            printPostContent: false
        )
    }</div>
    </div>
  </div>
</div><ul>
<li>序列化JSON
要想在pipeline脚本中将字符串反序列化成JSON对象，可以引入 <code>Pipeline Utility Step</code>插件，该插件提供了一些工具方法。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153442.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-2">def commits = readJSON text: commitText</div>
    </div>
  </div>
</div>


<blockquote>
    <p>流水线脚本使用<code>Groovy</code>语言，该语言基于<code>Java</code>编写，也集成了一些有趣的特性。在IDEA中编写只需要配置<code>Groovy Library</code>即可。</p>
</blockquote>
<h2 id="核心方法">核心方法</h2>
<ul>
<li>根据commits，定义patternMap，匹配到指定正则文件格式，构建指定组件。</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-3">    def modifiedFile = [];
    for (commit in commits) {
            modifiedFile.addAll(commit.getAt(&#34;added&#34;).findAll())
            modifiedFile.addAll(commit.getAt(&#34;modified&#34;).findAll())
            modifiedFile.addAll(commit.getAt(&#34;removed&#34;).findAll())
        }

        def buildComponents = new HashSet();
        def patternMap = [&#39;mark-engine-manager/.*&#39;: &#39;manager&#39;, &#39;mark-tools/.*&#39;: &#39;web&#39;,&#39;mark-engine-dm/.*&#39;:&#39;dm&#39;,&#39;mark-engine-web/.*&#39;:&#39;web&#39;,
        &#39;mark-engine-uc/.*&#39;:&#39;uc&#39;,&#39;mark-engine-gateway/.*&#39;:&#39;gateway&#39;];
//遍历所有修改了的文件
        for (file in modifiedFile) {
            for(entry in patternMap.entrySet()){
                if (file ==~ entry.key) {
                    buildComponents &lt;&lt; entry.value;
                }
            }
        }</div>
    </div>
  </div>
</div><ul>
<li>根据需要构建的组件，拼接<code>maven</code>构建指令。</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-4">String mvnCmd = &#39;mvn clean install -Dmaven.test.skip=true&#39;
for(component in buildComponents){
      mvnCmd = mvnCmd &#43; &#39; -pl mark-engine-&#39;&#43;component&#43;&#39;,&#39;;
}                    </div>
    </div>
  </div>
</div><p><strong>经过调试和测试push，三个目标全部完成。</strong></p>
<h1 id="总结">总结</h1>
<p><strong>一切都是代码</strong>，CICD当然也可以使用代码实现。经过实践我们可以探索出Jenkins更多有趣的玩法。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>vue-codemirror &#43; Java Compiler实现Java Web IDE</title>
      <link>https://ygria.site/web-ide/</link>
      <pubDate>Sat, 23 May 2020 17:02:00 +0000</pubDate>
      
      <guid>https://ygria.site/web-ide/</guid>
      <description>&lt;h1 id=&#34;背景&#34;&gt;背景&lt;/h1&gt;



&lt;blockquote&gt;
    &lt;p&gt;最近同事告诉我一个很有趣的需求：让用户（应用场景中，一般为其他开发者）自己填入&lt;strong&gt;Java代码片段&lt;/strong&gt;，代码片段的内容为已经规定好的模板类的&lt;strong&gt;继承类&lt;/strong&gt;，实现模板类定义的方法。我们的项目要实现动态编译代码片段，存储代码片段和用户操作记录的映射关系，并能够在业务中载入代码片段执行。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="vue-codemirror + Java Compiler实现Java Web IDE" /><h1 id="背景">背景</h1>



<blockquote>
    <p>最近同事告诉我一个很有趣的需求：让用户（应用场景中，一般为其他开发者）自己填入<strong>Java代码片段</strong>，代码片段的内容为已经规定好的模板类的<strong>继承类</strong>，实现模板类定义的方法。我们的项目要实现动态编译代码片段，存储代码片段和用户操作记录的映射关系，并能够在业务中载入代码片段执行。</p>
</blockquote>
<p>这有点像我们提供一个模板模式的架构，只不过模板类的实现类由外部接口填入代码片段动态实现。相较让其他开发者直接参与项目开发，无疑：</p>
<ol>
<li>降低了侵入风险</li>
<li>向其他开发者隐藏了大部分实现</li>
<li>降低操作难度和开发门槛</li>
<li>便于管理</li>
</ol>
<p>……
这相当于要实现一个简单的在线Java开发环境，提供基础的代码填写、编译和保存的功能。</p>
<h1 id="效果演示">效果演示</h1>
<p><img src="https://upload-images.jianshu.io/upload_images/6810620-031e296dac25d5c4.gif?imageMogr2/auto-orient/strip" alt="切换主题" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://upload-images.jianshu.io/upload_images/6810620-304fdf364d8713a2.gif?imageMogr2/auto-orient/strip" alt="联动填写类名" class="img-hide" loading="lazy" decoding="async" /></p>
<p><img src="https://upload-images.jianshu.io/upload_images/6810620-fd427cfe187231e9.gif?imageMogr2/auto-orient/strip" alt="测试编译" class="img-hide" loading="lazy" decoding="async" /></p>
<p>基于<code>vue-codemirror</code>和<code>Java Compiler</code>的动态编译，实现了上述需求，目前完成的Web端IDE主要功能点包括：</p>
<ul>
<li>页面展示Java代码块（代码高亮，有行号、可自动补全括号等）</li>
<li>从服务端获取模板类代码，并提供示例</li>
<li>实时动态编译并获取编译结果（通过/失败 todo:返回编译错误信息)</li>
<li>将输入字符串加载成Java Class
以及小的功能点：自动缩进、补全括号、切换主题、联动填写类名等等。
下面给出涉及到的技术和实现方法。</li>
</ul>
<hr>
<h1 id="codemirror">CodeMirror</h1>
<p>CodeMirror是一个JS库，可以支持实现有丰富的附加功能和多种语言支持。我们项目的前端使用Vue框架，可以很方便地集成并使用CodeMirror提供的插件，实现我们的在线IDE多种特性。
参考：
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9jb2RlbWlycm9yLm5ldC8%3d"
    
     target="_blank"
>
CodeMirror官网</a></p>
<h2 id="引入">引入</h2>
<p>安装依赖：<code>  &quot;vue-codemirror&quot;: &quot;^4.0.6&quot;</code>
在<code>src</code>目录下的<code>main.js</code>中引入：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-1">import VueCodeMirror from &#39;vue-codemirror&#39;
import &#39;codemirror/lib/codemirror.css&#39;
Vue.use(VueCodeMirror)</div>
    </div>
  </div>
</div><h2 id="使用">使用</h2>
<p>新建组件<code>JavaIDE.vue</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> vue</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="vue" data-theme="breeze" id="code-id-2">&lt;template&gt;
    &lt;codemirror ref=&#34;codeMirrorEditor&#34; :value=&#34;code&#34; :options=&#34;cmOptions&#34; @changes=&#34;onChange&#34;&gt;
  &lt;/codemirror&gt;
  &lt;/template&gt;  
  &lt;script&gt;
      import codemirror from &#34;codemirror/lib/codemirror&#34;;
      require(&#34;codemirror/mode/clike/clike.js&#34;);
      require(&#34;codemirror/addon/edit/closebrackets.js&#34;);
      components: {
          codemirror;
      }
      export default{
          data(){
              return{
                code: &#34;&#34;,
                cmOptions:{
                    mode: &#34;text/x-java&#34;,  //Java语言
                    theme: &#34;darcula&#34;, // 默认主题
                    autofocus: true,  
                    lineNumbers: true,   //显示行号
                    smartIndent: true, // 自动缩进
                    autoCloseBrackets: true// 自动补全括号
                }
              }
          }
  &lt;/script&gt;</div>
    </div>
  </div>
</div><p>组件化地使用它，我们可以方便地操作它绑定的值（code）和其他附加选项（cmOption)。
在组件创建时为code赋值，即可实现加载模板代码。</p>



<blockquote>
    <p>根据官网，我们可以直接使用CodeMirror的默认构造函数，也可以提供一个<code>textarea DOM</code>元素作为构造CodeMirror对象的参数。</p>
</blockquote>
<p>可以使用<code>readOnly</code>参数将代码块设置为只读。</p>
<h3 id="联动填写类名功能">联动填写类名功能</h3>
<p>希望实现：在上面顶栏中填写类名，在代码中联动填写。
实现方式： 使用正则匹配替换代码片段，再进行替换
使用相同的方法，也可以实现动态补全类名等功能</p>



<blockquote>
    <p>参考更多
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cucnVub29iLmNvbS9qcy9qcy1yZWdleHAuaHRtbA%3d%3d"
    
     target="_blank"
>
JavaScript的正则表达式</a></p>
</blockquote>
<p>为输入框加上监听函数<code>@input=&quot;changeClassName&quot;</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-3"> changeClassName(className) {
    var reg = new RegExp(/public class .*? extends ActionParamBuilder/);
    this.code = this.code.replace(reg,
                    &#34;public class &#34; &#43; className &#43; &#34; extends ActionParamBuilder&#34;
   );
 }</div>
    </div>
  </div>
</div><h3 id="切换主题">切换主题</h3>
<p>引入主题<code>css</code>样式文件</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-4"> import &#34;codemirror/theme/eclipse.css&#34;;
 import &#34;codemirror/theme/darcula.css&#34;;
 import &#34;codemirror/theme/blackboard.css&#34;;</div>
    </div>
  </div>
</div><p>使用String数组定义支持的主题，并使用 <code>Element-UI</code>提供的<code>Select</code>组件支持主题切换：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-5">&lt;el-select v-model=&#34;cmOptions.theme&#34; placeholder=&#34;切换主题&#34; @change=&#34;changeTheme&#34;&gt;
          &lt;span slot=&#34;prefix&#34;&gt;
             &lt;el-tooltip content=&#34;更换主题&#34;&gt;
              &lt;a-icon type=&#34;skin&#34; style=&#34;fontSize:16px;line-height=50px;&#34;/&gt;
      &lt;/el-tooltip&gt;
      &lt;/span&gt;
 &lt;el-option v-for=&#34;(item,index) in supportThemes&#34; :key=&#34;index&#34; :label=&#34;item&#34; :value=&#34;item&#34;&gt;
   &lt;/el-option&gt;
&lt;/el-select&gt;</div>
    </div>
  </div>
</div><ul>
<li>使用<code>slot</code>实现在选择器中嵌入图标，并支持<code>tooltip</code>功能，使工具栏更加紧凑。 <code>slot</code>意为插槽，是封装好的组件预留的可以自定义的空间，我们可以使用<code>slot = &quot;&quot;</code>把DOM元素置入到组件内部，非常灵活。</li>
</ul>
<h3 id="样式覆写">样式覆写</h3>
<p>使用<code>!important</code>关键字覆盖原有CodeMirror样式。注意，将该样式放在全局而不是局部<code>scoped</code>样式表中。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-6">.CodeMirror {
     height: 500px !important;
 }</div>
    </div>
  </div>
</div><h1 id="javacompiler">JavaCompiler</h1>
<p>不用将传入的代码保存成<code>.java</code>文件写入磁盘，直接就可以使用<code>JavaCompiler</code>工具对字符串进行编译。</p>



<blockquote>
    <p>为了实现实时动态编译功能，我搜索了关于如何将字符串编译成class的方法，还看了一些动态代理的实现思路。后来看到这一篇：
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly93d3cubGlhb3h1ZWZlbmcuY29tL2FydGljbGUvMTA4MDE5MDI1MDE4MTkyMA%3d%3d"
    
     target="_blank"
>

Java运行时动态生成class的方法</a>，发现这就是我想要的！</p>
</blockquote>
<p>使用Java SDK（since 1.6）提供的JavaCompiler工具。该工具提供编译方法：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-7">  CompilationTask getTask(Writer out,
                            JavaFileManager fileManager,
                            DiagnosticListener&lt;? super JavaFileObject&gt; diagnosticListener,
                            Iterable&lt;String&gt; options,
                            Iterable&lt;String&gt; classes,
                            Iterable&lt;? extends JavaFileObject&gt; compilationUnits);</div>
    </div>
  </div>
</div><ul>
<li><code>JavaFileManager</code>
自定义<code>MemoryJavaFileManager</code>，继承<code>ForwardingJavaFileManager&lt;JavaFileManager&gt;</code>，实现从内存字符串中读取JavaFileObject
重点是下面这个方法：</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-8">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-8">
          <button id="copyButton-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-8" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-8">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-8">	JavaFileObject makeStringSource(String name, String code) {
		return new MemoryInputJavaFileObject(name, code);
	}
	static class MemoryInputJavaFileObject extends SimpleJavaFileObject {
		final String code;
		MemoryInputJavaFileObject(String name, String code) {
			super(URI.create(&#34;string:///&#34; &#43; name), Kind.SOURCE);
			this.code = code;
		}
		@Override
		public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
			return CharBuffer.wrap(code);
		}
	}</div>
    </div>
  </div>
</div><ul>
<li><code>options</code>，可选参数列表，可以<strong>增加外部Jar包依赖</strong>
因为我们所需要编译的代码里依赖的类来源于外部的Jar包，所以需要将这些Jar包使用<code>option</code>将这些依赖加进去。这一步踩了坑，因为之前没用过，不知道怎么写……最后终于找到了正确的写法：
<code>List&lt;String&gt; optionList =Arrays.asList(&quot;-extdirs&quot;,extLib);</code>
<code>extLib</code>是外部jar包的路径（目录地址）。可以使用路径分隔符填入多个路径。</li>
<li><code>DiagnosticListener</code> 诊断信息监听
加入诊断信息监听器，我们可以拿到编译错误信息，把这些信息反馈给前端，实现实时编译并报错的功能。
<code>DiagnosticCollector diagnosticCollector = new DiagnosticCollector();</code></li>
<li><code>JavaFileObject</code> 待编译的Java对象，调用自定义类<code>MemoryJavaFileManager</code> 的<code>makeStringSource</code>方法。可以传入一组编译单元。
完整方法如下：</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-9">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-9">
          <button id="copyButton-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-9" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-9">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-9">public Map&lt;String, byte[]&gt; compile(String fileName, String source,String extLib) throws IOException {
		try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
			JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);    
            // 传入诊断监听器 size和传入的javaObject相同
            DiagnosticCollector diagnosticCollector = new DiagnosticCollector();
			List&lt;String&gt; optionList =Arrays.asList(&#34;-extdirs&#34;,extLib);
			CompilationTask task = compiler.getTask(null, manager,diagnosticCollector, optionList, null, Arrays.asList(javaFileObject));
			Boolean result = task.call();
			if (result == null || !result.booleanValue()) {
				throw new RuntimeException(&#34;Compilation failed.&#34;);
			}
			return manager.getClassBytes();
		}
	}</div>
    </div>
  </div>
</div><p>调用代码：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-10">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-10">
          <button id="copyButton-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-10" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-10">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-10"> Map&lt;String, byte[]&gt; results = javaStringCompiler.compile(className &#43; &#34;.java&#34;, CODE_TO_COMPILE, libDir);</div>
    </div>
  </div>
</div><h1 id="自定义classloader">自定义ClassLoader</h1>



<blockquote>
    <p>参考
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly9ib29rLmRvdWJhbi5jb20vc3ViamVjdC8zMDEzMzQ0MC8%3d"
    
     target="_blank"
>
《Java编程的逻辑》</a>中24.5中内容，我们可以使用自定义的<code>ClassLoader</code>来加载用户代码片段，成为可调用的Class对象。</p>
</blockquote>
<ul>
<li>继承<code>URLClassLoader</code></li>
<li>重写<code>findClass</code>方法</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-11">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-11">
          <button id="copyButton-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-11" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-11">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-11">class MemoryClassLoader extends URLClassLoader {

	// class name to class bytes:
	Map&lt;String, byte[]&gt; classBytes = new HashMap&lt;String, byte[]&gt;();

	public MemoryClassLoader(Map&lt;String, byte[]&gt; classBytes) {
		super(new URL[0], MemoryClassLoader.class.getClassLoader());
		this.classBytes.putAll(classBytes);
	}

	@Override
	protected Class&lt;?&gt; findClass(String name) throws ClassNotFoundException {
		byte[] buf = classBytes.get(name);
		if (buf == null) {
			return super.findClass(name);
		}
		classBytes.remove(name);
		return defineClass(name, buf, 0, buf.length);
	}

}</div>
    </div>
  </div>
</div><p>自定义类加载器有如下好处：</p>
<ul>
<li>可以自定义读取class文件字节码方法和形式，如：从内存中、指定jar包中，或从数据库/网络读取等</li>
<li>实现隔离，可以实现使用同一个类的不同版本</li>
<li>实现热部署，动态更新类的内容</li>
</ul>
<h1 id="总结">总结</h1>
<p>本篇中主要涉及知识点:</p>
<ul>
<li><code>vue-codemirror</code>集成和使用</li>
<li><code>JavaCompiler</code>的使用</li>
<li><code>JavaScript</code>正则和<code>Vue</code>中的插槽（<code>slot</code>）</li>
<li>自定义<code>ClassLoader</code>实现动态加载</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>CICD：Jenkins入门，流水线和插件使用</title>
      <link>https://ygria.site/how-to-use-jenkins/</link>
      <pubDate>Thu, 14 May 2020 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/how-to-use-jenkins/</guid>
      <description>&lt;p&gt;最近，我们使用的开发服务器被回收了，换了一台新的服务器，CI/CD平台需要重新搭建。
我的运维能力一直薄弱，所以借此机会学习了一番如何使用Jenkins进行持续集成开发和部署，实践并踩了一些坑，在此记录一下。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="CICD：Jenkins入门，流水线和插件使用" /><p>最近，我们使用的开发服务器被回收了，换了一台新的服务器，CI/CD平台需要重新搭建。
我的运维能力一直薄弱，所以借此机会学习了一番如何使用Jenkins进行持续集成开发和部署，实践并踩了一些坑，在此记录一下。</p>
<h1 id="引言">引言</h1>
<h2 id="假如没有cicd平台">假如没有CI/CD平台</h2>
<p>想要部署到服务器，我们需要本地打包上传至服务器，或上传源码至服务器上打包，覆盖原来的安装包，进行部署。</p>
<p>当所需要部署的只是一个jar包或者只是一个服务，并且代码不经常更新，这样是可以的。但是开发过程中，更常见的是代码经常迭代更新，并且项目中有多个组件。
这带来了大量的机械重复劳动，打包、上传、构建、测试、发布，如果都由人工操作，很容易混淆代码版本、不易跟踪异常。如果代码有多个分支版本，需要应对多个测试/生产环境，劳动量会指数级别飙升，到人无法承受的地步。
而Jenkins提供了解决方案，使我们可以一劳永逸地应对部署。</p>
<h1 id="jenkins可以做什么">Jenkins可以做什么</h1>
<p>它的流水线操作正如其名，将机械的工作流程提炼出来，重复执行，可以定义成定时操作，可以定义触发条件，可以填写参数，可以写入控制语句。
代替我们完成：
1、拉取源码至服务器（与代码管理平台直接集成，可集成gitlab/svn等）
2、打包源码（可选择使用maven、nodeJS等等打包工具）
3、测试
4、准备环境
5、发布
总之，一切需要的工作，都可以定义成流水线里的一个流程。</p>
<h1 id="jenkins搭建与配置">Jenkins搭建与配置</h1>
<p>本次安装的Jenkins是Jenkins中文社区提供的中文镜像版，不仅做了汉化，Jenkins可以灵活使用的1500多个插件也提供了国内的镜像地址，安装只需要一条docker指令。</p>
<h2 id="1环境准备">1、环境准备</h2>
<p>1、查看系统版本，预留空间和Jenkins运行端口
2、安装docker</p>
<h2 id="2快速开始">2、快速开始</h2>
<ul>
<li>创建容器</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> bash</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="bash" data-theme="breeze" id="code-id-1">docker run -d -v /jenkins_home:/var/jenkins_home -u 0 -p 8786:8080  --name jenkins jenkinszh/jenkins-zh</div>
    </div>
  </div>
</div><p><strong>指令解释：</strong>
<code>-d</code>： 后台运行
<code>-v</code>：将Jenkins主目录挂在出来
<code>-u 0</code> ：将系统用户传入（0为root用户），避免一些权限问题
<code>-p</code> ：指定映射到宿主机的端口，如果8786端口被占用也可以使用其他端口
<code>--name</code>：指定运行容器名称为jenkins
<code>jenkinszh/jenkins-zh</code> Jenkins中文社区提供的镜像名称，docker会检测本地有无该镜像，如果没有，会自动拉取。</p>
<ul>
<li><code>docker logs jenkins </code> 查看日志</li>
</ul>
<p>运行之后，访问宿主机ip+ 8786（指定端口），看到Jenkins初始页面。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152739.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>Jenkins创建成功！</p>
<h2 id="3初始化">3、初始化</h2>
<p>稍微等待之后，自动跳转至管理员登录页面。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152753.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>
<a 
    
        href="/tiaozhuan?target=Li4lMkYuLiUyRi4uJTJGdHJhdmVsLXRyYWNlJTJGaW5kZXguaHRtbA%3d%3d"
    
    
>
index.html</a>
创建容器时我们已经将该目录挂载到宿主机了，所以可以直接访问映射出的文件：
<code>cat /jenkins_home/secrets/initialAdminPassword</code></p>
<p>也可以使用<code>docker exec</code>指令访问容器内文件:
<code>docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword</code>
输入密码，登陆进入新手入门页面。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152811.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>选择安装推荐插件。
如果有安装失败的，可以点击继续，直接跳过，进入Jenkins管理页面，等待后续使用时再进行安装。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152823.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<h2 id="4新建声明式流水线">4、新建声明式流水线</h2>
<p>点击左侧新建item，可以创建新的任务，如下图所示。</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152841.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>选择流水线。</p>
<p>现在就可以创建流水线，用于部署正在开发的项目了。</p>
<h2 id="5常用流水线--工具-插件-使用及配置">5、常用流水线 + 工具 +插件 使用及配置</h2>
<p>流水线语法请参考：</p>
<p>
<a 
    
        href="/tiaozhuan?target=aHR0cDovL2plbmtpbnMtemguY24vd2VjaGF0L2FydGljbGVzLzIwMjAvMDUvMjAyMC0wNS0wOC1ob3ctdG8tdXNlLXRoZS1qZW5raW5zLWRlY2xhcmF0aXZlLXBpcGVsaW5lLw%3d%3d"
    
     target="_blank"
>
如何使用声明式流水线</a>
可以使用Jenkins自带的流水线语句生成工具来辅助编写：</p>
<p><img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152856.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>插件和其他配置均为可选，根据使用需求灵活安装。
在此记录一些插件/工具，并给出它们在流水线脚本中的使用范例。</p>
<h3 id="一集成gitgitlab">一、集成git/gitlab</h3>
<ul>
<li>
<p>配置凭据用于登陆验证gitlab
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152911.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
<li>
<p>参考语法</p>
</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-2">steps {
	git branch: &#39;dev&#39;, credentialsId: &#39;***&#39;, url: &#39;https://git.***.git&#39;
}</div>
    </div>
  </div>
</div><p>将创建好的凭据填入。</p>
<h3 id="二mavennodejs等构建工具使用">二、maven/nodeJS等构建工具使用</h3>
<ul>
<li>
<p>工具安装和配置
在全局工具配置中配置NodeJS和Maven
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152931.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
</li>
<li>
<p>使用maven打包</p>
</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-3">steps {
    script {
     sh &#34;mvn clean install -Dmaven.test.skip=true -Plinux&#34;
    }
}</div>
    </div>
  </div>
</div><ul>
<li>使用<code>ln -s</code>指令支持脚本中运行
若执行时报错无mvn指令（或npm、cnpm等），说明jenkins在/usr/local/bin目录下没有找到相应的指令。可以通过建立软连接方式实现支持。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417152952.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>
<p>可以将宿主机的工具包直接放入挂载目录，进行映射，也可以在工具配置中选择自动下载，在Jenkins内中再次安装。</p>
<h3 id="三连接其他机器执行脚本">三、连接其他机器/执行脚本</h3>
<ul>
<li>容器内
直接使用下列流水线语言格式即可。</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-4"> script {
     sh &#34;***&#34;
    }</div>
    </div>
  </div>
</div><ul>
<li>执行宿主机内脚本
（1）挂载进入容器内部
缺点：若脚本运行环境和操作内容是宿主机文件，该方法无法实现。
（2）使用ssh</li>
<li>语句使用
开启免密校验，使用<code> sh &quot;ssh -o StrictHostKeyChecking=no -l root ${IP} '****'&quot;</code>格式的指令。
注意指令格式为双引号内套单引号，单引号内为希望在宿主机执行的指令</li>
<li>插件使用</li>
</ul>
<ul>
<li><strong>SSH Pipeline Step</strong>
根据示例，可得，事先添加好凭据之后，很容易在指定ip上（包括宿主机）执行文件传输、脚本执行等操作。</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-5">def host = [:]
host.name = &#34;host&#34;
host.host = &#34;&#34;
host.allowAnyHosts = true
 
node {
    withCredentials([usernamePassword(credentialsId: &#39;&#39;, passwordVariable: &#39;password&#39;, usernameVariable: &#39;userName&#39;)]) {
        host.user = userName
        host.password = password
        stage(&#34;SSH Steps Rocks!&#34;) {
            writeFile file: &#39;test.sh&#39;, text: &#39;ls&#39;
            sshCommand remote: host, command: &#39;for i in {1..5}; do echo -n \&#34;Loop \$i \&#34;; date ; sleep 1; done&#39;
            sshScript remote: host, script: &#39;test.sh&#39;
            sshPut remote: host, from: &#39;test.sh&#39;, into: &#39;.&#39;
            sshGet remote: host, from: &#39;test.sh&#39;, into: &#39;test_new.sh&#39;, override: true
            sshRemove remote: host, path: &#39;test.sh&#39;
        }
    }
}</div>
    </div>
  </div>
</div><p>构建结果
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153012.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<ul>
<li><strong>Publish Over SSH</strong>
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153033.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>
<p>在Jenkins管理/系统管理/PublishOverSSH的相关配置中配置所需要的宿主机IP、用户、密码等等信息
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153042.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>可以在展开的高级设置中配置SSH服务器。
查看
<a 
    
        href="/tiaozhuan?target=W2h0dHBzOi8vcGx1Z2lucy5qZW5raW5zLmlvL3B1Ymxpc2gtb3Zlci1zc2gvXShodHRwczovL3BsdWdpbnMuamVua2lucy5pby9wdWJsaXNoLW92ZXItc3NoLyk%3d"
    
    
>
Publish-Over-Server 插件说明</a>获取高级用法。</p>
<h3 id="四集成docker">四、集成docker</h3>
<p>因为我们的Jenkins服务是作为docker容器运行的，所以需要在容器内调用docker命令。
容器内调用docker命令有两种：</p>
<ul>
<li>
<p>DinD（Docker in Docker）（不推荐）
容器内部再安装一个Docker应用。但是这种方法不推荐，参考：
<a 
    
        href="/tiaozhuan?target=aHR0cHM6Ly96aHVhbmxhbi56aGlodS5jb20vcC8yNzIwODA4NQ%3d%3d"
    
     target="_blank"
>
使用 Docker-in-Docker 来运行 CI 或集成测试环境？三思！</a></p>
</li>
<li>
<p><strong>DooD（Docker-outside-of-Docker）（推荐）</strong></p>
</li>
</ul>



<blockquote>
    <p>原理说明：
Docker采用的是Client/Server架构，我们常用的docker命令只是docker client，通过该命令行执行命令的时候，实际是通过client与docker engine通信。
默认情况下，Docker的守护进程会生成一个socket（<code>/var/run/docker.sock</code>）文件来进行本地进程通信，智是UNIX域套接字，可以通过文件系统（而非网络地址）进行寻址访问。</p>
</blockquote>
<p>根据上述原理，我们可以通过挂载宿主机socket文件进入Jenkins，使Jenkins可以使用宿主机docker命令。</p>
<ul>
<li><strong>直接使用第四点中的SSH用法执行：</strong></li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-6"> sshCommand remote: host, command: &#39;docker *** &#39;</div>
    </div>
  </div>
</div><h3 id="五生成归档文件">五、生成归档文件</h3>
<p>示例：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> groovy</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="groovy" data-theme="breeze" id="code-id-7">pipeline {
    
    agent any

 stages {
     
     stage(&#34;build&#34;){
         steps{
             sh &#39;touch archive.jar&#39;
         }
         
     }
     
     stage(&#39;archive&#39;) {  
            steps {
                archiveArtifacts artifacts: &#39;archive.jar&#39;, fingerprint: true 
            }
    }}
}</div>
    </div>
  </div>
</div><p>在build过程中生成归档文件，在archive过程中归档。
<img src="https://cdn.jsdelivr.net/gh/Ygria/Pictures@main/20240417153056.png" alt="image.png" class="img-hide" loading="lazy" decoding="async" /></p>
<p>在此页面上可以直接下载归档好的文件。</p>
<h1 id="其他待补充">其他（待补充）</h1>
<p>1、配置时区
打开系统管理/命令行，运行：
<code>System.setProperty('org.apache.commons.jelly.tags.fmt.timeZone', 'Asia/Shanghai')</code></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Java：自定义注解&#43;反射实现导入导出Excel文档</title>
      <link>https://ygria.site/excel-parse/</link>
      <pubDate>Fri, 08 May 2020 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/excel-parse/</guid>
      <description>&lt;h1 id=&#34;问题背景&#34;&gt;问题背景&lt;/h1&gt;
&lt;p&gt;最近遇到的需求：用户填写平台提供的模板文件（sample.xlsx），导入到平台中，代替填写表单/表格的动作。用户也可以将填好的表单/表格导出成Excel文件，便于以文档的形式存档或传输。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Java：自定义注解+反射实现导入导出Excel文档" /><h1 id="问题背景">问题背景</h1>
<p>最近遇到的需求：用户填写平台提供的模板文件（sample.xlsx），导入到平台中，代替填写表单/表格的动作。用户也可以将填好的表单/表格导出成Excel文件，便于以文档的形式存档或传输。</p>
<h1 id="问题分析">问题分析</h1>
<p>从上述需求得出，这就是一个Excel文档与<code>Java Bean</code>对象互相转换的问题。</p>
<p>Excel文件的读写、操作，可以使用<code>Apache Poi</code>或其他开源库，在此不多说。主要问题是，当模板文件内容较为复杂，或者需要处理多个模板时，怎样能快速解析出文档的内容，与<code>Java Bean</code>的字段对应上呢？
<strong>应用Java反射的原理，使用自定义注解 + 泛型，很容易实现灵活适配多个模板，并能快速支持模板的修改，便于扩展和维护。</strong></p>
<h1 id="模板文件分析">模板文件分析</h1>
<p>分析模板文件，我发现可以将模板分为两种。</p>
<ol>
<li>表单式
<img src="https://upload-images.jianshu.io/upload_images/6810620-edd21e42adeca658.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="表单式" class="img-hide" loading="lazy" decoding="async" /></li>
</ol>
<p>内容行标、列标均确定，例如该表格B1固定为姓名的值，将内容解析成单个JavaBean对象。</p>
<ol start="2">
<li>表格式
<img src="https://upload-images.jianshu.io/upload_images/6810620-cd02b99b72e481f4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="表格式" class="img-hide" loading="lazy" decoding="async" /></li>
</ol>
<p>内容列固定，例如A列均为学号，应将除去表头外的每一行解析成一个JavaBean 对象，返回Java Bean对象的列表。</p>
<p>分析完毕，发现Java Bean 对象的某个字段的值与Excel文档单元格内容的对应关系就是行标 + 列标，那么我们只需要记录这个坐标，就能实现转换。
使用在字段上加注解的方式，简明易懂，并且很容易维护。下面给出实现代码。</p>
<h1 id="实现">实现</h1>
<h2 id="1定义注解类">1、定义注解类</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-1">@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelAnnotation {
    /**
     * 中文名称 label  
     */
    String cnName() default &#34;&#34;;
    /**
     * 英文名称 对应到JavaClass上的类名
    
     */
    String enName() default &#34;&#34;;
    /**
     *
     文件中的行标 - 从0开始计数
     */
    int rowIndex() default -1;
 
    int sheetIndex() default -1;
    /**
     *
     * 文件中的列标 - 从0开始计数
     */
    int columnIndex() default  -1;
}</div>
    </div>
  </div>
</div><p>注解用来说明字段对应的单元格工作簿序号、行标、列标等信息，以及</p>
<h2 id="2定义java-bean">2、定义Java Bean</h2>
<ul>
<li>表单式对应的对象</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-2">@Data
class Person{
  @ExcelAnnotation(rowIndex = 0,columnIndex = 1,cnName = &#34;姓名&#34;)
  private String name;

  @ExcelAnnotation(rowIndex = 2,columnIndex = 1,cnName = &#34;电话号码&#34;)
  private String phoneNum;
 ...
}</div>
    </div>
  </div>
</div><ul>
<li>表格式对应的对象
只需要定义列的中文名（cnName）,不需要定义行标</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-3">@Data
class Student{
  @ExcelAnnotation(cnName = &#34;学号&#34;)
  private String number;

  @ExcelAnnotation(cnName = &#34;姓名&#34;)
  private String name;

  @ExcelAnnotation(cnName = &#34;电话号码&#34;)
  private String phoneNum;
}</div>
    </div>
  </div>
</div><h2 id="3工具类实现写入和写出">3、工具类实现写入和写出</h2>
<p>定义Excel操作的工具类
<code>ExcelUtils.java</code></p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-4">@Log4j2
public class ExcelUtils {


    public static &lt;T&gt; List&lt;T&gt; analysisExcelSheetAsTable(Sheet sheet,Class&lt;T&gt; clazz,int headerIndex) throws IntrospectionException {
        ArrayList&lt;Row&gt; rowContent = new ArrayList&lt;&gt;();
        TreeMap&lt;Integer, Method&gt; writeMethodTreeMap = new TreeMap&lt;&gt;();
        //  记录表头内容与
        HashMap&lt;String,Integer&gt; headerCnNameMap = new HashMap&lt;&gt;();
        // 默认的表头数据
        // 获取表头数据
        Row tableHeader = sheet.getRow(headerIndex);
        //
        int index = 0;
        for(Cell headerCell: tableHeader){
            String headerContent =   ExcelUtils.getCellFormatValue(headerCell).toString().trim();
            headerCnNameMap.put(headerContent,index);
            index&#43;&#43;;
        }
        // 忽略第一行表头数据
        for (int i = (headerIndex&#43;1); i &lt; sheet.getPhysicalNumberOfRows(); i&#43;&#43;) {
            rowContent.add(sheet.getRow(i));
        }
        for (Field field : clazz.getDeclaredFields()) {
            // 获取字段上的注解
            Annotation[] annotations = field.getAnnotations();
            if (annotations.length == 0) {
                continue;
            }
            for (Annotation an : annotations) {
                // 若扫描到ExcelAnnotation注解
                if (an.annotationType().getName().equals(ExcelAnnotation.class.getName())) {
                    // 获取指定类型注解
                    ExcelAnnotation excelAnnotation = field.getAnnotation(ExcelAnnotation.class);
                    try {
                        // 获取该字段的method方法
                        PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
                        // 从头部获取cnName
                        if(headerCnNameMap.containsKey(excelAnnotation.cnName())){
                            writeMethodTreeMap.put(headerCnNameMap.get(excelAnnotation.cnName()), pd.getWriteMethod());
                        }

                    } catch (IntrospectionException e) {
                        throw e;
                    }
                }
            }
        }
        DataFormatter dataFormatter = new DataFormatter();
        List&lt;T&gt; resultList = new ArrayList&lt;&gt;();
        for (Row row : rowContent) {
            String rowValue = dataFormatter.formatCellValue(row.getCell(0));
            try {
                T model = clazz.newInstance();
                if (!StringUtils.isEmpty(rowValue)) {
                    for(int cellIndex: writeMethodTreeMap.keySet()){
                        if(row.getCell(cellIndex) != null){
                            Cell cell = row.getCell(cellIndex);
                            String value = ExcelUtils.getCellFormatValue(cell).toString();
                            writeMethodTreeMap.get(cellIndex).invoke(model, value);
                        }

                    }
                    resultList.add(model);
                }
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }

        }
        return resultList;
    }




    /**
     * 解析Excel表格内容 - 按照表格解析
     * @param sheet
     * @param ignoreRowNum 忽略的行数（表头）

     * @param &lt;T&gt;
     * @return
     */
    public static &lt;T&gt; List&lt;T&gt; analysisExcelSheetAsTable(Sheet sheet, int ignoreRowNum, Class&lt;T&gt; clazz) {
        ArrayList&lt;Row&gt; rowContent = new ArrayList&lt;&gt;();
        TreeMap&lt;Integer, Method&gt; writeMethodTreeMap = new TreeMap&lt;&gt;();
        // 从忽略的表头开始读
        for (int i = ignoreRowNum; i &lt; sheet.getPhysicalNumberOfRows(); i&#43;&#43;) {
            rowContent.add(sheet.getRow(i));
        }
        for (Field field : clazz.getDeclaredFields()) {
            // 获取字段上的注解
            Annotation[] annotations = field.getAnnotations();
            if (annotations.length == 0) {
                continue;
            }
            for (Annotation an : annotations) {
                // 若扫描到ExcelAnnotation注解
                if (an.annotationType().getName().equals(ExcelAnnotation.class.getName())) {
                    // 获取指定类型注解
                    ExcelAnnotation excelAnnotation = field.getAnnotation(ExcelAnnotation.class);
                    try {
                        // 获取该字段的method方法
                        PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
                        writeMethodTreeMap.put(excelAnnotation.columnIndex(), pd.getWriteMethod());
                    } catch (IntrospectionException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        DataFormatter dataFormatter = new DataFormatter();
        List&lt;T&gt; resultList = new ArrayList&lt;&gt;();
        for (Row row : rowContent) {
            String rowValue = dataFormatter.formatCellValue(row.getCell(0));
            try {
                T model = clazz.newInstance();
                if (!StringUtils.isEmpty(rowValue)) {
                    // 遍历格子
                    int i = 0;
                    for (Cell cell : row) {

                        if (!writeMethodTreeMap.containsKey(i)) {
                            i&#43;&#43;;
                            continue;
                        }
                        String value = ExcelUtils.getCellFormatValue(cell).toString();
                        writeMethodTreeMap.get(i).invoke(model, value);
                        i&#43;&#43;;
                    }
                    resultList.add(model);
                }
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }

        }
        return resultList;
    }

    /**
     * 读取Cell
     *
     * @param cell
     * @return
     */
    public static Object getCellFormatValue(Cell cell)
    {
        Object cellValue;

        //判断cell类型
        switch (cell.getCellTypeEnum())
        {
            case NUMERIC:
            {
                cellValue = cell.getNumericCellValue();
                break;
            }
            case FORMULA:
            {
                //判断cell是否为日期格式
                if (DateUtil.isCellDateFormatted(cell))
                {
                    //转换为日期格式YYYY-mm-dd
                    cellValue = cell.getDateCellValue();
                }
                else
                {
                    //数字
                    cellValue = cell.getNumericCellValue();
                }
                break;
            }
            case STRING:
            {
                cellValue = cell.getRichStringCellValue().getString();
                break;
            }
            default:
                cellValue = &#34;&#34;;
        }
        return cellValue;
    }
}</div>
    </div>
  </div>
</div><h2 id="4调用">4、调用</h2>
<p>在业务类（<code>Service</code>）中调用</p>
<ul>
<li>调用仅为示范</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-5">// 导入
 public void importExcelFile(InputStream inputStream){
        try (Workbook workbook = WorkbookFactory.create(inputStream)) {
           
            Person person= ExcelUtil.analysisExcelSheetAsForm(workbook.getSheetAt(0),Person.class);
            List&lt;Student&gt; students= ExcelUtil.analysisExcelSheetAsTable(workbook.getSheetAt(1),1,Student.class);
// 仅示范调用方式，可自行返回
        
        } catch (Exception ex) {
            log.error(&#34;Excel解析失败！&#34;,ex);
            throw new BusinessException(&#34;Excel解析失败！&#34;);
        }
    }

    //导出
    public void exportExcelFile(List&lt;Student&gt; students,Person person,HttpServletResponse httpServletResponse){
        //1、读取excel模板文件，作为本次下载的模板
        try(InputStream inputStream = new FileInputStream(templatePath);
            Workbook workbook = WorkbookFactory.create(inputStream)){
            httpServletResponse.setContentType(&#34;application/octet-stream&#34;);
            httpServletResponse.setHeader(&#34;Content-Disposition&#34;, &#34;attachment;filename=test.xlsx&#34;);
            // 2.根据查询到的内容，填充Excel表格内容
            ExcelUtil.writeToWorkbookAsForm(person,workbook.getSheetAt(0));
            ExcelUtil.writeToWorkbookAsTable(students,workbook.getSheetAt(1,1,Student.class);
            workbook.write(httpServletResponse.getOutputStream());
        } catch (IOException | InvalidFormatException e) {
            e.printStackTrace();
        }

    }</div>
    </div>
  </div>
</div><p>这样就完成啦。
对于写入Excel和读取Excel的一些格式、编码/解码方式，我们可以放在通用配置里，也可以在注解类中增加项目来应对特殊需求。</p>
<h1 id="heading"></h1>
<h1 id="总结">总结</h1>
<p>本篇涉及知识
1、泛型
2、自定义注解 + <code>Java </code>反射
3、<code>Apache Poi</code>的使用</p>
<ul>
<li>为什么使用泛型？
1、将<strong>数据类型</strong>和<strong>算法</strong>进行剥离是一种很常用的设计思路，可以帮助我们更好地开发出通用方法。
2、使用泛型（而不是包容万物的Object类型）使代码更为可读，并能规避编译错误，还可以对传入类型的上界、下界进行规定。（super/extends）</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Vue基础：使用Vue.extend()实现自定义确认框</title>
      <link>https://ygria.site/vue-modal/</link>
      <pubDate>Tue, 28 Apr 2020 17:02:00 +0000</pubDate>
      
      <guid>https://ygria.site/vue-modal/</guid>
      <description>&lt;h1 id=&#34;问题背景&#34;&gt;问题背景&lt;/h1&gt;
&lt;p&gt;前端交互中经常使用确认框。在删除、修改等操作时，调用后端接口之前，先跳出弹框显示提示信息，提示用户确认，避免用户误操作。
项目中全局引入了Element，提供了一套模态对话框组件，用于消息提示、确认消息、提交内容，使用起来也非常简便。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Vue基础：使用Vue.extend()实现自定义确认框" /><h1 id="问题背景">问题背景</h1>
<p>前端交互中经常使用确认框。在删除、修改等操作时，调用后端接口之前，先跳出弹框显示提示信息，提示用户确认，避免用户误操作。
项目中全局引入了Element，提供了一套模态对话框组件，用于消息提示、确认消息、提交内容，使用起来也非常简便。</p>
<p>以下来自于element官网文档：</p>



<blockquote>
    <p>如果你完整引入了 Element，它会为<code>Vue.prototype</code>添加如下全局方法：  <code>$msgbox</code>, <code>$alert</code>, <code>$confirm</code> 和<code> $prompt</code>。因此在<code>Vue instance</code>中可以采用本页面中的方式调用 MessageBox。</p>
</blockquote>
<p>代码范例：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-1"> 
 this.$confirm(&#34;此操作将永久删除该文件, 是否继续?&#34;, &#34;提示&#34;, {
                    confirmButtonText: &#34;确定&#34;,
                    cancelButtonText: &#34;取消&#34;,
                    type: &#34;warning&#34;
                })
                    .then(() =&gt; {
                        this.$message({ type: &#34;success&#34;, message: &#34;删除成功!&#34; });
                    })
                    .catch(() =&gt; {
                        this.$message({ type: &#34;info&#34;, message: &#34;已取消删除&#34; });
                    });</div>
    </div>
  </div>
</div><p>Element允许复写样式，如果全局都需要，则可以进行写在全局自定义样式单中，覆盖掉原有样式。
<strong>当Element提供的默认组件不能满足需求时，需要思考一下如何实现？</strong></p>
<h1 id="实现尝试">实现尝试</h1>
<h2 id="在单组件内部实现确认框">在单组件内部实现确认框</h2>
<p>在组件内定义一个对话框，使用时将dialog显示为可见，点击确认时调用方法，点击取消/关闭时将dialog设置为不可见。
在当前页面只需要一个确认框的时候，dialog的标题、内容、确认时调用的方法（<code>@click = &quot;handler&quot;</code>）都可以写死。</p>
<ul>
<li>怎么修改对话框内容？
当页面上有多个不同方法均需要对话框确认，那么el-dialog对应的数据是变动的，使用 <code>v-model</code>指令绑定一个confirmDialog对象，在触发对话框时，实时修改该对话框所显示的内容，以及按钮对应的方法。</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-2">&lt;el-dialog width=&#34;600px&#34; :title=&#34;confirmDialog.title&#34; :visible.sync=&#34;confirmDialog.show&#34;&gt;
                &lt;span&gt;{{ confirmDialog.message }}&lt;/span&gt;
                &lt;div slot=&#34;footer&#34; class=&#34;dialog-footer&#34;&gt;
                    &lt;el-button @click=&#34;confirmDialog.show = false&#34;&gt;
                        取 消
                    &lt;/el-button&gt;
                    &lt;el-button type=&#34;primary&#34; @click=&#34;confirmDialog.handler&#34;&gt;
                        确 定
                    &lt;/el-button&gt;
                &lt;/div&gt;
            &lt;/el-dialog&gt;

&lt;el-button @click = &#34;&#34;&gt;&lt;/el-button&gt;</div>
    </div>
  </div>
</div><p>script中方法：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-3">           deleteFile() {
                this.confirmDialog.title = &#34;删除&#34;;
                this.confirmDialog.message = &#34;确认删除文件吗？&#34;;
                this.confirmDialog.handler = this.doDeleteFile;
                this.confirmDialog.show = true;
            },
            doDeleteFile() {
                // 删除文件方法
            }</div>
    </div>
  </div>
</div><ul>
<li>公有部分抽取
在上述实现中，我们使用多个不同方法去操作同一对象，并且每个操作都需要两个方法实现，第一个方法用来修改confirmDialog的值，第二个方法用来监听确认按钮的点击事件，执行操作。
很容易得出，第一个方法是每个操作都类似的，可以复用的，弹窗HTML代码和样式代码也是共用的，我们将公有的部分独立成组件，就避免了重复工作。</li>
</ul>
<h2 id="如何抽取">如何抽取？</h2>
<p>由上一节，我们容易得出，需要抽取的是确认框的DOM，样式，以及数据对象。</p>
<h3 id="为什么使用extend">为什么使用extend</h3>



<blockquote>
    <p>上一章总结了子组件如何抽取，并介绍了在父组件中如何使用子组件，使用方法为：
在父组件中引入并注册子组件，在父组件中传入数据，为子组件的prop赋值，并在父组件中控制子组件的显示。</p>
</blockquote>
<p>使用父子组件 +局部注册，无需关注子组件的创建，相对来说比较简单。但是有时也会遇到问题：</p>
<ul>
<li>子组件的模板都是事先定义好的，如果我要从接口动态渲染组件模板怎么办？</li>
<li>子组件都是在父组件内定义好的位置渲染，假如想要在JS代码中灵活调用，在任意地方渲染怎么办？
这时就轮到<code>Vue.extend()</code>出场了。</li>
</ul>
<h2 id="vueextend">Vue.extend()</h2>
<p><code>Vue.extend()</code>是 <code>Vue</code>框架提供的全局api，查阅官网文档，相关说明如下：<img src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy82ODEwNjIwLTJhNWM3NDc3NWRiNjY5NmMucG5n?x-oss-process=image/format,png" alt="vue.extend()接口说明" class="img-hide" loading="lazy" decoding="async" /></p>



<blockquote>
    <p>类比Java，可以将定义好的组件看成一个<strong>模板类</strong>，使用Vue.extend()生成该模板类的继承子类。模板类中提供了默认的变量（模板、样式、变量等），并定义了方法，在js代码中可以继承并覆盖父类的变量和方法。Vue.extend()中接收的参数相当于子类的构造参数。</p>
</blockquote>
<p>容易得出，我们需要传入的是对话框绑定的数据模型（data），以及点击确认后执行的方法。</p>
<ul>
<li>Promise
参考Element的做法，使用<code>ES6</code>中的<code>Promise</code>对象封装构造函数的返回，能使代码更加简洁。</li>
</ul>
<h1 id="代码实现">代码实现</h1>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-4">import Vue from &#39;vue&#39;;
import confirm from &#39;../Comfirm.vue&#39;;
let confirmConstructor = Vue.extend(confirm);
let theConfirm = function (content) {
    return new Promise((res, rej) =&gt; {
        //promise封装，ok执行resolve，no执行rejectlet
        let confirmDom = new confirmConstructor({
            el: document.createElement(&#39;div&#39;)
        })
        document.body.appendChild(confirmDom.$el); //new一个对象，然后插入body里面
        confirmDom.content = content; //为了使confirm的扩展性更强，这个采用对象的方式传入，所有的字段都可以根据需求自定义
        confirmDom.ok = function () {
            res()
            confirmDom.isShow = false
        }
        confirmDom.close = function () {
            rej()
            confirmDom.isShow = false
        }

    })
}
export default theConfirm;</div>
    </div>
  </div>
</div><p>Confirm.vue</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-5">&lt;template&gt;
    &lt;!-- 自定义确认弹窗样式 --&gt;
    &lt;el-dialog width=&#34;600px&#34; :title=&#34;content.title&#34; :visible.sync=&#34;content.show&#34; v-if=&#34;isShow&#34;&gt;
        &lt;span&gt;{{ content.message }}&lt;/span&gt;
        &lt;div slot=&#34;footer&#34; class=&#34;dialog-footer&#34;&gt;
            &lt;el-button @click=&#34;close&#34;&gt;
                取 消
            &lt;/el-button&gt;
            &lt;el-button type=&#34;primary&#34; @click=&#34;ok&#34;&gt;
                确 定
            &lt;/el-button&gt;
        &lt;/div&gt;
    &lt;/el-dialog&gt;

&lt;/template&gt;

&lt;script&gt;
    export default {
        data() {
            return {
                // 弹窗内容
                isShow: true,
                content: {
                    title: &#34;&#34;,
                    message: &#34;&#34;,
                    data: &#34;&#34;,
                    show: false
                }
            };
        },
        methods: {
            close() {
              
            },
            ok() {
   
            }
        }
    };
&lt;/script&gt;

&lt;style&gt;
&lt;/style&gt;</div>
    </div>
  </div>
</div><p>在main.js中引入</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-6">import confirm from &#39;@/confirm.js&#39; 
Vue.prototype.$confirm = confirm;</div>
    </div>
  </div>
</div><p>在任意方法中使用</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> text</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="text" data-theme="breeze" id="code-id-7"> this.$confirm({ title: &#34;删除&#34;, message: &#34;确认删除该文件吗？&#34;, show: true })
                    .then(() =&gt; {
                        //用户点击确认后执行

                    })
                    .catch(() =&gt; {
                    // 取消或关闭
                });
            }</div>
    </div>
  </div>
</div><h1 id="总结">总结</h1>
<p>本篇主要涉及知识点：</p>
<ul>
<li><code>Vue.prototype</code> 为<code>Vue</code>实例添加方法</li>
<li><code>Vue.extend()</code>使用方法</li>
<li><code>Promise</code>对象的定义和使用
可复用，易扩展，易维护，是我们编程过程中应当时刻注意的原则。</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Vue基础：子组件抽取与父子组件通信</title>
      <link>https://ygria.site/vue-learn-comp/</link>
      <pubDate>Sat, 25 Apr 2020 22:46:00 +0000</pubDate>
      
      <guid>https://ygria.site/vue-learn-comp/</guid>
      <description>&lt;blockquote&gt;
    &lt;p&gt;在工作中承担一部分前端工作，主要使用Vue + Element UI。
随着版本迭代，需求增加，页面往往变得更加臃肿，不易维护。学习子组件的封装和抽取，能更好适应需求。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Vue基础：子组件抽取与父子组件通信" />


<blockquote>
    <p>在工作中承担一部分前端工作，主要使用Vue + Element UI。
随着版本迭代，需求增加，页面往往变得更加臃肿，不易维护。学习子组件的封装和抽取，能更好适应需求。</p>
</blockquote>
<h1 id="为什么需要子组件">为什么需要子组件</h1>
<ul>
<li>可复用
将重复出现的元素封装成组件，可以灵活运用到各个页面中，避免重复劳动。</li>
<li>易维护
每个组件相当于独立的功能组件。独立的组件结构可以让其他开发者快速定位到每个页面元素所对应的事件方法、样式表，并在修改该组件时不影响其他页面的功能。</li>
</ul>
<h1 id="组件的使用方法">组件的使用方法</h1>
<p>子组件的定义方法和每一个Vue组件相同，使用时需要先注册，分为全局注册和局部注册两种。
<strong>全局&amp;局部？</strong>
对于全局通用的组件，可以将其注册为全局的。在项目中更常用的是<strong>局部注册</strong>，全局注册固然方便，但会使组件的依赖结构不够清晰，可能带来的更高的维护成本。
Vue官网教程中给出如下建议：</p>



<blockquote>
    <p>全局注册往往是不够理想的。比如，如果你使用一个像 webpack 这样的构建系统，全局注册所有的组件意味着即便你已经不再使用一个组件了，它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。</p>
</blockquote>
<p>局部注册需要在每个使用到的地方都引用一次，父组件引用之后，子组件必须再次引用才能使用。</p>
<h1 id="父子如何通信">父子如何通信</h1>
<p>组件之间相对独立，不共享变量，重中之重就是：如何传递信息?
我列出一些我目前接触到的常用数据传递方法:</p>
<ul>
<li>使用路由参数传递：在A组件中向路由中写入，在B组件通过$route.query.param获取</li>
<li>存储在session Storage中或使用Vuex，存储常用共有变量
……
组件之间构成父子关系，必然是结构和数据上存在依赖关系，当不能跳转路由或需要使用多个子组件时，上述方法不奏效了，所幸的是，Vue提供了一套现成的方法，可以总结为：</li>
<li>父传子用<code>props</code></li>
<li>子传父用<code>emit</code></li>
<li>双向绑定，<code>compute+sync</code></li>
</ul>
<h2 id="props">props</h2>
<p>父向子传递的信息，往往是子组件的初始化数据。假如将子组件看作一个类，在父组件中使用该类的实例，props有点类的构造参数。
props的写法也与构造函数形参类似，可以规定传参类型、是否必传等。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-1"> props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    }</div>
    </div>
  </div>
</div><h2 id="emit">$emit</h2>
<p>emit函数支持子组件调用父组件函数，并支持传数据作为父组件接受调用函数时的传参。</p>
<ul>
<li>使用场景示例
子组件完成动作后，调用父组件的刷新列表方法：</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-2">// 子组件中
this.$emit(&#34;queryList&#34;)

// 父组件中
//刷新列表方法
queryList(){
}</div>
    </div>
  </div>
</div><p>使用$emit特性，很容易实现将子组件的值传递给父组件，并能控制父组件的动作。</p>
<h2 id="双向绑定">双向绑定</h2>
<p>更常见的需求是需要父组件和子组件的值实现同步，比如：</p>
<ul>
<li>在父组件点击打开按钮，希望能控制子组件打开，在子组件内部点击关闭后，希望父组件的开关也被同步到关闭。</li>
<li>在父组件打开表单后，在子组件内填写，希望父组件知道子组件填了什么，实时同步在子组件的操作</li>
</ul>
<hr>
<h2 id="3在父组件中定义接收值的更新函数接收到新的值后将值赋给a">Vue规定了父子组件之间数据单向流动，不建议直接修改父组件传入的prop变量。所以为了实现双向绑定，我们需要：
1、在子组件中定义对应的变量B，拷贝父组件传入的初始值A
2、实时监测变量B，当B发生变化时，使用$emit，传递B的值给父组件
3、在父组件中定义接收值的更新函数，接收到新的值后，将值赋给A</h2>
<p>实现第1、2点，compute完美满足需求。
为实现第三点，Vue提供了.sync语法糖，避免每次都要写一个更新函数，默认的函数名是update。
在明确了步骤后，我们很容易就能写出代码。需要稍微留意的是，子组件中变量B的命名最好与变量A对称，这样一看就是一对，代码更加清晰易懂。
例如：
A叫openDialog，B叫dialogOpened
A叫selectOption，B叫optionSelected</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-3">父组件中：
//父组件引用
&lt;my-dialog :showDialog.sync=&#34;showDialog&#34; &gt;&lt;/my-dialog &gt;</div>
    </div>
  </div>
</div><p>子组件中：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> javascript</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="javascript" data-theme="breeze" id="code-id-4">// 子组件
 props: {
            // 是否展示弹窗
            showDialog: Boolean,     
        }

....
// 在代码中修改dialogShowed的值


  computed: {
            dialogShowed: {
                get() {
                    return this.showDialog;
                },

                set(val) {
                    this.$emit(&#34;update:showDialog&#34;, val);
                }
            }
        }</div>
    </div>
  </div>
</div><h1 id="总结">总结</h1>
<p>以上就是目前所总结到的抽取子组件的小经验~熟悉了这种模式之后，实现起来还是挺容易的。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Java多线程：大文件解析优化</title>
      <link>https://ygria.site/java-parse-big-file/</link>
      <pubDate>Fri, 10 Apr 2020 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/java-parse-big-file/</guid>
      <description>&lt;h1 id=&#34;问题背景&#34;&gt;问题背景&lt;/h1&gt;



&lt;blockquote&gt;
    &lt;p&gt;在应用系统中，常常需要建立文件管理系统，对存储在存储组件（常用有文件存储/数据库存储/对象存储等）中的物理文件、目录结构在应用数据库中进行逻辑建模，从而方便查询、读取和管理。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Java多线程：大文件解析优化" /><h1 id="问题背景">问题背景</h1>



<blockquote>
    <p>在应用系统中，常常需要建立文件管理系统，对存储在存储组件（常用有文件存储/数据库存储/对象存储等）中的物理文件、目录结构在应用数据库中进行逻辑建模，从而方便查询、读取和管理。</p>
</blockquote>
<p><em>这种设计体现了松耦合的特性，不论文件采取什么方式进行底层存储，应用层提供相同的接口，即使更换存储组件，上层接口不会改变，不影响到与其他模块的交互。</em>
应用系统为用户提供上传接口，该接口接收一个或多个压缩包（*.zip），返回文件存储路径。该接口为同步响应接口，响应时间不能太长，否则前端页面会失去响应，报超时异常。
当用户上传一个多目录结构、包含大批量文件的压缩包，处理速度会显著下降。对文件处理过程进行效率优化，能显著提高接口响应速度，带来更好的用户体验。</p>
<h1 id="性能瓶颈">性能瓶颈</h1>
<p>当前系统采用Amazon S3对象存储组件存储物理文件，MySQL数据库存储文件信息。
经过测试，现有解析并存储近2万个小文件的多目录压缩包，需要5分钟。</p>
<p>文件存储过程需要经历如下主要步骤：
<img src="https://upload-images.jianshu.io/upload_images/6810620-77b7bdc311fbcccd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="文件上传后主要步骤" class="img-hide" loading="lazy" decoding="async" /></p>
<p>文件存储步骤从上图可以看出，主要的时间开销为：</p>
<ul>
<li>向存储组件写入文件耗时</li>
<li>建立数据库实体，写入数据库</li>
</ul>
<p>分析业务逻辑代码，得到如下流程图：
<img src="https://upload-images.jianshu.io/upload_images/6810620-a01fc3ba5d5573ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="业务逻辑中存储逻辑" class="img-hide" loading="lazy" decoding="async" /></p>
<p>存储流程解决思路：将总任务拆解成独立、可重复执行的任务，多线程批量执行，减少与数据库交互次数。
<strong>核心：改单线程为多线程，改单次操作为批量操作。</strong></p>
<h1 id="拆解任务">拆解任务</h1>
<p>经过分析，可以将需要处理的文件分为两类：目录和文件。
对于目录，只需要存储MySQL数据库记录，不需要存储至S3。
拆解任务的主要难点在于减少任务之间的时序依赖关系，而文件存储过程中存在的时序为：</p>
<ul>
<li>目录层级
先向数据库写入父级目录，才能写入该目录下的子文件目录或子文件，以此类推。子文件实体的parentId字段记录父目录的id，存储路径为父目录地址 + 子文件名。
<img src="https://upload-images.jianshu.io/upload_images/6810620-05a1024001494817.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="文件实体" class="img-hide" loading="lazy" decoding="async" /></li>
</ul>
<p>模型如上面类图所示，数据库中不仅记录文件的大小、存储路径、类型等信息，同时保留层级结构。层级为树形，每一层目录信息需要依赖上一级而产生</p>
<ul>
<li>去重
存储路径为唯一索引，在保存记录时需要先判重，再存储。</li>
</ul>
<h2 id="快速实现">快速实现</h2>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-1">/**
递归解析文件夹
**/
private void handleDir(Long parentId, String parentPath, String dirPath, List&lt;String&gt; uploadRes) {  
	File[] filesAndDirs = new File(folderPath).listFiles();    
	for (File fileOrDir : filesAndDirs) {        
	if (fileOrDir.isFile()) {   
			//...向存储组件传输，并存储记录
	
  	} else {            
	   //建立父目录的文件实体           
		FileEntity fileEntity = FileEntity.builder().parentId(parentId)
													.path(parentPath &#43; fileOrDir.getName()&#34;/&#34;)                   
													.build();
		fileEntity = fileRepository.save(fileEntity);
      //递归调用       											     
		this.handleDir(fileEntity.getId(), fileEntity.getPath(), fileEntity.getPath(), uploadRes);        
		}    
	}
}</div>
    </div>
  </div>
</div><p>递归解析为单线程，该方法每次都需要执行判断逻辑，判断当前处理的是文件还是文件夹，并对每个目录文件都执行入库，再进行递归。</p>
<h2 id="改进方案">改进方案</h2>
<p>将根目录下所有文件和目录一次性读取至内存中</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-2">Collection&lt;File&gt; files = FileUtils.listFilesAndDirs(new 
File(testPath), TrueFileFilter.INSTANCE, 
TrueFileFilter.INSTANCE);</div>
    </div>
  </div>
</div><h3 id="处理目录">处理目录</h3>
<p>目录层级按照深度归类，并按深度升序排列。在存储时，先存入父目录，再存子目录。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-3">TreeMap&lt;Integer, List&lt;File&gt;&gt; allDirs = 
dirs.stream().collect(Collectors.groupingBy(ParseTest::getFileDeep, TreeMap::new, Collectors.toList()));

/**
获取目录深度
**/
static int getFileDeep(File file){    
    String path = file.getAbsolutePath();    
    String[] deep = path.split(&#34;\\\\&#34;);    
    return deep.length;
}</div>
    </div>
  </div>
</div><p>Java8特性：将List使用lambda表达式转化成TreeMap
<em>为什么使用TreeMap？key值有序</em>
逐层处理：
存储目录时，将该目录实体与源文件绝对路径的映射存入缓存HashMap中。
只要不是第一层，都从缓存absolutePathMap中获取信息。存储到数据库时保留文件目录信息。</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-4">       // 根据原文件的绝对路径，缓存该目录结构
        HashMap&lt;String, FileEntity&gt; absolutePathMap = new HashMap&lt;&gt;();
        for (int dirDeep : allDir.keySet()) {
            List&lt;File&gt; dirList = allDir.get(dirDeep);
            for (File dir : dirList) {
                // 获取该文件的父级目录绝对地址
                String parentAbsolutePath = dir.getAbsolutePath().substring(0, dir.getAbsolutePath().lastIndexOf(&#34;\\&#34;));
                // 若不是第一层级，从缓存map中取出保存好的父目录信息
                if (dirDeep != allDirs.firstKey()) {
                    parentId = absolutePathMap.get(parentAbsolutePath).getId();
                    parentPath = absolutePathMap.get(parentAbsolutePath).getPath();
                }
                FileEntity fileEntity = FileEntity.builder().parentId(parentId).path(parentPath &#43; dir.getName() &#43; &#34;/&#34;).build();
            
                fileEntity = fileRepository.save(fileEntity);
                absolutePathMap.put(dir.getAbsolutePath(), fileEntity);
            }

        }</div>
    </div>
  </div>
</div><p>遍历完成后，所有目录结构均已存入数据库中。</p>
<h3 id="处理文件">处理文件</h3>
<p>剩余需要处理的是文件。文件所依赖的父目录信息已全部存入absolutePathMap中，文件和文件之间处理逻辑不存在时序依赖关系，可以引入多线程来进行分割处理。</p>
<h4 id="callable类">Callable类</h4>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-5">@Log4j2
public class FileHandler implements Callable&lt;List&lt;String&gt;&gt; {

    // 分给本线程处理的文件
    private Collection&lt;File&gt; files;
    //存储父路径的地方
    HashMap&lt;String, DentryDTO&gt; absolutePathMap;
    public FileHandler(Collection&lt;File&gt; files,DentryDTO&gt; absolutePathMap) {
        this.files = files；
        this.absolutePathMap = absolutePathMap;
    }
 
    @Override
    public List&lt;String&gt; call() {
    // 处理文件
        return res;
    }</div>
    </div>
  </div>
</div><p>将需要处理的文件和所用到的absolutePathMap通过构造方法的参数传入。</p>
<ul>
<li>若需要使用其他类，也通过构造参数传入。</li>
<li>使用Callable，该线程运行后会返回Future类型，是我们需要获取该线程的回调。</li>
</ul>
<h4 id="多线程执行">多线程执行</h4>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-6"> 	List&lt;File&gt; subList;
 	int batchSize = 500;
    // 计算运行规模（需要多少个线程）
 	int runSize = ((Double) (Math.ceil(singleFiles.size() * 1d / batchSize))).intValue();
 	ExecutorService executor = Executors.newFixedThreadPool(runSize);
     // 使用阻塞容器记录结果
 	BlockingQueue&lt;Future&lt;List&lt;String&gt;&gt;&gt; queue = new LinkedBlockingQueue&lt;&gt;();
	for (int i = 0; i &lt; runSize; i&#43;&#43;) {
      if ((i &#43; 1) == runSize) {
           int startIndex = i * batchSize;
           subList = singleFiles.subList(startIndex, singleFiles.size());
        } else {
             int startIndex = i * batchSize;
              int endIndex = (i &#43; 1) * batchSize;
              subList = singleFiles.subList(startIndex, endIndex);
        }
       FileHandler fileHandler = new FileHandler(subList,absolutePathMap);
            Future&lt;List&lt;String&gt;&gt; res = executor.submit(fileHandler);
            queue.add(res);
        }

        List&lt;String&gt; resAll = new ArrayList&lt;&gt;();
        int queueSize = queue.size();
        // 循环获取结果
        for (int i = 0; i &lt; queueSize; i&#43;&#43;) {
            resAll.addAll(queue.take().get());
         }
         executor.shutdown();</div>
    </div>
  </div>
</div><h3 id="存储">存储</h3>
<p>在FileHandler中调用存储逻辑，也采用线程池方案。</p>
<h1 id="注意事项">注意事项</h1>
<p>在linux环境下运行，需要注意路径分隔符与windows系统不同。应将代码中的“\”使用File.separator代替。
线程数量可读取当前系统的CPU内核数量，从而容易取得更好的效率。</p>
<h1 id="总结">总结</h1>
<p>在对任务进行恰当的逻辑分割后，很容易找到多线程的解决方案，充分利用CPU资源。
使用现有的线程池方案，避免创建过多空闲线程，能使效率更优。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Docker笔记</title>
      <link>https://ygria.site/docker-notes/</link>
      <pubDate>Mon, 10 Sep 2018 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/docker-notes/</guid>
      <description>&lt;h1 id=&#34;常用指令&#34;&gt;常用指令&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;docker images&lt;/code&gt; 列出本地 docker 镜像
&lt;code&gt;docker ps -a&lt;/code&gt; 	列出所有正在运行的容器
&lt;code&gt;docker stop containerID&lt;/code&gt;	停止容器
&lt;code&gt;docker start containerID&lt;/code&gt;	开始容器
&lt;code&gt;sudo docker exec -it containerID /bin/bash&lt;/code&gt;  	进入容器内
&lt;code&gt;linux --mount&lt;/code&gt; 	 挂载 Unix 文件系统（ Unix File System ）之外的文件，或使用 Volume 数据卷。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Docker笔记" /><h1 id="常用指令">常用指令</h1>
<p><code>docker images</code> 列出本地 docker 镜像
<code>docker ps -a</code> 	列出所有正在运行的容器
<code>docker stop containerID</code>	停止容器
<code>docker start containerID</code>	开始容器
<code>sudo docker exec -it containerID /bin/bash</code>  	进入容器内
<code>linux --mount</code> 	 挂载 Unix 文件系统（ Unix File System ）之外的文件，或使用 Volume 数据卷。</p>
<p>容器互联：</p>
<p>推荐将容器加入自定义的Docker网络，连接多个容器，或使用<code>--link</code> 指令。</p>
<h1 id="定义">定义</h1>
<p>基于Linux内核和 LXC (Linux Container) 技术，对进程进行封装和隔离，属于操作系统层面的虚拟化技术，在容器的基础上，进行了进一步的封装，从文件系统、网络互联到进程隔离等等，极大的简化了容器的创建和维护。</p>
<h1 id="优势">优势</h1>
<p>相较于传统的虚拟机技术，docker 具有更加轻量级、易于管理和并发的特点。</p>
<p><strong>与传统虚拟机比较</strong></p>
<table>
  <thead>
      <tr>
          <th>特性</th>
          <th>容器</th>
          <th>虚拟机</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>启动</td>
          <td>秒级</td>
          <td>分钟级</td>
      </tr>
      <tr>
          <td>硬盘使用</td>
          <td>一般为MB</td>
          <td>一般为GB</td>
      </tr>
      <tr>
          <td>性能</td>
          <td>接近原生</td>
          <td>弱于</td>
      </tr>
      <tr>
          <td>系统支持量</td>
          <td>单机支持上千个容器</td>
          <td>一般几十个</td>
      </tr>
  </tbody>
</table>
<h1 id="docker-基本概念">Docker 基本概念：</h1>
<ul>
<li>镜像（ Image ）</li>
<li>容器 （ Container ）</li>
<li>仓库 （ Repository ）</li>
</ul>
<h2 id="镜像">镜像</h2>
<p>使用pull指令，可以拉取 DockerHub 中的开源镜像到本地使用。
docker pull mysql
docker images 列出本地的镜像</p>
<h3 id="定制镜像">定制镜像</h3>
<p>使用 DockerFile</p>
<p>FROM scratch
FROM指令为以什么为基础镜像，若为scratch意味着不以任何镜像为基础。</p>
<h2 id="容器">容器</h2>
<p>容器之于镜像，如同实例之于类，创建容器，意为给镜像生成了一个实例。</p>



<blockquote>
    <p>docker &ndash;name mysql -p 3306:3306 -d mysql
若不使用-d，容器会将输出的结果（STDOUT）打印到宿主机上。
如果使用了-d参数运行容器，容器会在后台运行，并不会在前台输出结果。若要看输出，可使用docker logs containerID指令进行查看。</p>
</blockquote>
<h2 id="仓库">仓库</h2>
<p>在定制好个人的镜像后，可以搭建仓库进行存储。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Java设计模式</title>
      <link>https://ygria.site/java-design-pattern/</link>
      <pubDate>Sun, 10 Jun 2018 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/java-design-pattern/</guid>
      <description>&lt;h1 id=&#34;定义&#34;&gt;定义&lt;/h1&gt;
&lt;p&gt;基本概念：保证一个类仅有一个实例，并提供一个访问它的全局访问点&lt;/p&gt;
&lt;h1 id=&#34;懒汉式&#34;&gt;懒汉式&lt;/h1&gt;
&lt;div class=&#34;code-block  is-closed show-line-numbers  tw-group tw-my-2&#34;&gt;
  &lt;div class=&#34;code-wrapper light&#34; data-theme=&#34;light&#34; id=&#34;code-block-id-1&#34;&gt;
    
    &lt;div class=&#34;withBackground breeze&#34; style = &#34;padding: 64px&#34;/&gt;
    
    &lt;div class=&#34;window&#34;&gt;
      &lt;div id=&#34;codeblock-header&#34; class=&#34;header&#34;&gt;
        &lt;div class=&#34;controls&#34;&gt;
          &lt;div class=&#34;control&#34;&gt;&lt;/div&gt;
          &lt;div class=&#34;control&#34;&gt;&lt;/div&gt;
          &lt;div class=&#34;control&#34;&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;div className={classNames(styles.fileName, styles.supabaseFileName)} id=&#34;codeblock-title&#34; class=&#34;fileName&#34;
          data-value={fileName}&gt;
          &lt;span&gt; java&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&#34;controls-buttons&#34; id=&#34;controls-button-id-1&#34;&gt;
          &lt;button id=&#34;copyButton-id-1&#34;&gt;
            &lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;#000&#34;
              stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; class=&#34;lucide lucide-copy&#34;&gt;
              &lt;rect width=&#34;14&#34; height=&#34;14&#34; x=&#34;8&#34; y=&#34;8&#34; rx=&#34;2&#34; ry=&#34;2&#34; /&gt;
              &lt;path d=&#34;M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2&#34; /&gt;
            &lt;/svg&gt;
          &lt;/button&gt;
          &lt;button id=&#34;copySuccess-id-1&#34; style=&#34;display: none;&#34; class=&#34;not-allowed&#34; &gt;
            &lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34;
              stroke=&#34;green&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;
              class=&#34;lucide lucide-clipboard-check&#34;&gt;
              &lt;rect width=&#34;8&#34; height=&#34;4&#34; x=&#34;8&#34; y=&#34;2&#34; rx=&#34;1&#34; ry=&#34;1&#34; /&gt;
              &lt;path d=&#34;M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2&#34; /&gt;
              &lt;path d=&#34;m9 14 2 2 4-4&#34; /&gt;
            &lt;/svg&gt;
          &lt;/button&gt;

          &lt;button id=&#34;exportImage-id-1&#34;&gt;
            &lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34;
              stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;
              class=&#34;lucide lucide-download&#34;&gt;
              &lt;path d=&#34;M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4&#34; /&gt;
              &lt;polyline points=&#34;7 10 12 15 17 10&#34; /&gt;
              &lt;line x1=&#34;12&#34; x2=&#34;12&#34; y1=&#34;15&#34; y2=&#34;3&#34; /&gt;
            &lt;/svg&gt;
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div data-language=&#34;java&#34; data-theme=&#34;breeze&#34; id=&#34;code-id-1&#34;&gt;public class Singleton {
	//持有私有的单例对象，防止被引用。赋值为null，目的是实现延迟加载
	private static Singleton instance = null;
	//私有的构造方法，防止被实例化
	private Singleton() {}
	//1：懒汉式，静态工程方法，创建实例
	public static Singleton getInstance() {
		if (intsatnce == null) {
        	instance = new Singleton();
		}
        return instance;
    }
}&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;调用：&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Java设计模式" /><h1 id="定义">定义</h1>
<p>基本概念：保证一个类仅有一个实例，并提供一个访问它的全局访问点</p>
<h1 id="懒汉式">懒汉式</h1>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-1">public class Singleton {
	//持有私有的单例对象，防止被引用。赋值为null，目的是实现延迟加载
	private static Singleton instance = null;
	//私有的构造方法，防止被实例化
	private Singleton() {}
	//1：懒汉式，静态工程方法，创建实例
	public static Singleton getInstance() {
		if (intsatnce == null) {
        	instance = new Singleton();
		}
        return instance;
    }
}</div>
    </div>
  </div>
</div><p>调用：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-2">Singleton.getInstance().method();</div>
    </div>
  </div>
</div><p>优点：延迟加载（需要的时候才去加载），适合单线程操作
缺点：线程不安全。在多线程中很容易出现不同步的情况（如在数据库对象进行频繁读写时）</p>
<h1 id="双重线程检查模式">双重线程检查模式</h1>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> java</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="java" data-theme="breeze" id="code-id-3">public class </div>
    </div>
  </div>
</div>]]></content:encoded>
    </item>
    
    <item>
      <title>Head First 深入浅出HTML CSS</title>
      <link>https://ygria.site/head-first-%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAhtml-css/</link>
      <pubDate>Sat, 24 Mar 2018 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/head-first-%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAhtml-css/</guid>
      <description>&lt;h2 id=&#34;引子&#34;&gt;引子&lt;/h2&gt;
&lt;h3 id=&#34;html&#34;&gt;HTML&lt;/h3&gt;
&lt;p&gt;当浏览器阅读HTML时，它会解析包围文本的所有标记。标记使用尖括号括起来的字母或单词，标记告诉浏览器文本的结构和意义。
用包围文本的成对标记告诉浏览器网页的结构。
元素=开始标记+内容+结束标记
元素可以拥有“属性”：
属性用来为一个元素提供附加信息。例如，如果有一个样式元素，属性允许你确切地描述这个元素——用来提供该元素的额外信息。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="Head First 深入浅出HTML CSS" /><h2 id="引子">引子</h2>
<h3 id="html">HTML</h3>
<p>当浏览器阅读HTML时，它会解析包围文本的所有标记。标记使用尖括号括起来的字母或单词，标记告诉浏览器文本的结构和意义。
用包围文本的成对标记告诉浏览器网页的结构。
元素=开始标记+内容+结束标记
元素可以拥有“属性”：
属性用来为一个元素提供附加信息。例如，如果有一个样式元素，属性允许你确切地描述这个元素——用来提供该元素的额外信息。</p>
<h3 id="css">CSS</h3>
<p><strong>HTML ：超文本标记语言</strong> <em>HyperText Markup Language</em>
<strong>CSS：级联样式表</strong> <em>Cascading Style Sheet</em>
CSS是与HTML完全不同的语言。
分别使用两种语言，HTML用于创建结构，CSS用于创建样式，使用它们在各自的领域完成工作。</p>
<h2 id="深入理解超文本">深入理解超文本</h2>
<p>使用</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-1">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-1">
          <button id="copyButton-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-1" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-1">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-1">&lt;a&gt;</div>
    </div>
  </div>
</div><p>标签，创建超文本链接到另外一个网页，&ldquo;a&quot;元素中的内容在网页中是可点击的，Href属性告诉浏览器链接的目的地。</p>
<h2 id="构建模块">构建模块</h2>
<ol>
<li>步骤：先计划好网页的结构，再绘制略图，最后写HTML。</li>
<li>使用大的块元素来创建网页，然后用内联元素修饰。</li>
<li>通常使用最能匹配内容含义的元素。例如，当你需要列表时绝不用段落表示。</li>
</ol>
<p><strong>块元素</strong>：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-2">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-2">
          <button id="copyButton-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-2" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-2">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-2">&lt;p&gt;、&lt;blockquote&gt;、&lt;ol&gt;、&lt;ul&gt;和&lt;li&gt;</div>
    </div>
  </div>
</div><p>都是块元素，它们独立显示，文本前后有空行。
<strong>内联元素</strong>：</p>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-3">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-3">
          <button id="copyButton-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-3" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-3">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-3">&lt;q&gt;、&lt;em&gt;、&lt;a&gt;</div>
    </div>
  </div>
</div><p>是内联元素，这些元素的内容和其他内容一起跟随在文字流中。</p>
<h3 id="q与blockquote">q与blockquote</h3>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-4">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-4">
          <button id="copyButton-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-4" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-4">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-4">&lt;q&gt;&lt;/q&gt;
``` 
引用标签：简短地“引用”现有文字的一部分
```html
&lt;blockquote&gt;&lt;/blockquote&gt;</div>
    </div>
  </div>
</div><p>区块引用：引用一大段文字并独立显示
blockquote和q是两种不同类型的元素，blockquote元素是块（block）元素而q是内联（inline）元素。
br元素：显示换行</p>
<h3 id="li与ol元素">li与ol元素</h3>
<p>用li把每个列表项封入，每个li元素将在列表中开始一个列表项。
<strong>u</strong>nordered <strong>l</strong>ist = <strong>ul</strong>** <strong>无序列表
<strong>o</strong>rdered <strong>l</strong>ist = <strong>ol</strong></strong> ** 有序列表
<strong>l</strong>ist <strong>i</strong>tem = <strong>li</strong> 列表项
ol和li必须一起使用
列表是一组项目：li元素用来确定每个项目，ol则是把它们组成一组。
自定义列表dl：列表中每个项目都有一个项限dt和一个描述dd。</p>
<h3 id="空元素">空元素</h3>
<p>br是个“空元素”，它没有内容，且仅由一个标记组成。</p>
<h3 id="嵌套">嵌套</h3>
<p>当把一个元素放入到另一个元素中，叫做嵌套。组织网页中的嵌套元素相当于画家族树。
作用：防止标记不匹配。</p>
<h2 id="部署与扩展">部署与扩展</h2>
<h3 id="http协议">HTTP协议</h3>
<p>HTTP协议是超文本传输协议，当找不到对应超文本时，返回404</p>
<h3 id="a标签的扩展">a标签的扩展</h3>
<ul>
<li>目标锚：给出目的标识：</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-5">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-5">
          <button id="copyButton-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-5" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-5">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-5">&lt;a href = &#34;index.html\#chai&#34;&gt;See Chai Tea&lt;/a&gt;&lt;a id = &#34;chai&#34;&gt;目的&lt;/ai&gt;</div>
    </div>
  </div>
</div><ul>
<li>使用target（对象）打开一个新窗口：</li>
</ul>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-6">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-6">
          <button id="copyButton-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-6" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-6">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-6">&lt;a target = &#34;\_blank&#34;&gt;</div>
    </div>
  </div>
</div><p>target属性告诉浏览器在哪打开href属性</p>
<h2 id="图像">图像</h2>
<p>浏览器遇到img标签时，需要进行特殊处理：浏览器必须先接收图像，然后才能在该页面上显示图像。
alt属性：一小段描述图像的文字，当图像不能显示时，用这一小段叙述代替原有的图像。</p>
<h2 id="css-1">CSS</h2>
<p>使用选择符+内容来定义样式
若要使某类的元素拥有同一个样式，使用一个句点，并且接一个类名，则这个规则会适用于该类内的所有成员。
常用规则：
<strong>font-family</strong> 定义网页中的字体
<strong>font-size</strong> 控制字体的大小
<strong>color</strong> 文本的颜色
<strong>font-weight</strong> 设置字体</p>
<h3 id="盒模式">盒模式</h3>
<p>Box-Model
padding（补白）——&gt;border（边框）——&gt;margin（边界）</p>
<h3 id="div与span">div与span</h3>
<p>div：用于划分页面，并改变块区样式。借助div，将页面划分为几个合理逻辑结构，有助于网页结构的清晰和样式化。
块元素的默认宽度是auto，也就是说会延伸到所有空间。
可以定义块的事迹大小，也可以定义百分数。如果使用百分数，则是元素所在的容器（可以是body、div）</p>
<h3 id="根据状态样式化元素">根据状态样式化元素</h3>
<h2 id="页面布局">页面布局</h2>
<p>使用流布局：
浏览器用<strong>流</strong>来布置页面上的HTML元素，它从HTML文件的开头开始，从头到尾跟着元素的流显示它遇到的每个元素。
当两个浏览器并排放置两个内联元素，且这些元素都有边界时，会将两个元素的边界加起来显示。
当浏览器并列放置两个块元素时，它把共同的边界重叠到一起，重叠边界的高度是最大边界的值。</p>
<h2 id="表格与表单">表格与表单</h2>
<h3 id="表格">表格</h3>
<div class="code-block  is-closed show-line-numbers  tw-group tw-my-2">
  <div class="code-wrapper light" data-theme="light" id="code-block-id-7">
    
    <div class="withBackground breeze" style = "padding: 64px"/>
    
    <div class="window">
      <div id="codeblock-header" class="header">
        <div class="controls">
          <div class="control"></div>
          <div class="control"></div>
          <div class="control"></div>
        </div>
        <div className={classNames(styles.fileName, styles.supabaseFileName)} id="codeblock-title" class="fileName"
          data-value={fileName}>
          <span> html</span>
        </div>
        <div class="controls-buttons" id="controls-button-id-7">
          <button id="copyButton-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#000"
              stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy">
              <rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
              <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
            </svg>
          </button>
          <button id="copySuccess-id-7" style="display: none;" class="not-allowed" >
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-clipboard-check">
              <rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
              <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
              <path d="m9 14 2 2 4-4" />
            </svg>
          </button>

          <button id="exportImage-id-7">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
              class="lucide lucide-download">
              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
              <polyline points="7 10 12 15 17 10" />
              <line x1="12" x2="12" y1="15" y2="3" />
            </svg>
          </button>
        </div>
      </div>
      <div data-language="html" data-theme="breeze" id="code-id-7">&lt;table&gt;&lt;/table&gt;</div>
    </div>
  </div>
</div><p>使用tr创建表格中的行，th创建表头，td创建表格中的内容。</p>
<h3 id="表单">表单</h3>
<p>textarea：可以输入多行文字，并使用rows和cols定义文本区域的高度与宽度。
select：为网页创建菜单空间，菜单提供了在一组选项中选择的方式，select与option共同创建一个菜单。</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>2017年10月阅读笔记</title>
      <link>https://ygria.site/2017-read-note/</link>
      <pubDate>Mon, 30 Oct 2017 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/2017-read-note/</guid>
      <description>&lt;h3 id=&#34;奥斯维辛一部历史&#34;&gt;《奥斯维辛：一部历史》&lt;/h3&gt;
&lt;p&gt;　　最初听说奥斯维辛是在语文教科书上的《奥斯维辛没有什么新闻》，此外对这段历史不过是远远一瞥——太过沉重，没办法毫无负担地阅读。对这个人类历史的丑陋伤疤，真的要看也是带着严肃的心态去看，本能生出些敬畏和尊重。
　　还是很值得一看，不过看的是埋在更深层原因。固然能看到惨像如斯：囚犯拿石碾子从活人身上轧过去，生生在卡车边缘摔死的孩子，一排排空荡荡的婴儿车……与此同时是被在通往死亡的铁轨末尾用鲜花装点得整洁漂亮的火车站，“劳动使人自由”的标语，是一个极端荒诞却又万分真实的近代讽刺。
　　埋在深层的是由来已久的反犹思想，对外战争失败矛盾的转移，用个体代替集体的仇恨积累，用书中的话说叫做“累积式激进”——所有纳粹和几乎所有非犹德国人都在为这场屠杀出谋划策，没有一片雪花是无辜的。
　　更让人触目惊心的是都发生在近现代，文明进化的产物被用来进行更高效无损的屠杀，所以有了毒气室，有了流水线一般的屠杀作业。当集体陷入某种狂热，连最起码的内疚都不会产生，思想倾向一旦形成，所有人都成了铁血集体的一部分，任何提出异议的不是被淹没，就是被毁灭。人性的自私，残忍，懦弱，无情，展露无遗。
　　在集中营中采用的某些做法，现在在某些集体主义的训练中也时有出现，比如实行丛林法则：从一群囚犯中选出一个头儿，示意他来监督所有其他的囚犯，并给他极大的权力。若他展现出稍微的仁慈和软弱，就将他降回到原先的地位去。这样的“捧杀”使善良没有任何容身之地了。此外还有连坐的做法等等，来来回回要约束人类，不过是用这几种手段，只是在集中营里惩罚更加残酷，威胁也更加明确——虐杀。
　　我印象最深的仍然是幸存者的待遇，被苏联红军解放的犹太女人很多又被俄国士兵强奸，回到家里，却发现自己的房屋早已被别人侵占，没有人欢迎犹太人。等他们终于熬过黑暗，发现等来的并不是黎明，即使是寥寥几个逃出去的幸存者，也往往在痛苦和惊恐中度过一生。
　　就好像去南京大屠杀纪念馆，看到各种惨绝人寰的虐杀手段都尚且可以忍耐，看到幸存者说自己每年都来看这些死者才痛哭失声。死者已矣，对生者的创痛更加隐蔽、绵长、酷烈，有时甚于死亡。&lt;/p&gt;</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="2017年10月阅读笔记" /><h3 id="奥斯维辛一部历史">《奥斯维辛：一部历史》</h3>
<p>　　最初听说奥斯维辛是在语文教科书上的《奥斯维辛没有什么新闻》，此外对这段历史不过是远远一瞥——太过沉重，没办法毫无负担地阅读。对这个人类历史的丑陋伤疤，真的要看也是带着严肃的心态去看，本能生出些敬畏和尊重。
　　还是很值得一看，不过看的是埋在更深层原因。固然能看到惨像如斯：囚犯拿石碾子从活人身上轧过去，生生在卡车边缘摔死的孩子，一排排空荡荡的婴儿车……与此同时是被在通往死亡的铁轨末尾用鲜花装点得整洁漂亮的火车站，“劳动使人自由”的标语，是一个极端荒诞却又万分真实的近代讽刺。
　　埋在深层的是由来已久的反犹思想，对外战争失败矛盾的转移，用个体代替集体的仇恨积累，用书中的话说叫做“累积式激进”——所有纳粹和几乎所有非犹德国人都在为这场屠杀出谋划策，没有一片雪花是无辜的。
　　更让人触目惊心的是都发生在近现代，文明进化的产物被用来进行更高效无损的屠杀，所以有了毒气室，有了流水线一般的屠杀作业。当集体陷入某种狂热，连最起码的内疚都不会产生，思想倾向一旦形成，所有人都成了铁血集体的一部分，任何提出异议的不是被淹没，就是被毁灭。人性的自私，残忍，懦弱，无情，展露无遗。
　　在集中营中采用的某些做法，现在在某些集体主义的训练中也时有出现，比如实行丛林法则：从一群囚犯中选出一个头儿，示意他来监督所有其他的囚犯，并给他极大的权力。若他展现出稍微的仁慈和软弱，就将他降回到原先的地位去。这样的“捧杀”使善良没有任何容身之地了。此外还有连坐的做法等等，来来回回要约束人类，不过是用这几种手段，只是在集中营里惩罚更加残酷，威胁也更加明确——虐杀。
　　我印象最深的仍然是幸存者的待遇，被苏联红军解放的犹太女人很多又被俄国士兵强奸，回到家里，却发现自己的房屋早已被别人侵占，没有人欢迎犹太人。等他们终于熬过黑暗，发现等来的并不是黎明，即使是寥寥几个逃出去的幸存者，也往往在痛苦和惊恐中度过一生。
　　就好像去南京大屠杀纪念馆，看到各种惨绝人寰的虐杀手段都尚且可以忍耐，看到幸存者说自己每年都来看这些死者才痛哭失声。死者已矣，对生者的创痛更加隐蔽、绵长、酷烈，有时甚于死亡。</p>
<h3 id="人间失格日太宰治">《人间失格》（[日]太宰治）</h3>
<p>　　听这个名字也有很久了。
　　日本小说含蓄细腻，“圆熟嫣丽”，有如屏风上的浮世绘。我不大能欣赏这种趣味。小说讲了一个少爷的潦倒，写的情感自然有动人之处，却未免觉得他们说话都太客气，带着颓丧，和腰封上粗体大字“** 日本文学巅峰人物、要读村上春树更要读太宰治，最具影响力小说 **”一比较，就对比尤为鲜明了。
　　近来看书功利，不带知识性不大愿读，可能便是辜负，但也不觉可惜。</p>
<h3 id="局外人法加缪">《局外人》（[法]加缪）</h3>
<p>　　“一时，我突然产生了这么一个滑稽的印象：这些人似乎是专来审判我的。”
　　全书看完，还是觉得这句话最震撼，好像所有人类都并不能互相理解，他们可以互相问候、握手乃至性交，但都不过是物理的接触，真实的灵魂跃居于肉体之外，漠然审视。主角自然是“局外人”，当主角身处于被审判的“局”中，其他人亦身处局外。人类生而群居，在社会中生存必要有的社交和共情能力，正是进步的本源。孤僻的人类好像是进化树上生错的旁枝。心理描写非常经典，刻画细微，唯其真实才让人感同身受。</p>
<h3 id="阴阳师第1卷">《阴阳师（第1卷）》</h3>
<p>　　
　　快Ａ掉游戏时才看的原著，意外的挺有意思，努力想从描写的故事中看出自己所养的式神的影子，结果当然是全然失败、难以找到，只有荒川和花鸟卷近可附会，然而人设和情节也截然不同。
　　两位主角晴明和博雅还是很有意思的。晴明近似全知全能的经典主角，而博雅则非常痴，常被晴明称赞为“好汉子”……连续三年的夜晚都去等待一位大师弹琵琶，或者对月吹笛到流泪，都是十分风雅而传奇的痴人形象啊。</p>
<h3 id="仿生人会梦见电子羊吗美国菲利普k狄克">《仿生人会梦见电子羊吗？》（[美国]菲利普·K·狄克）</h3>
<p>　　小说中的主人公里克生活的地方已被放射尘笼罩，动物大多灭绝，人类也大部分移民。地球已经成了一个巨大的垃圾场，而里克选择留下来，“说不定有哪一天尘埃会落定”，从一开始用情绪调节器将自己调节到适合工作的情绪，到巨大的寂寞攫住了他，吞噬他，使他终于感受到和妻子一样的绝望和抑郁。追杀仿生人时逐渐产生了对仿生人的移情，发现野生蛤蟆后的狂喜，最后又发现蛤蟆是电子的之后的麻木和空虚，结构流畅精悍，情绪非常有感染力。
　　这本书从书名到全书都透着一种冷峻的幽默，试图比附了一下，倒不是“含泪微笑”那样文艺优美，比较像一脸苦相地说笑话。一开始看就让我轻易联想起了《银河系漫游指南》里的情节：地球被随便炸毁，只留下了一行“基本上无害”的批语；深思算了无数年才算出来没头没脑的终极答案“42”，和此书中默瑟主义最后被发现只是一个跑龙套的演员在摄影棚里拍的片子意义相通——等号两端一边被放上沉重无比的生命、无穷多人的支柱和信仰、漫长无垠的岁月，另一边则轻飘飘地搁上一点破烂，一个敷衍的谎言，一个蹩脚滑稽的小丑，这其中的荒诞和讽刺，让人觉得心酸又好笑，比一般悲剧更加冷酷无情。</p>
<h3 id="鱼丽之宴木心">《鱼丽之宴》(木心)</h3>
<p>　　木心作品，在没正式翻开之前就有接触。常常会想到画家写文另有优势，因其审美超越常人，能写得更加色彩浓艳、生机勃勃。</p>
<p>　　</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>2017年观影/剧杂记</title>
      <link>https://ygria.site/2017-movie-note/</link>
      <pubDate>Wed, 23 Aug 2017 00:00:00 +0000</pubDate>
      
      <guid>https://ygria.site/2017-movie-note/</guid>
      <description>《豪斯医生》《银翼杀手2049》观后感</description>
      
     <content:encoded><![CDATA[<img loading="lazy" decoding="async" src="/" alt="2017年观影/剧杂记" /><h2 id="housemd">House.M.D</h2>
<p>　　快要看完《豪斯医生》了。
　　八季电视剧几乎是连续在看，还剩几集，可能看完后过一阵子会重头再看一遍——内心非常惆怅：要再过多久，才能遇到这样一个极其丰富、极具魅力的男主角啊！
　　豪斯常说两句话，一句是人人都撒谎，一句是人永远也不会变。如果说前一句是他对人性的洞察，后一句则更像是一句宣言：豪斯永远是豪斯，以自己固有的自毁、自傲、玩世不恭、保存自我，不存心获得理解也不期待理解，交付给世界的，唯有一点轻蔑。
　　整整八季都在说他，他也一直都没有任何改变，变得更丰富的只是他的经历，像从不同角度去窥探他原本就复杂多元的灵魂。佐证除了他出狱时威尔逊说的那句“你一点也没变”，还有别人问起豪斯腿没有残疾时是什么样，得到的回答也是“和现在没有什么区别”。这是他的痛苦之源，亦是魅力所在。
　　我看剧太容易真情实感，一方面是因为一开始就很喜欢豪斯，另一方面则是因为编剧非常善于使用对比和转折手法——室内春光融融，人人欢聚喜气洋洋，室外是大雪纷飞，豪斯独自伫立观望，然后缓缓走开。聚会热闹非凡，豪斯独自在浴缸里喝酒。一边是欢庆圣诞，一边是豪斯独自弹着旧钢琴。——这种孤独当然魅力十足。
　　转折也来得冷酷锐利，让人猝不及防：豪斯恢复了双腿，小孩儿一样玩滑板，下一瞬就是他腿又痛了，从滑板上走下来，忧心忡忡地扶着膝盖。豪斯以为自己终于得遂所愿，获得了真爱，转眼却发现一切不过是幻梦，爱人的口红是自己的药瓶。他遇到了心爱的姑娘，弹钢琴时交换着深情脉脉的眼神，随即自己不慎害得重伤的病人躺在轮椅上被推进房间……一切温情欢乐的瞬间都短暂，而疼痛来势汹汹，阴魂不散，是人生缠绕不去的底色。
　　豪斯常常被称赞、被感谢——因其绝顶聪明的头脑、极其优秀的专业能力——甚至被称颂为神明。可是哪一种神明会这样毫不吝惜地展露狼狈与伤痛？他一直就满身尘埃，无需他人将他从云端推落。无神主义者该去憎恶哪一种天道？该去祈求哪一路神灵？当他能听出安慰的虚伪，洞穿社交虚伪的本质，又怎能期待从中获得安慰？
　　可能正是因为看得太透彻，才让豪斯面对感情时幼稚消极：一开始不去获得，就无所谓失去，像是看见熊熊的炉火，虽然想靠近被温暖，但怕被火灼伤，所以干脆不靠近，也不承认想靠近。他所处的很多困境从旁观者看来都并非死局，甚至很多不幸压根都是他自己亲手造成的，只需要稍作妥协，就可以顺利摆脱。
　　但豪斯决不妥协，决不改变。将怜悯加诸豪斯更像是一种轻蔑和侮辱，我只是一个动情而无助的观众，唯一的选择是看着他按照自己选择的道路走下去。
　　一直都喜欢移情和类比，但是豪斯世无其二。</p>
<h2 id="银翼杀手2049">银翼杀手2049</h2>
<p>　　万圣节晚上去看的电影，本以为是一部爆米花，却没想到是非常晦涩压抑的科幻文艺片。没有了解过前作，不免觉得压抑无聊。
　　因为才看完《电影批评》，所以看的时候特别留意画面与配乐，画面带着宏大的荒凉感，高度文明却毫无生机，漫无边际的滂沱雨幕，光怪陆离的巨大光影，重复单调的嘈杂广告从高空跌落，给人无处遁逃的压抑感。配乐则是间或击打声，轰隆隆非常响亮，使得不大能欣赏作品、昏昏欲睡的人突然警醒，好像在快睡着的数学课上忽遭点名。
　</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>