<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ 谷中仁 - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ 谷中仁 - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 04 Jun 2026 15:37:34 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/author/guzhongren/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 使用 Lighthouse 持续优化你的 Web 性能 ]]>
                </title>
                <description>
                    <![CDATA[ 性能是留住用户的关键， 性能直接影响公司的命运。 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/continual-optimization-of-your-web-app-with-lighthouse/</link>
                <guid isPermaLink="false">624046f97f18d1062895bee5</guid>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Lighthouse ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 谷中仁 ]]>
                </dc:creator>
                <pubDate>Tue, 29 Mar 2022 02:32:44 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/03/Lighthouse-Chrome-DevTools.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <blockquote>
<p>性能是留住用户的关键，<br>
性能直接影响公司的命运。<br>
-- <a href="https://www.youtube.com/watch?v=Xryhxi45Q5M&amp;feature=youtu.be&amp;t=1366">Pinterest</a></p>
</blockquote>
<h2 id="">介绍</h2>
<p>互联网发展至今，网页性能始终是一个重要的问题，各大互联网公司都在不遗余力地优化自己的 Web 页面，为的就是更快地让用户更快的看到用户想看到的内容。</p>
<p>互联网在近几十年的发展过程中，度量 Web 性能各个指标、术语已经稳定了，各个产品的度量方式都趋于一致。</p>
<p>BBC 发现其网站的加载时间每增加 1 秒，便会多失去 10% 的用户。</p>
<p>DoubleClick by Google 发现，如果页面加载时间超过 3 秒，53% 的移动网站访问活动将遭到抛弃。</p>
<p>DoubleClick by Google 研究表明，与加载时间约为四倍（19 秒）的网站相比，加载时间在 5 秒以内的网站会话加长 70%、跳出率下降 35%、广告可见率上升 25%。</p>
<p>Mobify 的首页加载时间每减少 100 毫秒，基于会话的转化率增加 1.11%，年均收入增长近 380,000 美元。</p>
<p>AutoAnything 的页面加载时间减少一半后，其销售额提升 12-13%。</p>
<p>可见，Web 页面的性能在现今万物互联的时代有多重要。</p>
<h2 id="web">Web 性能在度量方面存在的问题</h2>
<h3 id="">不可本地运行，以尽可能早地发现性能问题</h3>
<p>很多工具都不可以在本地运行； 如果 Web 性能测试工具可以在本地运行，开发人员可以更早地发现问题，并尽可能早的在本地解决，避免了在 CI 上跑了一会了才发现问题，在 <code>CI/CD</code> 的敏捷开发过程中这样可以节省很多时间，提高生产效率。<code>Web 性能测试左移</code> 必定为 Web 产品性能带来更多好处，甚至为公司带来更多盈利。</p>
<h3 id="">不能持续度量性能指标</h3>
<p>目前市场上 Web 性能度量的产品大多都是 <code>Sass</code> 产品，使用其产品我们只能得到一个运行完性能测试的可视化结果页面，但是不能持续的记录 Web 网页性能的改进记录，不能很好的量化一个 Web 产品性能的生命周期。当然也有实现历史记录的 Web 性能测试工具，例如 <a href="https://treo.sh/">treo</a>。</p>
<h3 id="">费用</h3>
<p>开源免费的 Web 性能测试工具有不少，但是用起来可能没有那么爽；如果需要更多的特性，如持续记录 Web 网页性能，一般只有商业产品会支持，而且收费还不低。</p>
<h3 id="ci">CI 集成困难</h3>
<p>如前面所说，很多工具要么是本地不能运行，要么就是 Sass 产品，不能很好的与 Pipeline 集成， 导致 Web 性能结果反馈周期长、工程效率低等问题。</p>
<h2 id="lighthouse">Lighthouse</h2>
<blockquote>
<p>Lighthouse 是一个开源的、自动化的工具，用以提高网页质量。你可以在任何网页上运行它，公开的或需要认证的。它对性能、可访问性、渐进式web应用程序、SEO 等进行审计。<br>
你可以在 <code>Chrome DevTools</code>、 命令行甚至是 <code>Node</code> 模块中运行 <code>Lighthouse</code>。 向 Lighthouse 提供一个要审计的 URL，它会对页面运行一系列审计，随即会生成一个关于页面运行情况的报告。对于失败的审计项，可以使用对应项的改进方案。每个审计项都有一个参考文档，解释为什么审核很重要，以及如何修复它。</p>
</blockquote>
<p>使用方法非常简单，可以看一下我对我的开源项目 <code>Powerboard</code> 审计的结果。<br>
<img src="https://cdn.jsdelivr.net/gh/guzhongren/data-hosting@main/Tools/Lighthouse/Lighthouse-Chrome-DevTools.47rhl099s9c0.webp" alt="Lighthouse-Chrome-DevTools" width="600" height="400" loading="lazy"></p>
<h2 id="lighthousecilighthouseserver">Lighthouse CI 及 Lighthouse Server 的使用</h2>
<h3 id="lighthousecilhci">Lighthouse CI （LHCI）</h3>
<blockquote>
<p>Automate running Lighthouse for every commit, viewing the changes, and preventing regressions<br>
为每个提交自动化运行Lighthouse，查看更改，并防止回归<br>
-- <a href="https://github.com/GoogleChrome/lighthouse-ci">GoogleChrome/lighthouse-ci</a></p>
</blockquote>
<p><code>Lighthouse CI</code> 是 <code>Google Chrome</code> 团队开发的一套可以让持续运行、保存、检索和对 Lighthouse 结果进行<code>断言</code>变得尽可能简单的工具,可以很方便的集成在 CI 上。</p>
<h4 id="">使用</h4>
<p><code>LHCI</code> 提供 <code>npm</code> 安装包，可以很好的在 Pipeline 上集成，只需要在对应目录下运行 <code>autorun</code> 命令即可，命令如下</p>
<pre><code class="language-shell">npm install -g @lhci/cli
lhci autorun
</code></pre>
<p>运行完成后，LHCI 会将结果存放在 <code>.lighthouseci</code> 目录下，用浏览器打开对应的报告即可。</p>
<h3 id="lighthouseserver">Lighthouse Server</h3>
<blockquote>
<p>The LHCI server saves historical Lighthouse data, displays trends in a dashboard, and offers an in-depth build comparison UI to uncover differences between builds.<br>
LHCI Server 保存 Lighthouse 历史数据，并可在仪表板中显示趋势，并提供深入的构建比较 UI，以揭示构建之间的差异。</p>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://cdn.jsdelivr.net/gh/guzhongren/data-hosting@main/Tools/Lighthouse/lhci-server.5x95meg6f4w0.webp" alt="lhci-server" width="600" height="400" loading="lazy"></th>
<th><img src="https://cdn.jsdelivr.net/gh/guzhongren/data-hosting@main/Tools/Lighthouse/lhci-server-compare.64ass32uxhg0.webp" alt="lhci-server-compare" width="600" height="400" loading="lazy"></th>
</tr>
</thead>
</table>
<h4 id="lhciserver">LHCI Server 的安装和使用</h4>
<p>LHCI Server 的安装有多种方式，具体可参考<a href="https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/server.md#lhci-server">这里</a>，推荐使用 Docker 的方式运行。需要注意的是，第一次运行需要创建 Lighthouse App，需要在容器中运行 <code>lhci wizard</code> 并填入相应的信息，<strong>最后记录下生成的 <code>Build token</code> 和 <code>Admin token</code></strong>。</p>
<h3 id="lighthousecilighthouseserver">Lighthouse CI 和 Lighthouse Server 集成</h3>
<p><img src="https://user-images.githubusercontent.com/2301202/81843922-f2e17300-9513-11ea-85f9-d3d8a0b52633.png" alt="Lighthouse CI Recommended Setup Progression" width="600" height="400" loading="lazy"></p>
<p>前面已经讲了 <code>lhci</code> 和 <code>lhci server</code> 如何安装了，接下来就是需要将两者结合起来一起使用了。这里我们以 <code>GitHub Actions</code> 为例来搞一个 Demo。</p>
<p>构建 <code>GitHub workflow</code>，具体实践可参考 <a href="https://github.com/guzhongren/Powerboard">Powerboard</a> 的实现细节。</p>
<p>.github/workflows/Lighthouse.yml</p>
<pre><code class="language-yml">name: Lighthouse CI
on:
  push:
    branches:
      - main
jobs:
  lhci:
    name: Lighthouse
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: Use Node.js 10.x
        uses: actions/setup-node@v1
        with:
          node-version: 16.14.2
      - name: npm install, build
        run: |
          npm install
          npm run build
      - name: Upload Lighthouse Report
        run: |
          npm install -g @lhci/cli
          lhci autorun --config=.github/config/lighthouserc-no-condition.json
          lhci upload --serverBaseUrl=${{ secrets.LHCI_SERVER_URL }} --token=${{ secrets.LHCI_BUILD_TOKEN }}
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
      - name: Check Lighthouse Report
        run: |
          lhci autorun --config=.github/config/lighthouserc.json

</code></pre>
<p>.github/config/lighthouserc-no-condition.json</p>
<pre><code class="language-json">{
  "ci": {
    "collect": {
      "staticDistDir": "./dist",
      "settings": {
        "formFactor": "desktop",
        "screenEmulation": {
          "mobile": false,
          "width": 1920,
          "height": 1080,
          "deviceScaleFactor": 1,
          "disabled": false
        }
      }
    },
    "assert": {
      "assertions": {
        "categories:performance": "off",
        "categories:accessibility": "off",
        "categories:best-practices": "off",
        "categories:seo": "off",
        "categories:pwa": "off"
      }
    }
  }
}

</code></pre>
<p>.github/config/lighthouserc.json</p>
<pre><code class="language-json">{
  "ci": {
    "collect": {
      "staticDistDir": "./dist",
      "settings": {
        "formFactor": "desktop",
        "screenEmulation": {
          "mobile": false,
          "width": 1920,
          "height": 1080,
          "deviceScaleFactor": 1,
          "disabled": false
        }
      }
    },
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.8 }],
        "categories:accessibility": ["error", { "minScore": 0.95 }],
        "categories:best-practices": ["error", { "minScore": 1 }],
        "categories:seo": ["error", { "minScore": 0.9 }],
        "categories:pwa": ["warn", { "minScore": 0.99 }]
      }
    }
  }
}
</code></pre>
<p>需要将 <code>lhci Server</code> 生成的 <code>Build token</code> 和 <code>lhci Server</code> 的地址存放在 GitHub 项目的 Secrets 中。</p>
<p>这里执行了两次 <code>lhci</code> 命令。因为 <code>lhci autorun</code> 运行完成后会运行默认的断言(<code>Assertion</code>)，第一次用没有断言的命令，目的是将当前的网页性能可以上到 Server 端；第二次配置了各项指标的阈值，如果不满足要求，Pipeline 将会阻断，实现 <code>Web 性能测试左移</code>。</p>
<p><img src="https://cdn.jsdelivr.net/gh/guzhongren/data-hosting@main/Tools/Lighthouse/powerboard-lighthouse-actions.40eis8x91cu0.webp" alt="Powerboard Lighthouse Actions" width="600" height="400" loading="lazy"></p>
<h2 id="">总结</h2>
<p>开发人员、技术领导或者市场营销人员想持续量化并展示 Web 页面的<code>性能</code>，Lighthouse CI 是由 Google 编写的一套工具，可以持续运行、保存、检索并使对 Lighthouse 结果进行断言变得尽可能简单。它可以评估 Web 应用和页面，以及从开发的最佳实践中收集性能指标和洞见等信息。</p>
<p>它可以测试你的 Web 页面，得到 Web 页面的 <code>Performance</code> 、 <code>Accessibility</code> 、 <code>Best Practices</code>、<code>SEO</code> 和 <code>PWA</code> 在不同设备上的分数，这些分数可以用于分析产品性能，帮助提升用户转化率等。</p>
<p>不同于 <a href="https://treo.sh/">treo</a> 或者其他一些 Web 性能测试工具，它的优势是开源（<code>Open-Source</code>）、免费（<code>Free</code>）、自托管数据和服务器（<code>Self-hosted</code> data and Server）以及易于集成（<code>Easy to integrate</code>）。</p>
<h2 id="refs">Refs</h2>
<ul>
<li><a href="https://web.dev/i18n/zh/why-speed-matters/">Get Down to Business: Why the Web Matters (Chrome Dev Summit 2018)</a></li>
<li><a href="https://web.dev/i18n/zh/why-speed-matters">为什么速度很重要？</a></li>
<li><a href="https://developers.google.cn/web/tools/lighthouse">Lighthouse</a></li>
<li><a href="https://guzhongren.github.io/">博客</a></li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 构建更优的 GitHub Action 完成 Algolia 数据上传 ]]>
                </title>
                <description>
                    <![CDATA[ 场景 程序员喜欢写博客，大都喜欢自己 Host 一个自己的 Blog。通常 Blog 会有一个全局的搜索功能，开源博客一般都会选择 lunrjs [https://lunrjs.com/] 或者 algolia [https://www.algolia.com/] 等。我的 Blog 是基于 Hugo [https://gohugo.io/] 构建的，使用的主题是 LoveIt [https://hugoloveit.com/]，集成的是 algolia 的搜索方式。 对于存储在 algolia 上的数据，我是通过 GitHub Action：Algolia Docsearch Indexer [https://github.com/marketplace/actions/algolia-docsearch-indexer]  来上传的，之前使用是没有问题的。 问题 突然有一天我要使用搜索功能，但是怎么也搜索不到我想搜索的内容，看了 GitHub 项目的构建状态，一切正常，然后登陆到 algolia 后台一看， 最后一次的数据更新是在 21 年 8 月； 然后打开最近的博客构建记录 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/upload-algolia-index-with-github-action-build-by-myself/</link>
                <guid isPermaLink="false">61ebba03141b4e06737474a9</guid>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 谷中仁 ]]>
                </dc:creator>
                <pubDate>Sat, 22 Jan 2022 07:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/01/Github-Action.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="">场景</h2>
<p>程序员喜欢写博客，大都喜欢自己 Host 一个自己的 Blog。通常 Blog 会有一个全局的搜索功能，开源博客一般都会选择 <a href="https://lunrjs.com/">lunrjs</a> 或者 <a href="https://www.algolia.com/">algolia</a> 等。我的 Blog 是基于 <a href="https://gohugo.io/">Hugo</a> 构建的，使用的主题是 <a href="https://hugoloveit.com/">LoveIt</a>，集成的是 algolia 的搜索方式。</p>
<p>对于存储在 algolia 上的数据，我是通过 GitHub Action：<a href="https://github.com/marketplace/actions/algolia-docsearch-indexer">Algolia Docsearch Indexer</a> 来上传的，之前使用是没有问题的。</p>
<h2 id="">问题</h2>
<p>突然有一天我要使用搜索功能，但是怎么也搜索不到我想搜索的内容，看了 GitHub 项目的构建状态，一切正常，然后登陆到 algolia 后台一看， 最后一次的数据更新是在 21 年 8 月； 然后打开最近的博客构建记录，看了执行如下 GitHub workflow 的 yaml 日志，大吃一惊：程序执行错误，但还是在最后给我们送了一个🚀，同样是写代码，你能忍？</p>
<pre><code class="language-yaml">    - uses: darrenjennings/algolia-docsearch-action@master
      with:
        algolia_application_id: ${{secrets.ALGOLIA_APPLICATION_ID}}
        algolia_api_key: ${{secrets.ALGOLIA_API_KEY}}
        file: './public/index.json'
</code></pre>
<p>运行日志</p>
<pre><code class="language-log">Run darrenjennings/algolia-docsearch-action@master
/usr/bin/docker run --name a682564a13d76444749d3b720346ba2365371_9474ad --label 6a6825 --workdir /github/workspace --rm -e INPUT_ALGOLIA_APPLICATION_ID -e INPUT_ALGOLIA_API_KEY -e INPUT_FILE -e HOME -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RETENTION_DAYS -e GITHUB_RUN_ATTEMPT -e GITHUB_ACTOR -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL -e GITHUB_REF_NAME -e GITHUB_REF_PROTECTED -e GITHUB_REF_TYPE -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e RUNNER_OS -e RUNNER_ARCH -e RUNNER_NAME -e RUNNER_TOOL_CACHE -e RUNNER_TEMP -e RUNNER_WORKSPACE -e ACTIONS_RUNTIME_URL -e ACTIONS_RUNTIME_TOKEN -e ACTIONS_CACHE_URL -e GITHUB_ACTIONS=true -e CI=true -v "/var/run/docker.sock":"/var/run/docker.sock" -v "/home/runner/work/_temp/_github_home":"/github/home" -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" -v "/home/runner/work/_temp/_runner_file_commands":"/github/file_commands" -v "/home/runner/work/blog/blog":"/github/workspace" 6a6825:64a13d76444749d3b720346ba2365371  "***" "***" "./public/index.json"
Cloning into 'docsearch-scraper'...
Collecting pipenv
  Downloading pipenv-2021.11.23-py2.py3-none-any.whl (3.6 MB)
Collecting virtualenv
  Downloading virtualenv-20.10.0-py2.py3-none-any.whl (5.6 MB)
Requirement already satisfied: pip&gt;=18.0 in /usr/local/lib/python3.6/site-packages (from pipenv) (21.2.4)
Requirement already satisfied: setuptools&gt;=36.2.1 in /usr/local/lib/python3.6/site-packages (from pipenv) (57.5.0)
Collecting virtualenv-clone&gt;=0.2.5
  Downloading virtualenv_clone-0.5.7-py3-none-any.whl (6.6 kB)
Collecting certifi
  Downloading certifi-2021.10.8-py2.py3-none-any.whl (149 kB)
Collecting importlib-resources&gt;=1.0
  Downloading importlib_resources-5.4.0-py3-none-any.whl (28 kB)
Collecting backports.entry-points-selectable&gt;=1.0.4
  Downloading backports.entry_points_selectable-1.1.1-py2.py3-none-any.whl (6.2 kB)
Collecting six&lt;2,&gt;=1.9.0
  Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
Collecting importlib-metadata&gt;=0.12
  Downloading importlib_metadata-4.8.3-py3-none-any.whl (17 kB)
Collecting filelock&lt;4,&gt;=3.2
  Downloading filelock-3.4.1-py3-none-any.whl (9.9 kB)
Collecting distlib&lt;1,&gt;=0.3.1
  Downloading distlib-0.3.4-py2.py3-none-any.whl (461 kB)
Collecting platformdirs&lt;3,&gt;=2
  Downloading platformdirs-2.4.0-py3-none-any.whl (14 kB)
Collecting zipp&gt;=0.5
  Downloading zipp-3.6.0-py3-none-any.whl (5.3 kB)
Collecting typing-extensions&gt;=3.6.4
  Downloading typing_extensions-4.0.1-py3-none-any.whl (22 kB)
Installing collected packages: zipp, typing-extensions, importlib-metadata, six, platformdirs, importlib-resources, filelock, distlib, backports.entry-points-selectable, virtualenv-clone, virtualenv, certifi, pipenv
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Successfully installed backports.entry-points-selectable-1.1.1 certifi-2021.10.8 distlib-0.3.4 filelock-3.4.1 importlib-metadata-4.8.3 importlib-resources-5.4.0 pipenv-2021.11.23 platformdirs-2.4.0 six-1.16.0 typing-extensions-4.0.1 virtualenv-20.10.0 virtualenv-clone-0.5.7 zipp-3.6.0
WARNING: You are using pip version 21.2.4; however, version 21.3.1 is available.
You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.
Installing dependencies from Pipfile.lock (aabb41)...
Traceback (most recent call last):
  File "docsearch", line 5, in &lt;module&gt;
    run()
  File "/github/workspace/docsearch-scraper/cli/src/index.py", line 161, in run
    exit(command.run(sys.argv[2:]))
  File "/github/workspace/docsearch-scraper/cli/src/commands/run_config.py", line 21, in run
    return run_config(args[0])
  File "/github/workspace/docsearch-scraper/cli/../scraper/src/index.py", line 33, in run_config
    config = ConfigLoader(config)
  File "/github/workspace/docsearch-scraper/cli/../scraper/src/config/config_loader.py", line 72, in __init__
    for key, value in list(data.items()):
AttributeError: 'list' object has no attribute 'items'
🚀 Successfully indexed and uploaded the results to Algolia

</code></pre>
<p>面对一个磨洋工的工具，作为程序员的我们肯定不能忍。</p>
<p>打开 GitHub 上这个 Action 的<a href="https://github.com/darrenjennings/algolia-docsearch-action">源码</a>, 根据对 Action 构建的了解和现有代码，作者使用的是 Python 和 algolia 自己在 GitHub 上开源的<a href="https://github.com/algolia/docsearch-scraper.git">工具</a>, 然后执行一个  Python 脚本上传文件到 algolia 的；根据以往经验，由 Python 构建的项目镜像一般都比较大，在本地测试了一下，果不其然的大。</p>
<h3 id="">面临的问题</h3>
<ul>
<li>工具损坏</li>
<li>镜像体积大</li>
</ul>
<h2 id="spike">方案Spike</h2>
<table>
<thead>
<tr>
<th>Item</th>
<th>Option1 使用algolia 自己的 Restful 接口</th>
<th>Option 2 algolia SDK</th>
</tr>
</thead>
<tbody>
<tr>
<td>描述</td>
<td>使用 algolia自己的Restful 接口实现上传</td>
<td>使用其官方提供的SDK 编写代码来集成</td>
</tr>
<tr>
<td>是否推荐</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>实现难度</td>
<td>Middle</td>
<td>Low</td>
</tr>
<tr>
<td>优点</td>
<td>流程可控</td>
<td>简单直接，无需担心错误情况的处理</td>
</tr>
<tr>
<td>缺点</td>
<td>需要写更多的代码来控制整个流程</td>
<td>整个上传过程不可控</td>
</tr>
<tr>
<td>安全问题</td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>相对难度</td>
<td>Middle</td>
<td>Low</td>
</tr>
<tr>
<td>相对成本</td>
<td>Middle</td>
<td>Low</td>
</tr>
</tbody>
</table>
<p>综上分析，使用 Option2 SDK 的方案更佳。</p>
<h2 id="">执行方案</h2>
<p>新建 GitHub Action 项目，我们使用 Dockerfile 的方式构建上传索引的方案；</p>
<ul>
<li>新建 entrypoint.sh 并写入如下代码, 脚本执行需要传入如下几个变量：</li>
</ul>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody>
<tr>
<td>FILE_PATH</td>
<td>要上传的文件路径</td>
<td>Yes</td>
</tr>
<tr>
<td>ALGOLIA_APPLICATION_ID</td>
<td>Algolia 平台的应用Id</td>
<td>Yes</td>
</tr>
<tr>
<td>ADMIN_API_KEY</td>
<td>Algolia 上传所用的API Key</td>
<td>Yes</td>
</tr>
<tr>
<td>INDEX_NAME</td>
<td>在 Algolia 上你所创建的索引名</td>
<td>Yes</td>
</tr>
</tbody>
</table>
<pre><code class="language-shell">#!/bin/sh
set -eu

npm install -g @algolia/cli

algolia import -s $FILE_PATH -a $APPLICATION_ID -k $ADMIN_API_KEY -n $INDEX_NAME 

if [ "$?" != "0" ] ; then
  echo "😢 Failed to upload your data to Algolia, PLZ report an issue, thx!"
  exit 1
fi

echo "🚀 Successfully uploaded!"
</code></pre>
<ul>
<li>新建 Dockerfile 并写入如下代码，在此我们使用最小化的 Node 镜像 <code>node:lts-alpine</code></li>
</ul>
<pre><code class="language-dcokerfile">FROM node:lts-alpine
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
</code></pre>
<ul>
<li>更新README, 完善使用文档。</li>
</ul>
<p>详细代码请参考 <a href="https://github.com/guzhongren/algolia-docsearch-upload-action/blob/main/Dockerfile">algolia-docsearch-upload-action</a>。</p>
<h2 id="">验证结果</h2>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2022/01/-------1.svg" class="kg-image" alt="-------1" width="600" height="400" loading="lazy"><figcaption>运行效率对比</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://chinese.freecodecamp.org/news/content/images/2022/01/------.svg" class="kg-image" alt="------" width="600" height="400" loading="lazy"><figcaption>镜像大小对比</figcaption></figure><!--kg-card-begin: markdown--><h2 id="refs">Refs</h2>
<ul>
<li><a href="https://guzhongren.github.io/">博客: https://guzhongren.github.io/</a></li>
<li><a href="https://github.com/marketplace/actions/algolia-docsearch-indexer">Algolia Docsearch Indexer: https://github.com/marketplace/actions/algolia-docsearch-indexer</a></li>
<li><a href="https://docs.github.com/en/actions">GitHub Action: https://docs.github.com/en/actions</a></li>
</ul>
<!--kg-card-end: markdown--><p>欢迎阅读我的<a href="https://guzhongren.github.io/2022/01/%E4%BD%BF%E7%94%A8cypress%E5%88%9B%E5%BB%BA%E6%B5%8B%E8%AF%95%E9%95%9C%E5%83%8F%E5%B9%B6%E5%AE%8C%E6%88%90e2e%E6%B5%8B%E8%AF%95/">更多文章</a>。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 使用 Cypress 创建测试镜像并完成 E2E 测试 ]]>
                </title>
                <description>
                    <![CDATA[ 缘由 最近在做一个 Buildkite 的 Dashboard 的项目 Powerboard [https://github.com/guzhongren/Powerboard]，项目是托管在 GitHub 的 Git Pages 上的; 项目只是一个纯前端项目，且 E2E 测试是用 Cypress [https://www.cypress.io/]构建的；如果要进行 E2E 测试一般情况都是对着部署在 Git Pages 上的网站直接测试，而且也是这么做的😄。 痛点 测试滞后 这么做肯定是有问题的，产品都上线了才做测试，肯定已经迟了；如果程序有问题，那么就会影响所有用户。这种情况应该算是 P1 级别的产品事故，对用户来说简直就是灾难。应该在部署之前就应该完成 E2E 测试，如果测试通过不了，就不应该部署代码。所以测试应该前移。 解决方案 由于我们的测试需要自动化，需要在 Pipeline 上执行，所以必须是一个可以独立运行的程序和 Cypress 程序同时运行，并最终返回测试结果，由 Pipeline 来决定是否终止 Pipeline 运行。 在 GitHub Actions ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/do-e2e-test-with-cypress-image/</link>
                <guid isPermaLink="false">61e413486161280665ed8273</guid>
                
                    <category>
                        <![CDATA[ cypress ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 谷中仁 ]]>
                </dc:creator>
                <pubDate>Mon, 17 Jan 2022 07:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/01/pexels-photo-5667741-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="-">缘由</h2><p>最近在做一个 Buildkite 的 Dashboard 的项目 <a href="https://github.com/guzhongren/Powerboard" rel="noopener noreffer">Powerboard</a>，项目是托管在 GitHub 的 Git Pages 上的; 项目只是一个纯前端项目，且 E2E 测试是用 <a href="https://www.cypress.io/" rel="noopener noreffer">Cypress</a>构建的；如果要进行 E2E 测试一般情况都是对着部署在 Git Pages 上的网站直接测试，而且也是这么做的😄。</p><h2 id="--1">痛点</h2><h3 id="--2">测试滞后</h3><p>这么做肯定是有问题的，产品都上线了才做测试，肯定已经迟了；如果程序有问题，那么就会影响所有用户。这种情况应该算是 P1 级别的产品事故，对用户来说简直就是灾难。应该在部署之前就应该完成 E2E 测试，如果测试通过不了，就不应该部署代码。所以测试应该前移。</p><h2 id="--3">解决方案</h2><p>由于我们的测试需要自动化，需要在 Pipeline 上执行，所以必须是一个可以独立运行的程序和 Cypress 程序同时运行，并最终返回测试结果，由 Pipeline 来决定是否终止 Pipeline 运行。</p><p>在 GitHub Actions 的 Pipeline 上同时运行程序只能依靠 <code>docker-compose</code>, 在这我们可以使用 Cypress 官方出品的 <a href="https://hub.docker.com/r/cypress/included" rel="noopener noreffer">cypress/included</a>, 通过编排程序来进行测试。</p><h3 id="cypress-included">cypress/included</h3><p>cypress/included 可以让我们挂载 cypress 的测试脚本，然后自动执行，并在最终返回 Linux 命令状态值，如 0 ， 非 0 值。</p><h3 id="docker-compose">Docker-compose</h3><p><a href="https://docs.docker.com/compose/" rel="noopener noreffer">Docker-compose</a> 是一套容器编排工具，可以很轻松的管理容器的启动顺序等。在本地项目搭建中非常有用，比如构建数据库，执行 shell/yaml lint 等。</p><h2 id="--4">执行方案</h2><h3 id="--5">构建应用镜像</h3><p>在测试之前需要将应用构建好并部署好，我们可以用 Node 镜像打包应用，并利用容器的多阶段构建(<a href="https://docs.docker.com/develop/develop-images/multistage-build/" rel="noopener noreffer">multi-stage builds</a>) 完成应用轻量化构建，并部署在 <a href="https://hub.docker.com/_/nginx" rel="noopener noreffer">Nginx</a> 中。</p><pre><code class="language-yaml">FROM node:17-alpine as distPackage
COPY ./ /app
WORKDIR /app
RUN yarn
RUN yarn build

FROM nginx:latest
COPY --from=distPackage /app/dist /usr/share/nginx/html
</code></pre><h3 id="-service">编排 service</h3><p>因为我们的程序需要在测试的时候就要部署好，所以我们可以利用 Docker-compose 的 <a href="https://docs.docker.com/compose/compose-file/compose-file-v3/#build" rel="noopener noreffer">build</a> 参数，在容器启动时构建应用并部署。并在 cypress/included 启动是执行测试命令 <code>npx cy:docker</code>, 具体就是<code>cross-env ENV=docker cypress run --spec 'cypress/integration/dashboard.spec.js</code>。</p><pre><code class="language-yaml">version: '3'
services:
  web:
    build:
      context: ./
      dockerfile: ./Dockerfile
    container_name: web
    restart: always
    ports:
      - '80:80'

  e2e:
    image: cypress/included:9.2.1
    container_name: cypress
    depends_on:
      - web
    environment:
      - CYPRESS_baseUrl=http://web
      - ENV=docker
    command: npx cy:docker
    working_dir: /e2e
    volumes:
      - ./:/e2e

</code></pre><p>这样我们就可以独立的运行起真实程序和正式的测试程序了，具体的 Pipeline 可以参考 Powerboard 的 <a href="https://github.com/guzhongren/Powerboard/blob/main/.github/workflows/main.yml" rel="noopener noreffer">Workflow</a>。</p><pre><code class="language-yml">      - name: E2E
        run: |
          docker-compose up --build e2e

</code></pre><h2 id="--6">总结</h2><p><code>Docker-compopse</code> 有很好的应用编排能力，可以很轻松的构建多服务程序；并在构建应用的时候可以使用多阶段构建来优化镜像大小。使用 <code>Cypress</code> 可以提高开发效率并可在 <code>Pipeline</code> 上保证程序的正确性。</p><h2 id="refs">Refs</h2><ul><li><a href="https://guzhongren.github.io/" rel="noopener noreffer">博客:https://guzhongren.github.io/</a></li><li><a href="https://www.cypress.io/" rel="noopener noreffer">Cypress: https://www.cypress.io/</a></li><li><a href="https://hub.docker.com/r/cypress/included" rel="noopener noreffer">cypress/included: https://hub.docker.com/r/cypress/included</a></li><li><a href="https://docs.github.com/en/actions" rel="noopener noreffer">GitHub Actions: https://docs.github.com/en/actions</a></li><li><a href="https://github.com/guzhongren/Powerboard" rel="noopener noreffer">Powerboard: https://github.com/guzhongren/Powerboard</a></li></ul><p>欢迎阅读我的<a href=" https://guzhongren.github.io/2022/01/%E4%BD%BF%E7%94%A8cypress%E5%88%9B%E5%BB%BA%E6%B5%8B%E8%AF%95%E9%95%9C%E5%83%8F%E5%B9%B6%E5%AE%8C%E6%88%90e2e%E6%B5%8B%E8%AF%95/">更多文章</a>。</p><!--kg-card-begin: markdown--><p><img src="https://cdn.jsdelivr.net/gh/guzhongren/data-hosting@master/20210819/%E6%89%AB%E7%A0%81_%E6%90%9C%E7%B4%A2%E8%81%94%E5%90%88%E4%BC%A0%E6%92%AD%E6%A0%B7%E5%BC%8F-%E7%99%BD%E8%89%B2%E7%89%88.ae9zxgscqcg.png" alt="谷哥说-微信公众号" width="600" height="400" loading="lazy"></p>
<!--kg-card-end: markdown--><p></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Golang 依赖注入(Dependency Injection) ]]>
                </title>
                <description>
                    <![CDATA[  * Dependency Injection 🧪 * 工程意图     * 简化程序     * 初始化工程     * Test     * 运行测试     * 总结         1. Dependency Injection 🧪 依赖注入是目前很多优秀框架都在使用的一个设计模式。 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/golang-dependency-injection/</link>
                <guid isPermaLink="false">5d77122cfbfdee429dc5fbbc</guid>
                
                    <category>
                        <![CDATA[ 技术 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 开发者 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 自学编程 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 谷中仁 ]]>
                </dc:creator>
                <pubDate>Tue, 10 Sep 2019 03:03:18 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/09/chalk-chalkboard-exam-459793--1--1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <ul>
<li><a href="#dependency-injection-%F0%9F%A7%AA">Dependency Injection 🧪</a>
<ul>
<li><a href="#%E5%B7%A5%E7%A8%8B%E6%84%8F%E5%9B%BE">工程意图</a></li>
<li><a href="#%E7%AE%80%E5%8C%96%E7%A8%8B%E5%BA%8F">简化程序</a></li>
<li><a href="#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B">初始化工程</a></li>
<li><a href="#test">Test</a></li>
<li><a href="#%E8%BF%90%E8%A1%8C%E6%B5%8B%E8%AF%95">运行测试</a></li>
<li><a href="#%E6%80%BB%E7%BB%93">总结</a></li>
</ul>
</li>
</ul>
<!-- /TOC -->
<h1 id="1dependencyinjection">1. Dependency Injection 🧪</h1>
<p>依赖注入是目前很多优秀框架都在使用的一个设计模式。<br>
Dependency Injection 常常简称为：DI。它是实现控制反转（Inversion of Control – IoC）的一个模式。</p>
<p>在各种大工程中少不了各种测试，其中 TDD 就是非常流行的一种，在前端开发中用的比较多的 <a href="https://github.com/facebook/jest">Jest</a> 就是一种，在 Golang 开发命令行工具的时候也是需要 DI 这种模式来实现命令行测试的。因为传统的测试室获取不到命令行的输入输出的。</p>
<h2 id="11">1.1. 工程意图</h2>
<p>仓库：<a href="https://github.com/guzhongren/TDD/tree/master/10.dependency-injection">https://github.com/guzhongren/TDD/tree/master/10.dependency-injection</a><br>
编写一个命令行工具库，打包并运行程序，根据工具名称后面的名称来显示 <code>'Hello, + 名称'</code>。</p>
<h2 id="12">1.2. 简化程序</h2>
<p>我们知道 golang 打包后就是一个可执行程序，程序名称根据你指定的名称显示，那么要实现这个工具就是需要接收到程序名后面的参数并显示出来。但本次的重点是实现 DI, 所以我们将重点放在命令行的测试与实现上。<br>
我们只实现 Greet 函数的 DI 就可以了。</p>
<h2 id="13">1.3. 初始化工程</h2>
<pre><code class="language-shell">go mod init dependency-injection
</code></pre>
<p>按照惯例，测试的函数需要以 Test 开头，参数为 *testing.T 类型</p>
<h2 id="14test">1.4. Test</h2>
<ul>
<li>测试先行</li>
</ul>
<pre><code class="language-go">func TestGreet(t *testing.T) {
	// 申明 buffer，准备接受数据， 因为bytes.Buffer， 重点：bytes.Buffer实现了 io.Writer
	buffer := bytes.Buffer{}
	// 将buffer 传入，此时就是依赖注入的入口，
	Greet(&amp;buffer, "chris")
	// 获取程序运行的结果
	got := buffer.String()
	// 期望值
	want := "Hello, chris"
	// 测试判断
	if got != want {
		t.Errorf(`got %s, want %s`, got, want)
	}
}
</code></pre>
<ul>
<li>
<p>运行 <strong>go test</strong>, 程序会报错，因为没有实现 Greet 函数。</p>
</li>
<li>
<p>最小化的实现 Repeat</p>
</li>
</ul>
<pre><code class="language-go">// Greet 打印问候
func Greet(w io.Writer, name string) {
	fmt.Fprintf(w, "Hello, "+name)
}
</code></pre>
<p>重点说明，命令行的测试需要将结果打印在命令行窗口中，如果没有测试，我们可以用 fmt.Printf 等打印函数将结果打印出来，但是，<br>
测试需要拿到打印的内容，就需要将内容用标准输出；当然可以变相的先用其他打印函数将结果打印出来，然后再将结果 return 出去，<br>
在测试中，接受返回值，再比较；这样做不标准而已，学了今天内容其实就可以用 DI 来解决了。</p>
<h2 id="15">1.5. 运行测试</h2>
<ul>
<li>基本测试</li>
</ul>
<pre><code class="language-shell">$ go test
PASS
ok      dependency-injection    0.006s
</code></pre>
<h2 id="16">1.6. 总结</h2>
<p>基本测试很简单，不用解读了。作为开发者，我们应该用最直接的工具来保证我们程序的健壮性，而不一定要绕个弯来解决问题，如上面的打印结果的测试。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Golang 基准测试（Benchmark） ]]>
                </title>
                <description>
                    <![CDATA[ 简介 > 基准测试是对计算机系统的性能的测试。 在程序中，基准测试，是一种测试代码性能的方法；比如有一个问题你有多种不同的方案，你想选择一种性能最好的方案，那么你就需要基准测试。 > 基准测试主要是通过测试 CPU 和内存的效率问题，来评估被测试代码的性能，进而找到更好的解决方案。比如链接池的数量不是越多越好，那么哪个值才是最优值呢，这就需要配合基准测试不断调优了。 工程意图 仓库： https://github.com/guzhongren/TDD/tree/master/09.benchmar 根据输入的字符串和重复次数，输出重复次数后的字符串。 初始化工程 go mod init benchmark 测试的函数需要以 Test 开头，参数为 *testing.T 类型 Test  * 测试先行 # 测试 Repeat 函数 func TestRepeat(t *testing.T) { 	actual := Repeat(`a`, 6) 	expect := `aaaaaa` 	if actual != expect { 		t.Errorf(`expect ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/golang-benchmark/</link>
                <guid isPermaLink="false">5d69e90bfbfdee429dc5fa1f</guid>
                
                    <category>
                        <![CDATA[ 技术 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 开发者 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 自学编程 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 谷中仁 ]]>
                </dc:creator>
                <pubDate>Tue, 10 Sep 2019 02:58:37 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/09/chalk-chalkboard-exam-459793--1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="">简介</h2>
<blockquote>
<p>基准测试是对计算机系统的性能的测试。</p>
</blockquote>
<p>在程序中，基准测试，是一种测试代码性能的方法；比如有一个问题你有多种不同的方案，你想选择一种性能最好的方案，那么你就需要基准测试。</p>
<blockquote>
<p>基准测试主要是通过测试 CPU 和内存的效率问题，来评估被测试代码的性能，进而找到更好的解决方案。比如链接池的数量不是越多越好，那么哪个值才是最优值呢，这就需要配合基准测试不断调优了。</p>
</blockquote>
<h2 id="">工程意图</h2>
<p>仓库： <a href="https://github.com/guzhongren/TDD/tree/master/09.benchmar">https://github.com/guzhongren/TDD/tree/master/09.benchmar</a></p>
<p>根据输入的字符串和重复次数，输出重复次数后的字符串。</p>
<h2 id="">初始化工程</h2>
<pre><code class="language-shell">go mod init benchmark
</code></pre>
<p>测试的函数需要以 Test 开头，参数为 *testing.T 类型</p>
<h2 id="test">Test</h2>
<ul>
<li>测试先行</li>
</ul>
<pre><code class="language-go"># 测试 Repeat 函数
func TestRepeat(t *testing.T) {
	actual := Repeat(`a`, 6)
	expect := `aaaaaa`
	if actual != expect {
		t.Errorf(`expect %s, but got %s`, expect, actual)
	}
}
</code></pre>
<ul>
<li>
<p>运行 <strong>go test</strong>, 程序会报错，因为没有实现 Repeat 函数。</p>
</li>
<li>
<p>最小化的实现 Repeat</p>
</li>
</ul>
<pre><code class="language-go">// Repeat return a string with same char
func Repeat(char string, count int) (result string) {
	for i := 0; i &lt; count; i++ {
		result += char
	}
	return
}
</code></pre>
<p>上面的函数中 return 并没有返回值，是因为，在 Repeat 函数的返回值部分有一个result，<br>
当返回值是函数体里面的值的时候，可以不用写返回值，go 程序自动将该值返回。但return 依旧不能省略。</p>
<h2 id="benchmark">Benchmark</h2>
<p>基准测试的函数名须以 Benchmark 开头， 参数须为 *testing.B；循环中的 b.N， go 会根据系统情况生成，不用用户设定。</p>
<pre><code class="language-go">func BenchmarkRepeat(b *testing.B) {
	for i := 0; i &lt; b.N; i++ {
		Repeat(`b`, 5)
	}
}

</code></pre>
<h2 id="">运行测试</h2>
<ul>
<li>基本测试</li>
</ul>
<pre><code class="language-shell">$ go test
PASS
ok      benchmark       0.006s
</code></pre>
<p>基本测试很简单，不用解读了。</p>
<ul>
<li>基准测试</li>
</ul>
<pre><code class="language-shell">$ go test -bench=. -run=none
goos: darwin
goarch: amd64
pkg: benchmark
BenchmarkRepeat-12      10000000               116 ns/op
PASS
ok      benchmark       1.297s
</code></pre>
<p>运行基准测试也要使用go test命令，不过我们要加上-bench=标记，它接受一个表达式作为参数，匹配基准测试的函数，. 表示运行所有基准测试。</p>
<p>因为默认情况下 go test 会运行单元测试，为了防止单元测试的输出影响我们查看基准测试的结果，可以使用-run=匹配一个从来没有的单元测试方法，过滤掉单元测试的输出，我们这里使用none，因为我们基本上不会创建这个名字的单元测试方法。</p>
<p>下面着重解释下说出的结果，看到函数后面的-12了吗？这个表示运行时对应的 GOMAXPROCS 的值。接着的 10000000 表示运行 for 循环的次数，也就是调用被测试代码的次数，最后的 116 ns/op表示每次需要话费 116 纳秒。<br>
以上是测试时间默认是1秒，也就是1秒的时间，调用 10000000 次，每次调用花费 116 纳秒。如果想让测试运行的时间更长，可以通过 -lunchtime 指定，比如5秒。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Golang 与 SQLLite 实战 ]]>
                </title>
                <description>
                    <![CDATA[  * 简介  * 目标  * 目的  * Coding * 目录结构     * 封装 error 函数     * 安装 SQLLite 库及其他库     * 申明 DB 全局变量     * 初始化数据库     * 用户模型构建及原子操作 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/golang-with-sqllite-practice/</link>
                <guid isPermaLink="false">5d68db97fbfdee429dc5f9d2</guid>
                
                <dc:creator>
                    <![CDATA[ 谷中仁 ]]>
                </dc:creator>
                <pubDate>Sat, 31 Aug 2019 07:50:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2019/08/go-sqllite-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <ul>
<li><a href="#%E7%AE%80%E4%BB%8B">简介</a></li>
<li><a href="#%E7%9B%AE%E6%A0%87">目标</a></li>
<li><a href="#%E7%9B%AE%E7%9A%84">目的</a></li>
<li><a href="#coding">Coding</a>
<ul>
<li><a href="#%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84">目录结构</a></li>
<li><a href="#%E5%B0%81%E8%A3%85-error-%E5%87%BD%E6%95%B0">封装 error 函数</a></li>
<li><a href="#%E5%AE%89%E8%A3%85-sqllite-%E5%BA%93%E5%8F%8A%E5%85%B6%E4%BB%96%E5%BA%93">安装 SQLLite 库及其他库</a></li>
<li><a href="#%E7%94%B3%E6%98%8E-db-%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F">申明 DB 全局变量</a></li>
<li><a href="#%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E6%8D%AE%E5%BA%93">初始化数据库</a></li>
<li><a href="#%E7%94%A8%E6%88%B7%E6%A8%A1%E5%9E%8B%E6%9E%84%E5%BB%BA%E5%8F%8A%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C">用户模型构建及原子操作</a>
<ul>
<li><a href="#%E7%94%A8%E6%88%B7%E6%A8%A1%E5%9E%8B">用户模型</a></li>
<li><a href="#%E6%96%B0%E5%A2%9E">新增</a></li>
<li><a href="#%E5%88%A0%E9%99%A4">删除</a></li>
<li><a href="#%E4%BF%AE%E6%94%B9">修改</a></li>
<li><a href="#%E6%9F%A5%E8%AF%A2">查询</a></li>
</ul>
</li>
<li><a href="#%E5%9C%A8%E5%BA%94%E7%94%A8%E4%B8%AD%E5%90%AF%E5%8A%A8%E5%B9%B6%E8%B0%83%E7%94%A8%E7%94%A8%E6%88%B7%E6%A8%A1%E5%9E%8B%E7%9A%84%E6%96%B9%E6%B3%95">在应用中启动并调用用户模型的方法</a></li>
<li><a href="#%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C%E5%B1%95%E7%A4%BA">运行结果展示</a></li>
</ul>
</li>
<li><a href="#%E6%80%BB%E7%BB%93">总结</a></li>
</ul>
<!-- /TOC -->
<h2 id="11">1.1. 简介</h2>
<p><a href="about:blank">SQLite</a> 是一个进程内的库，实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库，这意味着与其他数据库一样，你不需要在系统中配置。在 Golang 中使用SQLLite 也相当简单，只需要安装 SQLLite 的Golang  包即可使用；<br>
Golang 就不多介绍了，能看到这个肯定对 <a href="https://golang.google.cn/">Golang</a> 有一定的了解。</p>
<p>仓库地址：<a href="https://github.com/AndorLab/golang-sqllite">https://github.com/AndorLab/golang-sqllite</a></p>
<h2 id="12">1.2. 目标</h2>
<p>使用 SQLLite 通过构建一个社区用户表，包含如下字段; 通过 SQLLite 的 API 实现对社区用户表进行增删改查。</p>
<table>
<thead>
<tr>
<th>序号</th>
<th>字段</th>
<th>类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>uid</td>
<td>int64</td>
<td>id</td>
</tr>
<tr>
<td>2</td>
<td>username</td>
<td>string</td>
<td>用户名</td>
</tr>
<tr>
<td>3</td>
<td>city</td>
<td>string</td>
<td>城市</td>
</tr>
<tr>
<td>4</td>
<td>skills</td>
<td>string</td>
<td>技能</td>
</tr>
<tr>
<td>5</td>
<td>created</td>
<td>int64</td>
<td>创建时间</td>
</tr>
</tbody>
</table>
<h2 id="13">1.3. 目的</h2>
<p>了解 SQLLite ，学习 Golang 操作 SQLLite, 巩固 Golang 基础知识。</p>
<h2 id="14coding">1.4. Coding</h2>
<h3 id="141">1.4.1. 目录结构</h3>
<p>项目采用 Golang 传统的平铺式目录</p>
<pre><code class="language-shell">.
├── LICENSE
├── Makefile      # 构建工具
├── README.md     # README
├── db.go         # 数据库操作
├── error.go      # 错误处理工具方法
├── fcc.db        # sqllite 数据库
├── go.mod        # go modules
├── go.sum        # go modules
├── main.go       # 项目入口
├── server.go     # 应用程序入口
└── userModel.go  # 用户模型

</code></pre>
<h3 id="142error">1.4.2. 封装 error 函数</h3>
<p>因为在 go 中会有很多的 error 的判断，为了代码精简，我们特封装一下 error; 下面的 <em>interface{}</em> 代表任何类型，类似 TypeScript 中的 <em>any</em>。</p>
<pre><code class="language-golang"># error.go
func checkErr(data interface{}, err error) (interface{}, error) {
	if err != nil {
		log.Error(err)
		return nil, err
	}
	return data, err
}
</code></pre>
<h3 id="143sqllite">1.4.3. 安装 SQLLite 库及其他库</h3>
<p>使用 go modules 之后，将所需的包放在 import 中，使用 <em>go mod tidy</em> 命令后，go 会自动安装程序使用到的包。</p>
<p>日志相关的库，主要用于在控制台打印结果</p>
<pre><code class="language-golang"># server.go
import (
	"github.com/labstack/gommon/log"
)

</code></pre>
<p>SQLLite 包</p>
<pre><code class="language-golang"># db.go
_ "github.com/mattn/go-sqlite3"
</code></pre>
<h3 id="144db">1.4.4. 申明 DB 全局变量</h3>
<p>因为在程序中，我们要通过数据库来获取数据，那么存在一个全局的数据库指针是很有必要的。</p>
<pre><code class="language-golang"># main.go
var db = new(sql.DB)
</code></pre>
<h3 id="145">1.4.5. 初始化数据库</h3>
<p>SQLLite 初始化数据库非常简单，只要指定数据库驱动和数据库文件就可以。为了在程序的整个生命周期中操作数据库，我们将 db 返回。</p>
<pre><code class="language-golang">// openDB 打开数据库
func openDB() *sql.DB {
	//打开数据库，如果不存在，则创建
	db, err := sql.Open("sqlite3", "./fcc.db")
	checkErr(db, err)
	return db
}
</code></pre>
<p>创建好 db 后，需要创建表结构，执行如下数据库操作命令即可完成用户表的创建。</p>
<pre><code class="language-golang">// initDB 初始化数据库
func initDB() {
	//创建表
	sqlTable := `
			CREATE TABLE IF NOT EXISTS userinfo(
					uid INTEGER PRIMARY KEY AUTOINCREMENT,
					username VARCHAR(64) NULL,
					city VARCHAR(64) NULL,
					skills VARCHAR(128) NULL,
					created BIGINT NULL
			);
			`
	db.Exec(sqlTable)
}
</code></pre>
<h3 id="146">1.4.6. 用户模型构建及原子操作</h3>
<p>构建现代程序，强调程序的健壮性，封装就是比较重要的；用 MVC、 MVVM 的观点，我们需要有一个 Model 来提供对象的原子操作。在这，我们将用户抽象为UserModel，对用户的增删改查封装到 <em>insert</em>、<em>dleete</em>、<em>update</em> 和 <em>query</em>。</p>
<h4 id="1461">1.4.6.1. 用户模型</h4>
<pre><code class="language-golang">// UserModel 用户模型
type UserModel struct {
	uid      int64
	username string
	city     string
	skills   string
	created  int64
}

</code></pre>
<p>对用户的原子操作</p>
<h4 id="1462">1.4.6.2. 新增</h4>
<pre><code class="language-golang">// insert 新增
func (u UserModel) insert() (sql.Result, error) {
	stmt, err := db.Prepare("insert into userinfo(username, city, skills, created) values(?,?,?,?)")
	checkErr(stmt, err)
	res, err := stmt.Exec(u.username, u.city, u.skills, time.Now().Unix())
	checkErr(res, err)
	return res, nil
}
</code></pre>
<h4 id="1463">1.4.6.3. 删除</h4>
<pre><code class="language-golang">// delete 删除
func (u UserModel) delete(id int64) int64 {
	stmt, err := db.Prepare("delete from userinfo where uid=?")
	checkErr(stmt, err)
	res, err := stmt.Exec(id)
	checkErr(res, err)
	affect, err := res.RowsAffected()
	checkErr(affect, err)
	return affect
}
</code></pre>
<h4 id="1464">1.4.6.4. 修改</h4>
<pre><code class="language-golang">// update	更新用户技能
func (u UserModel) update(id int) int64 {
	stmt, err := db.Prepare("update userinfo set skills=? where uid=?")
	checkErr(stmt, err)
	res, err := stmt.Exec(u.skills, id)
	checkErr(res, err)
	affect, err := res.RowsAffected()
	checkErr(affect, err)
	return affect
}
</code></pre>
<h4 id="1465">1.4.6.5. 查询</h4>
<pre><code class="language-golang">// query 查询
func (u UserModel) query() ([]UserModel, error) {
	rows, err := db.Query("select * from userinfo")
	checkErr(rows, err)
	var userList = []UserModel{}
	for rows.Next() {
		var user = UserModel{}
		err = rows.Scan(&amp;user.uid, &amp;user.username, &amp;user.city, &amp;user.skills, &amp;user.created)
		checkErr(nil, err)
		userList = append(userList, user)
	}
	rows.Close()
	return userList, nil
}
</code></pre>
<h3 id="147">1.4.7. 在应用中启动并调用用户模型的方法</h3>
<p>在上面我们完成了对用户模型及原子操作的封装，那么接下来就是通过应用程序将分装的内容调用，传入正确的参数进行调用。<br>
我们在此封装一个 <em>startAPP</em> 方法，在这个里面我们调用封装好的用户操作的接口，实现功能。</p>
<p>因为数据库要在整个生命周期存在，当程序结束的时候，我们应该将数据库链接释放，所以我们用到了 go 的 <em>defer</em> 关键字</p>
<pre><code class="language-golang"># server.go
  db = openDB()
  defer db.Close()
  initDB()
</code></pre>
<p>调用用户操作的增删改查并打印结果, 对于不同的操作，我们应该有不同的数据，所以在程序中会有 <em>user</em>、和 <em>updateUser</em> 两个对象</p>
<pre><code class="language-golang"># server.go
  user := UserModel{
  	username: "谷中仁",
  	city:     `西安`,
  	skills:   `TypeScript`,
  }
  // insert
  result, err := user.insert()
  id, err := result.LastInsertId()
  checkErr(id, err)
  log.Info("增：操作数据的id:", id)
  // update
  updateUser := UserModel{
  	skills: `golang`,
  }
  affectedRow := updateUser.updateSkills(1)
  log.Info("改：影响的行数：", affectedRow)
  // query
  queryUser := UserModel{}
  list, _ := queryUser.query()
  log.Info("查：", list)
  // delete
  affect := queryUser.delete(1)
  log.Info("删：", affect)  
  // query
  list, _ = queryUser.query()
  log.Info("查：", list)
</code></pre>
<h3 id="148">1.4.8. 运行结果展示</h3>
<pre><code class="language-shell">$ make run
go run *.go
{"time":"2019-08-31T14:21:48.941164+08:00","level":"INFO","prefix":"-","file":"server.go","line":"21","message":"增：操作数据的id:1"}
{"time":"2019-08-31T14:21:48.941842+08:00","level":"INFO","prefix":"-","file":"server.go","line":"27","message":"改：影响的行数：1"}
{"time":"2019-08-31T14:21:48.942034+08:00","level":"INFO","prefix":"-","file":"server.go","line":"31","message":"查：[{1 谷中仁 西安 golang 1567232508}]"}
{"time":"2019-08-31T14:21:48.942599+08:00","level":"INFO","prefix":"-","file":"server.go","line":"34","message":"删：1"}
{"time":"2019-08-31T14:21:48.942696+08:00","level":"INFO","prefix":"-","file":"server.go","line":"38","message":"查：[]"}
</code></pre>
<h2 id="15">1.5. 总结</h2>
<p>SQLLite 对开发者非常友好，不用安装在机器上，只要指定SQLLite的驱动和数据库存储文件即可对 SQLLite 数据库进行操作；Golang 作为比较流行的语言，对数据库也非常友好，提供了基本的数据库接口，<br>
至于用户需要什么样的数据库，自己开发对应的数据库驱动即可。当然在 GitHub 已经有很多开源爱好者开发了比较流行的数据库的驱动可以直接拿来用。</p>
<p>SQLLite 使用的也是标准的 SQL 语法，可以让不同的开发者快速入手。</p>
<p>为什么没有用到 Golang 的 Web 框架？</p>
<p>因为我们的侧重点在 Golang 与 SQLLite，不在 API 实现上，最小化的实现目标，才是我们学习知识最快速的途径。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
