<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>星海</title>
  
  <subtitle>Life&#39;s A Struggle!</subtitle>
  <link href="https://incoder.org/atom.xml" rel="self"/>
  
  <link href="https://incoder.org/"/>
  <updated>2024-08-11T12:14:45.511Z</updated>
  <id>https://incoder.org/</id>
  
  <author>
    <name>Jerry Xu</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Git 签名</title>
    <link href="https://incoder.org/2024/06/17/git-signature/"/>
    <id>https://incoder.org/2024/06/17/git-signature/</id>
    <published>2024-06-17T22:30:50.000Z</published>
    <updated>2024-08-11T12:14:45.511Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://docs.github.com/assets/cb-17614/mw-1440/images/help/commits/verified-commit.webp" alt="verified-commit"></p><p>通常 Push 代码到远程托管平台（GitHub，Gitlab，Gitee 等），需要提前在托管平台上传我们 Git 账户的公钥（*.pub），平台使用上传的公钥来验证身份（本地的 Git 私钥与平台上的公钥配对，以确保你有权限读写该仓库），该验证只会在 Push 时进行检查</p><span id="more"></span><h2 id="为什么要签名"><a class="header-anchor" href="#为什么要签名"></a>为什么要签名</h2><p>虽然对 Push 做了检查，但依然不够安全，因为任何拥有该仓库权限的人，都可以在 commit/tag 时使用 <code>git config user.name &quot;假的用户名&quot;</code>, <code>git config user.email &quot;假的邮箱地址&quot;</code> 命令来伪造提交者用户信息，这样我们根本无法追溯提交者的身份，所以我们需要给 commit/tag 签名，签名的目的是确保提交的代码在传输和存储过程中没有被篡改，并验证提交者的身份，同时也保证代码的可追溯性</p><p>commit 签名是在本地，在使用 <code>git commit</code> 命令时进行签名，push 时会将你的签名信息，原封不动 push 到远程仓库</p><p>commit 签名只是用于验证这条 commit 来自于你本人，与是否有权限操作远程仓库无关</p><h2 id="如何签名"><a class="header-anchor" href="#如何签名"></a>如何签名</h2><p>可以使用 GPG、SSH 或 S/MIME，可以在本地对 commit/tag 进行签名。 这些 commit/tag 在 GitHub 上标示为已验证，便于其他人信任更改来自可信的来源</p><h3 id="SSH、GPG、S-MIME-区别"><a class="header-anchor" href="#SSH、GPG、S-MIME-区别"></a>SSH、GPG、S/MIME 区别</h3><ul><li>SSH 签名是最容易生成的，甚至可以将现有身份验证密钥上传到 GitHub 以用作签名密钥</li><li>生成 GPG 签名比生成 SSH 密钥复杂，但 GPG 具有 SSH 没有的功能，GPG 密钥可以在不使用时过期或撤销</li><li>较大型组织的环境中通常需要 S/MIME 签名</li></ul><h3 id="👍-SSH（常用）"><a class="header-anchor" href="#👍-SSH（常用）"></a>👍 SSH（常用）</h3><blockquote><p>SSH 签名验证需 <font color=red>Git 2.3.4</font> 及以上版本</p></blockquote><h4 id="检查现有-SSH-密钥"><a class="header-anchor" href="#检查现有-SSH-密钥"></a>检查现有 SSH 密钥</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看本地 ~/.ssh 路径现有密钥</span></span><br><span class="line"><span class="built_in">ls</span> -al ~/.ssh</span><br><span class="line"><span class="comment"># 检查输出的列表</span></span><br></pre></td></tr></table></figure><p>检查已有密钥列表已有 RSA 密钥，如果你已经有 <strong>SHA-2 算法生成 RSA 密钥</strong> 那么你可以 <font color=red>跳过</font> <code>生产新 SSH 密钥</code>，如果没有，为了安全，还是建议重新生成 SHA-2 算法生成 RSA 密钥</p><h4 id="生产新-SSH-密钥"><a class="header-anchor" href="#生产新-SSH-密钥"></a>生产新 SSH 密钥</h4><div class="note info"><ol><li>2022.03.15<ul><li>在该日期 GitHub 删除旧的、不安全的密钥类型来提高安全性</li><li>自该日期起，<font color=red>不再支持</font> <strong>DSA 密钥</strong> (ssh-dss)。 <font color=red>无法</font> 在 <span class="exturl" data-url="aHR0cDovL2dpdGh1Yi5jb20=">github.com<i class="fa fa-external-link-alt"></i></span> 上向个人帐户添加新的 <strong>DSA 密钥</strong></li></ul></li><li>2021.11.02<ul><li>在该日期 <strong>之前</strong> 带有 valid_after 的 <strong>RSA 密钥</strong> (ssh-rsa) 可以继续使用任何签名算法</li><li>在该日期 <strong>之后</strong> 生成的 RSA 密钥 <font color=red>必须</font> 使用 <strong>SHA-2 签名算法</strong>。 一些较旧的客户端可能需要升级才能使用 SHA-2 签名</li></ul></li></ol></div><p>在生成 SSH 密钥时，我们可以给 SSH 密钥添加密码，也可以不添加密码，这里根据需要选择是否添加密码</p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1722773384/blog/git-ssh-keygen.png" alt="git-ssh-keygen"></p><ol><li>执行 <code>ssh-keygen -t ed25519 -C &quot;Jerry.x@outlook.com&quot;</code> 命令，这里的邮箱换成你的 GitHub 邮箱</li><li>确认密钥存放位置<ul><li>如果不需调整（默认：<code>/User/blade/.ssh/id_ed25519</code>），可 Enter 键进入下一步</li><li>这里我重命名了密钥 <code>/User/blade/.ssh/id_ed25519_test</code></li></ul></li><li>给密钥设置密码<ul><li>如果无需设置，可 Enter 键进入下一步</li><li>如果需设置，输入密码即可</li></ul></li><li>确认输入的密钥密码，和上一步输入内容一致</li><li>提示生成的密钥存放位置和指纹信息</li></ol><details class="note primary"><summary><p>如果密钥 <strong>设置了密码</strong>，需要将 SSH 密钥添加到 ssh-agent</p></summary><p>在向 ssh-agent 添加新的 SSH 密钥管理密钥前，应该检查现有 SSH 密钥并生成新的 SSH 密钥</p><div class="tabs" id="sixth-unique-name"><ul class="nav-tabs"><li class="tab active"><a href="#sixth-unique-name-1"><i class="fa-brands fa-windows"></i>Windows</a></li><li class="tab"><a href="#sixth-unique-name-2"><i class="fa-brands fa-apple"></i>macOS</a></li><li class="tab"><a href="#sixth-unique-name-3"><i class="fa-brands fa-linux"></i>Linux</a></li></ul><div class="tab-content"><div class="tab-pane active" id="sixth-unique-name-1"><blockquote><p>如果已安装 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2FwcHMvZGVza3RvcA==">GitHub Desktop<i class="fa fa-external-link-alt"></i></span>，可使用它克隆存储库，而无需处理 SSH 密钥</p></blockquote><ol><li><p>在新的“管理员提升”__ PowerShell 窗口中，确保 ssh-agent 正在运行。 可以使用“使用 SSH 密钥密码”中的“自动启动 ssh agent”说明，或者手动启动它</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># start the ssh-agent in the background</span></span><br><span class="line">Get-Service -Name ssh-agent | Set-Service -StartupType Manual</span><br><span class="line">Start-Service ssh-agent</span><br></pre></td></tr></table></figure></li><li><p>在无提升权限的终端窗口中，将 SSH 私钥添加到 ssh-agent。 如果使用其他名称创建了密钥，或要添加具有其他名称的现有密钥，请将命令中的 id_ed25519 替换为私钥文件的名称</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-add c:/Users/YOU/.ssh/id_ed25519</span><br></pre></td></tr></table></figure></li></ol></div><div class="tab-pane" id="sixth-unique-name-2"><blockquote><p>将 SSH 密钥添加到该代理时，应使用默认的 macOS ssh-add 命令，而不是使用通过 macports、homebrew 或某些其他外部来源安装的应用程序</p></blockquote><ol><li><p>在后台启动 ssh 代理</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">eval</span> <span class="string">&quot;<span class="subst">$(ssh-agent -s)</span>&quot;</span></span><br><span class="line">&gt; Agent pid 59566</span><br></pre></td></tr></table></figure><p>根据您的环境，您可能需要使用不同的命令。 例如，在启动 <code>ssh-agent</code> 之前，你可能需要通过运行 <code>sudo -s -H</code> 根访问，或者可能需要使用 <code>exec ssh-agent bash</code> 或 <code>exec ssh-agent zsh</code> 运行 <code>ssh-agent</code></p></li><li><p>如果你使用的是 macOS Sierra <strong>10.12.2</strong> 或更高版本，则需要修改 <code>~/.ssh/config</code> 文件以自动将密钥加载到 <code>ssh-agent</code> 中并在密钥链中存储密码</p><ul><li><p>检查你的 <code>~/.ssh/config</code> 文件是否在默认位置</p></li><li><p>如果文件不存在，请创建该文件</p></li><li><p>打开你的 <code>~/.ssh/config</code> 文件，然后修改文件以包含以下行。 如果您的 SSH 密钥文件与示例代码具有不同的名称或路径，请修改文件名或路径以匹配您当前的设置</p>   <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Host github.com</span><br><span class="line">  <span class="comment"># 如果看到了 Bad configuration option: usekeychain 错误，</span></span><br><span class="line">  <span class="comment"># 取消下一行注释，使用 IgnoreUnknown 配置</span></span><br><span class="line">  <span class="comment"># IgnoreUnknown UseKeychain</span></span><br><span class="line">  AddKeysToAgent <span class="built_in">yes</span></span><br><span class="line">  <span class="comment"># 如果你选择不向密钥添加密码，应该省略 UseKeychain 行</span></span><br><span class="line">  UseKeychain <span class="built_in">yes</span></span><br><span class="line">  IdentityFile ~/.ssh/id_ed25519</span><br></pre></td></tr></table></figure></li></ul></li><li><p>将 SSH 私钥添加到 <code>ssh-agent</code> 并将密码存储在密钥链中。 如果使用其他名称创建了密钥，或要添加具有其他名称的现有密钥，请将命令中的 <strong>id_ed25519</strong> 替换为私钥文件的名称</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-add --apple-use-keychain ~/.ssh/id_ed25519</span><br></pre></td></tr></table></figure></li></ol><details class="note warning no-icon"><summary><p>注意</p></summary><ol><li>当你将 SSH 密钥添加到 ssh-agent 时，<code>--apple-use-keychain</code> 选项会将密码存储在你的密钥链中。 如果选择不向密钥添加密码，请运行命令，而不使用 <code>--apple-use-keychain</code> 选项</li><li>选项 <code>--apple-use-keychain</code> 位于 Apple 的 ssh-add 标准版本中。 在 Monterey (12.0) 之前的 macOS 版本中，<code>--apple-use-keychain</code> 和 <code>--apple-load-keychain</code> 标志分别使用语法 <mark class="label ">-K</mark> 和 <mark class="label ">-A</mark></li><li>如果您没有安装 Apple 的 ssh-add 标准版本，可能会收到错误消息。 有关详细信息，请参阅 “<span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmdpdGh1Yi5jb20vemgvYXV0aGVudGljYXRpb24vdHJvdWJsZXNob290aW5nLXNzaC9lcnJvci1zc2gtYWRkLWlsbGVnYWwtb3B0aW9uLS0tLWFwcGxlLXVzZS1rZXljaGFpbg==">错误：ssh-add：非法选项 – apple-use-keychain<i class="fa fa-external-link-alt"></i></span>”</li><li>如果系统继续提示你输入密码，则可能需要将命令添加到 <code>~/.zshrc</code> 文件（或 bash 对应的 <code>~/.bashrc</code> 文件）</li></ol></details></div><div class="tab-pane" id="sixth-unique-name-3"><ol><li><p>在后台启动 ssh 代理</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">eval</span> <span class="string">&quot;<span class="subst">$(ssh-agent -s)</span>&quot;</span></span><br><span class="line">&gt; Agent pid 59566</span><br></pre></td></tr></table></figure><p>根据您的环境，您可能需要使用不同的命令。 例如，在启动 <code>ssh-agent</code> 之前，你可能需要通过运行 <code>sudo -s -H</code> 根访问，或者可能需要使用 <code>exec ssh-agent bash</code> 或 <code>exec ssh-agent zsh</code> 运行 <code>ssh-agent</code></p></li><li><p>将 SSH 私钥添加到 ssh-agent</p><p>如果使用其他名称创建了密钥，或要添加具有其他名称的现有密钥，请将命令中的 <strong>id_ed25519</strong> 替换为私钥文件的名称</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-add ~/.ssh/id_ed25519</span><br></pre></td></tr></table></figure></li></ol></div></div></div></details><p>除了上述的方式生成密钥，还有 <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmdpdGh1Yi5jb20vemgvYXV0aGVudGljYXRpb24vY29ubmVjdGluZy10by1naXRodWItd2l0aC1zc2gvZ2VuZXJhdGluZy1hLW5ldy1zc2gta2V5LWFuZC1hZGRpbmctaXQtdG8tdGhlLXNzaC1hZ2VudCNnZW5lcmF0aW5nLWEtbmV3LXNzaC1rZXktZm9yLWEtaGFyZHdhcmUtc2VjdXJpdHkta2V5">为硬件安全密钥生成新的 SSH 密钥<i class="fa fa-external-link-alt"></i></span> 方式，这里不做说明，可以移步官方文档查看</p><h4 id="将-SSH-密钥添加到-GitHub-账户"><a class="header-anchor" href="#将-SSH-密钥添加到-GitHub-账户"></a>将 SSH 密钥添加到 GitHub 账户</h4><p><img src="https://res.cloudinary.com/incoder/image/upload/v1722781365/blog/github-ssh-add.png" alt="github-ssh-add"></p><blockquote><p>其他更多设置可参考官方文档 <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmdpdGh1Yi5jb20vemgvYXV0aGVudGljYXRpb24vY29ubmVjdGluZy10by1naXRodWItd2l0aC1zc2gvYWRkaW5nLWEtbmV3LXNzaC1rZXktdG8teW91ci1naXRodWItYWNjb3VudA==">新增 SSH 密钥到 GitHub 帐户<i class="fa fa-external-link-alt"></i></span></p></blockquote><h4 id="将签名密钥告诉-Git"><a class="header-anchor" href="#将签名密钥告诉-Git"></a>将签名密钥告诉 Git</h4><div class="note warning"><ul><li>如需全局配置：添加 <code>--global</code> 参数</li><li>如果你有多个 Git 账号，推荐 <font color=red>按照不同的账号分别进行配置</font>，具体可参考 <a href="git-account.md#%E6%B7%BB%E5%8A%A0%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6">Git 多账号配置</a> 这篇文章</li></ul></div><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 配置 Git 使用 SSH 对提交和标记签名</span></span><br><span class="line">git config gpg.format ssh</span><br><span class="line"><span class="comment"># 2. 配置指定 SSH 签名密钥</span></span><br><span class="line"><span class="comment">#    将 ~/.ssh/id_ed25519 替换为要使用的公钥路径</span></span><br><span class="line">git config user.signingkey ~/.ssh/id_ed25519.pub</span><br><span class="line"><span class="comment"># commit 开启自动签名</span></span><br><span class="line">git config commit.gpgsign <span class="literal">true</span></span><br><span class="line"><span class="comment"># tag 开启自动签名</span></span><br><span class="line">git config tag.gpgsign <span class="literal">true</span></span><br></pre></td></tr></table></figure><h4 id="配置可信公钥列表"><a class="header-anchor" href="#配置可信公钥列表"></a>配置可信公钥列表</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p ~/.config/git</span><br><span class="line"><span class="built_in">touch</span> ~/.config/git/allowed_signers</span><br><span class="line"><span class="comment"># 将指定的密钥文件（~/.ssh/id_ed25519）内容复制到 allowed_signers 文件中</span></span><br><span class="line"><span class="comment"># 如果是继续追加, 将 &gt; 替换为 &gt;&gt;</span></span><br><span class="line"><span class="built_in">cat</span> ~/.ssh/id_ed25519.pub &gt; ~/.config/git/allowed_signers</span><br><span class="line"><span class="comment"># 配置可信公钥</span></span><br><span class="line">git config gpg.ssh.allowedSignersFile <span class="string">&quot;~/.config/git/allowed_signers&quot;</span></span><br></pre></td></tr></table></figure><h4 id="对-commit-签名"><a class="header-anchor" href="#对-commit-签名"></a>对 commit 签名</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 当本地分支中的提交更改时，请将 -S 标志添加到 git commit 命令</span></span><br><span class="line">git commit -S -m <span class="string">&quot;YOUR_COMMIT_MESSAGE&quot;</span></span><br><span class="line"><span class="comment"># 2. 在本地完成创建提交后，将其推送到 GitHub 上的远程仓库</span></span><br><span class="line">git push</span><br></pre></td></tr></table></figure><h4 id="对-tag-签名"><a class="header-anchor" href="#对-tag-签名"></a>对 tag 签名</h4><blockquote><p>注意：如果 Git 客户端配置为默认对提交进行签名，GitHub Desktop 仅支持提交签名</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 若要对标记进行签名，请将 -s 添加到 git tag 命令</span></span><br><span class="line">git tag -s MYTAG</span><br><span class="line"><span class="comment"># 2. 通过运行 git tag -v [tag-name] 验证已签名的标记</span></span><br><span class="line">git tag -v MYTAG</span><br></pre></td></tr></table></figure><h3 id="GPG"><a class="header-anchor" href="#GPG"></a>GPG</h3><blockquote><p>具体实践可参考官方文档 <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmdpdGh1Yi5jb20vemgvYXV0aGVudGljYXRpb24vbWFuYWdpbmctY29tbWl0LXNpZ25hdHVyZS12ZXJpZmljYXRpb24vYWJvdXQtY29tbWl0LXNpZ25hdHVyZS12ZXJpZmljYXRpb24jZ3BnLWNvbW1pdC1zaWduYXR1cmUtdmVyaWZpY2F0aW9u">GPG 提交签名验证<i class="fa fa-external-link-alt"></i></span></p></blockquote><h3 id="S-MIME"><a class="header-anchor" href="#S-MIME"></a>S/MIME</h3><blockquote><p>S/MIME 签名验证需 Git 2.19 及以上版本</p></blockquote><p>由于 S/MIME 用的比较少，这里就不做具体的演示，可参考 <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmdpdGh1Yi5jb20vemgvYXV0aGVudGljYXRpb24vbWFuYWdpbmctY29tbWl0LXNpZ25hdHVyZS12ZXJpZmljYXRpb24vYWJvdXQtY29tbWl0LXNpZ25hdHVyZS12ZXJpZmljYXRpb24jc21pbWUtY29tbWl0LXNpZ25hdHVyZS12ZXJpZmljYXRpb24=">官方文档<i class="fa fa-external-link-alt"></i></span></p><h2 id="验证签名"><a class="header-anchor" href="#验证签名"></a>验证签名</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看一下本地签名信息</span></span><br><span class="line"><span class="comment"># 正常情况，在 commit 提交号下</span></span><br><span class="line"><span class="comment"># good &quot;git&quot; signature for $(email) with $(publicKey)</span></span><br><span class="line">git <span class="built_in">log</span> --show-signature</span><br></pre></td></tr></table></figure><h2 id="问题"><a class="header-anchor" href="#问题"></a>问题</h2><h3 id="验证签名提示异常"><a class="header-anchor" href="#验证签名提示异常"></a>验证签名提示异常</h3><div class="tabs" id="signature-unique-name"><ul class="nav-tabs"><li class="tab active"><a href="#signature-unique-name-1">No signature</a></li><li class="tab"><a href="#signature-unique-name-2">No principal matched</a></li><li class="tab"><a href="#signature-unique-name-3">invalid key</a></li></ul><div class="tab-content"><div class="tab-pane active" id="signature-unique-name-1"><p>No signature:</p><ul><li>表示 Git 不知道要信任哪些 SSH 密钥</li><li>解决方法：配置<a href="#%E9%85%8D%E7%BD%AE%E5%8F%AF%E4%BF%A1%E5%85%AC%E9%92%A5%E5%88%97%E8%A1%A8">可信公钥列表</a></li></ul><p><img src="https://res.cloudinary.com/incoder/image/upload/v1723359614/blog/git-no-signature.png" alt="no-signature"></p></div><div class="tab-pane" id="signature-unique-name-2"><p>No principal matched</p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1723359614/blog/git-no-principal.png" alt="no-principal"></p></div><div class="tab-pane" id="signature-unique-name-3"><p>invalid key</p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1723359614/blog/git-invalid-key.png" alt="invalid-key"></p></div></div></div><h3 id="如何给现有密钥更新密码"><a class="header-anchor" href="#如何给现有密钥更新密码"></a>如何给现有密钥更新密码</h3><p>通过输入以下命令，您可以 <font color="red">更改</font> <strong>现有私钥</strong> 的密码而无需重新生成密钥对</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 修改 id_ed25519 密钥密码</span></span><br><span class="line">$ ssh-keygen -p -f ~/.ssh/id_ed25519</span><br><span class="line">&gt; Enter old passphrase: [Type old passphrase]</span><br><span class="line">&gt; Key has comment <span class="string">&#x27;your_email@example.com&#x27;</span></span><br><span class="line">&gt; Enter new passphrase (empty <span class="keyword">for</span> no passphrase): [Type new passphrase]</span><br><span class="line">&gt; Enter same passphrase again: [Repeat the new passphrase]</span><br><span class="line">&gt; Your identification has been saved with the new passphrase.</span><br></pre></td></tr></table></figure><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly9waWFvaHVhLmdpdGh1Yi5pby9wb3N0L2dpdC8yMDIzMDYyNC1naXQtc3NoLWdwZy8=">Git 提交使用 SSH 签名和 GPG 签名验证<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9scnVpaGFvLmNuL3Bvc3RzL3NzaC1zaWduLw==">SSH 提交签名验证<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9heWsubW9lL2FydGljbGVzL2NvbW1pdC1zaWduYXR1cmUtZ3VpZGUvaW5kZXguaHRtbA==">GitHub commit 签名指南<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9qdWVqaW4uY24vcG9zdC83MjY4NTkzNTY5NzgyMzAwNzI3">维护代码的尊严：GPG签名让你的Git commit不再裸奔<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;img src=&quot;https://docs.github.com/assets/cb-17614/mw-1440/images/help/commits/verified-commit.webp&quot; alt=&quot;verified-commit&quot;&gt;&lt;/p&gt;
&lt;p&gt;通常 Push 代码到远程托管平台（GitHub，Gitlab，Gitee 等），需要提前在托管平台上传我们 Git 账户的公钥（*.pub），平台使用上传的公钥来验证身份（本地的 Git 私钥与平台上的公钥配对，以确保你有权限读写该仓库），该验证只会在 Push 时进行检查&lt;/p&gt;</summary>
    
    
    
    <category term="Git" scheme="https://incoder.org/categories/Git/"/>
    
    
    <category term="git signature" scheme="https://incoder.org/tags/git-signature/"/>
    
  </entry>
  
  <entry>
    <title>重要说明</title>
    <link href="https://incoder.org/2022/06/05/top1/"/>
    <id>https://incoder.org/2022/06/05/top1/</id>
    <published>2022-06-05T06:05:00.000Z</published>
    <updated>2024-08-11T12:14:45.519Z</updated>
    
    <content type="html"><![CDATA[<p>距离上一次更新文章已经过去了 1 年多了，时间可过的真快。原计划将现在的主站点要进行按照领域划分，将平时工作中遇到的、实践的技术整理到对应的领域，方便快速查找，提供一个沉浸式的学习知识体验，但由于个人也是一个懒癌拖更患者，加之在过去的一年工作中处在新领域，学习了很多技术，很多笔记还没有整理完善，因此没有及时更新博客，同时最近也遇到了站点无法正常访问，经过一系列的排查，发现了是由于开源的 CDN 提供方 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2pzZGVsaXZyL2pzZGVsaXZyL2lzc3Vlcy8xODM5Nw==">jsdelivr<i class="fa fa-external-link-alt"></i></span> 被污染了 DNS，因此不得不先更新站点的基础服务，以便能正常访问</p><span id="more"></span><blockquote><p>话说，国内的网络环境真的是一言难尽，不得不说在技术、技术基础设施、技术思想等发展道路上任重道远</p></blockquote><p>对于个人几个主要的站点，规划如下</p><ol><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuaW5jb2Rlci5vcmc=">incoder.org<i class="fa fa-external-link-alt"></i></span>: 作为分享生活、感悟、个人状态为主的地方，偶尔汇总某些技术等的综合性文章</li><li><span class="exturl" data-url="aHR0cHM6Ly9iYWNrZW5kLmluY29kZXIub3Jn">backend.incoder.org<i class="fa fa-external-link-alt"></i></span>: 记录以 Java 为基础的后端开发生态技术领域</li><li><span class="exturl" data-url="aHR0cHM6Ly9tb2JpbGUuaW5jb2Rlci5vcmc=">mobile.incoder.org<i class="fa fa-external-link-alt"></i></span>: 记录以原生开发为基础的移动端开发生态技术</li><li><span class="exturl" data-url="aHR0cHM6Ly9pbmNvZGVyLmFwcA==">incoder.app<i class="fa fa-external-link-alt"></i></span>: 记录个人开源应用</li></ol><div class="note info"><p>后续会迁移本站点部分文章到具体的领域站点</p></div>]]></content>
    
    
    <summary type="html">&lt;p&gt;距离上一次更新文章已经过去了 1 年多了，时间可过的真快。原计划将现在的主站点要进行按照领域划分，将平时工作中遇到的、实践的技术整理到对应的领域，方便快速查找，提供一个沉浸式的学习知识体验，但由于个人也是一个懒癌拖更患者，加之在过去的一年工作中处在新领域，学习了很多技术，很多笔记还没有整理完善，因此没有及时更新博客，同时最近也遇到了站点无法正常访问，经过一系列的排查，发现了是由于开源的 CDN 提供方 &lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9naXRodWIuY29tL2pzZGVsaXZyL2pzZGVsaXZyL2lzc3Vlcy8xODM5Nw==&quot;&gt;jsdelivr&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt; 被污染了 DNS，因此不得不先更新站点的基础服务，以便能正常访问&lt;/p&gt;</summary>
    
    
    
    <category term="Top" scheme="https://incoder.org/categories/Top/"/>
    
    
    <category term="Top" scheme="https://incoder.org/tags/Top/"/>
    
  </entry>
  
  <entry>
    <title>评论不自由，赞美无意义</title>
    <link href="https://incoder.org/2021/05/05/freedom-pact/"/>
    <id>https://incoder.org/2021/05/05/freedom-pact/</id>
    <published>2021-05-05T12:30:10.000Z</published>
    <updated>2024-08-11T12:14:45.511Z</updated>
    
    <content type="html"><![CDATA[<p>就像文章标题所述。每到三，五月这个时间，网络变得异常脆弱。各种“奇怪”的网站访问起来很费劲。对于一个技术人员，这些问题可以解决，但是每次都需要花费一定的时间和精力来应对这些，而且随着 <span class="exturl" data-url="aHR0cHM6Ly9iYWlrZS5iYWlkdS5jb20vaXRlbS9HV0Y=">GFW<i class="fa fa-external-link-alt"></i></span> 的不断升级和加强，应对的策略和技术也是在不断迭代，前前后后已经出现了多种技术，本篇就以这个契机，梳理截止到 2021-05-05 所了解到关于 “代理” 相关的知识。现在主流的科学上网技术有 VPN、SS、SSR、V2Ray、Trojan、Trojan-Go，小众的 WireGuard、Brook、Snell 和 NaiveProxy 等</p><span id="more"></span><div class="note info"><p>本篇文章大量使用了 <span class="exturl" data-url="aHR0cHM6Ly9peWlkZW5nLm5ldC9hYm91dC9teWhvbWUuaHRtbA==">一灯不是和尚<i class="fa fa-external-link-alt"></i></span> 作者发布 《<span class="exturl" data-url="aHR0cHM6Ly9peWlkZW5nLm5ldC9ibGFjay10ZWNobm9sb2d5L2NnZncvdnBuLXNzLXNzci12MnJheS10cm9qYW4td2lyZWd1YXJkLWJ5cGFzcy1nZncuaHRtbA==">科学上网工具哪个好？<i class="fa fa-external-link-alt"></i></span> 》文章中的内容，在此感谢作者对各技术的汇总以及经验总结，本篇文章是在原文章的基础上进行的扩展补充</p></div><h2 id="VPN"><a class="header-anchor" href="#VPN"></a>VPN</h2><p><span class="exturl" data-url="aHR0cHM6Ly96aC53aWtpcGVkaWEub3JnL3dpa2kvJUU4JTk5JTlCJUU2JTkzJUFDJUU3JUE3JTgxJUU0JUJBJUJBJUU3JUI2JUIyJUU4JUI3JUFG">虚拟专用网络<i class="fa fa-external-link-alt"></i></span>（Virtual Private Network，缩写：VPN）是常用于连接中，大型企业或团体间私人网络的通讯方法。它利用隧道协议（Tunneling Protocol）来达到发送端认证，消息保密与准确性等功能。</p><p>VPN 只是一个统称，它有多种具体实现。比如：</p><ul><li>PPTP（点对点隧道协议：Point to Point Tunneling Protocol）;</li><li>L2TP（第二层隧道协议：Layer Two Tunneling Protocol）;</li><li>IPsec（互联网安全协议：Internet Protocol Security）;</li><li>WireGuard（一种协议）;</li><li>OpenVPN（一种虚拟专用网络 <strong>V</strong>irtual <strong>P</strong>rivate <strong>N</strong>etwork（VPN）系统）;</li><li>IKEv2（因特网密钥交换：Internet Key Exchange） 等</li></ul><h3 id="WireGuard"><a class="header-anchor" href="#WireGuard"></a>WireGuard</h3><p>WireGuard 是由 Jason A. Donenfeld 开发，是最新的协议实现（在 2020 年，WireGuard 协议已被添加到 Linux 和 Android 内核中，从而为 VPN 提供商所采用。默认情况下，WireGuard 使用 Curve25519 进行秘钥交换，并使用 ChaCha20 进行加密，但还具有客户端和服务器之间预共享对称秘钥的功能）</p><h3 id="OpenVPN"><a class="header-anchor" href="#OpenVPN"></a>OpenVPN</h3><p>OpenVPN 是由 James Yonan 编写，实现了在路由或桥接配置和远程访问设施中创建安全点对点或站点对站点连接的技术。它实现了客户端和服务器应用程序。它不与IPsec兼容</p><p>OpenVPN 允许对等方使用预先共享的密钥、证书或用户名/密码相互验证。当在多客户端服务器配置中使用时，它允许服务器使用签名和证书颁发机构为每个客户端发布身份验证证书。</p><h2 id="SS"><a class="header-anchor" href="#SS"></a>SS</h2><p>SS 是 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3NoYWRvd3NvY2tz">Shadowsocks<i class="fa fa-external-link-alt"></i></span> 的缩写，中文名为影梭，为了避免关键词过滤，网友喜欢将 Shadowsocks 称为 “酸酸”，是一种基于 Socks5 代理方式的加密传输协议，也可以指定实现这个协议的各种开发包。Shadowsocks 是由 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2Nsb3d3aW5keQ==">Clowwindy<i class="fa fa-external-link-alt"></i></span> 为了自己使用谷歌查资料而编写；Shadowsocks 分为服务端和客户端，在使用之前，需要先将服务器端程序部署到服务器上面，然后通过客户端连接并创建本地代理。后来，他觉得这个东西非常好用，速度也很快，于是将源码提交到了 GitHub。由于其优秀的使用体验，Shadowsocks 被广泛传播，导致作者被某部门请去 “喝茶”。迫于压力 Clowwindy 于 2015-08-22 宣布停止维护此项目，并移除其个人页面所存储的源代码，而且保证永不再参与维护更新</p><p>虽然 Clowwindy 被迫放弃了 Shadowsocks，但开源界没有放弃，各路大神依旧在为 Shadowsocks 添砖加瓦，这就是开源的力量，倒下一个 Clowwindy，会有千千万万个 “Clowwindy” 站出来</p><h2 id="SSR"><a class="header-anchor" href="#SSR"></a>SSR</h2><p>SSR 是 ShadowsocksR 的缩写，网名爱称 “酸酸乳”，是在 Shadowsocks 的作者被请去喝茶之后，网名为 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2JyZWFrd2ExMQ==">breakwa11<i class="fa fa-external-link-alt"></i></span> 的用户发起的 Shadowsocks 的一个分支版本，它在 Shadowsocks 的基础上增加了一些数据混淆方式，修复了部分安全问题并提高了 QoS 优先级。由于 ShadowsocksR 在协议和混淆方面做了改进，更加不容易被 GFW 检测到，而且兼容原 Shadowsocks，并为新项目命名为 Shadowsocks-R，一开始部分代码由社区人员进行更新。由于不完全开源，也导致后来使用 SS 和 SSR 的用户分为两个阵营，互相撕逼，直到开发者 breakwa11 被人肉出来。breakwa11 最终决定删除 Shadowsocks-R 项目的所有代码，并解散了所有相关群组</p><h3 id="事件始末澄清"><a class="header-anchor" href="#事件始末澄清"></a>事件始末澄清</h3><p>ShadowsocksR 的作者一开始曾有过违反 GPL 协议，在发布二进制文件时不开放源码的争议。不过后来 Shadowsocks-R 项目由 breakwa11 采用了与 Shadowsocks 相同的 GPL、Apache、MIT 等多重自由软件许可协议</p><ul><li>2017-07-19，ShadowsocksR 作者 breakwa11 在 Telegram 频道 ShadowsocksR news 里转发了深圳市启动 SS 协议检测的消息并被大量用户转发，在电报（TG）圈引发恐慌。</li><li>2017-07-24，breakwa11 遭到自称 “<span class="exturl" data-url="aHR0cDovL0VTVS5UVg==">ESU.TV<i class="fa fa-external-link-alt"></i></span>” 的不明省份人士人身攻击，对方宣传如果不停止开发并阻止用户讨论此事件将发布更多包含个人隐私的资料，随后 breakwa11 表示遭到对方人肉搜索并公开个人资料。为防止对方继续伤害无关人士，breakwa11 删除了 GitHub 上的所有代码、解散相关交流群组，并停止 ShadowsocksR 项目</li></ul><p>从本质上来说，Shadowsocks 与 ShadowsocksR 的基本原理相同，都是基于 Socks5 的代理工具，只在本地客户端和服务器对数据包加解密，然后使用 Socks5 协议转发加密的数据包，而不用在乎使用什么协议，所以 Socks5 代理比其他应用层代理速度要快的多</p><h3 id="Socks5"><a class="header-anchor" href="#Socks5"></a>Socks5</h3><p>这里顺带科普一下 Socks5，Socks5 代理的原理是把你的网络数据请求先发送到你的代理服务器，然后由代理服务器转发给目标；如果目标有反馈发送到代理服务器，那么代理服务器会将数据包直接传回到你的本地网络，整个过程只需要数据的二次传输，并没有额外的处理。</p><blockquote><p>示例：现在呢在深圳，你的代理服务器在香港，如果你想要访问 Google，那么你首先需要把数据请求通过本地 Socks5 代理客户端发给在你在香港的服务器上的 Socks5 代理服务端，然后你在香港的服务器将数据请求发送给 Google，再把 Google 反馈的结果传到你香港的代理服务器，然后通过 Socks5 服务端回传到本地的 Socks5 客户端，这样就可以绕开 GFW 的检测而实现科学上网</p></blockquote><p>显而易见，Socks5 代理的所有数据走的任然是公网，而且在公网传输过程中，没有对数据进行任何加密和混淆，这跟 VPN 在公网建立虚拟专用通道传输过程中，对数据高强度加密的方式完全不同。Shadowsocks 和 ShadowsocksR 只在客户端和服务器端对数据做了简单加密和认证，主要功能是流量转发，过强才是主要目的。虽然 ShadowsocksR 已经停止更新很久了，而 Shadowsocks 仍处于社区人员的更新和维护之中，不断修复漏洞并增加新功能，所以现在 Shadowsocks 比 ShadowsocksR 更强大</p><h3 id="提醒"><a class="header-anchor" href="#提醒"></a>提醒</h3><p>不要迷信 SSR 一定比 SS 强，也包括现在的 V2Ray，Trojan，甚至是 WireGuard 等，因为增加混淆意味着损失速度，混淆加密越是强悍，那么其速度和稳定性损失就越大，另外 SSR 至今已被研究透了，而且长时间没有更新维护，其流量特征是可以被 GFW 精准识别，所以用 SSR 和 SS 没有本质区别，由于 SS 一直更新维护，反而更稳定。</p><h2 id="V2Ray"><a class="header-anchor" href="#V2Ray"></a>V2Ray</h2><p><span class="exturl" data-url="aHR0cHM6Ly92MmZseS5vcmc=">V2Ray<i class="fa fa-external-link-alt"></i></span> 是在 Shadowsocks 被封杀后，为表示抗议而开发，属于后起之秀，功能更加强大，为抗 GFW 封锁而生。V2Ray 现在已经是 Project V 项目的核心工具。而 Project V 是一个平台，其中也包括支持 Shadowsocks 协议。由于 V2Ray 早于 Project V 项目，且名声更大，我们习惯称 Project V 项目为 V2Ray，所以我们平常所说的 V2Ray 其实就是 Project V 这个平台，也就是一个工具集。其中，只有 VMess 协议是 V2Ray 社区原创的专属加密通讯协议</p><p>V2Ray 目前支持一下协议（截止 2019-12）</p><ul><li>Blackhole: 中文名称“黑洞”，是一个出站数据协议，它会阻碍所有的数据的出站，配合路由（Routing）一起使用，可以访问被封杀的网站</li><li>DNS: 是一个出站协议，主要用于拦截和转发 DNS 查询。此出站协议只能接收 DNS 流量（包含基于 UDP 和 TCP 协议的查询），其它类型的流量会导致错误。在处理 DNS 查询时，此出站协议会将 IP 查询（即 A 和 AAAA）转发给内置的 DNS 服务器。其它类型的查询流量将被转发至原本的目标地址，DNS 出站协议在 V2Ray 4.15 中引入</li><li>Dokodemo-door: 中文名称“任意门”，是一个入站数据协议，它可以监听一个本地端口，并把所有进入此端口的数据发送到指定服务器的一个端口，从而达到端口映射的效果</li><li>Freedom: 是一个出站协议，可以用来向任意网络发起（正常的）TCP 或 UDP 数据</li><li>HTTP: 超文本传输协议，是传统的代理协议</li><li>Socks: 标准的 Socks 协议实现，兼容 <span class="exturl" data-url="aHR0cDovL2Z0cC5pY20uZWR1LnBsL3BhY2thZ2VzL3NvY2tzL3NvY2tzNC9TT0NLUzQucHJvdG9jb2w=">Socks 4<i class="fa fa-external-link-alt"></i></span>、<span class="exturl" data-url="aHR0cDovL2Z0cC5pY20uZWR1LnBsL3BhY2thZ2VzL3NvY2tzL3NvY2tzNC9TT0NLUzRBLnByb3RvY29s">Socks 4a<i class="fa fa-external-link-alt"></i></span>、<span class="exturl" data-url="aHR0cDovL3d3dy5zb2Nrcy5uZWMuY29t">Socks 5<i class="fa fa-external-link-alt"></i></span>『🙃目前无法正常访问』，也属于是一种传统的代理协议</li><li>VMess: 是 V2Ray 专用的加密传输协议，它分为入站和出站两部分，通常作为 V2Ray 客户端和服务器之间的桥梁。因为增加了混淆和加密，据说比 Shadowsocks 更安全。VMess 依赖于系统时间，请确保使用 V2Ray 的系统 UTC 时间误差在 90 秒之内，与时区无关。在 Linux 系统中可以安装 ntp 服务来自动同步系统时间</li><li>Shadowsocks: 包含入站，出站两部分协议，兼容大部分其它版本的实现。最早被个人开发的科学上网梯子协议，但 V2Ray 目前不支持 ShadowsocksR</li><li><span class="exturl" data-url="aHR0cHM6Ly90cm9qYW4tZ2Z3LmdpdGh1Yi5pby90cm9qYW4vcHJvdG9jb2w=">Trojan<i class="fa fa-external-link-alt"></i></span>: 特洛伊木马服务器如何对有效的特洛伊木马协议和其他协议（可能是 HTTPS 或任何其他探针）做出反应</li><li>VLESS: 是一个无状态的轻量传输协议，它分为入站和出站两部分，可以作为 V2Ray 客户端和服务器之间的桥梁，与 VMess 不同，VLESS 不依赖与系统时间，认证方式同样为 UUID，但不需要 alterId</li><li>Loopback: 是一个出站协议，可使出站连接被重新路由，最低支持版本 v4.36.0+</li></ul><p>截止到 2021-05，V2Ray 可选的传输层配置有：TCP、mKCP，WebSocket、HTTP/2，DomainSocket、QUIC、gRPC。其中 mKCP、QUIC 和 TCP 用于优化网络质量；WebSocket 用于伪装；HTTP/2 和 DomainSocket 用于传输以及 TLS 加密</p><p>V2Ray 不仅可以在传输层配置 TLS 使用 HTTP 和 Socks 变成 HTTPS 和 Socks over TLS 协议，也可以使用 MTProto、Shadowsocks 和 VMess 通过传输层配置 TLS 加密伪装成 TLS 流量。所以，VMess 配置是 TLS 加密的最常见的做法，但没人会对 Shadowsocks 使用 TLS 加密，因为完全没有意义</p><h3 id="客户端"><a class="header-anchor" href="#客户端"></a>客户端</h3><blockquote><p><span class="exturl" data-url="aHR0cHM6Ly92Mnh0bHMub3JnL3YycmF5JWU1JWFlJWEyJWU2JTg4JWI3JWU3JWFiJWFmLw==">V2Ray 客户端<i class="fa fa-external-link-alt"></i></span></p></blockquote><h2 id="Xray-与-XTLS"><a class="header-anchor" href="#Xray-与-XTLS"></a>Xray 与 XTLS</h2><p>Xray 与 V2Ray 完全类同，Xray 是 Project X 项目的核心模块。因为 Xray 和 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL1hUTFMv">XTLS 黑科技<i class="fa fa-external-link-alt"></i></span> 的作者 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3Jwcng=">rpfx<i class="fa fa-external-link-alt"></i></span> 曾是 V2fly 社区的重要成员，所以 Xray 直接 fork 全部 V2Ray 的功能，然后进行性能优化，并增加了新的功能，使 Xray 在功能上成为了 V2Ray 的超集，且完全兼容 V2Ray。</p><p>简而言之，Xray 是 V2Ray 的项目分支，Xray 是 V2Ray 的超集，就跟 Trojan-Go 和 Trojan-GFW 的关系类似，而且 Xray 性能更好，速度更快，更新迭代也更频繁。</p><blockquote><p>由于 V2Ray-core 4.33.0 版本起，删除了 XTLS 黑科技，但任然支持 VLESS，所以是否原生支持 XTLS 是 Xray 和 V2Ray 最大的区别之一</p></blockquote><h2 id="Trojan-与-Trojan-Go"><a class="header-anchor" href="#Trojan-与-Trojan-Go"></a>Trojan 与 Trojan-Go</h2><p>Trojan 原特指特洛伊木马，是一种计算机病毒程序。但是，我们今天所说的 Trojan 是一种新型的科学上网技术，全称为 Trojan-GFW，是目前最成功的科学上网伪装技术之一。你可以认为 Trojan 是 V2Ray 的 “WS + TLS” 模式的精简版，速度比 V2Ray 更快，伪装比 V2Ray 更逼真，更难以备 GFW 识别</p><p>Trojan 工作原理：Trojan 通过监听 443 端口，模仿互联网上最常见的 HTTPS 协议，把合法的 Trojan 数据伪装成正常的 HTTPS 通信，并真正地完整完成 TLS 握手，以诱骗 GFW 认为它就是 HTTPS，从而不被识别。Trojan 处理来自外界的 HTTPS 请求，如果是合法的，那么为该请求提供服务，否则将该流量交给 Caddy、Nginx 等 Web 服务器，由 Caddy、Nginx 等为其提供网页访问服务。基于整个交互过程，这样能让你的 VPS 更像一个正常的 Web 服务器，因为 Trojan 的所有行为均为 Caddy、Nginx 等 Web 服务器一致，并没有引入额外特征，从而达到难以识别的效果</p><p>Trojan-Go 是 Trojan-GFW 的分支项目，对 Trojan 进行性能优化，并增加不少新特性，Trojan-Go 性能和功能均有大幅度的提升，而且支持分流和 CDN</p><h2 id="总结"><a class="header-anchor" href="#总结"></a>总结</h2><p>通过上述，以及其他信息来源对这些技术有了一定的基本认识，从以下的几个方面来进行总结</p><h3 id="原理不同"><a class="header-anchor" href="#原理不同"></a>原理不同</h3><p>VPN：强调对公网传输过程中数据的加解密<br>SS/SSR/V2Ray/Xray/Traojan：专注于在客户端和服务器间加密，公网传输过程中特征没有 VPN 明显</p><h3 id="目的不同"><a class="header-anchor" href="#目的不同"></a>目的不同</h3><p>VPN：是走在公网中自建的虚拟专用通道，使用强大的加解密算法，为数据传输安全性、私密性而生，被广泛应用于企业、高校、科研部门等远程数据传输领域<br>SS/SSR/V2Ray/Xray/Trojan/Trojan-Go：是为了数据能够安全通过 GFW 而生，更强调的是对数据的混淆和伪装，加解密只是为了更好的隐藏数据特征而顺利通过 GFW 的检测</p><h3 id="项目诞生的大致顺序"><a class="header-anchor" href="#项目诞生的大致顺序"></a>项目诞生的大致顺序</h3><p>VPN &gt; SS &gt; SSR/V2Ray/WireGuard &gt; Trojan/Trojan-Go &gt; Xray</p><h2 id="致每一个追求真理的人"><a class="header-anchor" href="#致每一个追求真理的人"></a>致每一个追求真理的人</h2><p>凡事都有两面性，看如何去看待。其实我一直认为，有墙确实是一件好事，毕竟祖国互联网发展也不过 20 多年，在一定程度上过滤掉了一些没有自我认知，自我思考盲目跟风，觉得国外的月亮圆，国外什么都好的一群人；保障了在互联网开始萌芽的本土企业（毕竟国外的产品和技术都已经发展了好几轮，从解决问题的能力，使用的用户体验（不含本地化）等等方面都是完全甩开本地企业的服务）发展，提供给企业和网民一起成长的安全环境，在这一点上 GFW 功不可没。但作为一个技术从业人员来说，其中大部分的技术理念思想，技术平台等纯技术领域相关的东西来说不是那么友好，常常伴随着由于网络原因而造成的各种乱七八糟的问题，对于暂无本土相关替代服务支持时，在一定程度上阻碍了国内相关技术的发展进度和创新。</p><p>真如前面所述，凡事都有两面性。对于未知的事物，人类本能的会产生恐惧，因为它不可控，而互联网正真不可控的不是技术而是人，人是复杂的个体，一个技术的好坏不是它本身，而是使用的人在做什么样的事，而做的事在一定的环境下它是有好坏之分的。因此我在这里对自我约束，仅为获取相关学习的知识，不参与散布谣言、政治相关等言论，踏踏实实搞技术</p><blockquote><p>希望未来的有一天，国内技术人员的输出是全球技术的风向标</p></blockquote><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly93d3cub3JhY2xlLmNvbS9jbi9jbG91ZC9mcmVlLw==">试用 Always Free 云服务<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuZGFuaWFvLm9yZy82NTM3Lmh0bWw=">申请 Oracle Cloud 永久免费服务<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly96aHVhbmxhbi56aGlodS5jb20vcC8zNTI3MzYzNzI=">2021年申请永久免费甲骨文云 Oracle Cloud 并创建实例最全攻略<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9tZXJsaW5ibG9nLnh5ei93aWtpcGFnZWd1aWRlLmh0bWw=">多种科学上网指导教程汇总<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly92Mnh0bHMub3Jn">V2ray XTLS黑科技<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;就像文章标题所述。每到三，五月这个时间，网络变得异常脆弱。各种“奇怪”的网站访问起来很费劲。对于一个技术人员，这些问题可以解决，但是每次都需要花费一定的时间和精力来应对这些，而且随着 &lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9iYWlrZS5iYWlkdS5jb20vaXRlbS9HV0Y=&quot;&gt;GFW&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt; 的不断升级和加强，应对的策略和技术也是在不断迭代，前前后后已经出现了多种技术，本篇就以这个契机，梳理截止到 2021-05-05 所了解到关于 “代理” 相关的知识。现在主流的科学上网技术有 VPN、SS、SSR、V2Ray、Trojan、Trojan-Go，小众的 WireGuard、Brook、Snell 和 NaiveProxy 等&lt;/p&gt;</summary>
    
    
    
    <category term="Agent" scheme="https://incoder.org/categories/Agent/"/>
    
    
    <category term="VPN" scheme="https://incoder.org/tags/VPN/"/>
    
  </entry>
  
  <entry>
    <title>OSS 初体验</title>
    <link href="https://incoder.org/2021/03/27/oss/"/>
    <id>https://incoder.org/2021/03/27/oss/</id>
    <published>2021-03-27T09:44:46.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>在之前 <a href="https://incoder.org/2021/03/10/springboot12">SpringBoot（十二）文件上传</a> 文章中，已经学习了使用 SpringBoot 基础的功能，完成静态资源的管理，本片文章我们同样也是对非结构化的静态数据进行管理，不过这次我们使用的是比较常用的 OSS 服务，废话不说，我们一起开始 OSS 之旅吧</p><span id="more"></span><h2 id="什么是-OSS"><a class="header-anchor" href="#什么是-OSS"></a>什么是 OSS</h2><p>OSS 是一种面向海量数据规模的分布式存储服务，具有稳定，可靠，安全，低成本的特点。主要用来存储各种非结构化的数据，比如视频，图像，日志，文本文件等。OSS 服务提供标准的 RESTful API 接口，并提供一些常用语言的 SDK 包，方便开发者进行快速开发和二次处理</p><h2 id="常用的-OSS"><a class="header-anchor" href="#常用的-OSS"></a>常用的 OSS</h2><p>市面上提供云服务的厂商有很多，这里以阿里云的 OSS 服务为主来，完成 OSS 相关的学习和实践</p><h2 id="依赖"><a class="header-anchor" href="#依赖"></a>依赖</h2><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- https://mvnrepository.com/artifact/com.aliyun.oss/aliyun-sdk-oss --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.aliyun.oss<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>aliyun-sdk-oss<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.11.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="OSS-工具类"><a class="header-anchor" href="#OSS-工具类"></a>OSS 工具类</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * OSS 文件上传</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> : Jerry xu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> : 2020/11/3  09:12</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OssUtils</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 访问域名</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ENDPOINT</span> <span class="operator">=</span> <span class="string">&quot;xxxxx&quot;</span>;</span><br><span class="line">    <span class="comment">// 存储空间</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">BUCKET_NAME</span> <span class="operator">=</span> <span class="string">&quot;xxxxx&quot;</span>;</span><br><span class="line">    <span class="comment">//==================访问密钥==================</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ACCESS_KEY_ID</span> <span class="operator">=</span> <span class="string">&quot;xxxxx&quot;</span>;</span><br><span class="line">    <span class="comment">// 用户用于加密签名字符串和OSS用来验证签名字符串的密钥，必须保密</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ACCESS_KEY_SECRET</span> <span class="operator">=</span> <span class="string">&quot;xxxxx&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 通过文件上传图片</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> file 文件</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 上传结果地址</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">uploadFileByFile</span><span class="params">(File file)</span> &#123;</span><br><span class="line"><span class="comment">//        // NIO 方式</span></span><br><span class="line"><span class="comment">//        byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());</span></span><br><span class="line"><span class="comment">//        // 其他方式</span></span><br><span class="line"><span class="comment">//        byte[] fileByte = Files.readAllBytes(Paths.get(file.getPath()));</span></span><br><span class="line"><span class="comment">//        return uploadFileByByte(fileByte, file);</span></span><br><span class="line">        URL url;</span><br><span class="line">        <span class="type">String</span> <span class="variable">urlStr</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        String[] split = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">OSS</span> <span class="variable">ossClient</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OSSClientBuilder</span>().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);</span><br><span class="line">            <span class="comment">// 获取文件名</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">fileName</span> <span class="operator">=</span> file.getName();</span><br><span class="line">            ossClient.putObject(BUCKET_NAME, fileName, file);</span><br><span class="line">            <span class="comment">// 设置过期时间</span></span><br><span class="line">            url = ossClient.generatePresignedUrl(BUCKET_NAME, fileName, <span class="keyword">new</span> <span class="title class_">Date</span>(System.currentTimeMillis() + <span class="number">3600</span> * <span class="number">24</span> * <span class="number">365</span> * <span class="number">10</span>));</span><br><span class="line">            log.info(<span class="string">&quot;原始图片地址：&#123;&#125;&quot;</span>, url);</span><br><span class="line">            urlStr = url.toString();</span><br><span class="line"><span class="comment">//            byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());</span></span><br><span class="line"><span class="comment">//            PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, &quot;test&quot;, new ByteArrayInputStream(fileByte));</span></span><br><span class="line"><span class="comment">//            // 不设置过期时间</span></span><br><span class="line"><span class="comment">//            PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);</span></span><br><span class="line"><span class="comment">//            // 去除过期时间参数地址</span></span><br><span class="line"><span class="comment">//            split = urlStr.split(&quot;\\?&quot;);</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.warn(e.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> urlStr;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 通过字节数组上传图片</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> binaryBytes 字节数组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> fileName    文件名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 上传结果地址</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">uploadFileByByte</span><span class="params">(<span class="type">byte</span>[] binaryBytes, String fileName)</span> &#123;</span><br><span class="line">        <span class="type">InputStream</span> <span class="variable">inputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(binaryBytes);</span><br><span class="line">        <span class="keyword">return</span> uploadFileByInputStream(inputStream, fileName);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 通过输入流上传图片</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> inputStream 输入流</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> fileName    文件名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 上传结果地址</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">uploadFileByInputStream</span><span class="params">(InputStream inputStream, String fileName)</span> &#123;</span><br><span class="line">        URL url;</span><br><span class="line">        <span class="type">String</span> <span class="variable">urlStr</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        String[] split = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>];</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">OSS</span> <span class="variable">ossClient</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OSSClientBuilder</span>().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);</span><br><span class="line"><span class="comment">//            String fileName = UUID.randomUUID().toString() + &quot;.jpeg&quot;;</span></span><br><span class="line">            ossClient.putObject(BUCKET_NAME, fileName, inputStream);</span><br><span class="line">            <span class="comment">// 设置过期时间</span></span><br><span class="line">            url = ossClient.generatePresignedUrl(BUCKET_NAME, fileName, <span class="keyword">new</span> <span class="title class_">Date</span>(System.currentTimeMillis() + <span class="number">3600</span> * <span class="number">24</span> * <span class="number">365</span> * <span class="number">10</span>));</span><br><span class="line">            log.info(<span class="string">&quot;原始图片地址：&#123;&#125;&quot;</span>, url);</span><br><span class="line">            urlStr = url.toString();</span><br><span class="line"><span class="comment">//            byte[] fileByte = Files.readAllBytes(new File(file.getPath()).toPath());</span></span><br><span class="line"><span class="comment">//            PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, &quot;test&quot;, new ByteArrayInputStream(fileByte));</span></span><br><span class="line"><span class="comment">//            // 不设置过期时间</span></span><br><span class="line"><span class="comment">//            PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);</span></span><br><span class="line"><span class="comment">//            // 去除过期时间参数地址</span></span><br><span class="line"><span class="comment">//            split = urlStr.split(&quot;\\?&quot;);</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.warn(e.getMessage());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> urlStr;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="上传"><a class="header-anchor" href="#上传"></a>上传</h2><p>这里我们写一个上传接口</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ApiOperation(&quot;文件上传&quot;, notes = &quot;支持多图上传&quot;)</span></span><br><span class="line"><span class="meta">@PostMapping(value = &quot;/upload&quot;, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)</span></span><br><span class="line"><span class="keyword">public</span> List&lt;String&gt; <span class="title function_">uploadTest</span><span class="params">(<span class="meta">@RequestParam(&quot;file&quot;)</span> List&lt;MultipartFile&gt; file)</span> &#123;</span><br><span class="line">    List&lt;String&gt; uploadList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(file.size());</span><br><span class="line">    file.forEach(t -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">url</span> <span class="operator">=</span> OssUtils.uploadFileByInputStream(t.getInputStream(), t.getOriginalFilename());</span><br><span class="line">            uploadList.add(url);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> uploadList;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="测试"><a class="header-anchor" href="#测试"></a>测试</h2><p>不废话了，直接看图就好了<br><img src="https://res.cloudinary.com/incoder/image/upload/v1618848130/blog/oss-upload.png" alt=""></p><h2 id="问题"><a class="header-anchor" href="#问题"></a>问题</h2><ol><li>对于上传获取到的文件地址是一个会过期的地址，并不是一个固定不变的地址，如上截图所示，我偷懒直接将地址链接出的相关参数删去，拿到了一个永久存储的访问连接地址。但这里需要注意，这需要在你的 OSS 管理后台去设置你的文件存储的过期策略。这里就不进行截图演示了（主要是我没有登录系统的账号密码，逃 ~）</li><li>对于上传的文件我没有自定义文件名，这里有个问题是当用户上传 OSS 服务中已经存在的文件名的文件时，新上传的会覆盖旧文件，因此这个地方需要根据实际的业务场景选择合适的方式。在 <mark>OssUtils</mark> 工具类中我已经注释掉了将文件名重命名的代码，你可以在此处按照你的业务进行更改</li><li>第三个问题就是结合上面的两点的汇总方案，其实呢，对于一般的系统，这些静态资源就存永久的连接地址即可。但目前新的系统对用户的资料等也有了 “稍微” 高一点的保护，就是这些资源都是有时效性的，获取的地址就是我们上传拿到的原始地址，而我们存放在数据库中当然也不会是之前那种永久的连接地址，而是对应图片的一个唯一标识信息（可以是重命名后的文件名或者其他能够唯一标识资源你的字段），然后用户访问这些资源时，用存放在数据库中的唯一标识去 OSS 服务上查询对应的资源，然后加载这个地址去显示。</li></ol><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly9oZWxwLmFsaXl1bi5jb20vcHJvZHVjdC8zMTgxNS5odG1s">阿里云对象存储 OSS<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cucWluaXUuY29tL3Byb2R1Y3RzL2tvZG8=">对象存储 Kodo<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9zdXBwb3J0Lmh1YXdlaWNsb3VkLmNvbS9vYnMvaW5kZXguaHRtbA==">华为云对象存储服务 OBS<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;在之前 &lt;a href=&quot;https://incoder.org/2021/03/10/springboot12&quot;&gt;SpringBoot（十二）文件上传&lt;/a&gt; 文章中，已经学习了使用 SpringBoot 基础的功能，完成静态资源的管理，本片文章我们同样也是对非结构化的静态数据进行管理，不过这次我们使用的是比较常用的 OSS 服务，废话不说，我们一起开始 OSS 之旅吧&lt;/p&gt;</summary>
    
    
    
    <category term="OSS" scheme="https://incoder.org/categories/OSS/"/>
    
    
    <category term="OSS" scheme="https://incoder.org/tags/OSS/"/>
    
  </entry>
  
  <entry>
    <title>OSS 之 Minio 初体验</title>
    <link href="https://incoder.org/2021/03/16/minio/"/>
    <id>https://incoder.org/2021/03/16/minio/</id>
    <published>2021-03-16T15:30:10.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>MinIO 是一个基于 Apache License v2.0 开源协议使用 Go 语言开发的对象存储服务。它兼容亚马逊 S3 云存储服务接口，非常适合于存储大容量非结构化的数据，例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等，而一个对象文件可以是任意大小，从几 kb 到最大 5T 不等。MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合，类似 NodeJS, Redis 或者 MySQL。</p><span id="more"></span><p>MinIO 包含 MinIO Server, MinIO Client 以及方便开发基于不同编程语言使用的 MinIO SDK，这三部分组成，使用步骤也很简单，在服务器上安装 MinIO Server 应用，在项目中集成对应的 MinIO SDK，然后按照你的业务情况编写相应的实现即可，在开始前，我们先看看为什么我选择 MiniIO 作为自建的 OSS 服务</p><ol><li>MinIO 由良好的存储机制</li><li>兼容 Amason 的 S3 分布式存储</li><li>天然的支持云原生</li><li><mark>支持私有部署</mark>，可分布式，可单机，100%开源</li><li>友好简单的部署方式，提供管理页面</li><li>还可以配合其他的健康管理工具进行监控，比如 <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLm1pbi5pby9kb2NzL2hvdy10by1tb25pdG9yLW1pbmlvLXVzaW5nLXByb21ldGhldXMuaHRtbA==">Prometheus<i class="fa fa-external-link-alt"></i></span></li></ol><h2 id="安装"><a class="header-anchor" href="#安装"></a>安装</h2><p>由于 MinIO Server 已经提供了 Docker 的安装镜像，那我们就以 Docker 安装为例，其他安装方式可参考官方教程 <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLm1pbi5pby9kb2NzL21pbmlvLXF1aWNrc3RhcnQtZ3VpZGUuaHRtbA==">MinIO Quickstart Guide<i class="fa fa-external-link-alt"></i></span></p><p>关于 Docker 的安装这里不再赘述，Docker 相关详细的使用等知识，可参考我之前的文章 <a href="">Docker（一）</a></p><figure class="highlight docker"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 拉取 minio docker 镜像</span></span><br><span class="line">docker pull minio/minio</span><br><span class="line"><span class="comment"># 2. 运行 minio 服务</span></span><br><span class="line">docker <span class="keyword">run</span><span class="language-bash"> -p 9000:9000 --name minio \</span></span><br><span class="line"><span class="language-bash">  -v /opt/docker/minio/data:/data \</span></span><br><span class="line"><span class="language-bash">  -v /opt/docker/minio/config:/root/.minio \</span></span><br><span class="line"><span class="language-bash">  -d --restart=always \</span></span><br><span class="line"><span class="language-bash">  -d minio/minio server /data</span></span><br></pre></td></tr></table></figure><blockquote><p>这里简单说一下命令的含义，应用命名为 minio ，运行服务在 9000 端口，同时将容器的相关路径文件映射到宿主机的 <code>/opt/docker/minio</code> 路径，开机自启</p></blockquote><p>成功运行服务，可查看日志</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Endpoint: http://172.17.0.2:9000 http://127.0.0.1:9000</span><br><span class="line">Browser Access:</span><br><span class="line">http://172.17.0.2:9000 http://127.0.0.1:9000</span><br><span class="line">Object API (Amazon S3 compatible):</span><br><span class="line">Go: https://docs.min.io/docs/golang-client-quickstart-guide</span><br><span class="line">Java: https://docs.min.io/docs/java-client-quickstart-guide</span><br><span class="line">Python: https://docs.min.io/docs/python-client-quickstart-guide</span><br><span class="line">JavaScript: https://docs.min.io/docs/javascript-client-quickstart-guide</span><br><span class="line">.NET: https://docs.min.io/docs/dotnet-client-quickstart-guide</span><br><span class="line">Detected default credentials &#x27;minioadmin:minioadmin&#x27;, please change the credentials immediately using &#x27;MINIO_ROOT_USER&#x27; and &#x27;MINIO_ROOT_PASSWORD&#x27;</span><br></pre></td></tr></table></figure><p>安装完成后，我们就可以通过 <span class="exturl" data-url="aHR0cDovL2xvY2FsaG9zdDo5MDAw">http://localhost:9000<i class="fa fa-external-link-alt"></i></span> 访问 MinIO 服务，默认用户名和密码分别为: <mark>minioadmin</mark>, <mark>minioadmin</mark></p><h2 id="使用"><a class="header-anchor" href="#使用"></a>使用</h2><h3 id="页面操作"><a class="header-anchor" href="#页面操作"></a>页面操作</h3><p><img src="https://res.cloudinary.com/incoder/image/upload/v1617090692/blog/minio-web.png" alt=""></p><p>我们直接看图，输入账号密码后，可以看到 MinIO 的管理页面，我们就可以上传文件，是不是很方便。第一次上传必须先要创建一个 bucket 后，才可以上传，如下图操作结果</p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1617091291/blog/minio-upload.png" alt=""></p><h3 id="Client-操作"><a class="header-anchor" href="#Client-操作"></a>Client 操作</h3><h3 id="SDK-操作"><a class="header-anchor" href="#SDK-操作"></a>SDK 操作</h3><div class="note warning"><p>这里以 Java 语言为例，查看官方文档时，一定要查看英文文档，中文文档已年久失修落后很多，其他的语言实现请参考官方文档</p></div><h4 id="导入依赖"><a class="header-anchor" href="#导入依赖"></a>导入依赖</h4><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">dependencies &#123;</span><br><span class="line">  implementation <span class="string">&quot;io.minio:minio:8.1.0&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="功能实现"><a class="header-anchor" href="#功能实现"></a>功能实现</h4><p>由于我这里是 SpringBoot 项目，为了方便在应用的 <code>application.yml</code> 文件中配置了 MinIO 相关的参数</p><h4 id="配置文件"><a class="header-anchor" href="#配置文件"></a>配置文件</h4><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">minio:</span></span><br><span class="line">  <span class="comment"># minio 服务运行的地址</span></span><br><span class="line">  <span class="attr">endpoint:</span> <span class="string">http://127.0.0.1</span></span><br><span class="line">  <span class="comment"># minio 服务运行的端口</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">9000</span></span><br><span class="line">  <span class="comment"># minio 服务登录账号</span></span><br><span class="line">  <span class="attr">accessKey:</span> <span class="string">minioadmin</span></span><br><span class="line">  <span class="comment"># minio 服务登录密码</span></span><br><span class="line">  <span class="attr">secretKey:</span> <span class="string">minioadmin</span></span><br><span class="line">  <span class="comment"># minio 设置上传默认存放桶</span></span><br><span class="line">  <span class="attr">bucketName:</span> <span class="string">cpe-manager-test</span></span><br></pre></td></tr></table></figure><h4 id="工具类"><a class="header-anchor" href="#工具类"></a>工具类</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 资源上传工具类</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> : Jerry xu</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> : 2021/3/18  14:05</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MinioUtils</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * minio:</span></span><br><span class="line"><span class="comment">     * endpoint: http://192.168.1.163</span></span><br><span class="line"><span class="comment">     * port: 9000</span></span><br><span class="line"><span class="comment">     * accessKey: minioadmin</span></span><br><span class="line"><span class="comment">     * secretKey: minioadmin</span></span><br><span class="line"><span class="comment">     * bucketName: cpe-manager-test</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;minio.endpoint&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ENDPOINT</span> <span class="operator">=</span> <span class="string">&quot;http://192.168.1.163&quot;</span>;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;minio.port&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Integer</span> <span class="variable">PORT</span> <span class="operator">=</span> <span class="number">19000</span>;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;minio.accessKey&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ACCESS_KEY</span> <span class="operator">=</span> <span class="string">&quot;minioadmin&quot;</span>;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;minio.secretKey&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">SECRET_KEY</span> <span class="operator">=</span> <span class="string">&quot;minioadmin&quot;</span>;</span><br><span class="line">    <span class="meta">@Value(&quot;$&#123;minio.bucketName&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">BUCKET_NAME</span> <span class="operator">=</span> <span class="string">&quot;cpe-manager-test&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> MinioClient minioClient;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> MinioClient <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (minioClient == <span class="literal">null</span>) &#123;</span><br><span class="line">            minioClient = MinioClient.builder().endpoint(ENDPOINT, PORT, <span class="literal">false</span>).credentials(ACCESS_KEY, SECRET_KEY).build();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> minioClient;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取minio所有的桶</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> java.util.List&lt;io.minio.messages.Bucket&gt;</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> Exception exception</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> List&lt;Bucket&gt; <span class="title function_">getAllBucket</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 获取minio中所以的 bucket</span></span><br><span class="line">        List&lt;Bucket&gt; buckets = getInstance().listBuckets();</span><br><span class="line">        <span class="keyword">for</span> (Bucket bucket : buckets) &#123;</span><br><span class="line">            log.info(<span class="string">&quot;bucket 名称:  &#123;&#125;      bucket 创建时间: &#123;&#125;&quot;</span>, bucket.name(), bucket.creationDate());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> buckets;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 将图片上传到minio服务器</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> inputStream 输入流</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> objectName  存储的文件名称，必须包含后缀</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> bucketName  自定义存储桶</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">uploadToMinio</span><span class="params">(InputStream inputStream, String objectName, String bucketName)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 获取文件后缀</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">fileSuffix</span> <span class="operator">=</span> Objects.requireNonNull(objectName).substring(objectName.lastIndexOf(<span class="string">&quot;.&quot;</span>));</span><br><span class="line">            <span class="type">String</span> <span class="variable">contentType</span> <span class="operator">=</span> FileType.getContentType(fileSuffix);</span><br><span class="line"><span class="comment">//            // 重新生成文件名，避免重复</span></span><br><span class="line"><span class="comment">//            String objectName = UUID.randomUUID().toString() + fileSuffix;</span></span><br><span class="line">            <span class="type">long</span> <span class="variable">size</span> <span class="operator">=</span> inputStream.available();</span><br><span class="line">            <span class="type">PutObjectArgs</span> <span class="variable">putObjectArgs</span> <span class="operator">=</span> PutObjectArgs.builder()</span><br><span class="line">                    .bucket(bucketName)</span><br><span class="line">                    .object(objectName)</span><br><span class="line">                    .stream(inputStream, size, -<span class="number">1</span>)</span><br><span class="line">                    .contentType(contentType)</span><br><span class="line">                    .build();</span><br><span class="line">            <span class="comment">// 上传到minio</span></span><br><span class="line">            <span class="type">ObjectWriteResponse</span> <span class="variable">objectWriteResponse</span> <span class="operator">=</span> getInstance().putObject(putObjectArgs);</span><br><span class="line">            inputStream.close();</span><br><span class="line">            <span class="keyword">if</span> (!StringUtils.isEmpty(objectWriteResponse.etag())) &#123;</span><br><span class="line">                <span class="comment">// 返回上传获取到的地址</span></span><br><span class="line">                <span class="keyword">return</span> getUrlByObjectName(objectName);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(e.getMessage());</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 将图片上传到minio服务器，默认存放在 cpe-manager-test 桶内</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> inputStream 输入流</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> objectName  存储的文件名称，必须包含后缀</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">uploadToMinio</span><span class="params">(InputStream inputStream, String objectName)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 获取文件后缀</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">fileSuffix</span> <span class="operator">=</span> Objects.requireNonNull(objectName).substring(objectName.lastIndexOf(<span class="string">&quot;.&quot;</span>));</span><br><span class="line">            <span class="type">String</span> <span class="variable">contentType</span> <span class="operator">=</span> FileType.getContentType(fileSuffix);</span><br><span class="line"><span class="comment">//            // 重新生成文件名，避免重复</span></span><br><span class="line"><span class="comment">//            String objectName = UUID.randomUUID().toString() + fileSuffix;</span></span><br><span class="line">            <span class="type">long</span> <span class="variable">size</span> <span class="operator">=</span> inputStream.available();</span><br><span class="line">            <span class="type">PutObjectArgs</span> <span class="variable">putObjectArgs</span> <span class="operator">=</span> PutObjectArgs.builder()</span><br><span class="line">                    .bucket(BUCKET_NAME)</span><br><span class="line">                    .object(objectName)</span><br><span class="line">                    .stream(inputStream, size, -<span class="number">1</span>)</span><br><span class="line">                    .contentType(contentType)</span><br><span class="line">                    .build();</span><br><span class="line">            <span class="comment">// 上传到minio</span></span><br><span class="line">            <span class="type">ObjectWriteResponse</span> <span class="variable">objectWriteResponse</span> <span class="operator">=</span> getInstance().putObject(putObjectArgs);</span><br><span class="line">            inputStream.close();</span><br><span class="line">            <span class="keyword">if</span> (!StringUtils.isEmpty(objectWriteResponse.etag())) &#123;</span><br><span class="line">                <span class="comment">// 返回上传获取到的地址</span></span><br><span class="line">                <span class="keyword">return</span> getUrlByObjectName(objectName);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(e.getMessage());</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据指定的objectName获取下载链接，需要bucket设置可下载的策略</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> objectName 对象的名称</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> java.lang.String</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getUrlByObjectName</span><span class="params">(String objectName)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> getInstance().getPresignedObjectUrl(</span><br><span class="line">                    GetPresignedObjectUrlArgs.builder()</span><br><span class="line">                            .method(Method.GET)</span><br><span class="line">                            .bucket(BUCKET_NAME)</span><br><span class="line">                            .object(objectName)</span><br><span class="line">                            <span class="comment">// 过期策略【默认有效期7天】</span></span><br><span class="line"><span class="comment">//                            .expiry(2, TimeUnit.HOURS)</span></span><br><span class="line">                            .build());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(e.getMessage());</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据objectName从minio中下载文件到指定的目录</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> objectName minio上的文件名称</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> fileName   下载生成的文件名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> dir        文件目录</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> Exception exception</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">downloadFromMinioToFile</span><span class="params">(String objectName, String fileName, String dir)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">GetObjectArgs</span> <span class="variable">objectArgs</span> <span class="operator">=</span> GetObjectArgs.builder()</span><br><span class="line">                .bucket(BUCKET_NAME)</span><br><span class="line">                .object(objectName)</span><br><span class="line">                .build();</span><br><span class="line">        <span class="type">File</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(dir);</span><br><span class="line">        <span class="keyword">if</span> (!file.exists()) &#123;</span><br><span class="line">            <span class="keyword">if</span> (file.mkdirs()) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;创建失败&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">InputStream</span> <span class="variable">inputStream</span> <span class="operator">=</span> getInstance().getObject(objectArgs);</span><br><span class="line">        <span class="type">FileOutputStream</span> <span class="variable">outputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="keyword">new</span> <span class="title class_">File</span>(dir, fileName.substring(fileName.lastIndexOf(<span class="string">&quot;/&quot;</span>) + <span class="number">1</span>)));</span><br><span class="line">        <span class="type">int</span> length;</span><br><span class="line">        <span class="type">byte</span>[] buffer = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">1024</span>];</span><br><span class="line">        <span class="keyword">while</span> ((length = inputStream.read(buffer)) != -<span class="number">1</span>) &#123;</span><br><span class="line">            outputStream.write(buffer, <span class="number">0</span>, length);</span><br><span class="line">        &#125;</span><br><span class="line">        outputStream.close();</span><br><span class="line">        inputStream.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据文件名批量删除（默认删除 BUCKET_NAME 下的文件）</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> listFile 文件名(含后缀)列表，例如：demo.png</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 成功返回为null， 失败返回Map&lt;objectName, failMessage&gt;</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@SneakyThrows</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Map&lt;String, String&gt; <span class="title function_">removeObjects</span><span class="params">(List&lt;String&gt; listFile)</span> &#123;</span><br><span class="line">        List&lt;DeleteObject&gt; objects = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line">        Map&lt;String, String&gt; resultMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">        listFile.forEach(t -&gt; objects.add(<span class="keyword">new</span> <span class="title class_">DeleteObject</span>(t)));</span><br><span class="line">        Iterable&lt;Result&lt;DeleteError&gt;&gt; results =</span><br><span class="line">                getInstance().removeObjects(</span><br><span class="line">                        RemoveObjectsArgs.builder()</span><br><span class="line">                                .bucket(BUCKET_NAME)</span><br><span class="line">                                .objects(objects)</span><br><span class="line">                                .build());</span><br><span class="line">        <span class="keyword">for</span> (Result&lt;DeleteError&gt; result : results) &#123;</span><br><span class="line">            <span class="type">DeleteError</span> <span class="variable">error</span> <span class="operator">=</span> result.get();</span><br><span class="line">            resultMap.put(error.objectName(), error.message());</span><br><span class="line">            log.error(<span class="string">&quot;Error in deleting:&#123;&#125;, message&#123;&#125;&quot;</span>, error.objectName(), error.message());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> resultMap;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="上传接口"><a class="header-anchor" href="#上传接口"></a>上传接口</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@PostMapping(value = &quot;/upload&quot;, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)</span></span><br><span class="line"><span class="meta">@ApiOperation(value = &quot;文件上传&quot;, notes = &quot;支持多文件上传&quot;)</span></span><br><span class="line"><span class="keyword">public</span> List&lt;String&gt; <span class="title function_">uploadTest</span><span class="params">(<span class="meta">@ApiParam(value = &quot;文件&quot;)</span> <span class="meta">@RequestParam(&quot;file&quot;)</span> List&lt;MultipartFile&gt; file)</span> &#123;</span><br><span class="line">    <span class="comment">// 上传的图片地址</span></span><br><span class="line">    List&lt;String&gt; successFile = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(file.size());</span><br><span class="line">    file.forEach(t -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">url</span> <span class="operator">=</span> MinioUtils.uploadToMinio(t.getInputStream(), t.getOriginalFilename());</span><br><span class="line">            log.info(<span class="string">&quot;图片地址&#123;&#125;&quot;</span>, url);</span><br><span class="line">            successFile.add(url);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">return</span> successFile;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="测试"><a class="header-anchor" href="#测试"></a>测试</h4><h2 id="问题"><a class="header-anchor" href="#问题"></a>问题</h2><h3 id="bucket命名"><a class="header-anchor" href="#bucket命名"></a>bucket命名</h3><p>创建 bucket 时，命名不可以使用下划线符号 “<font color="red">_</font>”</p><h3 id="账号密码修改"><a class="header-anchor" href="#账号密码修改"></a>账号密码修改</h3><p>通过网页管理页面修改登录账号及密码，提示 “Credentials of this user cannot be updated through MinIO Browser.” ，原因是安装应用时，并未显示的指定用户名和密码，可在运行启动时添加如下配置</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-e <span class="string">&quot;MINIO_ROOT_USER=admin&quot;</span> \</span><br><span class="line">-e <span class="string">&quot;MINIO_ROOT_PASSWORD=admin123456&quot;</span> \</span><br></pre></td></tr></table></figure><h3 id="最长7天有效"><a class="header-anchor" href="#最长7天有效"></a>最长7天有效</h3><p>通过网页管理页面共享图片或者是使用 SDK 上传图片得到的图片 URL 地址，有效期最长为7天</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">mc config host add minio http://192.168.1.163:19000 minioadmin minioadmin --api S3v4</span><br><span class="line">mc policy set public minio/cpe-manager-test</span><br><span class="line"></span><br><span class="line">mc config host add minio http://127.0.0.1:9000 minioadmin minioadmin --api S3v4</span><br><span class="line">mc policy set public minio/bucket (bucket修改成你自己的名字)</span><br></pre></td></tr></table></figure><h3 id="图片无法查看"><a class="header-anchor" href="#图片无法查看"></a>图片无法查看</h3><ol><li>使用 SDK 上传时，需要注意设置content-type信息</li><li>无权限查看</li></ol><h2 id="小结"><a class="header-anchor" href="#小结"></a>小结</h2><p>关于 MinIO 还有很多知识点，本片只是站在使用者角度，把一些使用过程和问题进行了汇总，谈不上深度</p><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly9kb2NzLm1pbi5pby9jbi9taW5pby1xdWlja3N0YXJ0LWd1aWRlLmh0bWw=">Minio 手册<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL21pbmlvL21pbmlvLWphdmEvdHJlZS9yZWxlYXNl">Minio 示例<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Rhbms5OXRhbmsvYXJ0aWNsZS9kZXRhaWxzLzEwOTQ2NDMyNQ==">Minio 修改密码<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC82OGFjMDQ3NzI5MWQ=">Minio<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vZ2FvaG9uZ3l1L3AvMTM5ODY5NjQuaHRtbA==">Minio 安装以及使用<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1d1Smlhbmdhbmc1MTEyL2FydGljbGUvZGV0YWlscy8xMTI5ODgwNzQ=">Minio 设置文件链接永久有效<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;MinIO 是一个基于 Apache License v2.0 开源协议使用 Go 语言开发的对象存储服务。它兼容亚马逊 S3 云存储服务接口，非常适合于存储大容量非结构化的数据，例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等，而一个对象文件可以是任意大小，从几 kb 到最大 5T 不等。MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合，类似 NodeJS, Redis 或者 MySQL。&lt;/p&gt;</summary>
    
    
    
    <category term="Linux" scheme="https://incoder.org/categories/Linux/"/>
    
    
    <category term="FileUpdate" scheme="https://incoder.org/tags/FileUpdate/"/>
    
  </entry>
  
  <entry>
    <title>琅嬛福地</title>
    <link href="https://incoder.org/2021/03/06/scenically/"/>
    <id>https://incoder.org/2021/03/06/scenically/</id>
    <published>2021-03-06T21:50:11.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>在金庸武侠《天龙八部》中，“琅嬛福地”存放了无崖子和李秋水搜罗天下各门各派的武功，江湖人士练成这里的一门武功绝学，就能在江湖中有自己的一席之地。而这里存放了我计算机相关学习、实践应用，以及经常使用的一些网站资源</p><span id="more"></span><h2 id="计算机网络"><a class="header-anchor" href="#计算机网络"></a>计算机网络</h2><div class="link-grid"><div class="link-grid-container"><object class="link-grid-image" data="https://i0.hdslb.com/bfs/face/abb12931aed341d6dcc67dd13162fddb35240622.jpg@96w_96h_1c.webp"></object><p>方方方已经存在了</p><p>计算机网络（谢希仁第七版）.</p><a href="https://www.bilibili.com/video/BV1yE411G7Ma"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i1.hdslb.com/bfs/face/1814653848d0a645c053efa7a7b40b9c53929d38.jpg@96w_96h_1c.webp"></object><p>韩立刚</p><p>韩立刚计算机网络 谢希仁 第7版.</p><a href="https://www.bilibili.com/video/BV1gV411h7r7"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i2.hdslb.com/bfs/face/507c26c8bca9a4b96ff7fb820da36c05960ea7ca.jpg@96w_96h_1c.webp"></object><p>王道论坛</p><p>2019 王道考研 计算机网络.</p><a href="https://www.bilibili.com/video/BV19E411D78Q"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i0.hdslb.com/bfs/face/member/noface.jpg@96w_96h_1c.webp"></object><p>湖科大教书匠</p><p>计算机网络微课堂（陆续更新中......）.</p><a href="https://www.bilibili.com/video/BV1c4411d7jb"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i2.hdslb.com/bfs/face/872982590cd7c2de9f5a3f595059a71fb9c95004.jpg@96w_96h_1c.webp"></object><p>中科大-郑老师</p><p>中科大郑烇老师全套《计算机网络（自顶向下方法 第7版，James F.Kurose，Keith W.Ross）》课程.</p><a href="https://www.bilibili.com/video/BV1JV411t7ow"></a></div></div><h2 id="数据结构与算法"><a class="header-anchor" href="#数据结构与算法"></a>数据结构与算法</h2><div class="link-grid"><div class="link-grid-container"><object class="link-grid-image" data="https://i2.hdslb.com/bfs/face/48aa2e4e420c660b88fe3deef44975c296fd91bd.gif"></object><p>临风笑笑生</p><p>【郝斌】-数据结构入门.</p><a href="https://www.bilibili.com/video/BV11s41167h6"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i2.hdslb.com/bfs/face/c38f4b346eed167f55183f1dc398376326c8ecc4.jpg@96w_96h_1c.webp"></object><p>星球杯25</p><p>数据结构-浙江大学.</p><a href="https://www.bilibili.com/video/BV1JW411i731"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i0.hdslb.com/bfs/face/4335c48c1a30a3d4e862c21eeb5f71b1218567ec.jpg@96w_96h_1c.webp"></object><p>87师兄</p><p>数据结构与算法基础（青岛大学-王卓）.</p><a href="https://www.bilibili.com/video/BV1nJ411V7bd"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i2.hdslb.com/bfs/face/7ea5132de4ecdc8b594a98c11d9f224f0e741c0b.jpg@96w_96h_1c.webp"></object><p>尚硅谷</p><p>尚硅谷Java数据结构与java算法（Java数据结构与算法）.</p><a href="https://www.bilibili.com/video/BV1E4411H73v"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i2.hdslb.com/bfs/face/55ea7d0f3b2038cec6b99d44068b3579f27065e5.jpg@128w_128h_1o.webp"></object><p>大雪菜</p><p>LeetCode 的大神，刷题讲解.</p><a href="https://space.bilibili.com/7836741"></a></div></div><h2 id="操作系统"><a class="header-anchor" href="#操作系统"></a>操作系统</h2><div class="link-grid"><div class="link-grid-container"><object class="link-grid-image" data="https://i1.hdslb.com/bfs/face/bc288c3544209fb2bdadaf45015721555175df17.jpg@96w_96h_1c.webp"></object><p>绿导师原谅你了</p><p>2020 南京大学 “操作系统：设计与实现” (蒋炎岩).</p><a href="https://www.bilibili.com/video/BV1N741177F5"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i1.hdslb.com/bfs/face/f81c108b4e7ced4b8e16c9a0d4ee3370e17e12bf.jpg@96w_96h_1c.webp"></object><p>星球杯25</p><p>操作系统_清华大学(向勇、陈渝).</p><a href="https://www.bilibili.com/video/BV1js411b7vg"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i0.hdslb.com/bfs/face/51d41d81499b7913458145045e13107bf152b694.jpg@96w_96h_1c.webp"></object><p>FCCJK</p><p>操作系统（哈工大李治军老师）32讲（全）超清.</p><a href="https://www.bilibili.com/video/BV1d4411v7u7"></a></div></div><h2 id="计算机组成原理"><a class="header-anchor" href="#计算机组成原理"></a>计算机组成原理</h2><div class="link-grid"><div class="link-grid-container"><object class="link-grid-image" data="https://i0.hdslb.com/bfs/face/51d41d81499b7913458145045e13107bf152b694.jpg@96w_96h_1c.webp"></object><p>绿导师原谅你了</p><p>计算机组成原理（哈工大刘宏伟）.</p><a href="https://www.bilibili.com/video/BV1t4411e7LH"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://i0.hdslb.com/bfs/face/member/noface.jpg@96w_96h_1c.webp"></object><p>东南偏南2018</p><p>计算机组成原理 清华大学刘卫东 全58讲 国家精品课程 1080P 更完.</p><a href="https://www.bilibili.com/video/BV1c4411w7nd"></a></div></div><h2 id="编译原理"><a class="header-anchor" href="#编译原理"></a>编译原理</h2><div class="link-grid"><div class="link-grid-container"><object class="link-grid-image" data="https://i0.hdslb.com/bfs/face/8ae1f165fef1fff75d8fced295a5f87b0f9d2e92.jpg@96w_96h_1c.webp"></object><p>执念缘不浅</p><p>编译原理（哈工大）.</p><a href="https://www.bilibili.com/video/BV1t4411e7LH"></a></div></div><h2 id="资源"><a class="header-anchor" href="#资源"></a>资源</h2><div class="link-grid"><div class="link-grid-container"><object class="link-grid-image" data="https://tse3-mm.cn.bing.net/th/id/OIP.RomlWHLG15NaBmLbJAHtvwAAAA?w=171&h=180&c=7&o=5&dpr=2&pid=1.7"></object><p>阿里云开发者藏经阁</p><p>各种实战经验，顶级技术电子书.</p><a href="https://developer.aliyun.com/ebook"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://tse3-mm.cn.bing.net/th/id/OIP.RomlWHLG15NaBmLbJAHtvwAAAA?w=171&h=180&c=7&o=5&dpr=2&pid=1.7"></object><p>阿里云开发者实验室</p><p>免费云资源，真实云环境，丰富实践场景.</p><a href="https://developer.aliyun.com/adc/labs"></a></div><div class="link-grid-container"><object class="link-grid-image" data="https://tse3-mm.cn.bing.net/th/id/OIP.RomlWHLG15NaBmLbJAHtvwAAAA?w=171&h=180&c=7&o=5&dpr=2&pid=1.7"></object><p>阿里云开发者学习中心</p><p>各种学习路线图，热门技术训练营.</p><a href="https://developer.aliyun.com/learning"></a></div></div><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvMkxLUDUzVlZBaGdsLVI4SDVZTmFUUQ==">聊一聊我在B站上自学编程的经历吧<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;在金庸武侠《天龙八部》中，“琅嬛福地”存放了无崖子和李秋水搜罗天下各门各派的武功，江湖人士练成这里的一门武功绝学，就能在江湖中有自己的一席之地。而这里存放了我计算机相关学习、实践应用，以及经常使用的一些网站资源&lt;/p&gt;</summary>
    
    
    
    <category term="Resources" scheme="https://incoder.org/categories/Resources/"/>
    
    
    <category term="DevTool" scheme="https://incoder.org/tags/DevTool/"/>
    
  </entry>
  
  <entry>
    <title>微信小程序之 Vant实战（一）</title>
    <link href="https://incoder.org/2021/02/12/wechat-mini1/"/>
    <id>https://incoder.org/2021/02/12/wechat-mini1/</id>
    <published>2021-02-12T11:11:11.000Z</published>
    <updated>2024-08-11T12:14:45.519Z</updated>
    
    <content type="html"><![CDATA[<p>过年正好时间比较集中，可以把之前的一个想法付诸实践，之前一直想给老爸做一个类似于账单管理的应用，方便他每天把客户需要物品记录成一个清单进行管理，其中主要包含已下功能点。其一，支持添加任务列表（账单）；其二，支持任务列表分享（账单）。以上是我的第一期规划功能规划，话不多说我们就一起来跟着我来完成这个小程序的开发吧，本篇主要讲小程序的初始化相关工作</p><span id="more"></span><p>这是我第一次来开发小程序，虽然之前有开发 Android 客户端的经验，有一定的客户端经验，但是小程序却一直没有去实践，我主要是觉得小程序的使用体验真的很差。但随着现在人们的硬件设备越来越好，并且微信团队在应用底层也做了很多的扩展和优化，现在使用小程序开发轻量级的应用还是很方便且高效</p><h2 id="环境及选型"><a class="header-anchor" href="#环境及选型"></a>环境及选型</h2><ol><li>OS：macOS（11.2.1）</li><li><span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXJzLndlaXhpbi5xcS5jb20vbWluaXByb2dyYW0vZGV2L2RldnRvb2xzL2Rvd25sb2FkLmh0bWw=">IDE：WeChat Devtools（1.05.2102010）<i class="fa fa-external-link-alt"></i></span></li><li>Node：v15.5.0</li><li><span class="exturl" data-url="aHR0cHM6Ly92YW50LWNvbnRyaWIuZ2l0ZWUuaW8vdmFudC13ZWFwcC8jL2ludHJv">Vant：1.6.7<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29t">微信小程序账号<i class="fa fa-external-link-alt"></i></span></li></ol><blockquote><p>关于账号的申请，这里不做讲解，请自行解决</p></blockquote><h2 id="初始化项目"><a class="header-anchor" href="#初始化项目"></a>初始化项目</h2><h3 id="创建项目"><a class="header-anchor" href="#创建项目"></a>创建项目</h3><p>不废话，直接看图</p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1613191822/blog/wechat-create.png" alt=""></p><h3 id="项目结构"><a class="header-anchor" href="#项目结构"></a>项目结构</h3><p>项目是一个基于云开发的方式，创建完成后会包含云相关的一些操作实例</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">bill</span><br><span class="line">  ├── cloudfunctions/               # 云函数管理【清空当前文件夹下的内容】</span><br><span class="line">  │    │── callback/</span><br><span class="line">  │    │── echo/</span><br><span class="line">  │    │── login/</span><br><span class="line">  │    └── openapi/    </span><br><span class="line">  ├── minprogram/</span><br><span class="line">  │    │── components/              # 组件【清空当前文件夹下内容】</span><br><span class="line">  │    │── images/                  # 图片管理【清空当前文件夹下内容】</span><br><span class="line">  │    │── pages/                   # 页面管理【除 index 页面，其余都删除】</span><br><span class="line">  │    │    │── addFunction/</span><br><span class="line">  │    │    │── chooseLib/</span><br><span class="line">  │    │    │── databaseGuide/</span><br><span class="line">  │    │    │── deployFunctions/</span><br><span class="line">  │    │    │── im/</span><br><span class="line">  │    │    │── index/</span><br><span class="line">  │    │    │    │── index.js</span><br><span class="line">  │    │    │    │── index.json</span><br><span class="line">  │    │    │    │── index.wxml</span><br><span class="line">  │    │    │    │── index.wxss</span><br><span class="line">  │    │    │    └── user-unlogin.png</span><br><span class="line">  │    │    │── openapi/</span><br><span class="line">  │    │    │── storageConsole/</span><br><span class="line">  │    │    └── userConsole/</span><br><span class="line">  │    │── style/                   # 样式管理</span><br><span class="line">  │    │── app.js                   # 项目入口逻辑管理</span><br><span class="line">  │    │── app.json                 # 组件库配置</span><br><span class="line">  │    │── app.wxss                 # 全局样式设置</span><br><span class="line">  │    └── sitemap.json             # </span><br><span class="line">  ├── project.config.json           # 项目配置文件</span><br><span class="line">  └── README.md                     # 项目说明</span><br></pre></td></tr></table></figure><h3 id="精简项目"><a class="header-anchor" href="#精简项目"></a>精简项目</h3><p>从上面我们可知初始化的项目，包含了一些示例，我们对其精简</p><ol><li>清空 cloudfunctions 目录下的云函数内容</li><li>根据项目结构里的备注，进行删除相关的文件<ul><li>清空 components 文件夹下的组件</li><li>清空 images 文件夹中的内容</li><li>删除 pages 文件夹下，<strong>除</strong> index 的文件夹</li><li>删除 index 文件夹下的 user-unlogin.png 文件</li></ul></li><li>修改文件内容<ul><li>清空 index 文件夹下 index.wxml，index.wxss 文件中的内容</li><li>修改 index 文件夹下 index.js 文件内容</li><li>清空 minprogram 文件夹下 app.wxss 文件中的样式内容</li></ul></li><li>修改配置<ul><li>移除 app.json 文件中 已经移除掉的 pages 的配置</li><li>修改 project.config.json 文件，移除 miniprogram 的配置</li></ul></li></ol><h3 id="添加-vant-组件"><a class="header-anchor" href="#添加-vant-组件"></a>添加 vant 组件</h3><p>整个步骤，如下截图<br><img src="https://res.cloudinary.com/incoder/image/upload/v1613403600/blog/wechat-init.png" alt=""></p><blockquote><p>执行命令根据官方提供的方式和自身喜好选择，我这里使用的是 <code>yarn</code> 命令进行安装相关的依赖</p></blockquote><h3 id="测验效果"><a class="header-anchor" href="#测验效果"></a>测验效果</h3><p>这里以添加 Button 为例来查看是否生效</p><ol><li>在 pages/index 路径下的 <code>index.json</code> 文件中，添加 vant 的 Button 组件<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;usingComponents&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;van-button&quot;</span><span class="punctuation">:</span> <span class="string">&quot;@vant/weapp/button/index&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li>在 pages/index 路径下的 <code>index.wxml</code> 文件中，添加 vant 的相关组件 <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">van-button</span> <span class="attr">type</span>=<span class="string">&quot;primary&quot;</span>&gt;</span>主要按钮<span class="tag">&lt;/<span class="name">van-button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">van-button</span> <span class="attr">type</span>=<span class="string">&quot;info&quot;</span>&gt;</span>信息按钮<span class="tag">&lt;/<span class="name">van-button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">van-button</span> <span class="attr">type</span>=<span class="string">&quot;warning&quot;</span>&gt;</span>警告按钮<span class="tag">&lt;/<span class="name">van-button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">van-button</span> <span class="attr">type</span>=<span class="string">&quot;danger&quot;</span>&gt;</span>危险按钮<span class="tag">&lt;/<span class="name">van-button</span>&gt;</span></span><br></pre></td></tr></table></figure></li><li>编译，在模拟器中查看效果</li></ol><blockquote><p>对于引用的组件，是公共的，可以写在 <code>app.json</code> 文件中</p></blockquote><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXJzLndlaXhpbi5xcS5jb20vbWluaXByb2dyYW0vZGV2L2ZyYW1ld29yaw==">官方开发文档<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tL3ZpZGVvL0JWMThWNDExQzdWVg==">微信小程序组件库Vant weapp的使用与weui零基础入门课程<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;过年正好时间比较集中，可以把之前的一个想法付诸实践，之前一直想给老爸做一个类似于账单管理的应用，方便他每天把客户需要物品记录成一个清单进行管理，其中主要包含已下功能点。其一，支持添加任务列表（账单）；其二，支持任务列表分享（账单）。以上是我的第一期规划功能规划，话不多说我们就一起来跟着我来完成这个小程序的开发吧，本篇主要讲小程序的初始化相关工作&lt;/p&gt;</summary>
    
    
    
    <category term="Wechat" scheme="https://incoder.org/categories/Wechat/"/>
    
    
    <category term="Wechat" scheme="https://incoder.org/tags/Wechat/"/>
    
    <category term="Vant" scheme="https://incoder.org/tags/Vant/"/>
    
  </entry>
  
  <entry>
    <title>第 100 篇原创文章</title>
    <link href="https://incoder.org/2020/12/31/milepost1/"/>
    <id>https://incoder.org/2020/12/31/milepost1/</id>
    <published>2020-12-31T17:26:20.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>未曾想过，居然能写到第 100 篇文章。虽然大部分文章都是线性流水操作，但全部是自己经过<strong>实践</strong>的总结；虽然没有精彩的故事，但都是自己成长的<strong>思考</strong>；虽然有时一篇文章需要要长达一个多月的反复核对，但还是能默默<strong>坚持</strong>。只是这第 100 篇来的有点晚，断断续续大概有 3 年的时间，时间是个坏老头，把我给你写情话，悄悄的改成了谎话！</p><span id="more"></span><p>回想起之前写文章主要是为了记录一些操作步骤和一些知识点，方便遇到类似问题，快速定位，解决问题。但随着文章的越写越多，包含的内容也越来越多，需要去了解的知识也越来越多，真的是有一种 “你知道的越多，你不知道的越多” 的感觉，这种感觉让我对待每个知识点都能有往深去深挖的动力，对每一个知识点用自己文字将它讲出来时有一种让我欲罢不能成就感，这或许就是上瘾吧</p><p>总结下第一个里程碑，主要是平时接触到领域算是一些入门级别的一些文章，以及一些比较粗浅的见闻，缺乏深层次的剖析和思考，这也是第二个里程碑首要做的事情，把每个接触到的知识点进行深挖，打通自己的技术栈。技术领域能不能走得远，很大程度上并不是你的技能宽度，而是深度，是在一个方向上的深耕，并且对于底层的技能也是要有足够的涉猎，只有这样就算是技术的花样任它怎么去变，你都能以不变应万变（透过现象找到本质）；第二点是是对第一阶段内容完善补充；第三点就是打磨自己的语言表达能力，让文章更加的通俗易懂；第四点就是不能太拖拉，要保持高效的内容输出</p><p>这一阶段，对开源项目贡献评价最高的是 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3RoeC9yYXAyLWRlbG9zL2lzc3Vlcy8xMTk=">rap2-delos<i class="fa fa-external-link-alt"></i></span> 项目了。努力在接下来的里程中，提高质量和参与度，争取早日在大型项目中做到 Committer</p><p>很感谢这一路走来，大伙对我的认可和期待以及赞赏，下一个里程碑我们见~</p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1611485411/blog/milepost1-admire.png" alt=""></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;未曾想过，居然能写到第 100 篇文章。虽然大部分文章都是线性流水操作，但全部是自己经过&lt;strong&gt;实践&lt;/strong&gt;的总结；虽然没有精彩的故事，但都是自己成长的&lt;strong&gt;思考&lt;/strong&gt;；虽然有时一篇文章需要要长达一个多月的反复核对，但还是能默默&lt;strong&gt;坚持&lt;/strong&gt;。只是这第 100 篇来的有点晚，断断续续大概有 3 年的时间，时间是个坏老头，把我给你写情话，悄悄的改成了谎话！&lt;/p&gt;</summary>
    
    
    
    <category term="Milepost" scheme="https://incoder.org/categories/Milepost/"/>
    
    
    <category term="note" scheme="https://incoder.org/tags/note/"/>
    
  </entry>
  
  <entry>
    <title>SpringBoot 源码构建</title>
    <link href="https://incoder.org/2020/12/31/springboot11/"/>
    <id>https://incoder.org/2020/12/31/springboot11/</id>
    <published>2020-12-31T10:24:00.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>前两天刚刚学习了 Gradle 构建 SpringBoot 项目，再查看官方文档时，得知 SpringBoot 从 <span class="exturl" data-url="aHR0cHM6Ly9zcHJpbmcuaW8vYmxvZy8yMDIwLzA2LzA4L21pZ3JhdGluZy1zcHJpbmctYm9vdC1zLWJ1aWxkLXRvLWdyYWRsZQ==">Spring Boot 2.3.0.M1<i class="fa fa-external-link-alt"></i></span> 版本开始完全切换到使用 Gradle 来构建项目，那么本篇文章就来实践，基于源码来编译构建 SpringBoot，话不多说，本次构建构建是 2020 年的最后一次发布的版本 2.4.1</p><span id="more"></span><h2 id="环境"><a class="header-anchor" href="#环境"></a>环境</h2><ul><li>OS：macOS 11.1</li><li>JDK：JDK1.8</li><li>Gradle：6.7.1-bin</li><li>IDE：IntelliJ IDEA Community 2020.3</li></ul><p>Gradle 版本通过 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3NwcmluZy1wcm9qZWN0cy9zcHJpbmctYm9vdC9ibG9iL21hc3Rlci9ncmFkbGUvd3JhcHBlci9ncmFkbGUtd3JhcHBlci5wcm9wZXJ0aWVz">https://github.com/spring-projects/spring-boot/blob/master/gradle/wrapper/gradle-wrapper.properties<i class="fa fa-external-link-alt"></i></span> 文件可知，使用的 6.7.1-bin，那么本地也使用该版本编译，对于 Gradle 的安装可参考 <a href="https://incoder.org/2020/12/10/gradle1/#Gradle-%E5%AE%89%E8%A3%85%E9%85%8D%E7%BD%AE">Gradle（一）基础</a> 文章</p><h2 id="获取源码"><a class="header-anchor" href="#获取源码"></a>获取源码</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 这里使用 cnpmjs 来提高 clone 速度</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com.cnpmjs.org/spring-projects/spring-boot.git</span><br></pre></td></tr></table></figure><h2 id="编译构建"><a class="header-anchor" href="#编译构建"></a>编译构建</h2><p>使用 IDEA 打开项目，会自动创建索引以及，下载项目的依赖，由于依赖的 jar 比较多，建议使用 <span class="exturl" data-url="aHR0cHM6Ly9tYXZlbi5hbGl5dW4uY29tLw==">阿里云<i class="fa fa-external-link-alt"></i></span> 镜像，关于 Gradle 怎么修改依赖镜像源，可参考 <a href="https://incoder.org/2020/02/27/fuck-gfw/#Gradle">专治各种网络不服</a> 文章，阿里云镜像能加速大部分的 jar，但有一部分在阿里云上并没有，你可以通过手动方式导入到本地</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;前两天刚刚学习了 Gradle 构建 SpringBoot 项目，再查看官方文档时，得知 SpringBoot 从 &lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9zcHJpbmcuaW8vYmxvZy8yMDIwLzA2LzA4L21pZ3JhdGluZy1zcHJpbmctYm9vdC1zLWJ1aWxkLXRvLWdyYWRsZQ==&quot;&gt;Spring Boot 2.3.0.M1&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt; 版本开始完全切换到使用 Gradle 来构建项目，那么本篇文章就来实践，基于源码来编译构建 SpringBoot，话不多说，本次构建构建是 2020 年的最后一次发布的版本 2.4.1&lt;/p&gt;</summary>
    
    
    
    <category term="SpringBoot" scheme="https://incoder.org/categories/SpringBoot/"/>
    
    
    <category term="SpringBoot" scheme="https://incoder.org/tags/SpringBoot/"/>
    
    <category term="Gradle" scheme="https://incoder.org/tags/Gradle/"/>
    
  </entry>
  
  <entry>
    <title>Gradle（三）SpringBoot 单工程</title>
    <link href="https://incoder.org/2020/12/16/gradle3/"/>
    <id>https://incoder.org/2020/12/16/gradle3/</id>
    <published>2020-12-16T13:30:46.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>在 <a href="https://incoder.org/2020/12/10/gradle1/">Gradle（一）基础</a> 的文章中，我们已经对 Gradle 有了一定的认识，本篇来看一看在后端开发中使用 Gradle 构建 SpringBoot 项目的开发。通常有两种方式来构建项目，第一种：每个功能模块即是一个代码工程，用一个 Git 仓库来管理，每个模块只负责完成一件事情；第二种：整个系统的多个模块聚合在一个代码工程里面，也就是我们常说的多模块项目，本篇先来讲一讲单工程</p><span id="more"></span><h2 id="工程选择"><a class="header-anchor" href="#工程选择"></a>工程选择</h2><p>对于单工程，和聚合工程的选择主要根据你所在项目团队的大小，项目分工，以及项目的复杂程度等来考虑。</p><p>单工程：适用于项目分工明确，项目庞大复杂，架构服务边界划分明确，配套的自动化等设施完善<br>聚合工程：适用于项目人员不是很多，项目功能一般，需要一个人集中化管理等</p><h2 id="环境"><a class="header-anchor" href="#环境"></a>环境</h2><ul><li>OS：macOS 11.1</li><li>JDK：JDK1.8</li><li>Gradle：6.7.1-bin</li><li>IDE：IntelliJ IDEA Community 2020.3</li><li>SpringBoot：2.4.1</li></ul><h2 id="build-gradle"><a class="header-anchor" href="#build-gradle"></a>build.gradle</h2><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 项目使用插件，可从 https://plugins.gradle.org 库中寻找合适的插件</span></span><br><span class="line">plugins &#123;</span><br><span class="line">    id <span class="string">&#x27;org.springframework.boot&#x27;</span> version <span class="string">&#x27;2.4.1&#x27;</span></span><br><span class="line">    id <span class="string">&#x27;io.spring.dependency-management&#x27;</span> version <span class="string">&#x27;1.0.10.RELEASE&#x27;</span></span><br><span class="line">    id <span class="string">&#x27;java&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里和 maven类似，用于项目唯一坐标</span></span><br><span class="line">group = <span class="string">&#x27;com.example&#x27;</span></span><br><span class="line">version = <span class="string">&#x27;0.0.1-SNAPSHOT&#x27;</span></span><br><span class="line"><span class="comment">// 项目兼容版本</span></span><br><span class="line">sourceCompatibility = <span class="string">&#x27;1.8&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 依赖第三方jar从哪个仓库去下载</span></span><br><span class="line">repositories &#123;</span><br><span class="line">    mavenCentral()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 项目所需的第三方依赖</span></span><br><span class="line">dependencies &#123;</span><br><span class="line">    implementation <span class="string">&#x27;org.springframework.boot:spring-boot-starter&#x27;</span></span><br><span class="line">    testImplementation <span class="string">&#x27;org.springframework.boot:spring-boot-starter-test&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 测试相关</span></span><br><span class="line">test &#123;</span><br><span class="line">    useJUnitPlatform()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一个 SpringBoot 项目基本的 build.gradle 文件由 plugins，项目坐标，repositories，dependencies，test 基础内容组成。关于 plugins 使用常见有两种方式，核心的依赖，是没有版本号，它和你使用的 Gradle 关联，你无需过多关系这些核心插件的依赖版本</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 旧方式</span></span><br><span class="line">apply <span class="attr">plugin:</span> <span class="string">&#x27;java&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 新方式（推荐）</span></span><br><span class="line">plubins &#123;</span><br><span class="line">    id <span class="string">&#x27;java&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="settings-gradle"><a class="header-anchor" href="#settings-gradle"></a>settings.gradle</h2><p>用于项目模块管理，由于这个单工程，这里只有一个模块</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rootProject.name = <span class="string">&#x27;demo&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="多环境"><a class="header-anchor" href="#多环境"></a>多环境</h2><p>可通过自定义 task 来出来</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// prod</span></span><br><span class="line">tasks.register(<span class="string">&quot;bootRunProd&quot;</span>) &#123;</span><br><span class="line">    group = <span class="string">&quot;application&quot;</span></span><br><span class="line">    description = <span class="string">&quot;Runs the Spring Boot application with the prod profile&quot;</span></span><br><span class="line">    doFirst &#123;</span><br><span class="line">        tasks.bootRun.configure &#123;</span><br><span class="line">            systemProperty(<span class="string">&quot;spring.profiles.active&quot;</span>, <span class="string">&quot;prod&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    finalizedBy(<span class="string">&quot;bootRun&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// dev</span></span><br><span class="line">tasks.register(<span class="string">&quot;bootRunDev&quot;</span>) &#123;</span><br><span class="line">    group = <span class="string">&quot;application&quot;</span></span><br><span class="line">    description = <span class="string">&quot;Runs the Spring Boot application with the dev profile&quot;</span></span><br><span class="line">    doFirst &#123;</span><br><span class="line">        tasks.bootRun.configure &#123;</span><br><span class="line">            systemProperty(<span class="string">&quot;spring.profiles.active&quot;</span>, <span class="string">&quot;dev&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    finalizedBy(<span class="string">&quot;bootRun&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>启动方式</p><ul><li><p>方式一：图形化界面中，直接运行对应环境<br><img src="https://res.cloudinary.com/incoder/image/upload/v1609691501/blog/gradle-task-gui.png" alt=""></p></li><li><p>方式二：在命令行中，使用命令来运行对应环境，比如 <code>gradlew bootRunDev</code><br><img src="https://res.cloudinary.com/incoder/image/upload/v1609691543/blog/gradle-task-terminal.png" alt=""></p></li><li><p>方式三：当然你也可以在启动时指定你需要激活的环境</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 这里激活的 test 环境，把 $&#123;jar_name&#125; 参数换成对应启动的应用文件</span></span><br><span class="line">java -jar <span class="variable">$&#123;jar_name&#125;</span> --spring.profiles.active=<span class="built_in">test</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="排除依赖"><a class="header-anchor" href="#排除依赖"></a>排除依赖</h2><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">testImplementation(<span class="string">&#x27;org.springframework.boot:spring-boot-starter-test&#x27;</span>) &#123;</span><br><span class="line">    exclude <span class="attr">group:</span> <span class="string">&#x27;org.junit.vintage&#x27;</span>, <span class="attr">module:</span> <span class="string">&#x27;junit-vintage-engine&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="打包"><a class="header-anchor" href="#打包"></a>打包</h2><p>打包时需要，注意我们的 SpringBoot 应用它本质上是一个 bootJar（Fatjar） 应用，因此需要将应用打成一个 bootJar（Fatjar）。而对于什么是 bootJar 和 jar 的区别，可以查看之前在 SpringBoot（二） 启动分析JarLauncher 文章中对于 <a href="https://incoder.org/2019/07/05/springboot2/#jar%E8%A7%84%E8%8C%83">jar 规范</a> 说明</p><p>打包方式</p><ul><li><p>方式一：图形化操作</p></li><li><p>方式二：命名执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在项目的根目录执行，Windows 使用：gradlew；Linux/macOS：./gradlew</span></span><br><span class="line"><span class="comment"># 当然如果那你已安装且配置好 gradle 的环境，你可以直接使用 gradle 代替 ./gradlew 的相关命令</span></span><br><span class="line">gradlew bootJar</span><br></pre></td></tr></table></figure></li></ul><h2 id="发布"><a class="header-anchor" href="#发布"></a>发布</h2><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Zvcm11bGFyb29tL2FydGljbGUvZGV0YWlscy83MDM1NDU2Mg==">SpringBoot+gradle 构建多模块项目<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poNDUyNjQ3NDU3L2FydGljbGUvZGV0YWlscy8xMDg4NDQwNzg=">IDEA 2020.2 + Gradle 6.6.1 + Spring Boot 2.3.4 创建多模块项目<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2J1aHVpZ3Vvd2FuZy9hcnRpY2xlL2RldGFpbHMvMTEwNzAwNTg1">Spring-boot 2.3.x 源码基于Gradle编译<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vZGF2ZW5raW4vcC9ncmFkbGUtc3ByaW5nLWJvb3QuaHRtbA==">用 Gradle 构建 Spring Boot 项目<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vaG91emhlbmcvcC8xMTAyNDg2NS5odG1s">使用 Gradle 构建 springboot 多模块项目,并混合groovy开发<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9kb2NzLnNwcmluZy5pby9zcHJpbmctYm9vdC9kb2NzL2N1cnJlbnQvZ3JhZGxlLXBsdWdpbi9yZWZlcmVuY2UvaHRtbHNpbmdsZS8=">Spring Boot Gradle Plugin Reference Guide<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 &lt;a href=&quot;https://incoder.org/2020/12/10/gradle1/&quot;&gt;Gradle（一）基础&lt;/a&gt; 的文章中，我们已经对 Gradle 有了一定的认识，本篇来看一看在后端开发中使用 Gradle 构建 SpringBoot 项目的开发。通常有两种方式来构建项目，第一种：每个功能模块即是一个代码工程，用一个 Git 仓库来管理，每个模块只负责完成一件事情；第二种：整个系统的多个模块聚合在一个代码工程里面，也就是我们常说的多模块项目，本篇先来讲一讲单工程&lt;/p&gt;</summary>
    
    
    
    <category term="Gradle" scheme="https://incoder.org/categories/Gradle/"/>
    
    
    <category term="Gradle" scheme="https://incoder.org/tags/Gradle/"/>
    
  </entry>
  
  <entry>
    <title>Gradle（二）Android</title>
    <link href="https://incoder.org/2020/12/15/gradle2/"/>
    <id>https://incoder.org/2020/12/15/gradle2/</id>
    <published>2020-12-15T09:00:46.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>在上一篇 Gradle 的文章中，已经对 Gradle 有了一定的认识，Gradle 在 Android 有着广泛的应用，用作 Android 包依赖管理，应用构建，测试，等一些列自动化，我们本篇就来了解下在 Android 领域 Gradle 的使用。其实 Android 项目结构和之前在第一篇 Gradle 项目结构基本相同，只是在 module 级别多了的 <span class="exturl" data-url="aHR0cDovL3Byb2d1YXJkLXJ1bGVzLnBybw==">proguard-rules.pro<i class="fa fa-external-link-alt"></i></span>。对于不管是 Android 项目或是 Spring 系列项目的子 module 都会有 build.gradle 文件</p><span id="more"></span><h2 id="Project-级别"><a class="header-anchor" href="#Project-级别"></a>Project 级别</h2><h3 id="build-gradle"><a class="header-anchor" href="#build-gradle"></a>build.gradle</h3><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// gradle 脚本执行所需依赖，分别是对应的maven库和插件</span></span><br><span class="line">buildscript &#123;</span><br><span class="line">    repositories &#123;</span><br><span class="line">        google()</span><br><span class="line">        jcenter()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 声明依赖 Android Gradle 插件版本</span></span><br><span class="line">    dependencies &#123;</span><br><span class="line">        classpath <span class="string">&#x27;com.android.tools.build:gradle:3.5.3&#x27;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// <span class="doctag">NOTE:</span> Do not place your application dependencies here; they belong</span></span><br><span class="line">        <span class="comment">// in the individual module build.gradle files</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 项目所有 module 配置需要的依赖</span></span><br><span class="line">allprojects &#123;</span><br><span class="line">    repositories &#123;</span><br><span class="line">        google()</span><br><span class="line">        jcenter()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 一个 clean 任务，用于删除 build 目录的文件</span></span><br><span class="line">task clean(<span class="attr">type:</span> Delete) &#123;</span><br><span class="line">    delete rootProject.buildDir</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="settings-gradle"><a class="header-anchor" href="#settings-gradle"></a>settings.gradle</h3><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 默认指的是创建 Android 项目生成的 app 模块，也是默认的应用启动模块</span></span><br><span class="line">include <span class="string">&#x27;:app&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="Module-级别"><a class="header-anchor" href="#Module-级别"></a>Module 级别</h2><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 表示这是一个应用程序模块，可直接运行</span></span><br><span class="line">apply <span class="attr">plugin:</span> <span class="string">&#x27;com.android.application&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 编译时间</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">def</span> releaseTime() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> Date().format(<span class="string">&#x27;yyyy-MM-dd&#x27;</span>, TimeZone.getTimeZone(<span class="string">&#x27;UTC&#x27;</span>))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">android &#123;</span><br><span class="line">    <span class="comment">// 编译 Android 版本</span></span><br><span class="line">    compileSdkVersion <span class="number">29</span></span><br><span class="line">    <span class="comment">// 默认配置</span></span><br><span class="line">    defaultConfig &#123;</span><br><span class="line">        <span class="comment">// 应用 ID，手机中用于识别应用的唯一标识</span></span><br><span class="line">        applicationId <span class="string">&quot;org.incoder.android&quot;</span></span><br><span class="line">        <span class="comment">// 目标 Android 版本</span></span><br><span class="line">        targetSdkVersion <span class="number">29</span></span><br><span class="line">        <span class="comment">// 申明应用可超过 65536 的方法，可参考：https://developer.android.google.cn/studio/build/multidex?hl=zh_cn</span></span><br><span class="line">        multiDexEnabled <span class="literal">true</span></span><br><span class="line">        <span class="comment">// 申明要使用AndroidJUnitRunner进行单元测试</span></span><br><span class="line">        testInstrumentationRunner <span class="string">&quot;android.support.test.runner.AndroidJUnitRunner&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    buildTypes &#123;</span><br><span class="line">        release &#123;</span><br><span class="line">            minifyEnabled <span class="literal">false</span></span><br><span class="line">            proguardFiles getDefaultProguardFile(<span class="string">&#x27;proguard-android.txt&#x27;</span>), <span class="string">&#x27;proguard-rules.pro&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 签名配置，相关信息放置在 gradle.properties 文件中</span></span><br><span class="line">    signingConfigs &#123;</span><br><span class="line">        debug &#123;</span><br><span class="line">            storeFile file(DEBUG_STORE_FILE)</span><br><span class="line">            storePassword DEBUG_STORE_PASSWORD</span><br><span class="line">            keyAlias DEBUG_KEY_ALIAS</span><br><span class="line">            keyPassword DEBUG_KEY_PASSWORD</span><br><span class="line">        &#125;</span><br><span class="line">        release &#123;</span><br><span class="line">            storeFile file(RELEASE_STORE_FILE)</span><br><span class="line">            storePassword RELEASE_STORE_PASSWORD</span><br><span class="line">            keyAlias RELEASE_KEY_ALIAS</span><br><span class="line">            keyPassword RELEASE_KEY_PASSWORD</span><br><span class="line">            v2SigningEnabled <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    buildTypes &#123;</span><br><span class="line">        debug &#123;</span><br><span class="line">            minifyEnabled <span class="literal">false</span></span><br><span class="line">            zipAlignEnabled <span class="literal">false</span></span><br><span class="line">            shrinkResources <span class="literal">false</span></span><br><span class="line">            <span class="comment">// 签名</span></span><br><span class="line"><span class="comment">//            signingConfig signingConfigs.debug</span></span><br><span class="line">            manifestPlaceholders = [</span><br><span class="line">                    <span class="comment">//JPush</span></span><br><span class="line">                    <span class="attr">JPUSH_APPKEY :</span> <span class="string">&quot;&quot;</span>,</span><br><span class="line">                    <span class="symbol">JPUSH_CHANNEL:</span> <span class="string">&quot;&quot;</span>,</span><br><span class="line">                    <span class="comment">// Pgy</span></span><br><span class="line">                    <span class="attr">PGYER_APPID  :</span> <span class="string">&quot;7907554687e4c116316feedb3820ce52&quot;</span>,</span><br><span class="line">                    <span class="comment">// Bugly</span></span><br><span class="line">                    <span class="attr">BUGLY_APPID  :</span> <span class="string">&quot;&quot;</span>,</span><br><span class="line">                    <span class="attr">VERSION_NAME :</span> <span class="string">&quot;0.1.0&quot;</span>,</span><br><span class="line">            ]</span><br><span class="line">            ndk &#123;</span><br><span class="line">                <span class="comment">// 设置支持的SO库架构</span></span><br><span class="line">                abiFilters <span class="string">&#x27;armeabi&#x27;</span>, <span class="string">&#x27;x86&#x27;</span>, <span class="string">&#x27;armeabi-v7a&#x27;</span>, <span class="string">&#x27;x86_64&#x27;</span>, <span class="string">&#x27;arm64-v8a&#x27;</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        release &#123;</span><br><span class="line">            <span class="comment">// 混淆</span></span><br><span class="line">            minifyEnabled <span class="literal">false</span></span><br><span class="line">            <span class="comment">// Zipalign优化</span></span><br><span class="line">            zipAlignEnabled <span class="literal">true</span></span><br><span class="line">            <span class="comment">// 移除无用的resource文件</span></span><br><span class="line">            shrinkResources <span class="literal">false</span></span><br><span class="line">            <span class="comment">// 前一部分代表系统默认的android程序的混淆文件，该文件已经包含了基本的混淆声明</span></span><br><span class="line">            proguardFiles getDefaultProguardFile(<span class="string">&#x27;proguard-android.txt&#x27;</span>), <span class="string">&#x27;proguard-rules.pro&#x27;</span></span><br><span class="line">            <span class="comment">// 签名</span></span><br><span class="line">            signingConfig signingConfigs.release</span><br><span class="line">            <span class="comment">// AppAnalytics key</span></span><br><span class="line">            manifestPlaceholders = [</span><br><span class="line">                    <span class="comment">// JPush</span></span><br><span class="line">                    <span class="attr">JPUSH_APPKEY :</span> <span class="string">&quot;&quot;</span>,</span><br><span class="line">                    <span class="symbol">JPUSH_CHANNEL:</span> <span class="string">&quot;&quot;</span>,</span><br><span class="line">                    <span class="comment">// Pgy</span></span><br><span class="line">                    <span class="attr">PGYER_APPID  :</span> <span class="string">&quot;7907554687e4c116316feedb3820ce52&quot;</span>,</span><br><span class="line">                    <span class="comment">// Bugly</span></span><br><span class="line">                    <span class="attr">BUGLY_APPID  :</span> <span class="string">&quot;&quot;</span>,</span><br><span class="line">                    <span class="attr">VERSION_NAME :</span> <span class="string">&quot;0.1.0&quot;</span>,</span><br><span class="line">            ]</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重命名安装包</span></span><br><span class="line">    android.applicationVariants.all &#123;</span><br><span class="line">        variant -&gt;</span><br><span class="line">            variant.outputs.all &#123;</span><br><span class="line">                output -&gt;</span><br><span class="line">                    output.outputFileName = variant.flavorName + buildType.name +</span><br><span class="line">                            <span class="string">&quot;_&quot;</span> + releaseTime() + <span class="string">&quot;.apk&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 产品变种</span></span><br><span class="line">    flavorDimensions <span class="string">&quot;minSDK&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 针对不同渠道的配置</span></span><br><span class="line">    productFlavors &#123;</span><br><span class="line">        <span class="comment">// 测试环境渠道包</span></span><br><span class="line">        dev &#123;</span><br><span class="line">            applicationId <span class="string">&#x27;org.incoder.test&#x27;</span></span><br><span class="line">            minSdkVersion <span class="number">19</span></span><br><span class="line">            <span class="comment">// 测试环境IP配置 API 接口地址</span></span><br><span class="line">            buildConfigField <span class="string">&#x27;String&#x27;</span>, <span class="string">&#x27;API&#x27;</span>, <span class="string">&#x27;&quot;http://xxx.xxx.xxx.xxx:8888&quot;&#x27;</span></span><br><span class="line">            versionCode <span class="number">2020122501</span></span><br><span class="line">            versionName <span class="string">&quot;2.0&quot;</span></span><br><span class="line">            <span class="comment">// 指定产品变种</span></span><br><span class="line">            dimension <span class="string">&quot;minSDK&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 正式环境渠道包</span></span><br><span class="line">        rel &#123;</span><br><span class="line">            applicationId <span class="string">&quot;org.incoder.android&quot;</span></span><br><span class="line">            minSdkVersion <span class="number">16</span></span><br><span class="line">            <span class="comment">// 正式环境域名 API 接口地址</span></span><br><span class="line">            buildConfigField <span class="string">&#x27;String&#x27;</span>, <span class="string">&#x27;API&#x27;</span>, <span class="string">&#x27;&quot;http://api.xxx.xxx/&quot;&#x27;</span></span><br><span class="line">            versionCode <span class="number">2020122501</span></span><br><span class="line">            versionName <span class="string">&quot;2.1&quot;</span></span><br><span class="line">            <span class="comment">// 指定产品变种</span></span><br><span class="line">            dimension <span class="string">&quot;minSDK&quot;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 过滤指定产品变种（渠道，构建类型）</span></span><br><span class="line"><span class="comment">//    variantFilter &#123; variant -&gt;</span></span><br><span class="line"><span class="comment">//        def names = variant.flavors*.name</span></span><br><span class="line"><span class="comment">//        def isDebug = variant.buildType.debuggable</span></span><br><span class="line"><span class="comment">//        // To check for a certain build type, use variant.buildType.name == &quot;&lt;buildType&gt;&quot;</span></span><br><span class="line"><span class="comment">//        if (names.contains(&quot;rel&quot;) &amp;&amp; isDebug) &#123;</span></span><br><span class="line"><span class="comment">//            // Gradle ignores any variants that satisfy the conditions above.</span></span><br><span class="line"><span class="comment">//            setIgnore(true)</span></span><br><span class="line"><span class="comment">//        &#125;</span></span><br><span class="line"><span class="comment">//    &#125;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 多渠道配置</span></span><br><span class="line">    productFlavors.all &#123;</span><br><span class="line">        flavor -&gt;</span><br><span class="line">            flavor.manifestPlaceholders = [<span class="attr">CHANNEL_VALUE:</span> name]</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 执行lint检查，有任何的错误或者警告提示，都会终止构建</span></span><br><span class="line">    lintOptions &#123;</span><br><span class="line">        disable <span class="string">&#x27;MissingTranslation&#x27;</span>, <span class="string">&#x27;ExtraTranslation&#x27;</span></span><br><span class="line">        <span class="comment">// abortOnError一定要设为false，这样即使有报错也不会停止打包了</span></span><br><span class="line">        abortOnError <span class="literal">false</span></span><br><span class="line">        <span class="comment">// 在打包Release版本的时候进行检测,可以打开，这样报错还会显示出来</span></span><br><span class="line">        checkReleaseBuilds <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    dexOptions &#123;</span><br><span class="line">        jumboMode <span class="literal">true</span></span><br><span class="line">        javaMaxHeapSize <span class="string">&quot;4g&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 打包时的配置</span></span><br><span class="line">    packagingOptions &#123;</span><br><span class="line">        exclude <span class="string">&#x27;META-INF/LICENSE&#x27;</span></span><br><span class="line">        exclude <span class="string">&#x27;META-INF/NOTICE&#x27;</span></span><br><span class="line">        exclude <span class="string">&#x27;META-INF/rxjava.properties&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    aaptOptions.cruncherEnabled = <span class="literal">false</span></span><br><span class="line">    aaptOptions.useNewCruncher = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">    compileOptions &#123;</span><br><span class="line">        sourceCompatibility JavaVersion.VERSION_1_8</span><br><span class="line">        targetCompatibility JavaVersion.VERSION_1_8</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 项目依赖的包</span></span><br><span class="line">dependencies &#123;</span><br><span class="line">    implementation fileTree(<span class="attr">include:</span> [<span class="string">&#x27;*.jar&#x27;</span>], <span class="attr">dir:</span> <span class="string">&#x27;libs&#x27;</span>)</span><br><span class="line">    implementation <span class="string">&#x27;com.android.support:appcompat-v7:28.0.0&#x27;</span></span><br><span class="line">    implementation <span class="string">&#x27;com.android.support:design:28.0.0&#x27;</span></span><br><span class="line">    implementation <span class="string">&#x27;com.android.support:support-v4:28.0.0&#x27;</span></span><br><span class="line">    implementation <span class="string">&#x27;com.android.support:support-v13:28.0.0&#x27;</span></span><br><span class="line">    implementation <span class="string">&#x27;com.android.support.constraint:constraint-layout:1.1.3&#x27;</span></span><br><span class="line">    implementation <span class="string">&#x27;com.android.support:support-vector-drawable:28.0.0&#x27;</span></span><br><span class="line">    androidTestImplementation <span class="string">&#x27;com.android.support.test:runner:1.0.2&#x27;</span></span><br><span class="line">    androidTestImplementation <span class="string">&#x27;com.android.support.test.espresso:espresso-core:3.0.2&#x27;</span></span><br><span class="line">    testImplementation <span class="string">&#x27;junit:junit:4.12&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关于 module 中的 build.gradle 配置文件中的各项已在示例中加入了注释说明，其中一些配置，这里再简单的说明下</p><h3 id="apply-plugin"><a class="header-anchor" href="#apply-plugin"></a>apply plugin</h3><p>这里的 <code>apply plugin</code> 有两种模式：</p><ol><li>com.android.application：表示这是一个应用程序模块</li><li>com.android.library：表示这是一个库模块</li></ol><p>前者可以直接运行，后着是依附别的应用程序运行</p><h3 id="buildTypes"><a class="header-anchor" href="#buildTypes"></a>buildTypes</h3><p>这里主要是生成安装文件的配置信息，一个 debug 类型，用于指定生成测试版安装文件配置，可忽略不写；另一个是 release，用于指定生成正式版安装文件的配置。</p><ul><li>minifyEnabled：是否对代码进行混淆，默认 false</li><li>proguardFiles：指定混淆的规则文件，默认指定了 proguard-android.txt 文件和 <span class="exturl" data-url="aHR0cDovL3Byb2d1YXJkLXJ1bGVzLnBybw==">proguard-rules.pro<i class="fa fa-external-link-alt"></i></span> 文件。<ul><li>proguard-android.txt：默认的混淆文件，里面定义了一些通用的混淆规则</li><li><span class="exturl" data-url="aHR0cDovL3Byb2d1YXJkLXJ1bGVzLnBybw==">proguard-rules.pro<i class="fa fa-external-link-alt"></i></span>：位于当前项目的根目录下，可以在该文件中定义一些项目特有的混淆规则</li></ul></li><li>buildConfigField：可用于解决不同渠道不同的服务地址，或不同渠道 LOG 打印控制等</li><li>debuggable：是否支持断点调试，release 默认为 false，debug 默认 true</li><li>jniDebuggable：是否可以调试 NDK 代码，使用 lldb 进行 C 和 C++ 代码调试，release 默认为 false</li><li>signingConfig：设置签名信息，通过 singingConfig.release 或 singingConfig.debug，配置相应的签名，但是添加此配置前需要先添加 singingConfig 闭包</li><li>renderscriptDebuggable：是否开启渲染脚本，就是一些 C 写的渲染方法，默认为 false</li><li>renderscriptOptimLevel：渲染等级，默认为 3</li><li>zipAlignEnabled：是否对 apk 包执行 zip 对齐优化，减少 zip 体积，提高运行效率，release 和 debug 都默认 true</li><li>pseudoLocalesEnabled：是否在 apk 中生成伪语言环境，帮助国际化，一般很少使用</li><li>applicationIdSuffix：和 defaultConfig 中配置一样，指在 applicationId 中添加一个后缀</li><li>versionNameSuffix：添加版本名称的后缀，一般使用较少</li></ul><h3 id="productFlavors"><a class="header-anchor" href="#productFlavors"></a>productFlavors</h3><p>这个配置主要是解决应用发布在不同应用市场，而需要对不同应用市场做一些不同配置，比如包名，应用名，以及一些统计，而需要不同渠道统计 ID 等</p><h3 id="packagingOptions"><a class="header-anchor" href="#packagingOptions"></a>packagingOptions</h3><p>packagingOptions 常见的设置项有 exclude、pickFirst、doNotStrip、merge</p><ol><li>exclude：过滤掉某些文件或者目录不添加到APK中，作用于APK，不能过滤aar和jar中的内容<figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">packagingOptions &#123;</span><br><span class="line">    exclude <span class="string">&#x27;META-INF/**&#x27;</span></span><br><span class="line">    exclude <span class="string">&#x27;lib/arm64-v8a/libmediaplayer.so&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>pickFirst：匹配到多个相同文件，只提取第一个。只作用于APK，不能过滤aar和jar中的文件<figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">packagingOptions &#123;</span><br><span class="line">    pickFirst <span class="string">&quot;lib/armeabi-v7a/libaaa.so&quot;</span></span><br><span class="line">    pickFirst <span class="string">&quot;lib/armeabi-v7a/libbbb.so&quot;</span> </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>doNotStrip：可以设置某些动态库不被优化压缩<figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">packagingOptions&#123;</span><br><span class="line">    doNotStrip <span class="string">&quot;*/armeabi/*.so&quot;</span></span><br><span class="line">    doNotStrip <span class="string">&quot;*/armeabi-v7a/*.so&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>merge：将匹配的文件都添加到APK中，和pickFirst有些相反，会合并所有文件<figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">packagingOptions &#123;</span><br><span class="line">    merge <span class="string">&#x27;**/LICENSE.txt&#x27;</span></span><br><span class="line">    merge <span class="string">&#x27;**/NOTICE.txt&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h2 id="统一版本"><a class="header-anchor" href="#统一版本"></a>统一版本</h2><p>应用由多个 module 构成，而不同地方引用的包，需要做到全局的统一时，可以创建一个 <code>xxx.gradle</code> 的文件（这里的 xxx，自行取一个表明含义的内容即可），然后在使用的地方时，统一调用定义的版本即可，使用步骤如下</p><ol><li>创建 <code>xxx.gradle</code> 文件（一般放在项目的根目录，和顶级 build.gradle 文件在同一层级），并添加如下内容，可根据自身需要调整 <figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br></pre></td><td class="code"><pre><span class="line">ext &#123;</span><br><span class="line"></span><br><span class="line">    android = [</span><br><span class="line">            <span class="symbol">compileSdkVersion:</span> <span class="number">29</span>,</span><br><span class="line">            <span class="symbol">buildToolsVersion:</span> <span class="string">&quot;29.0.2&quot;</span>,</span><br><span class="line">            <span class="attr">minSdkVersion    :</span> <span class="number">19</span>,</span><br><span class="line">            <span class="attr">targetSdkVersion :</span> <span class="number">29</span>,</span><br><span class="line">            <span class="attr">versionCode      :</span> <span class="number">2020010102</span>,</span><br><span class="line">            <span class="attr">versionName      :</span> <span class="string">&quot;0.1.0&quot;</span></span><br><span class="line">    ]</span><br><span class="line"></span><br><span class="line">    version = [</span><br><span class="line">            <span class="symbol">androidSupportSdkVersion:</span> <span class="string">&quot;29.0.0&quot;</span>,</span><br><span class="line">            <span class="attr">retrofitSdkVersion      :</span> <span class="string">&quot;2.6.3&quot;</span>,</span><br><span class="line">            <span class="attr">okhttpSdkVersion        :</span> <span class="string">&quot;4.3.0&quot;</span>,</span><br><span class="line">            <span class="attr">dagger2SdkVersion       :</span> <span class="string">&quot;2.22.1&quot;</span>,</span><br><span class="line">            <span class="attr">glideSdkVersion         :</span> <span class="string">&quot;4.9.0&quot;</span>,</span><br><span class="line">            <span class="attr">butterknifeSdkVersion   :</span> <span class="string">&quot;10.2.1&quot;</span>,</span><br><span class="line">            <span class="attr">rxlifecycle2SdkVersion  :</span> <span class="string">&quot;2.2.1&quot;</span>,</span><br><span class="line">            <span class="attr">espressoSdkVersion      :</span> <span class="string">&quot;3.0.2&quot;</span>,</span><br><span class="line">            <span class="attr">canarySdkVersion        :</span> <span class="string">&quot;1.5.4&quot;</span></span><br><span class="line">    ]</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Android support 与 AndroidX support 对比</span></span><br><span class="line">    <span class="comment">// https://developer.android.google.cn/jetpack/androidx/migrate</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// support 库说明</span></span><br><span class="line">    <span class="comment">// https://developer.android.com/topic/libraries/support-library/features?hl=zh-cn</span></span><br><span class="line">    dependencies = [</span><br><span class="line">            <span class="comment">// support</span></span><br><span class="line">            <span class="string">&quot;appcompat&quot;</span>                   : <span class="string">&quot;androidx.appcompat:appcompat:1.1.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;annotations&quot;</span>                 : <span class="string">&quot;androidx.annotation:annotation:1.0.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;cardview-v7&quot;</span>                 : <span class="string">&quot;androidx.cardview:cardview:1.0.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;constraint-layout&quot;</span>           : <span class="string">&quot;androidx.constraintlayout:constraintlayout:1.1.3&quot;</span>,</span><br><span class="line">            <span class="string">&quot;swiperefreshlayout&quot;</span>          : <span class="string">&quot;androidx.swiperefreshlayout:swiperefreshlayout:1.0.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;material&quot;</span>                    : <span class="string">&quot;com.google.android.material:material:1.1.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;viewpager&quot;</span>                   : <span class="string">&quot;androidx.viewpager:viewpager:1.0.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;recyclerview&quot;</span>                : <span class="string">&quot;androidx.recyclerview:recyclerview:1.1.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;vectordrawable&quot;</span>              : <span class="string">&quot;androidx.vectordrawable:vectordrawable:1.1.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;support-v4&quot;</span>                  : <span class="string">&quot;androidx.legacy:legacy-support-v4:1.0.0&quot;</span>,</span><br><span class="line"></span><br><span class="line">            <span class="comment">// network</span></span><br><span class="line">            <span class="string">&quot;retrofit&quot;</span>                    : <span class="string">&quot;com.squareup.retrofit2:retrofit:$&#123;version[&quot;</span>retrofitSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;retrofit-converter-gson&quot;</span>     : <span class="string">&quot;com.squareup.retrofit2:converter-gson:$&#123;version[&quot;</span>retrofitSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;retrofit-converter-simplexml&quot;</span>: <span class="string">&quot;com.squareup.retrofit2:converter-simplexml:$&#123;version[&quot;</span>retrofitSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;retrofit-adapter-rxjava2&quot;</span>    : <span class="string">&quot;com.squareup.retrofit2:adapter-rxjava2:$&#123;version[&quot;</span>retrofitSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;okhttp3&quot;</span>                     : <span class="string">&quot;com.squareup.okhttp3:okhttp:$&#123;version[&quot;</span>okhttpSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;okhttp3-logging-interceptor&quot;</span> : <span class="string">&quot;com.squareup.okhttp3:logging-interceptor:$&#123;version[&quot;</span>okhttpSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;mockwebserver&quot;</span>               : <span class="string">&quot;com.squareup.okhttp3:mockwebserver:$&#123;version[&quot;</span>okhttpSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;glide&quot;</span>                       : <span class="string">&quot;com.github.bumptech.glide:glide:$&#123;version[&quot;</span>glideSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="comment">// （annotationProcessor）</span></span><br><span class="line">            <span class="string">&quot;glide-compiler&quot;</span>              : <span class="string">&quot;com.github.bumptech.glide:compiler:$&#123;version[&quot;</span>glideSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;glide-loader-okhttp3&quot;</span>        : <span class="string">&quot;com.github.bumptech.glide:okhttp3-integration:$&#123;version[&quot;</span>glideSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line"></span><br><span class="line">            <span class="comment">// view</span></span><br><span class="line">            <span class="string">&quot;butterknife&quot;</span>                 : <span class="string">&quot;com.jakewharton:butterknife:$&#123;version[&quot;</span>butterknifeSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;butterknife-compiler&quot;</span>        : <span class="string">&quot;com.jakewharton:butterknife-compiler:$&#123;version[&quot;</span>butterknifeSdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;brvah&quot;</span>                       : <span class="string">&quot;com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.49-androidx&quot;</span>,</span><br><span class="line">            <span class="string">&quot;psid&quot;</span>                        : <span class="string">&quot;com.oushangfeng:PinnedSectionItemDecoration:1.3.2-androidx&quot;</span>,</span><br><span class="line">            <span class="string">&quot;material-dialogs&quot;</span>            : <span class="string">&quot;com.afollestad.material-dialogs:core:2.8.1&quot;</span>,</span><br><span class="line">            <span class="string">&quot;material-input&quot;</span>              : <span class="string">&quot;com.afollestad.material-dialogs:input:2.8.1&quot;</span>,</span><br><span class="line">            <span class="string">&quot;material-files&quot;</span>              : <span class="string">&quot;com.afollestad.material-dialogs:files:2.8.1&quot;</span>,</span><br><span class="line">            <span class="string">&quot;material-color&quot;</span>              : <span class="string">&quot;com.afollestad.material-dialogs:color:2.8.1&quot;</span>,</span><br><span class="line">            <span class="string">&quot;material-datetime&quot;</span>           : <span class="string">&quot;com.afollestad.material-dialogs:datetime:2.8.1&quot;</span>,</span><br><span class="line">            <span class="string">&quot;pickerview&quot;</span>                  : <span class="string">&quot;com.contrarywind:Android-PickerView:4.1.8&quot;</span>,</span><br><span class="line">            <span class="string">&quot;photoview&quot;</span>                   : <span class="string">&quot;com.github.chrisbanes.photoview:library:2.0.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;lottie&quot;</span>                      : <span class="string">&quot;com.airbnb.android:lottie:3.0.1&quot;</span>,</span><br><span class="line">            <span class="string">&quot;badge-view&quot;</span>                  : <span class="string">&quot;q.rorbin:badgeview:1.1.3&quot;</span>,</span><br><span class="line"></span><br><span class="line">            <span class="comment">// rx2</span></span><br><span class="line">            <span class="string">&quot;rxandroid2&quot;</span>                  : <span class="string">&quot;io.reactivex.rxjava2:rxandroid:2.1.1&quot;</span>,</span><br><span class="line">            <span class="string">&quot;rxjava2&quot;</span>                     : <span class="string">&quot;io.reactivex.rxjava2:rxjava:2.2.16&quot;</span>,</span><br><span class="line">            <span class="comment">// https://github.com/VictorAlbertos/RxCache</span></span><br><span class="line">            <span class="string">&quot;rxcache2&quot;</span>                    : <span class="string">&quot;com.github.VictorAlbertos.RxCache:runtime:1.8.3-2.x&quot;</span>,</span><br><span class="line">            <span class="comment">// https://github.com/tbruyelle/RxPermissions</span></span><br><span class="line">            <span class="string">&quot;rxpermissions2&quot;</span>              : <span class="string">&quot;com.github.tbruyelle:rxpermissions:0.10.2&quot;</span>,</span><br><span class="line"></span><br><span class="line">            <span class="comment">// tools（implementation）</span></span><br><span class="line">            <span class="string">&quot;dagger2&quot;</span>                     : <span class="string">&quot;com.google.dagger:dagger:$&#123;version[&quot;</span>dagger2SdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;dagger2-android&quot;</span>             : <span class="string">&quot;com.google.dagger:dagger-android:$&#123;version[&quot;</span>dagger2SdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;dagger2-android-support&quot;</span>     : <span class="string">&quot;com.google.dagger:dagger-android-support:$&#123;version[&quot;</span>dagger2SdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;eventbus&quot;</span>                    : <span class="string">&quot;org.greenrobot:eventbus:3.1.1&quot;</span>,</span><br><span class="line">            <span class="string">&quot;gson&quot;</span>                        : <span class="string">&quot;com.google.code.gson:gson:2.8.5&quot;</span>,</span><br><span class="line">            <span class="comment">// https://projectlombok.org/setup/android</span></span><br><span class="line">            <span class="string">&quot;lombok&quot;</span>                      : <span class="string">&quot;org.projectlombok:lombok:1.18.8&quot;</span>,</span><br><span class="line">            <span class="string">&quot;multidex&quot;</span>                    : <span class="string">&quot;com.android.support:multidex:1.0.3&quot;</span>,</span><br><span class="line">            <span class="string">&quot;arouter-api&quot;</span>                 : <span class="string">&quot;com.alibaba:arouter-api:1.4.1&quot;</span>,</span><br><span class="line">            <span class="string">&quot;arouter-compiler&quot;</span>            : <span class="string">&quot;com.alibaba:arouter-compiler:1.2.2&quot;</span>,</span><br><span class="line">            <span class="comment">//（annotationProcessor）</span></span><br><span class="line">            <span class="string">&quot;dagger2-compiler&quot;</span>            : <span class="string">&quot;com.google.dagger:dagger-compiler:$&#123;version[&quot;</span>dagger2SdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;dagger2-android-processor&quot;</span>   : <span class="string">&quot;com.google.dagger:dagger-android-processor:$&#123;version[&quot;</span>dagger2SdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line"></span><br><span class="line">            <span class="comment">// test</span></span><br><span class="line">            <span class="string">&quot;junit&quot;</span>                       : <span class="string">&quot;junit:junit:4.12&quot;</span>,</span><br><span class="line">            <span class="string">&quot;androidJUnitRunner&quot;</span>          : <span class="string">&quot;androidx.test.runner.AndroidJUnitRunner&quot;</span>,</span><br><span class="line">            <span class="string">&quot;runner&quot;</span>                      : <span class="string">&quot;androidx.test:runner:1.1.1&quot;</span>,</span><br><span class="line">            <span class="string">&quot;espresso-core&quot;</span>               : <span class="string">&quot;androidx.test.espresso:espresso-core:3.2.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;espresso-contrib&quot;</span>            : <span class="string">&quot;androidx.test.espresso:espresso-contrib:3.2.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;espresso-intents&quot;</span>            : <span class="string">&quot;androidx.test.espresso:espresso-intents:3.3.0&quot;</span>,</span><br><span class="line">            <span class="string">&quot;canary-debug&quot;</span>                : <span class="string">&quot;com.squareup.leakcanary:leakcanary-android:$&#123;version[&quot;</span>canarySdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;canary-release&quot;</span>              : <span class="string">&quot;com.squareup.leakcanary:leakcanary-android-no-op:$&#123;version[&quot;</span>canarySdkVersion<span class="string">&quot;]&#125;&quot;</span>,</span><br><span class="line">            <span class="string">&quot;umeng-analytics&quot;</span>             : <span class="string">&quot;com.umeng.analytics:analytics:6.0.1&quot;</span>,</span><br><span class="line"></span><br><span class="line">            <span class="comment">// util</span></span><br><span class="line">            <span class="comment">// https://github.com/Blankj/AndroidUtilCode/blob/master/utilcode/README-CN.md</span></span><br><span class="line">            <span class="string">&quot;utilcode&quot;</span>                    : <span class="string">&quot;com.blankj:utilcode:1.23.7&quot;</span>,</span><br><span class="line"></span><br><span class="line">            <span class="comment">// help</span></span><br><span class="line">            <span class="string">&quot;logger&quot;</span>                      : <span class="string">&quot;com.orhanobut:logger:2.2.0&quot;</span>,</span><br><span class="line">            <span class="comment">// https://www.pgyer.com/doc/view/new_sdk_android_guide</span></span><br><span class="line">            <span class="string">&quot;pgy&quot;</span>                         : <span class="string">&quot;com.pgyersdk:sdk:3.0.3&quot;</span>,</span><br><span class="line">            <span class="comment">// SDK 包</span></span><br><span class="line">            <span class="comment">// https://bugly.qq.com/docs/release-notes/release-android-bugly/?v=20180709165613</span></span><br><span class="line">            <span class="comment">// https://jcenter.bintray.com/com/tencent/bugly/crashreport/</span></span><br><span class="line">            <span class="string">&quot;crashreport&quot;</span>                 : <span class="string">&quot;com.tencent.bugly:crashreport:3.1.0&quot;</span>,</span><br><span class="line">            <span class="comment">// 升级 SDK 包</span></span><br><span class="line">            <span class="comment">// https://bugly.qq.com/docs/release-notes/release-android-bugly/?v=20180709165613</span></span><br><span class="line">            <span class="comment">// https://jcenter.bintray.com/com/tencent/bugly/crashreport_upgrade/</span></span><br><span class="line">            <span class="string">&quot;bugly-crash-upgrade&quot;</span>         : <span class="string">&quot;com.tencent.bugly:crashreport_upgrade:1.4.2&quot;</span>,</span><br><span class="line">            <span class="comment">// NDK 动态库</span></span><br><span class="line">            <span class="comment">// https://bugly.qq.com/docs/release-notes/release-android-ndk/?v=20180709165613</span></span><br><span class="line">            <span class="comment">// https://jcenter.bintray.com/com/tencent/bugly/nativecrashreport/</span></span><br><span class="line">            <span class="string">&quot;bugly-ndk&quot;</span>                   : <span class="string">&quot;com.tencent.bugly:nativecrashreport:3.7.1&quot;</span>,</span><br><span class="line"></span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>在顶级的 <code>build.gradle</code> 文件底部，表明添加对 <code>xxx.gradle</code> 的使用<figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apply <span class="attr">from:</span> <span class="string">&quot;xxx.gradle&quot;</span></span><br></pre></td></tr></table></figure></li><li>在 module 级别的 <code>build.gradle</code> 文件中，修改哪些固定写死的依赖版本<figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 之前固定的版本</span></span><br><span class="line">minSdkVersion <span class="number">19</span></span><br><span class="line"><span class="comment">// 修改通过 xxx.gradle 中定义的版本</span></span><br><span class="line">minSdkVersion rootProject.ext.android[<span class="string">&quot;minSdkVersion&quot;</span>]</span><br></pre></td></tr></table></figure></li></ol><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXIuYW5kcm9pZC5nb29nbGUuY24vc3R1ZGlvL3JlbGVhc2VzL2dyYWRsZS1wbHVnaW4/aGw9emhfY24=">Android Gradle 插件版本说明<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXIuYW5kcm9pZC5nb29nbGUuY24vc3R1ZGlvL2J1aWxkP2hsPXpoX2Nu">配置构建<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXIuYW5kcm9pZC5nb29nbGUuY24vc3R1ZGlvL2J1aWxkL2J1aWxkLXZhcmlhbnRzP2hsPXpoX2NuI3Byb2R1Y3QtZmxhdm9ycw==">配置构建变体<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;在上一篇 Gradle 的文章中，已经对 Gradle 有了一定的认识，Gradle 在 Android 有着广泛的应用，用作 Android 包依赖管理，应用构建，测试，等一些列自动化，我们本篇就来了解下在 Android 领域 Gradle 的使用。其实 Android 项目结构和之前在第一篇 Gradle 项目结构基本相同，只是在 module 级别多了的 &lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cDovL3Byb2d1YXJkLXJ1bGVzLnBybw==&quot;&gt;proguard-rules.pro&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;。对于不管是 Android 项目或是 Spring 系列项目的子 module 都会有 build.gradle 文件&lt;/p&gt;</summary>
    
    
    
    <category term="Gradle" scheme="https://incoder.org/categories/Gradle/"/>
    
    
    <category term="Gradle" scheme="https://incoder.org/tags/Gradle/"/>
    
  </entry>
  
  <entry>
    <title>Gradle（一）基础</title>
    <link href="https://incoder.org/2020/12/10/gradle1/"/>
    <id>https://incoder.org/2020/12/10/gradle1/</id>
    <published>2020-12-10T10:11:46.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>GitHub 上 Gralde 是这样描述，“Adaptable, fast automation for all”（让一切都能<code>快速</code>的<code>自动化</code>）<br>Gradle是一个构建工具，专注于构建自动化和对多语言开发的支持。对于在任何平台上的构建，测试，发布和部署，Gralde 提供了一种灵活的模型，可以支持从编译和打包代码到发布网站的整个生命周期。Gralde 旨在支持跨多种语言和平台的构建自动化，包括 Java，Scala，Android，Kotlin，C/C++ 和 Groovy，并于开发工具和包括 Eclipse，IntelliJ 和 Jenkins 的持续集成服务器紧密集成</p><span id="more"></span><ul><li>Gradle official：<span class="exturl" data-url="aHR0cHM6Ly9ncmFkbGUub3Jn">https://gradle.org<i class="fa fa-external-link-alt"></i></span></li><li>Gradle docs：<span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmdyYWRsZS5vcmc=">https://docs.gradle.org<i class="fa fa-external-link-alt"></i></span></li><li>Gradle plugins：<span class="exturl" data-url="aHR0cHM6Ly9wbHVnaW5zLmdyYWRsZS5vcmc=">https://plugins.gradle.org<i class="fa fa-external-link-alt"></i></span></li></ul><h2 id="Gradle-特点"><a class="header-anchor" href="#Gradle-特点"></a>Gradle 特点</h2><ol><li>Gradle 基于 JVM 的构建工具</li><li>兼容支持 Maven，Ant 等</li><li>支持基于 Groovy 的构建脚本</li><li>编译构建执行效率更高</li><li>支持多种语言等</li><li>易于迁移</li></ol><h2 id="Gradle-安装配置"><a class="header-anchor" href="#Gradle-安装配置"></a>Gradle 安装配置</h2><ul><li>Gradle 官方：<span class="exturl" data-url="aHR0cHM6Ly9zZXJ2aWNlcy5ncmFkbGUub3JnL2Rpc3RyaWJ1dGlvbnMv">https://services.gradle.org/distributions/<i class="fa fa-external-link-alt"></i></span></li><li>Tencent 镜像：<span class="exturl" data-url="aHR0cHM6Ly9taXJyb3JzLmNsb3VkLnRlbmNlbnQuY29tL2dyYWRsZS8=">https://mirrors.cloud.tencent.com/gradle/<i class="fa fa-external-link-alt"></i></span></li></ul><blockquote><p>Tencent Gradle 镜像同步有一定的延迟，需要注意</p></blockquote><h3 id="下载"><a class="header-anchor" href="#下载"></a>下载</h3><p>下载需要的版本即可，推荐最新版，这里以最新稳定版 6.7.1 为例，每个正式版本包含如下文件，我们选择 <code>xxx-bin.zip</code>（二进制版，只包含了二进制文件（可执行文件），没有文档和源代码） 或 <code>xxx-all.zip</code>（完整版，包含了各种二进制文件，源代码文件，和离线的文档）的文件即可，进行手动安装</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">gradle-6.7.1</span><br><span class="line">    ├── gradle-6.7.1-wrapper.jar.sha256     <span class="comment"># wrapper.jar hash 校验文件 </span></span><br><span class="line">    ├── gradle-6.7.1-docs.zip               <span class="comment"># gradle 文档压缩文件</span></span><br><span class="line">    ├── gradle-6.7.1-docs.zip.sha256        <span class="comment"># gradle 文档 hash 校验文件</span></span><br><span class="line">    ├── gradle-6.7.1-src.zip                <span class="comment"># gradle 源码版，只包含了 Gradle 源代码，不能用来编译你的工程</span></span><br><span class="line">    ├── gradle-6.7.1-src.zip.sha256         <span class="comment"># gradle 源码版 hash 校验文件</span></span><br><span class="line">    ├── gradle-6.7.1-bin.zip                <span class="comment"># gradle 核心压缩文件</span></span><br><span class="line">    ├── gradle-6.7.1-bin.zip.sha256         <span class="comment"># gradle 核心 hash 校验文件</span></span><br><span class="line">    ├── gradle-6.7.1-all.zip                <span class="comment"># gradle 全部资源压缩文件</span></span><br><span class="line">    └── gradle-6.7.1-all.zip.sha256         <span class="comment"># gradle 全部资源 hash 校验文件</span></span><br></pre></td></tr></table></figure><p>当然如果你使用的 macOS 系统，且也已经安装了 <code>homebrew</code> 包管理工具，那么同样你也可以使用 brew 命令来安装 Gradle，那么你将不需要再去手动配置 Gradle 的环境，它的安装默认路径在 <code>/usr/local/bin/gradle</code>，安装完成后你就可以使用 gradle 的相关命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># gradle 安装</span></span><br><span class="line">brew install gradle</span><br><span class="line"><span class="comment"># gradle 升级</span></span><br><span class="line">brew upgrade gradle</span><br><span class="line"><span class="comment"># 检查是否安装成功</span></span><br><span class="line">gradle -v</span><br></pre></td></tr></table></figure><h3 id="配置"><a class="header-anchor" href="#配置"></a>配置</h3><p>手动下载解压的文件进行安装，则需要配置 Gradle 的环境，这样方便我们在任何地方都可以调用 Gradle 的命令，对于 macOS 上手动安装配置 Gradle 环境的操作，可以参考 <a href="https://incoder.org/2018/11/10/mac-init/#Gradle%E9%85%8D%E7%BD%AE">MacBook Pro 初始化</a> 这篇文章 Gradle 配置</p><p>对于 Windows 系统，按照如下步骤进行添加环境变量，我这里 Windows 上为了和项目中 Gradle 版本有所区分，配置的是 6.7 版本<br><img src="https://res.cloudinary.com/incoder/image/upload/v1608634993/blog/gradle-home.png" alt=""><br><img src="https://res.cloudinary.com/incoder/image/upload/v1608634993/blog/gradle-path.png" alt=""><br>配置完成后，老规矩我们需要验证下我们的配置是否生效，在命令行中输入 <code>gradle  -v</code> 命令，查看有 Gradle 相关的版本信息提示，我们的配置就已成功<br><img src="https://res.cloudinary.com/incoder/image/upload/v1608634993/blog/gradlew-or-gradle.png" alt=""></p><h4 id="GRADLE-HOME"><a class="header-anchor" href="#GRADLE-HOME"></a>GRADLE_HOME</h4><p>GRADLE_HOME 这个环境变量，它主要是我们手动配置指定 GRADLE 使用的命令环境</p><h4 id="GRADLE-USER-HOME"><a class="header-anchor" href="#GRADLE-USER-HOME"></a>GRADLE_USER_HOME</h4><p>GRADLE_USER_HOME 指配置 Gradle 的安装下载的路径。默认 <code>/Users/&lt;PC NAME&gt;/.gradle</code> 路径，如果你在系统环境中设置了 GRADLE_USER_HOME 的环境变量，那么下载的路径就变成了你自定义设置的路径</p><h2 id="Gradle-基础"><a class="header-anchor" href="#Gradle-基础"></a>Gradle 基础</h2><p>刚刚在上面我们配置时，使用了 <code>gradlew</code> 命令，那这个又是啥呢，这里简单解释下，gradlew 是 gradle wrapper 的简写，对于 Gradle 构建的项目，用于解决 Gradle 安装，部署以及统一项目的 Gradle 的构建版本等一系列问题。</p><p>Gradle 有两个基本的概念：project 和 task，Gradle 里面的所有东西基于这两个概念</p><ul><li>project：通常指一个项目</li><li>task：指构建过程中的任务</li></ul><p>一次构建可以有 1 到 n 个 project，而每个 project 有 1 到 n 个 task</p><h2 id="Gradle-项目"><a class="header-anchor" href="#Gradle-项目"></a>Gradle 项目</h2><p>Android 项目工程一开始就默认使用 Gradle 来构建，在 Android 领域里使用花样也是比较多，更好体现了 Gradle 的灵活性，对于后端 Spring 系列项目，现在也是越来越多的开始使用 Gradle 来构建了，在 <span class="exturl" data-url="aHR0cHM6Ly9zcHJpbmcuaW8vYmxvZy8yMDIwLzA2LzA4L21pZ3JhdGluZy1zcHJpbmctYm9vdC1zLWJ1aWxkLXRvLWdyYWRsZQ==">Spring Boot 2.3.0.M1<i class="fa fa-external-link-alt"></i></span> 版本官方已开始在生产环境开始使用 Gradle 代替 Maven 进行构建，测试，发布项目。这从侧面也印证了 Gradle 对于复杂庞大的系统更加友好和<strong>高效</strong>。</p><p>对于使用 Gradle 构建的 Android 项目也好，Java 项目也好，还是 SpringBoot 项目也罢，它们都有共同的特点。在结构上有下面的相同点</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">project</span><br><span class="line">    ├── ……</span><br><span class="line">    ├── .gradle/                            <span class="comment"># 项目使用 gradle 编译生成的临时文件存放位置</span></span><br><span class="line">    ├── gradle/wrapper</span><br><span class="line">    │    │── gradle-wrapper.jar             <span class="comment"># gradlew 核心执行文件</span></span><br><span class="line">    │    └── gradle-wrapper.properties      <span class="comment"># gradle 运行环境配置文件</span></span><br><span class="line">    ├── build.gradle                        <span class="comment"># 项目依赖配置，脚本配置文件</span></span><br><span class="line">    ├── gradlew                             <span class="comment"># Linux or macOS 下可执行脚本</span></span><br><span class="line">    ├── gradlew.bat                         <span class="comment"># Windows 下可执行脚本</span></span><br><span class="line">    ├── settings.gradle                     <span class="comment"># 配置构建应用时应将哪些模块包含在内</span></span><br><span class="line">    └── ……</span><br></pre></td></tr></table></figure><h3 id="gradle-wrapper-properties"><a class="header-anchor" href="#gradle-wrapper-properties"></a>gradle-wrapper.properties</h3><ul><li>gradle-wrapper.jar 文件是项目中执行 gradlew 相关命令的具体实现，感兴趣的可以查看其中的具体源码实现</li><li>gradle-wrapper.properties 是 Gradle 项目版本管理的核心<ul><li>distributionBase=GRADLE_USER_HOME：指定了 wrapper 保存下载的 Gradle 的<code>主路径</code></li><li>distributionPath=wrapper/dists：指定了 wrapper 保存下载的 Gradle 的<code>子路径</code></li><li>zipStoreBase=GRADLE_USER_HOME：指定了 wrapper 保存下载 gradle-6.7.1-bin.zip 文件的<code>主路径</code></li><li>zipStorePath=wrapper/dists：指定了 gradle-6.7.1-bin.zip 文件的<code>子路径</code></li><li>distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-bin.zip：gradle 文件下载的源地址</li></ul></li></ul><div class="note info"><p>distributionBase 和 zipStoreBase 有两种取值</p><ul><li>GRADLE_USER_HOME：默认使用方式，表示用户目录，默认路径 <code>/Users/&lt;PC NAME&gt;/.gradle</code></li><li>PROJECT：表示工程的当前目录，不常用</li></ul></div><p>对应 Gradle 的下载及解压目录这里还需要注意下</p><div class="note danger"><p>Gradle 的存放地址，比如：~/.gradle/wrapper/dists/gradle-6.7.1-bin/<code>bwlcbys1h7rz3272sye1xwiv6</code> 这里一个看起来无规则的文件夹，我们的 gradle 下载及解压必须放在这个文件夹内，而这个看似无规则的文件夹，实质是根据 distributionUrl 路径字符串计算 md5 值得来的</p></div><h3 id="build-gradle-及-settings-gradle"><a class="header-anchor" href="#build-gradle-及-settings-gradle"></a>build.gradle 及 settings.gradle</h3><p>对于<code>build.gradle</code> 及 <code>settings.gradle</code> 文件在 Android 应用和 SpringBoot 应用是不一样，因此关于他两介绍请移步 <a href="https://incoder.org/2020/12/15/gradle2">Gradle（二）Android</a>，<a href="">Gradle（三）SpringBoot</a> 文章进行查看</p><h2 id="Gradle-依赖"><a class="header-anchor" href="#Gradle-依赖"></a>Gradle 依赖</h2><p>用于声明依赖关系的配置</p><table><thead><tr><th style="text-align:center">配置名称</th><th style="text-align:center">角色</th><th style="text-align:center">是否可消费</th><th style="text-align:center">是否可分解</th><th style="text-align:center">描述</th></tr></thead><tbody><tr><td style="text-align:center"><code>api</code></td><td style="text-align:center">声明API依赖项</td><td style="text-align:center">N</td><td style="text-align:center">N</td><td style="text-align:center">在这里，您可以声明依赖关系，这些依赖关系会在编译时和运行时以可传递方式导出到使用者</td></tr><tr><td style="text-align:center"><code>implementation</code></td><td style="text-align:center">声明实现依赖性</td><td style="text-align:center">N</td><td style="text-align:center">N</td><td style="text-align:center">在这里，您可以声明纯属内部的依赖关系，而不是要向使用方公开（在运行时仍向使用方公开）</td></tr><tr><td style="text-align:center"><code>compileOnly</code></td><td style="text-align:center">声明仅编译依赖项</td><td style="text-align:center">N</td><td style="text-align:center">N</td><td style="text-align:center">在这里可以声明在编译时需要的依赖项，而在运行时则不需要。这通常包括在运行时找到时会被阴影化的依赖项</td></tr><tr><td style="text-align:center">compileOnlyApi</td><td style="text-align:center">声明仅编译API依赖项</td><td style="text-align:center">N</td><td style="text-align:center">N</td><td style="text-align:center">在这里，您可以声明模块和使用者在编译时需要的依赖项，而在运行时则不需要。这通常包括在运行时找到时会被阴影化的依赖项</td></tr><tr><td style="text-align:center"><code>runtimeOnly</code></td><td style="text-align:center">声明运行时依赖项</td><td style="text-align:center">N</td><td style="text-align:center">N</td><td style="text-align:center">在这里可以声明仅在运行时才需要的依赖关系，而在编译时则不需要</td></tr><tr><td style="text-align:center">testImplementation</td><td style="text-align:center">测试依赖</td><td style="text-align:center">N</td><td style="text-align:center">N</td><td style="text-align:center">在这里声明用于编译测试的依赖项</td></tr><tr><td style="text-align:center">testCompileOnly</td><td style="text-align:center">声明测试仅编译依赖项</td><td style="text-align:center">N</td><td style="text-align:center">N</td><td style="text-align:center">在这里声明仅在测试编译时需要的依赖项，而不应泄漏到运行时。这通常包括在运行时找到时会被阴影化的依赖项</td></tr><tr><td style="text-align:center">testRuntimeOnly</td><td style="text-align:center">声明测试运行时依赖项</td><td style="text-align:center">N</td><td style="text-align:center">N</td><td style="text-align:center">在这里可以声明仅在测试运行时才需要的依赖项，而在测试编译时则不需要</td></tr></tbody></table><p>核心需要掌握的是 <code>api</code>，<code>implementation</code>，<code>compileOnly</code>，<code>runtimeOnly</code> 这4种依赖方式</p><blockquote><p>对于你可能看到依赖方式，compile（api），provided（compileOnly），apk（runtimeOnly） 这些方式是比较旧的依赖方式，在 gradle plugin 3.0 开始已废弃，请使用新的依赖方式</p></blockquote><h3 id="本地依赖"><a class="header-anchor" href="#本地依赖"></a>本地依赖</h3><h4 id="本地依赖-module-lib"><a class="header-anchor" href="#本地依赖-module-lib"></a>本地依赖 module lib</h4><p>通过这种方式依赖的弊端是每次都需要构建 module，但 module 比较多时构建非常耗时，建议控制 module 的依赖数量，避免构建耗时</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// module 需要在项目根目录下的 settings.gradle 中通过 include 引入</span></span><br><span class="line">implementation project(<span class="string">&#x27;:libname&#x27;</span>)  </span><br></pre></td></tr></table></figure><h4 id="本地二进制-lib-依赖"><a class="header-anchor" href="#本地二进制-lib-依赖"></a>本地二进制 lib 依赖</h4><p>本地的 jar 或者 aar 需要放在 module 的 libs 文件夹下，通过这种方式依赖</p><h5 id="依赖-jar"><a class="header-anchor" href="#依赖-jar"></a>依赖 jar</h5><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 方式一：可以一次性依赖 libs 下的所有 jar</span></span><br><span class="line">implementation fileTree(<span class="attr">dir:</span> <span class="string">&#x27;libs&#x27;</span>, <span class="attr">include:</span> [<span class="string">&#x27;*.jar&#x27;</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式二：可以指定依赖一个或几个 jar</span></span><br><span class="line">implementation files(<span class="string">&#x27;libs/xxxx1.jar&#x27;</span>, <span class="string">&#x27;libs/xxxx2.jar&#x27;</span>)</span><br></pre></td></tr></table></figure><h5 id="依赖-aar"><a class="header-anchor" href="#依赖-aar"></a>依赖 aar</h5><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在 module 的 build.gradle 中添加目录指定</span></span><br><span class="line">repositories &#123;</span><br><span class="line">    flatDir &#123;</span><br><span class="line">        dirs <span class="string">&#x27;libs&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在 dependencies 中加入对 aar 的引入</span></span><br><span class="line"><span class="comment">// 方式一：可以一次性依赖 libs 下所有的 aar</span></span><br><span class="line">implementation fileTree(<span class="attr">dir:</span> <span class="string">&#x27;libs&#x27;</span>, <span class="attr">include:</span> [<span class="string">&#x27;*.aar&#x27;</span>])</span><br><span class="line"><span class="comment">// 方式二：可以指定依赖某一个aar</span></span><br><span class="line">implementation files(<span class="attr">name:</span> <span class="string">&#x27;aar-lib-name&#x27;</span>, <span class="attr">ext:</span> <span class="string">&#x27;aar&#x27;</span>)</span><br></pre></td></tr></table></figure><h3 id="远程二进制-lib-依赖"><a class="header-anchor" href="#远程二进制-lib-依赖"></a>远程二进制 lib 依赖</h3><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 依赖明确的版本，标明 group、name 和 version</span></span><br><span class="line">implementation <span class="attr">group:</span> <span class="string">&#x27;org.mybatis.spring.boot&#x27;</span>, <span class="attr">name:</span> <span class="string">&#x27;mybatis-spring-boot-starter&#x27;</span>, <span class="attr">version:</span> <span class="string">&#x27;2.1.1&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 常用的简写方式引用</span></span><br><span class="line">implementation <span class="string">&#x27;org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="Gradle-命令"><a class="header-anchor" href="#Gradle-命令"></a>Gradle 命令</h2><p>在项目中推荐使用 gradlew 命令来进行执行，这样本质是使用项目所依赖的 Gradle 版本进行执行。当然如果你本地配置了 Gradle 的环境变量，你可以将 <code>gradlew</code> 命令更改成 <code>gradle</code> 来执行</p><ul><li>gradlew clean: 清除 build 文件夹</li><li>gradlew check: 执行 lint 检查</li><li>gradlew assemble: 编译并打包你的代码，但并不运行单元测试</li><li>gradlew build: 编译和测试你的代码，并生成一个包含所有类与资源的文件</li><li>gradlew dependencies: 查看所有依赖库<ul><li>gradlew dependencies -configuration runtime: 查看运行时依赖库</li></ul></li></ul><div class="note warning"><p>注意：</p><ul><li>Windows：在项目根目录，使用的是 <code>gradlew</code></li><li>Linux or macOS：在项目的根目录，使用的是 <code>./gradlew</code></li></ul></div><h2 id="总结"><a class="header-anchor" href="#总结"></a>总结</h2><ol><li>对于 Gradle 我们不需要配置 GRADLE_USER_HOME 的环境，原因是项目中已对使用 Gradle 的版本做出了统一，我们仅需要根据自身的网络需要（如果从默认地址下载很慢，则需要配置好项目依赖镜像源）做出合适的配置。而如果你需要在任何地方使用 <code>gradle</code> 相关的命令，则配置 GRADLE_HOME 即可</li><li>依赖方式，我们选择 implementation 方式，这样可屏蔽掉不同应用之间因为引用了同一 lib 而不同版本造成的麻烦问题等</li></ol><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ul><li><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM1NTM1MjkvYXJ0aWNsZS9kZXRhaWxzLzU1MDExNjAy">gradle-wrapper.properties中各属性的含义<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmdyYWRsZS5vcmcvY3VycmVudC91c2VyZ3VpZGUvY29yZV9kZXBlbmRlbmN5X21hbmFnZW1lbnQuaHRtbA==">Dependency management in Gradle<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmdyYWRsZS5vcmcvY3VycmVudC91c2VyZ3VpZGUvamF2YV9saWJyYXJ5X3BsdWdpbi5odG1sI3NlYzpqYXZhX2xpYnJhcnlfY29uZmlndXJhdGlvbnNfZ3JhcGg=">The Java Library Plugin<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmdyYWRsZS5vcmcvY3VycmVudC91c2VyZ3VpZGUvZGlzdHJpYnV0aW9uX3BsdWdpbi5odG1s">The Distribution Plugin<i class="fa fa-external-link-alt"></i></span></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;GitHub 上 Gralde 是这样描述，“Adaptable, fast automation for all”（让一切都能&lt;code&gt;快速&lt;/code&gt;的&lt;code&gt;自动化&lt;/code&gt;）&lt;br&gt;
Gradle是一个构建工具，专注于构建自动化和对多语言开发的支持。对于在任何平台上的构建，测试，发布和部署，Gralde 提供了一种灵活的模型，可以支持从编译和打包代码到发布网站的整个生命周期。Gralde 旨在支持跨多种语言和平台的构建自动化，包括 Java，Scala，Android，Kotlin，C/C++ 和 Groovy，并于开发工具和包括 Eclipse，IntelliJ 和 Jenkins 的持续集成服务器紧密集成&lt;/p&gt;</summary>
    
    
    
    <category term="Gradle" scheme="https://incoder.org/categories/Gradle/"/>
    
    
    <category term="Gradle" scheme="https://incoder.org/tags/Gradle/"/>
    
  </entry>
  
  <entry>
    <title>迷宫如意琳琅图籍</title>
    <link href="https://incoder.org/2020/12/10/play-maze/"/>
    <id>https://incoder.org/2020/12/10/play-maze/</id>
    <published>2020-12-10T09:44:46.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://res.cloudinary.com/incoder/image/upload/v1611486360/blog/G2.png" alt=""></p><span id="more"></span><p>故宫博物院出品，奥秘之家设计制作（曾推出线下实景地铁逃脱游戏，2018联合《唐人街探案 2》推出《侦探笔记》的互动解密游戏，以及配合电影开发Crimaster），到手快一年了还没有完全解锁线上的关卡，倒不是玩不下去，而是懒，刷 B 站多香，动啥脑子，哈哈哈。言归正传，本篇记录自己解锁线上关卡的步骤，持续更新</p><h2 id="进度"><a class="header-anchor" href="#进度"></a>进度</h2><ol start="0"><li>初 ------------------ 100%<ul><li>✅ 梦入紫禁</li><li>✅ 太和异象</li><li>✅ 十八棵槐</li></ul></li><li>壹 ------------------ 23%<ul><li>万寿盛筵</li><li>殿前观礼</li><li>礼乐度量</li><li>一等画师</li><li>腰牌买卖</li><li>慈宁画样</li></ul></li><li>贰 ------------------ 0%<ul><li>宫女禾心</li><li>嘉祉初遇</li><li>淑芳听戏</li><li>戏里玄机</li><li>上元之约</li></ul></li><li>叁 ------------------ 0%<ul><li>结伴寻宝</li><li>皇十五子</li><li>档房探秘</li><li>一路狂奔</li><li>逢凶化吉</li><li>五行八卦</li><li>夜探御园</li></ul></li><li>肆 ------------------ 0%<ul><li>琳琅宝藏</li><li>花叶之谜</li><li>宫中怪人</li><li>图籍作者</li><li>祸不单行</li><li>五蕴皆空</li></ul></li><li>伍 ------------------ 0%<ul><li>将破未破</li><li>孤注一掷</li></ul></li><li>隐 ------------------ 0%<ul><li>多年以后</li></ul></li><li>众 ------------------ 0%<ul><li>众筹专属</li></ul></li></ol><h2 id="初"><a class="header-anchor" href="#初"></a>初</h2><h3 id="梦入紫禁"><a class="header-anchor" href="#梦入紫禁"></a>梦入紫禁</h3><h3 id="太和异象"><a class="header-anchor" href="#太和异象"></a>太和异象</h3><h3 id="十八棵槐"><a class="header-anchor" href="#十八棵槐"></a>十八棵槐</h3><h2 id="壹"><a class="header-anchor" href="#壹"></a>壹</h2><h3 id="万寿盛筵"><a class="header-anchor" href="#万寿盛筵"></a>万寿盛筵</h3><h3 id="殿前观礼"><a class="header-anchor" href="#殿前观礼"></a>殿前观礼</h3><h3 id="礼乐度量"><a class="header-anchor" href="#礼乐度量"></a>礼乐度量</h3><h3 id="一等画师"><a class="header-anchor" href="#一等画师"></a>一等画师</h3><h3 id="腰牌买卖"><a class="header-anchor" href="#腰牌买卖"></a>腰牌买卖</h3><h3 id="慈宁画样"><a class="header-anchor" href="#慈宁画样"></a>慈宁画样</h3><h2 id="贰"><a class="header-anchor" href="#贰"></a>贰</h2><h3 id="宫女禾心"><a class="header-anchor" href="#宫女禾心"></a>宫女禾心</h3><h3 id="嘉祉初遇"><a class="header-anchor" href="#嘉祉初遇"></a>嘉祉初遇</h3><h3 id="淑芳听戏"><a class="header-anchor" href="#淑芳听戏"></a>淑芳听戏</h3><h3 id="戏里玄机"><a class="header-anchor" href="#戏里玄机"></a>戏里玄机</h3><h3 id="上元之约"><a class="header-anchor" href="#上元之约"></a>上元之约</h3><h2 id="叁"><a class="header-anchor" href="#叁"></a>叁</h2><h3 id="结伴寻宝"><a class="header-anchor" href="#结伴寻宝"></a>结伴寻宝</h3><h3 id="皇十五子"><a class="header-anchor" href="#皇十五子"></a>皇十五子</h3><h3 id="档房探秘"><a class="header-anchor" href="#档房探秘"></a>档房探秘</h3><h3 id="一路狂奔"><a class="header-anchor" href="#一路狂奔"></a>一路狂奔</h3><h3 id="逢凶化吉"><a class="header-anchor" href="#逢凶化吉"></a>逢凶化吉</h3><h3 id="五行八卦"><a class="header-anchor" href="#五行八卦"></a>五行八卦</h3><h3 id="夜探御园"><a class="header-anchor" href="#夜探御园"></a>夜探御园</h3><h2 id="肆"><a class="header-anchor" href="#肆"></a>肆</h2><h3 id="琳琅宝藏"><a class="header-anchor" href="#琳琅宝藏"></a>琳琅宝藏</h3><h3 id="花叶之谜"><a class="header-anchor" href="#花叶之谜"></a>花叶之谜</h3><h3 id="宫中怪人"><a class="header-anchor" href="#宫中怪人"></a>宫中怪人</h3><h3 id="图籍作者"><a class="header-anchor" href="#图籍作者"></a>图籍作者</h3><h3 id="祸不单行"><a class="header-anchor" href="#祸不单行"></a>祸不单行</h3><h3 id="五蕴皆空"><a class="header-anchor" href="#五蕴皆空"></a>五蕴皆空</h3><h2 id="伍"><a class="header-anchor" href="#伍"></a>伍</h2><h3 id="将破未破"><a class="header-anchor" href="#将破未破"></a>将破未破</h3><h3 id="孤注一掷"><a class="header-anchor" href="#孤注一掷"></a>孤注一掷</h3><h2 id="隐"><a class="header-anchor" href="#隐"></a>隐</h2><h3 id="多年以后"><a class="header-anchor" href="#多年以后"></a>多年以后</h3><h2 id="众"><a class="header-anchor" href="#众"></a>众</h2><h3 id="众筹专属"><a class="header-anchor" href="#众筹专属"></a>众筹专属</h3><h2 id="附录"><a class="header-anchor" href="#附录"></a>附录</h2><ol><li>奥秘之家官网：<span class="exturl" data-url="aHR0cDovL3d3dy5pdGFvdHVvLmNvbS8=">http://www.itaotuo.com/<i class="fa fa-external-link-alt"></i></span></li><li>《唐人街探案 2》之《侦探笔记》：<span class="exturl" data-url="aHR0cHM6Ly93d3cuemhpaHUuY29tL3F1ZXN0aW9uLzI2NzM0MTQ2NA==">https://www.zhihu.com/question/267341464<i class="fa fa-external-link-alt"></i></span></li><li>《唐人街探案 3》之《侦探笔记》：<span class="exturl" data-url="aHR0cHM6Ly96aG9uZ2Nob3UubW9kaWFuLmNvbS9pdGVtLzkwMzE1Lmh0bWw=">https://zhongchou.modian.com/item/90315.html<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/incoder/image/upload/v1611486360/blog/G2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Play" scheme="https://incoder.org/categories/Play/"/>
    
    
    <category term="迷宫" scheme="https://incoder.org/tags/%E8%BF%B7%E5%AE%AB/"/>
    
  </entry>
  
  <entry>
    <title>搞定 m.2 接口 SSD</title>
    <link href="https://incoder.org/2020/12/10/play-ssd/"/>
    <id>https://incoder.org/2020/12/10/play-ssd/</id>
    <published>2020-12-10T09:44:46.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p><img src="https://res.cloudinary.com/incoder/image/upload/v1611489458/blog/dell-ssd.jpg" alt=""></p><span id="more"></span><p>公司原装配置电脑磁盘性能太差，实在是不能满足我的日常骚操作，然后就自己买了一个 m.2 接口的 SSD 硬盘，毕竟电脑之前已经有系统了，而且也已经安装好了开发环境，如果现在在新的 SSD 上直接安装新的系统，那么需要将之前的开发环境再折腾一遍，实在是伤不起。那么有没有别的方式。你别说哦，还真的有，方法是用一些工具对现有系统进行 clone 到新的 SSD 磁盘上。这都很好办，比如：<span class="exturl" data-url="aHR0cHM6Ly93d3cuZGlza3Rvb2wuY24=">傲梅分区助手<i class="fa fa-external-link-alt"></i></span>，<span class="exturl" data-url="aHR0cHM6Ly93d3cuZGlza2dlbml1cy5jbg==">DiskGenius<i class="fa fa-external-link-alt"></i></span> 都有系统迁移功能，可参考文章下方的参考地址，内有视频教程</p><blockquote><p>注意：要设置好设置默认系统启动引导为新的磁盘</p></blockquote><h2 id="问题"><a class="header-anchor" href="#问题"></a>问题</h2><p>一开始，我觉得这么简单的操作能有什么问题，迁移完系统，并设置好系统引导，然而我发现并不能按照预期使用 SSD 来启动，试了好几遍，调整了 BIOS 的启动选项，依旧不能解决。后来我将原系统的磁盘拆下来，只留 SSD 磁盘，开机就能按照预期启动了，正常后在把原系统磁盘再装回去，同时记得检查下系统引导，确保还依旧是使用 SSD 系统盘</p><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuZGlza3Rvb2wuY24vamlhb2NoZW5nL21pZ3JhdGUtc3lzdGVtLmh0bWw=">SSD系统迁移工具：轻松迁移系统到SSD<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuZGlza3Rvb2wuY24vamlhb2NoZW5nLW5ldy8yMDE5L2hvdy10by1taWdyYXRlLXN5c3RlbS5odG1sdg==">使用分区助手快速将Windows系统迁移到新磁盘<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/incoder/image/upload/v1611489458/blog/dell-ssd.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Play" scheme="https://incoder.org/categories/Play/"/>
    
    
    <category term="SSD" scheme="https://incoder.org/tags/SSD/"/>
    
  </entry>
  
  <entry>
    <title>该死的 Base64，我惹你了？</title>
    <link href="https://incoder.org/2020/11/27/damn-base64/"/>
    <id>https://incoder.org/2020/11/27/damn-base64/</id>
    <published>2020-11-27T14:43:46.000Z</published>
    <updated>2024-08-11T12:14:45.511Z</updated>
    
    <content type="html"><![CDATA[<p>在上一个项目中，由于客观原因，双方进行数据交换，用到对媒体文件（图片）进行了 Base64 编码处理，将编码后的数据存入了数据库，使用方再从数据库中取出数据进行解码恢复成图片，在实际处理中，这是<strong>最不推荐</strong>的做法。正确有效的做法是将资源文件存入到 OSS 系统中，数据库中记录文件的地址即可。但由于项目历史原因，无法使用 OSS 来处理，虽然说技术本质不难，编码存入，解码查看而已。但由于对方没有告知具体的编码方式，询问了好几次才最终给到对应的编码方式，浪费了大量的时间去沟通和试错，得不偿失</p><span id="more"></span><p>对于 Base64 ，开发者或多或少都有听过，严格意义上讲 Base64 不是加密方式，它只是一种编码方式，本篇文章就来详细的聊一聊 Base64 这个熟悉又陌生的朋友</p><h2 id="什么是-Base64"><a class="header-anchor" href="#什么是-Base64"></a>什么是 Base64</h2><h2 id="Base64-的原理"><a class="header-anchor" href="#Base64-的原理"></a>Base64 的原理</h2><h2 id="常见的-Base64"><a class="header-anchor" href="#常见的-Base64"></a>常见的 Base64</h2><h2 id="解决实际问题"><a class="header-anchor" href="#解决实际问题"></a>解决实际问题</h2><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC9kOTg0NGJmN2RiNmQ=">密码学 | 庐山真面！你认为 Base64 是加密算法吗？<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vY2hlbnhpYm9iby9wLzE0MTA5MDY2Lmh0bWw=">什么是Base64？<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vbGliaW4tMS9wLzYxNjU0ODUuaHRtbA==">Base64编码原理分析<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC9lOTUyNzhlZDk4YjQ=">Base64编码<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC9iNmFmMzAxNzdjMGE=">Base64算法不一致可能会导致的坑<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;在上一个项目中，由于客观原因，双方进行数据交换，用到对媒体文件（图片）进行了 Base64 编码处理，将编码后的数据存入了数据库，使用方再从数据库中取出数据进行解码恢复成图片，在实际处理中，这是&lt;strong&gt;最不推荐&lt;/strong&gt;的做法。正确有效的做法是将资源文件存入到 OSS 系统中，数据库中记录文件的地址即可。但由于项目历史原因，无法使用 OSS 来处理，虽然说技术本质不难，编码存入，解码查看而已。但由于对方没有告知具体的编码方式，询问了好几次才最终给到对应的编码方式，浪费了大量的时间去沟通和试错，得不偿失&lt;/p&gt;</summary>
    
    
    
    <category term="Java" scheme="https://incoder.org/categories/Java/"/>
    
    
    <category term="Exp" scheme="https://incoder.org/tags/Exp/"/>
    
  </entry>
  
  <entry>
    <title>开源协议，该如何选择</title>
    <link href="https://incoder.org/2020/11/25/open-license/"/>
    <id>https://incoder.org/2020/11/25/open-license/</id>
    <published>2020-11-25T22:30:10.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>现如今软件行业的发展完全离不开开源社区，很多优秀的软件应用、技术都能看到开源软件的影子，我们都是站在巨人的肩膀上。对于软件行业的从业者，能为开源项目贡献自己的力量，或是将自己对某一个细分领域所做的研究实践开源出来，这是一件非常值得骄傲的事情。而要参与一个大型的开源项目，你除了需要该项目涉及的核心技术知识外，还需要了解一定的开源项目运转方式等，对于如何参与开源项目，这里暂不做过多的介绍，有兴趣的可以移步 Gitee 发起的《<span class="exturl" data-url="aHR0cHM6Ly9naXRlZS5jb20vZ2l0ZWUtY29tbXVuaXR5L29wZW5zb3VyY2UtZ3VpZGU=">开源指北<i class="fa fa-external-link-alt"></i></span>》项目，该项目中详细介绍了如何参与开源项目。本篇文章也不啰嗦这一点，仅仅围绕开源协议，我们应该清楚的常识和注意的点</p><span id="more"></span><p>在软件开发中<strong>通常</strong>有两种情况我们需要考虑软件的开源协议或者使用协议</p><ol><li>我们需要使用到业界的一些优秀的软件包来提高我们开发的效率，避免了重复早轮子，所选择的这些软件包我们不但考虑功能的同时，也要考虑软件包的授权协议</li><li>我们需要将自己的经验或者软件产品需要开源时，为了保护自己的权益，我们也需要选择一个合适的开源协议</li></ol><p>我这里还是引用比较经典 <span class="exturl" data-url="aHR0cDovL3d3dy5ydWFueWlmZW5nLmNvbS9ibG9nLzIwMTEvMDUvaG93X3RvX2Nob29zZV9mcmVlX3NvZnR3YXJlX2xpY2Vuc2VzLmh0bWw=">阮一峰<i class="fa fa-external-link-alt"></i></span> 文章中所绘制关于如何选择开源协议的图<br><img src="http://www.ruanyifeng.com/blogimg/asset/201105/free_software_licenses.png" alt=""><br>图中已经很清楚的表示了如何去选择 <span class="exturl" data-url="aHR0cHM6Ly9iYWlrZS5iYWlkdS5jb20vaXRlbS9MR1BM">LGPL<i class="fa fa-external-link-alt"></i></span>, <span class="exturl" data-url="aHR0cHM6Ly93d3cubW96aWxsYS5vcmcvZW4tVVMvTVBMLw==">Mozilla<i class="fa fa-external-link-alt"></i></span>, <a href="">GPL</a>, <a href="">BSD</a>, <a href="">MIT</a>, <a href="">Apache</a> 这 6 种协议</p><h2 id="常用协议"><a class="header-anchor" href="#常用协议"></a>常用协议</h2><p>这里我们通过表格的形式介绍下这 6 种协议，当然除了表中列出的这些协议之外还有很多协议，我们就挨个来简单对他们有一个了解和认识</p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1609258585/blog/license.png" alt=""></p><h2 id="其他协议"><a class="header-anchor" href="#其他协议"></a>其他协议</h2><h3 id="BY-NC-SA"><a class="header-anchor" href="#BY-NC-SA"></a>BY-NC-SA</h3><p>你会发现每篇文章下面都有申明版权，这里使用的是 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><i class="fab fa-creative-commons"></i> BY-NC-SA</a> 4.0 的协议，他们的含义如下</p><ul><li><i class="fab fa-creative-commons"></i>：知识共享（CreativeCommons）</li><li>NC：非商业性使用（NonCommercial），您不得将本作品用于商业目的</li><li>SA：相同方式共享（ShareAlike），如果您再混合、转换或者基于本作品进行创作，您必须基于与原先许可协议相同的许可协议 分发您贡献的作品</li></ul><p>使用此协议，您可以自由地</p><ol><li>共享 — 在任何媒介以任何形式复制、发行本作品</li><li>演绎 — 修改、转换或以本作品为基础进行创作</li></ol><blockquote><p>只要你遵守许可协议条款，许可人就无法收回你的这些权利</p></blockquote><h2 id="选择"><a class="header-anchor" href="#选择"></a>选择</h2><p>上面说了那么多，有些协议并没有展开来说可能并不适用你当前的所需要选择的协议，那么你可以根据实际情况去筛选，可通过 <span class="exturl" data-url="aHR0cHM6Ly9jaG9vc2VhbGljZW5zZS5jb20=">https://choosealicense.com<i class="fa fa-external-link-alt"></i></span>,  <span class="exturl" data-url="aHR0cHM6Ly9rYWl5dWFuc2hlLmNuL2xpY2Vuc2UtdG9vbA==">https://kaiyuanshe.cn/license-tool<i class="fa fa-external-link-alt"></i></span> 这两个网站按照步骤去选择，最终确定协议即可</p><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ol><li><span class="exturl" data-url="aHR0cHM6Ly9rYWl5dWFuc2hlLmNuL2xpY2Vuc2UtdG9vbC8=">开源许可证选择器<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9jaG9vc2VhbGljZW5zZS5jb20v">Choose an open source license<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9zZWdtZW50ZmF1bHQuY29tL2EvMTE5MDAwMDAyMjk3MzEwNQ==">博云违反 Apache 2.0 开源协议被要求整改，开源协议到底应该如何遵守？<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cDovL2MuYmlhbmNoZW5nLm5ldC92aWV3LzI5NDcuaHRtbA==">开源协议是什么？有哪些？如何选择？<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vV2F5b3UvcC9ob3dfdG9fY2hvb3NlX2FfbGljZW5zZS5odG1s">如何为你的代码选择一个开源协议<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9naXRlZS5jb20vZ2l0ZWUtY29tbXVuaXR5L29wZW5zb3VyY2UtZ3VpZGU=">开源指北 Gitee<i class="fa fa-external-link-alt"></i></span></li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;现如今软件行业的发展完全离不开开源社区，很多优秀的软件应用、技术都能看到开源软件的影子，我们都是站在巨人的肩膀上。对于软件行业的从业者，能为开源项目贡献自己的力量，或是将自己对某一个细分领域所做的研究实践开源出来，这是一件非常值得骄傲的事情。而要参与一个大型的开源项目，你除了需要该项目涉及的核心技术知识外，还需要了解一定的开源项目运转方式等，对于如何参与开源项目，这里暂不做过多的介绍，有兴趣的可以移步 Gitee 发起的《&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9naXRlZS5jb20vZ2l0ZWUtY29tbXVuaXR5L29wZW5zb3VyY2UtZ3VpZGU=&quot;&gt;开源指北&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;》项目，该项目中详细介绍了如何参与开源项目。本篇文章也不啰嗦这一点，仅仅围绕开源协议，我们应该清楚的常识和注意的点&lt;/p&gt;</summary>
    
    
    
    <category term="Open Source" scheme="https://incoder.org/categories/Open-Source/"/>
    
    
    <category term="License" scheme="https://incoder.org/tags/License/"/>
    
  </entry>
  
  <entry>
    <title>Hexo Blog 高级指南</title>
    <link href="https://incoder.org/2020/11/20/hexo-advanced/"/>
    <id>https://incoder.org/2020/11/20/hexo-advanced/</id>
    <published>2020-11-20T18:18:18.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p><span class="exturl" data-url="aHR0cHM6Ly90aGVtZS1uZXh0LmpzLm9yZw==">NexT<i class="fa fa-external-link-alt"></i></span> 是 <span class="exturl" data-url="aHR0cHM6Ly9oZXhvLmlvL3poLWNuL2luZGV4Lmh0bWw=">Hexo<i class="fa fa-external-link-alt"></i></span> 非常受欢迎的博客主题，方便简洁，但却不简单的功能，你可以在提供的强大功能基础上进行扩展或者自定义，来满足你的个性化需求。本篇文章主要是对应 NexT 提供的一些高级功能的使用，作为一个持续更新的文章吧，记录自己 SX 操作，当然也是我平时在使用 NexT 时遇到的一些问题的记录。好了废话不多说了，我们直接进入正题</p><span id="more"></span><h2 id="博客升级"><a class="header-anchor" href="#博客升级"></a>博客升级</h2><p>每次对于 NexT 的升级或多或少都会遇到些问题，这次也不例外，首先是对于不同版本的管理，由于一些历史原因有三个组织仓库分别对应不同的版本域，升级是需要注意下，本次我是从 7.8.0 版本升级到 8.0.x 版本，以后跟随官方，每月更新 NexT</p><h3 id="npm-改成-yarn（可选）"><a class="header-anchor" href="#npm-改成-yarn（可选）"></a>npm 改成 yarn（可选）</h3><blockquote><p>yarn 的安装，请自行根据你的系统去安装，我这里 macOS 使用命令即可 <code>brew install yarn</code></p></blockquote><ol><li>删除根目录的 <code>package-lock.json</code>，并在根目录执行 <code>hexo clean &amp;&amp; rm -rf node_modules/</code></li><li>根目录下执行 <code>yarn install</code></li></ol><h3 id="更改-NexT-主题仓库"><a class="header-anchor" href="#更改-NexT-主题仓库"></a>更改 NexT 主题仓库</h3><ol><li>删除当前主题，在根目录下执行 <code>rm -rf themes/</code></li><li>安装新的主题，<ul><li>方案一：在根目录下执行命令添加主题</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/next-theme/hexo-theme-next themes/next</span><br></pre></td></tr></table></figure><ul><li>方案二：通过 yarn 来管理主题</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn add hexo-theme-next</span><br></pre></td></tr></table></figure></li></ol><h3 id="修改配置"><a class="header-anchor" href="#修改配置"></a>修改配置</h3><p>之前为了使主题更新不受影响，在项目的根目录 <code>source/_data</code> 路径下有一个 <code>next.yml</code> 文件来进行对 NexT 的自定义设置，那么在 8.0 版本开始，在项目根目录 <code>_config.&#123;theme&#125;.yml</code> 文件来代替之前在 <code>source/_data</code> 路径下的 <code>next.yml</code> 文件</p><h3 id="问题"><a class="header-anchor" href="#问题"></a>问题</h3><h4 id="node-trace-warnings"><a class="header-anchor" href="#node-trace-warnings"></a>node --trace-warnings</h4><h5 id="异常信息"><a class="header-anchor" href="#异常信息"></a>异常信息</h5><p>由于 NexT 需要 Hexo5.0+，在升级到 NexT 8.0.x 版本警告信息如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">(node:17336) Warning: Accessing non-existent property &#x27;lineno&#x27; of module exports inside circular dependency</span><br><span class="line">(Use `node --trace-warnings ...` to show where the warning was created)</span><br><span class="line">(node:17336) Warning: Accessing non-existent property &#x27;column&#x27; of module exports inside circular dependency</span><br><span class="line">(node:17336) Warning: Accessing non-existent property &#x27;filename&#x27; of module exports inside circular dependency</span><br><span class="line">(node:17336) Warning: Accessing non-existent property &#x27;lineno&#x27; of module exports inside circular dependency</span><br><span class="line">(node:17336) Warning: Accessing non-existent property &#x27;column&#x27; of module exports inside circular dependency</span><br><span class="line">(node:17336) Warning: Accessing non-existent property &#x27;filename&#x27; of module exports inside circular dependency</span><br></pre></td></tr></table></figure><h5 id="原因分析"><a class="header-anchor" href="#原因分析"></a>原因分析</h5><p>是由于 Hexo 项目嵌套依赖了 <code>stylus</code> 包，而对于 <code>0.54.5</code> 版本在 Node 14+ 版本存在问题，比如这里 <code>hexo-renderer-stylus</code> 包的依赖</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">……</span><br><span class="line">│ │</span><br><span class="line">├─┬ hexo-renderer-stylus@2.0.1</span><br><span class="line">│ ├─┬ nib@1.1.2</span><br><span class="line">│ │ └─┬ stylus@0.54.5</span><br><span class="line">│ │   │ </span><br><span class="line">……</span><br></pre></td></tr></table></figure><h5 id="解决方法"><a class="header-anchor" href="#解决方法"></a>解决方法</h5><ol><li>可以降低你的 Node 版本到 12 版本 <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">brew uninstall node</span><br><span class="line">brew install node@12</span><br><span class="line">brew <span class="built_in">link</span> --overwrite --force node@12</span><br></pre></td></tr></table></figure></li><li>推荐，更改替换 stylus 版本，在你的 <code>package.json</code> 文件中，添加如下配置 <figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;resolutions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;stylus&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^0.54.8&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li></ol><h5 id="总结"><a class="header-anchor" href="#总结"></a>总结</h5><p>🌀 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3N0eWx1cy9zdHlsdXMvcHVsbC8yNTM4">pull-2538<i class="fa fa-external-link-alt"></i></span><br>🐞 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3N0eWx1cy9zdHlsdXMvaXNzdWVzLzI1MzQ=">issues-2534<i class="fa fa-external-link-alt"></i></span><br>🛠 <span class="exturl" data-url="aHR0cHM6Ly93d3cuaGFveWl6ZWJvLmNvbS9wb3N0cy83MTA5ODRkMC8=">solve-Accessing non-existent property<i class="fa fa-external-link-alt"></i></span></p><h4 id="hexo-douban"><a class="header-anchor" href="#hexo-douban"></a>hexo-douban</h4><p>之前用了 hexo-douban 插件来进行对 books 和 movies 进行管理，在升级到 Node 14+版本上，当前的插件也停止工作了，异常日志如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">INFO  0 books have been loaded in 1130 ms, because you are offline or your network is bad</span><br><span class="line">INFO  0 movies have been loaded in 1329 ms, because you are offline or your network is bad</span><br><span class="line">INFO  0 games have been loaded in 1004 ms, because you are offline or your network is bad</span><br></pre></td></tr></table></figure><p>作者在🐞 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL215dGhzbWFuL2hleG8tZG91YmFuL2lzc3Vlcy83Nw==">issues-2534<i class="fa fa-external-link-alt"></i></span> 做了回复，暂时没有替代方案，故在新版中，我停止了 <code>hexo-douban</code> 插件的使用，挖个坑，等自己有时间或者有人修复此问题再或者有替代插件后再重新启用</p><ol><li>移除 hexo-douban 插件 <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># yarn</span></span><br><span class="line">yarn remove hexo-douban</span><br><span class="line"><span class="comment"># npm</span></span><br><span class="line">npm uninstall hexo-douban</span><br></pre></td></tr></table></figure></li><li>移除 <code>_config.yml</code> 配置文件中，douban 的相关的配置</li><li>移除 <code>_config.&#123;theme&#125;.yml</code> 配置文件中，<code>menu</code> 配置的站点入口设置</li></ol><h2 id="博客评论"><a class="header-anchor" href="#博客评论"></a>博客评论</h2><p>在 NexT version 8.1.0 版本，由于安全问题，<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL25leHQtdGhlbWUvaGV4by10aGVtZS1uZXh0L2lzc3Vlcy80I3Y4LjEuMCUyMCVFNyVBNyVCQiVFOSU5OSVBNCUyMFZhbGluZQ==">Valine被移除<i class="fa fa-external-link-alt"></i></span>，<s>暂时我并未迁移 Valine 的评论</s></p><p>博客已启用 <span class="exturl" data-url="aHR0cHM6Ly91dHRlcmFuYy5lcw==">utterances<i class="fa fa-external-link-alt"></i></span> 评论支持，配置也比较简单，如下</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">utterances:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">repo:</span> <span class="string">BladeCode/BladeCode.github.io</span> <span class="comment"># Github repository name</span></span><br><span class="line">  <span class="comment"># Available values: pathname | url | title | og:title</span></span><br><span class="line">  <span class="attr">issue_term:</span> <span class="string">title</span></span><br><span class="line">  <span class="comment"># Available values: github-light | github-dark | preferred-color-scheme | github-dark-orange | icy-dark | dark-blue | photon-dark | boxy-light</span></span><br><span class="line">  <span class="attr">theme:</span> <span class="string">github-light</span></span><br></pre></td></tr></table></figure><h2 id="文章加密"><a class="header-anchor" href="#文章加密"></a>文章加密</h2><p>对于 NexT 的文章，有时需要进行加密访问，那么该怎么去处理呢，其实这一点在 NexT 的生态里已经有了这样的插件，我们可以直接在使用在我们的 NexT 里面，只需要简单的配置</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># npm </span></span><br><span class="line">npm i hexo-blog-encrypt --save</span><br><span class="line"><span class="comment"># yarn</span></span><br><span class="line">yarn add hexo-blog-encrypt</span><br></pre></td></tr></table></figure><p>加密优先级：文章信息头 &gt; 按标签加密</p><h3 id="站点配置（-config-yml）"><a class="header-anchor" href="#站点配置（-config-yml）"></a>站点配置（_config.yml）</h3><h4 id="简单配置"><a class="header-anchor" href="#简单配置"></a>简单配置</h4><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 文章密码访问 hexo-blog-encrypt</span></span><br><span class="line"><span class="attr">encrypt:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><h4 id="更多配置"><a class="header-anchor" href="#更多配置"></a>更多配置</h4><p>可以对一类（标签）来进行统一的密码设置</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 文章密码访问 hexo-blog-encrypt</span></span><br><span class="line"><span class="attr">encrypt:</span> </span><br><span class="line">  <span class="attr">abstract:</span> <span class="string">有东西被加密了,</span> <span class="string">请输入密码查看.</span></span><br><span class="line">  <span class="attr">message:</span> <span class="string">您好,</span> <span class="string">这里需要密码.</span></span><br><span class="line">  <span class="attr">tags:</span></span><br><span class="line">  <span class="bullet">-</span> &#123;<span class="attr">name:</span> <span class="string">tagNameA</span>, <span class="attr">password:</span> <span class="string">密码A</span>&#125;</span><br><span class="line">  <span class="bullet">-</span> &#123;<span class="attr">name:</span> <span class="string">tagNameB</span>, <span class="attr">password:</span> <span class="string">密码B</span>&#125;</span><br><span class="line">  <span class="attr">template:</span> <span class="string">&lt;div</span> <span class="string">id=&quot;hexo-blog-encrypt&quot;</span> <span class="string">data-wpm=&quot;&#123;&#123;hbeWrongPassMessage&#125;&#125;&quot;</span> <span class="string">data-whm=&quot;&#123;&#123;hbeWrongHashMessage&#125;&#125;&quot;&gt;&lt;div</span> <span class="string">class=&quot;hbe-input-container&quot;&gt;&lt;input</span> <span class="string">type=&quot;password&quot;</span> <span class="string">id=&quot;hbePass&quot;</span> <span class="string">placeholder=&quot;&#123;&#123;hbeMessage&#125;&#125;&quot;</span> <span class="string">/&gt;&lt;label&gt;&#123;&#123;hbeMessage&#125;&#125;&lt;/label&gt;&lt;div</span> <span class="string">class=&quot;bottom-line&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;script</span> <span class="string">id=&quot;hbeData&quot;</span> <span class="string">type=&quot;hbeData&quot;</span> <span class="string">data-hmacdigest=&quot;&#123;&#123;hbeHmacDigest&#125;&#125;&quot;&gt;&#123;&#123;hbeEncryptedData&#125;&#125;&lt;/script&gt;&lt;/div&gt;</span></span><br><span class="line">  <span class="attr">wrong_pass_message:</span> <span class="string">抱歉,</span> <span class="string">这个密码看着不太对,</span> <span class="string">请再试试.</span></span><br><span class="line">  <span class="attr">wrong_hash_message:</span> <span class="string">抱歉,</span> <span class="string">这个文章不能被校验,</span> <span class="string">不过您还是能看看解密后的内容.</span></span><br></pre></td></tr></table></figure><h3 id="单文章配置"><a class="header-anchor" href="#单文章配置"></a>单文章配置</h3><p>在你需要加密的文章前面，根据需要添加对应的参数，这里仅是一个示例</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">title: Hello World</span><br><span class="line">tags:</span><br><span class="line"><span class="bullet">-</span> 加密文章tag</span><br><span class="line">date: 2020-11-20 18:18:18</span><br><span class="line">password: helloworld</span><br><span class="line">abstract: 该文章已加密, 请输入密码查看。</span><br><span class="line">message: 该文章已加密, 请输入密码查看。</span><br><span class="line">wrong<span class="emphasis">_pass_</span>message: 密码不正确，请重新输入！</span><br><span class="line"><span class="section">wrong<span class="emphasis">_hash_</span>message: 文章不能被校验, 不过您还是能看看解密后的内容！</span></span><br><span class="line"><span class="section">---</span></span><br></pre></td></tr></table></figure><p>各参数说明</p><ul><li>password：文章密码</li><li>abstract：文章摘要，会显示在博客的列表页</li><li>message：文章查看时，密码输入框上面的描述性文字</li><li>wrong_pass_message：校验失败提示</li><li>wrong_hash_message：hash 验证失败</li></ul><h2 id="多语言"><a class="header-anchor" href="#多语言"></a>多语言</h2><p>对于多语言，根据自身需要添加，默认，修改博客项目根目录 <code>_connfig.yml</code> 文件 <code>language</code> 属性即可</p><ol><li>对于单语言：language: xxx（具体语言可查看下方的官方说明）</li><li>对于多语言：<ul><li>语言添加   <figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">language:</span></span><br><span class="line">   <span class="bullet">-</span> <span class="string">zh-CN</span></span><br><span class="line">   <span class="bullet">-</span> <span class="string">en</span></span><br></pre></td></tr></table></figure></li><li>更改语言切换，<code>_config.&#123;theme&#125;.yml</code> 文件，<code>language_switcher</code>设置为 true</li></ul></li><li>字段定义，如果一些字段的翻译不是你想要的，你可以自行修改<ul><li>在根目录的 <code>source/_data</code> 文件夹下，创建 <code>languages.yml</code> 文件</li><li>在文件中，修改对应语言的字段   <figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">zh-CN:</span></span><br><span class="line">  <span class="comment"># items</span></span><br><span class="line">  <span class="attr">post:</span></span><br><span class="line">    <span class="attr">copyright:</span></span><br><span class="line">      <span class="comment"># the translation you perfer</span></span><br><span class="line">      <span class="attr">author:</span> <span class="string">本文博主</span></span><br><span class="line"><span class="attr">en:</span></span><br><span class="line">  <span class="attr">menu:</span></span><br><span class="line">    <span class="attr">schedule:</span> <span class="string">Calendar</span></span><br></pre></td></tr></table></figure></li></ul></li></ol><blockquote><p><span class="exturl" data-url="aHR0cHM6Ly90aGVtZS1uZXh0LmpzLm9yZy9waXNjZXMvZG9jcy90aGVtZS1zZXR0aW5ncy9pbnRlcm5hdGlvbmFsaXphdGlvbi5odG1sP2hpZ2hsaWdodD1sYW5ndWFnZQ==">多语言配置<i class="fa fa-external-link-alt"></i></span></p></blockquote><h2 id="GitHub-Action"><a class="header-anchor" href="#GitHub-Action"></a>GitHub Action</h2><h2 id="Hexo-PWA"><a class="header-anchor" href="#Hexo-PWA"></a>Hexo PWA</h2><blockquote><p>由于暂未支持 Hexo5.0+版本，先占坑</p></blockquote><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ul><li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL25leHQtdGhlbWUvaGV4by10aGVtZS1uZXh0L2lzc3Vlcy80">更新说明及常见问题<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly90b21teS5uZXQuY24vMjAyMC8wOC8wOC91cGdyYWRlLWhleG8tdG8tdjUtMC0wLw==">将 Hexo 升级到 v5.0.0<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly90b21teS5uZXQuY24vMjAyMC8wOC8wNi9kZXBsb3ktaGV4by13aXRoLWdpdGh1Yi1hY3Rpb25zLw==">用 GitHub Actions 来自动部署 Hexo<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9saW53aGl0ZWhhdC5naXRodWIuaW8vQmxvZy8yMDIwLzAyLzA5L0hleG8lRTUlOEQlOUElRTUlQUUlQTIlRTklODMlQTglRTclQkQlQjJQV0EuaHRtbA==">Hexo博客部署PWA<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9zaXRvaS5jbi9wb3N0cy80OTExNS5odG1s">博客完美支持 PWA<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmRlY2F5LmZ1bi8yMDE5LzA4LzE5L2VuaGFuY2UtaGV4by13aXRoLXB3YS1pbi10aHJlZS1zdGVwcy8=">三步，让 Hexo 轻松支持 PWA<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cucHdhYnVpbGRlci5jb20v">Pwabuilder<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMyNzY3MDQxL2FydGljbGUvZGV0YWlscy8xMDMyODUxNDc=">Hexo 相关问题和优化<i class="fa fa-external-link-alt"></i></span></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly90aGVtZS1uZXh0LmpzLm9yZw==&quot;&gt;NexT&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt; 是 &lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly9oZXhvLmlvL3poLWNuL2luZGV4Lmh0bWw=&quot;&gt;Hexo&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt; 非常受欢迎的博客主题，方便简洁，但却不简单的功能，你可以在提供的强大功能基础上进行扩展或者自定义，来满足你的个性化需求。本篇文章主要是对应 NexT 提供的一些高级功能的使用，作为一个持续更新的文章吧，记录自己 SX 操作，当然也是我平时在使用 NexT 时遇到的一些问题的记录。好了废话不多说了，我们直接进入正题&lt;/p&gt;</summary>
    
    
    
    <category term="Hexo" scheme="https://incoder.org/categories/Hexo/"/>
    
    
    <category term="Build" scheme="https://incoder.org/tags/Build/"/>
    
  </entry>
  
  <entry>
    <title>2020 年秋季面试经历</title>
    <link href="https://incoder.org/2020/11/15/interview-2020/"/>
    <id>https://incoder.org/2020/11/15/interview-2020/</id>
    <published>2020-11-15T08:10:17.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="抱歉, 这个密码看着不太对, 请再试试……" data-whm="OOPS, these decrypted content may changed, but you can still have a look.">  <script id="hbeData" type="hbeData" data-hmacdigest="7df5a630483ac756d9ef49dddde6909b3a7656915830d435fb5c23cae727eaa5">4af8291d7d4bfe809382abee478622f72a8728909570f59a242101bb5048f9adba8274839e73ed927bbce86c3054e1f4b944e19a2c39c92f8416236a0dd5b6b3485910f3c121578234b2b2cff7fbb867ae964c83ca6738cc4dc93af43c28d46be17f4a5192303287bcaa02fbd17bd754aa86083f70ed6907744b244bd8cbfeb39797366ba73312d81d495ceb69a82ad1a77f929c59b5117b70373837a069da68ce901569f1dfd6e5e7cd3270bac36c0c6bd1862aa08df7157fbddfe45e08ca0a16f00d12b559e839d2e122032d1a9bc313ef91b051e90b4c0bf402f0b4251fe48c91ae1d7f5257c872cd60b932c694384146eeab4030e7e2cc0767d9180cf8efd63aae3ccedae5b484f3cbbb39d8e99c6550e2046373bab8b40c757ec762cf7f9e9a1a90958bb9bb7aa44ea36ca5f7fdab3dba1a482347ec4e6cc8099e63f3c715c2b9cb115d0b214a7e6474c493e276d8ec7cff07258e7edb26e77d23eb3ede485e7eb004156fb487841c7dfba8d899c04cedc62dc61fbe24fd0f2419a595353ae6099ab1ea3c32f9f132d1eb0073bdc5d5860395ce7b1c7e47b96d9a48fe9b347ae61df591db55ade0e46217013ba89ad512fc60e71cb9123de03c634dc310b4a24c374f1e99615c9fe67c492f34342303f37e2514da5277f1ae770a7950f508e642a04a2e6f610530df79c8f1459065aae3ea90620ccec298da6e2e04214f289269a652ed13057349fdcecfd9305909d09829f33b6f42a07fad145bfef7d1172e973282b91bab0a1af2812fafdb5e8cbe887b7ea850c1eedc97b8839409e2ed97a458d05366c75da74cd301b508a1f9450174e62958dac63b33b20114184985f940a68d5155999a181b4570bc4f878dbe2b761937fd618ce1d18788a02d02a48e09523cb2b5a1a099e65fbca1ebcacc28e81b9f6d567ee1fc73a76884f4015043fb538d8c51be403d21f42ec01e45a989a6fa61b23e3e0f1c28e69d72b58a6f07c51e6006c0c9e6734c584053b9985d4ca051cab235d33b51656b12bf39a314f78d7a7eb6fa8107159fd2597c9db437c276402859a9551d090dde609d1a564f869597b8369b79bee65be3ef7ad45207943b07d5cc10b7817323a621719b5ddc7471281ddee3016c5f286e54d7397ff9f0c0a29e37faded050e5dcd8da1c660b5742d191c4ad86e9b4f9423a1ebd45fa813106cf7eca7c7da77632297cfffde7b870b14e7a4016d8150f6be155d06727e684b661be77a951d2992a59a9385b24602a37c281004305ed767f270f5920a8a417bb27d7b608b985d016063a8806d7c130a123106099d6be8c0ec8bfea909e7478a6e878d264d0e9cb0b492faf8c9f7f37517cd7a9f82f222384d76af96a2017a31d3039d3a2cd8dae96eb8a9d98014f6d907675f689ed804fdbeb1508464f1d5a61e13bb4597aa08483e126ffa2005a9c4bd29a7e850f44633aa74538b27f82364250f9c504dc47816143fd58dd1670cc060174a5182dd24f739e628ad1133b5c6b26a85b439366c65356c343ee6509f62492d24b7b0922204bb9fa507dd0dbb71f9d1fc3a2f702673e6d7c2fa34cd7cfd79a1f6d235eabb41eb495556b7da3df4ed6ebd66303f86eacd36f0efab115d4d7539df297a2c6096110111f0ae3e5a57902ec8251d3a4792d2071b1add88402a464384eccd79dbfa887f115cb4f23a0f4b2af9312bfafa6805b0ae7c1af363524bfd3531da30413c750139e4c5a090534f54e80b8747d1deeb7e461196c8f1043f055954d8d42805eff47109449707a027b7f6eb0237ac7581f6f3384af0fc32fd83ef088ed74651571f61591fa052fce42e07dbf1d01c86e95eccb5e9be441d80da80da13363285bec6b0cd2b639da0c59e8ecbaf9b203038fb48cd049fd1457a3861eeac74c26d2184667cf2ca32c410180994675183ae6d93763172ab080764f7bb17ec6ebdd6ac441dea025eb0b1f8d18ecf8acf6d1266b5ffbc555c61379f75e3f795614c595d02af944d3a7f9217463137c2dc6065dd1bef6d628a29c9feae5d2ba33f09299ed271c1725b71f4a79827cf2ae120fa6c51d35d4157e2872142c2f23d4a17321db05e3b3945cd5d9f960fa48e507d387236204f74afb46a357b6828a4f9bc6926c32fcc1b3a098019d3d38f6e47b7e6e53c45ca81e258f9bc0c95c19b439c79e9b8807495eaea9021f0ac6dfdc1ab9ca961de1360a5def3fa716426a4c29ad5c576054904de806ee1d70cdae4ef534ec7d409f3412dc97049574f828ec36581617cab474994447ed0133103465d4492e438c8560437f66bdd5d7bbde11fe6ec077cf2b21cbc1f4c6da498d9b12a4bb4f6c6695a12ceed5cb6ce212d4eb50d94f1d44cf2d8d998cfb6b45c6909eeacc1f9622a01105d8ac36c3809685e6beebea3e57afbc1ef7ce6819728849bf1eb6abd729139a3348002654b6351431418c53366dcf473788abea1d02e6edd27e5d3b1e6a17326aeffdc6cbe76f1fe7c4ab1aea3afd32792b460f016f5d506496b20ac2f7b58d90f7b96085d63052c5d8016d78f09cdc38a69f48896afa62bae8b8ac0a8b280f632c86c73cf8617b5cc09d927572e34640c084003d5ccee25ccb29a4214d5db7d8248a5513725e97b76768750aea4f22055660b88591423a7784589404a913b021b9f6872132e683dad5569eab1824a8f316aab70d8d91d3479ec08f7b273ff43ca8e2fa375f46c0fc573129785e8752cdad32aed7cb2040faace497bc1d7adfe41c9371b8711b1564a46cf51c4e107bc3c8e918bef8ba803741aaf3c141cdcd41a774169cdc4824c3279ca8167353edae53eb33d1752f0a253ac01efb3a875e70505de553a0d3bfdb303564f60c9831178eabacfe62abd65b2506f16ab28962ce73eaa8d800bccc0fd90cf948777532acd546656e17191c3388cf5d629628ab5e35580cf681d3ad06706512de71785010277a88e4ecbeeb898655821f96b2d4c820de67cd711355266c3fc512cf14e13e7b73e86ff2402302517c326cf8d1a6a2505965be4c136a1c4bbf079c301f2f9c90ad6dca90866fccad3185da4d4f408efdd7dc81319a8667adbb9e7c681456c9e5083eaf4b01ab9ec6e1340086b867a829a0572a6bac2f6979c473078609352610d102fed5495ff1310c5f0959bcffd35711112e35bb6d9296b27a4dce286bc69f83b5ee8205d029d7baed0987282ba9564d9f990a54b040b56f644410205b3d152250f8e1b93d59b452041cecfe5c4a4fb63e08e3fddb3bbf82e9549af268099111de394f7e39bef57fc194699bdd8bcf42e8ded3085b363372a2bf84f0f66119cae8bc5496ebc73f7d8f7653a3a7855617f788f7f12386e2a9c32e3178ad305a983c3f0419f17f22305b54b4b9671fbb46ef91ec805387c04a9b14a2fcf09fd5e9a992a7822499c51adbdacd565a464792e774878811ffee661038b1b398e48cc9acfee3d2abe2b2cae574cdd767f8ffae68ec1c963ef8fbb129c3191cee2232f92f9e653f783befa6314482a00271960a2557ea464349eace618b66e1291f1bfe24283c57025c6fc6606d7e8958cdc5507ada0f0cf57859f33aac29879132ddb3681321bcc000aca9b68c0b0e5fb1f8cde202bc421d7a6bd3ad0ea1943f1238dbbbe4d2ba6afdfebb2fac7185b5d577838f43b0b97a953d2b5fd52c2db2a2819d829ec230ca18d4825a3bcb055c7c3f9d74f02ce3fdbd1a2c9ee4fae712b6851532c9395e0034508c5212c1817d59bd420d8efbfe54a271243b863e535535576b53038737b153dfe96e29f1b2e975ceb3b15835d4e838386c7cd705362161031633ddf91399d6d41a359b0252ba3f94a3b10bd5363dc156c499a80e2f3a2570d014409370bdb111487617b1fa30dcab22b97074aa6a9c917f2c5641b6a970467b86095c60c5c42f1c89d838da4fea8d88543cac19942e07f15b2577e98625e01257ce2ba8a8ac433d3b4c3baec84b82dbfb7b2b5cb1fbb72336bdfbe3c107a2c1dc33ad33e9749c0bd0635e0cf315c53886202050e5288c169c695f9bf3ca9eafc77599202d38398ca6ae5165420fd02fdecd3c38c43d3f9996f2e2bd6391462aac6eb43adda46f2113f3fc86824952a1a5edc6c35cc2c183c01fa1e78856475f3657eb31d0e295a88921f34462ff430134b07afa98c411ca642eceea36271293318d0fee6894323467597a3b707c0bc84001084abec919b1521e2a0756f756b537966c3f72c9f2f2fb30970a1ea96d51dd1d3b3ba1a01c8d218c6eebae0c231221e16a702bf413da82dacc1d03b4e17928d47d93a7335afbe6b3c1d321b58e6acce63cc230cdb2b4913329e36e9151d1d60cc3720d97d562d29d1bff2bb5ca2b460c04c90447ff044d6ab83e722635144336229706786ff19346d13071ee698cbf3a819a4e63b0b4cbe98270d1e15d3ab02fb396a9fe743d872b50ee1f419aaf36f9b43cd6ba9d639f4bee946100229d72bbcd2f3f3c5e005284f03f9e4438395af4bd5d7b23e55a30fc66b7398c87aeeb4236bf1e7c414c3341919213309b792365c15871e38d10478a6a97b8f78e34dbdb338e42e03bcbff3dd61679090d59719ea81ccd5e6520330584f9374e8a2aac74001e4920136cd2e3d18d58e346e2b217ddb31ceff47dab5427f4745f132afcdcbbeba51c9618acf9c36f445bbd8aea47095c3b909b5ed67e276f42204f3201693ae16c9f187ff35de5a1450b958e749544d5d73f4e61289070bee8cdb5398a8d0c0ba624be47ab80c8ff115791eac4f7fec2ade8f9724ffa539587670ba136c4977f4fad70f05718b91b2f6e4cc334afc63bca973d97e21b967d4231ead19f5ea8f925d94381b57c7bec5cb3dfc2405bd7038f4124d7e7ebfb9f0aac1abb92664dc529ddf201b83138eaf270fc80b05a3b0e2fd43c7184253fa5282a6f447dacfd6ab08a1bc64bdeb4b7754f70a4c812e10b03d4fc9f6434f0479ec01bd187efd29f53d91898eb57095bf4e05736531d4758718c2fc77da7778f9414e4444f3bf7913ea4b35346ead90cfdbd695bbc1f63161fb64eb05e0d6f3fa87339b27e41747a9af108f07bc32496919833069944a998e6d3a7d963fffa46ec7facd32174842cca5badd6d9b95014234e30febe5b8fcc3e7dbbd9e51fd4be603fc92711bbf0af000f777181daa27a6be4071be2a1c854da51bebcb46c9db703a64854466fcf19f3ca2878a2ef15e636c7a032b04a45b114a6763c52a4e1c48963ceaeec83cf9d0d12164933bf4dbdaaef08b13829ddc64021caf2b00a9ff8da4f558d98ddfdb06cd129e3c0151d435147ff3e1b14726c51fb9d64f5681bf48823c864a578eb32a50a4a615f23130892b6f66fbdb6ed1d4d202d7a743ffed4770f825d9a19bbe3c65dcf37c1ec954434d3fe2c4016bf21572869b6fad713867a666cf662375c735ef909e59e0b3a51487ff87882a5df5f12032418400072d5ea213ede00af7368a41dda51fc5678e7cbe17580fda832fb53723a53b893def4e799ef8510fbf23d953fd46625ed041e36bf1b0d76fc394959425448861fef00a2ff847a1d125935a5148b517ba2088989c3860158ce457ccc122d358375e15c66888638ebc72a827de57bf7d77fb41977e44bc07160d813869c03dd803552df9b8d331bcb231b5593be9e1c7e2dd748f0ebdf984d9aff298777d761e2b94204132e2f011550deb9fb8d055fa10707a953fe9f42a3270907c1c4d2dcc1475534b67abfa1a81e8c5144015cf5805690ed851c3224a6324b27555b75c278121b3608ebe909b157281ab89073ec8fa07b046d4bb1ec906bb37599d653edb991dfdb968cc583ee9d07bc70fae6b044b27e1637e8d236d7a3702479ef700f0a6a4a1296102a49056782b632ab28278248bcf2ec372d03934f3f1d52b65f5fc8c33ecac97c1d9df752622fc513608401ff30ac1c5d0fdb9c15161e0642a95d22d9c5333fe3f9c72028e11404c545dcf11e610db64a6b093ccd632ff1269d07775e8bdeeed9d169dfd8b4f38f6d0d5a365433b47cec5ad56fc7900a0f2fd5997dc4fd9c2525082c49cafb26a710fbdb4226a76126b75a310197e859d3f06d28d7cb08fc0222053ef4eb6386f0b0c305edf5efa6ad28434931ba42daf55e093b2c5a4cbf074b17afba3ae8c6217149d124454b6052485241426f8f68378094418eaba1f4c1f1da9449d63002aa1207de4c607a7966978be86eb7ee3dd76e9c8457e8c38210aa7ac9ca279c0d92b9c9154c0ab493521bfc68d94748b6278792753fcfd47f029c6a0cb2ead8505d2722d04db0191bac90170c81a738cc77b769f84801ef946a5b6cedfed155844ffe5b375a521e9f78533755dbdc6b68063d4759f37431ad32576db6212f2a907197aabd8e42007067</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-default">      <input class="hbe hbe-input-field hbe-input-field-default" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-default" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-default">这里需要密码才能访问</span>      </label>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">这是一篇加密博文，请输入密码后查看</summary>
    
    
    
    <category term="Summary" scheme="https://incoder.org/categories/Summary/"/>
    
    
    <category term="Interview" scheme="https://incoder.org/tags/Interview/"/>
    
    <category term="Summary" scheme="https://incoder.org/tags/Summary/"/>
    
  </entry>
  
  <entry>
    <title>MacBook Pro 疑难杂症</title>
    <link href="https://incoder.org/2020/11/13/mac-question/"/>
    <id>https://incoder.org/2020/11/13/mac-question/</id>
    <published>2020-11-13T02:04:46.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>这是一篇记录使用macOS系统时遇到的一些疑难杂症</p><h2 id="macOS-Big-Sur"><a class="header-anchor" href="#macOS-Big-Sur"></a>macOS Big Sur</h2><p>在 2020.11.13 正式推送了 macOS Big Sur version 11.0.1 版本，这一个版本是改动比较大的版本，这里关于它的新特性就不做介绍了，有兴趣的请查看官方网站介绍 <span class="exturl" data-url="aHR0cHM6Ly93d3cuYXBwbGUuY29tLmNuL21hY29zL2JpZy1zdXI=">Big Sur<i class="fa fa-external-link-alt"></i></span></p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1605885064/blog/macOS_Big_Sur.png" alt=""></p><span id="more"></span><h3 id="Glance-失效"><a class="header-anchor" href="#Glance-失效"></a>Glance 失效</h3><p>Glance 是一个快速预览增强，可以对一些文件进行快速预览，大大提高我们的日常效率，但该应用在 Big Sur 版本中不兼容，由于作者已入职 Apple，且对项目做了归档，不在维护，因此该问题依旧没有解决，可以使用一个付费的应用<span class="exturl" data-url="aHR0cHM6Ly9hcHBzLmFwcGxlLmNvbS9jbi9hcHAvaXByZXZpZXctcG93ZXJmdWwtcXVpY2stbG9vay9pZDE1MTkyMTM1MDk/bD1lbiZtdD0xMg==">iPreView<i class="fa fa-external-link-alt"></i></span>来满足当前需要</p><blockquote><p><span class="exturl" data-url="aHR0cHM6Ly92MmV4LmNvbS90LzcyNTkwOQ==">Glance 在 Big Sur 系统中失效<i class="fa fa-external-link-alt"></i></span></p></blockquote><h2 id="AirPods-异常"><a class="header-anchor" href="#AirPods-异常"></a>AirPods 异常</h2><p>在 AirPods 使用过程中，发现有时候耳机并不能正常工作。通常情况下，我会断开与 macOS 的连接，重新连接，如果还是不能正常工作，在 macOS 的系统蓝牙设置里面，移除连接的耳机设备，将耳机放入 AirPods 盒子里面，先盖上盒子，然后再打开盒子，此时并按住 AirPods 盒子背后的按钮，直到前面呼吸灯变成白色，然后再 macOS 的蓝牙里面找到新的设备，并连接配对。同时也可参考官方指引步骤 <span class="exturl" data-url="aHR0cHM6Ly9zdXBwb3J0LmFwcGxlLmNvbS96aC1jbi9IVDIwNzAxMA==">连接并使用 AirPods 和 AirPods Pro<i class="fa fa-external-link-alt"></i></span></p><h3 id="单耳工作"><a class="header-anchor" href="#单耳工作"></a>单耳工作</h3><p>换一个连接设备，检查耳机是否正常，如果是正常，那说明耳机没有问题，问题就出在 macOS 声音管理上面，打开<code>系统设置</code> -&gt; <code>声音</code> -&gt; <code>输出模式</code> -&gt;<code>设置为居中的平衡模式（既双耳工作）</code></p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1605689283/blog/airpods-single.png" alt="airpods-settings"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;这是一篇记录使用macOS系统时遇到的一些疑难杂症&lt;/p&gt;
&lt;h2 id=&quot;macOS-Big-Sur&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;#macOS-Big-Sur&quot;&gt;&lt;/a&gt;macOS Big Sur&lt;/h2&gt;
&lt;p&gt;在 2020.11.13 正式推送了 macOS Big Sur version 11.0.1 版本，这一个版本是改动比较大的版本，这里关于它的新特性就不做介绍了，有兴趣的请查看官方网站介绍 &lt;span class=&quot;exturl&quot; data-url=&quot;aHR0cHM6Ly93d3cuYXBwbGUuY29tLmNuL21hY29zL2JpZy1zdXI=&quot;&gt;Big Sur&lt;i class=&quot;fa fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://res.cloudinary.com/incoder/image/upload/v1605885064/blog/macOS_Big_Sur.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="macOS" scheme="https://incoder.org/categories/macOS/"/>
    
    
    <category term="Exp" scheme="https://incoder.org/tags/Exp/"/>
    
    <category term="iTerm2" scheme="https://incoder.org/tags/iTerm2/"/>
    
  </entry>
  
  <entry>
    <title>微服务架构 - Alibaba 生态整合（一）</title>
    <link href="https://incoder.org/2020/11/11/microservices-alibaba1/"/>
    <id>https://incoder.org/2020/11/11/microservices-alibaba1/</id>
    <published>2020-11-11T07:10:00.000Z</published>
    <updated>2024-08-11T12:14:45.515Z</updated>
    
    <content type="html"><![CDATA[<p>曾几何时，市面上对于微服务，分两个派系，一个派系以阿里为主的 Dubbo 生态体系，还有一派以 Spring Cloud 生态为主的体系，这两个系列的讨论也一直没有停息过。但现在 Spring Cloud Alibaba 的出现，提供了一整套构建分布式应用开发的微服务组件，由于这些组件是构建在原生的 Spring Cloud 之上，因此其服务治理方面的能力可认为是 Spring Cloud Plus， 不仅完全覆盖 Spring Cloud 原生特性，而且提供更为稳定和成熟的实现。那么从本系列就开始跟着我一起用阿里系的应用搭建分布式微服务应用，满足企业级的应用需要，而不是停留在 Dome 级别的应用框架使用。废话不多说，我们一起开始这一系列的实践</p><span id="more"></span><p>本篇文章主要讲一讲在构建分布式微服务应用时，经常遇到的问题以及对于同类型组件选择，以及在开发过程中相关问题的思考，对于在整个应用开发过程中，开发人员应该怎么去配合等等，那第一个问题是面对我们的业务场景该如何去做技术选型，我们先看 Spring 官方经典的微服务架构图</p><p><img src="https://spring.io/images/diagram-microservices-88e01c7d34c688cb49556435c130d352.svg" alt=""></p><p>微服务的核心组件由：网关，服务注册发现，服务配置，熔断限流等组成</p><div class="note info"><p>注意这里微服务主要以 <code>Alibaba</code> 系相关的开源组件为基础构建，并非是 <a href="https://github.com/alibaba/spring-cloud-alibaba"><code>Spring Cloud Alibaba</code></a> 项目的照搬，而是基于企业实际业务需求的抽象整合，只为提高效率、总结编程套路以及提升编程思想</p></div><h2 id="选型"><a class="header-anchor" href="#选型"></a>选型</h2><ul><li>编程语言：<span class="exturl" data-url="aHR0cHM6Ly9kb2NzLm9yYWNsZS5jb20vamF2YXNlLzgv">Oracle JDK 8<i class="fa fa-external-link-alt"></i></span></li><li>构建工具：<span class="exturl" data-url="aHR0cHM6Ly9ncmFkbGUub3Jn">Gradle<i class="fa fa-external-link-alt"></i></span></li><li>网关路由：<span class="exturl" data-url="aHR0cHM6Ly9zcHJpbmcuaW8vcHJvamVjdHMvc3ByaW5nLWNsb3VkLWdhdGV3YXk=">Spring Cloud Gateway<i class="fa fa-external-link-alt"></i></span></li><li>服务通信：<span class="exturl" data-url="aHR0cHM6Ly9kdWJiby5hcGFjaGUub3JnL3po">Dubbo<i class="fa fa-external-link-alt"></i></span></li><li>消息管理：<span class="exturl" data-url="aHR0cDovL3JvY2tldG1xLmFwYWNoZS5vcmc=">RockerMQ<i class="fa fa-external-link-alt"></i></span></li><li>分布式事务：<span class="exturl" data-url="aHR0cDovL3NlYXRhLmlvL3poLWNu">Seata<i class="fa fa-external-link-alt"></i></span></li><li>注册中心及配置中心：<span class="exturl" data-url="aHR0cHM6Ly9uYWNvcy5pby96aC1jbg==">Nacos<i class="fa fa-external-link-alt"></i></span></li><li>限流，熔断，降级：<span class="exturl" data-url="aHR0cHM6Ly9zZW50aW5lbGd1YXJkLmlvL3poLWNu">Sentinel<i class="fa fa-external-link-alt"></i></span></li><li>文档管理：<span class="exturl" data-url="aHR0cDovL3NwcmluZ2ZveC5naXRodWIuaW8vc3ByaW5nZm94">SpringFox<i class="fa fa-external-link-alt"></i></span> + <span class="exturl" data-url="aHR0cHM6Ly9kb2MueGlhb21pbmZvLmNvbQ==">Knife4j<i class="fa fa-external-link-alt"></i></span> + <span class="exturl" data-url="aHR0cHM6Ly9kdWJiby5hcGFjaGUub3JnL3poL2Jsb2cvMjAyMC8xMi8yMi9kdWJiby1hcGktZG9jcy1hcGFjaGUtZHViYm8lRTYlOTYlODclRTYlQTElQTMlRTUlQjElOTUlRTclQTQlQkElRTYlQjUlOEIlRTglQUYlOTUlRTUlQjclQTUlRTUlODUlQjcv">Dubbo-Api-Docs<i class="fa fa-external-link-alt"></i></span></li><li>部署发布：<span class="exturl" data-url="aHR0cHM6Ly93d3cuZG9ja2VyLmNvbQ==">Docker<i class="fa fa-external-link-alt"></i></span> + <span class="exturl" data-url="aHR0cHM6Ly93d3cuc29uYXR5cGUuY29tL25leHVzL3JlcG9zaXRvcnktb3Nz">Nexus Repository OSS<i class="fa fa-external-link-alt"></i></span></li><li>运维监控：<span class="exturl" data-url="aHR0cHM6Ly9wcm9tZXRoZXVzLmlv">Prometheus<i class="fa fa-external-link-alt"></i></span> + <span class="exturl" data-url="aHR0cHM6Ly9ncmFmYW5hLmNvbQ==">Grafana<i class="fa fa-external-link-alt"></i></span></li></ul><p><img src="https://res.cloudinary.com/incoder/image/upload/v1616294894/blog/Spring-Cloud-Alibaba.png" alt=""></p><h2 id="SpringCloud-VS-SpringCloud-Alibaba"><a class="header-anchor" href="#SpringCloud-VS-SpringCloud-Alibaba"></a>SpringCloud VS SpringCloud Alibaba</h2><p>这里我汇总到表格中，方便查看比较</p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1616302368/blog/Spring_Cloud_VS_Spring_Cloud_Alibaba.png" alt=""></p><h2 id="相关问题"><a class="header-anchor" href="#相关问题"></a>相关问题</h2><h3 id="JDK"><a class="header-anchor" href="#JDK"></a>JDK</h3><h4 id="OpenJDK"><a class="header-anchor" href="#OpenJDK"></a>OpenJDK</h4><p>Java 最早由 SUN（Sun Microsystems，发起于美国斯坦福大学，SUN 是 Stanford University Network 的缩写）发明，2006 年 SUN 公司将 Java 开源，此时的 JDK 即为 OpenJDK</p><p><span class="exturl" data-url="aHR0cDovL29wZW5qZGsuamF2YS5uZXQv">OpenJDK<i class="fa fa-external-link-alt"></i></span> 是 Java SE 的开源实现，由 SUN 和 Java 社区提供支持，2009 年 Oracle 收购了 SUN 公司，自此 Java 的维护方之一的 SUN 也就变成了 Oracle</p><p>大多数 JDK 都是在 OpenJDK 的基础上编写实现的，比如 IBM J9，Azul Zulu，Azul Zing 和 Oracle JDK。几乎所有的 JDK 都派生自 OpenJDK，他们之间不同的是授权许可证。常见的 OpenJDK 发行商</p><table><thead><tr><th style="text-align:center"><strong>发行商</strong></th><th style="text-align:center"><strong>长期支持（TLS）</strong></th><th style="text-align:center"><strong>许可证（license）</strong></th><th style="text-align:center"><strong>TCK 测试</strong></th><th style="text-align:center"><strong>未修改的上游构建</strong></th><th style="text-align:center"><strong>提供商业支持</strong></th></tr></thead><tbody><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly9hZG9wdG9wZW5qZGsubmV0Lw==">AdoptOpenJDK<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Optional</td><td style="text-align:center">Optional（IBM）</td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cDovL2RyYWdvbndlbGwtamRrLmlvLw==">Alibaba Dragonwell<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center"><font color=red>No</font></td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly9hd3MuYW1hem9uLmNvbS9jb3JyZXR0by8=">Amazon Corretto<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Optional</br>（on AWS）</td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly93d3cuYXp1bC5jb20vZG93bmxvYWRzL3p1bHUtY29tbXVuaXR5Lz9wYWNrYWdlPWpkaw==">Azul Zulu<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Optional</td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly9iZWxsLXN3LmNvbS9wYWdlcy9kb3dubG9hZHMv">BellSoft Liberica JDK<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Optional</td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly93d3cuaWJtLmNvbS9zdXBwb3J0L3BhZ2VzL2phdmEtc2RrLWRvd25sb2Fkcw==">IBM Java JDK<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Yes</td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29qZGtidWlsZC9vamRrYnVpbGQ=">ojdkbuild<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly93d3cub3BlbmxvZ2ljLmNvbS9vcGVuamRrLWRvd25sb2Fkcw==">OpenLogic OpenJDK<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Optional</td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly93d3cub3JhY2xlLmNvbS9qYXZhL3RlY2hub2xvZ2llcy9qYXZhc2UtZG93bmxvYWRzLmh0bWw=">Oracle Java SE<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Yes</td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly9vcGVuamRrLmphdmEubmV0Lw==">Oracle OpenJDK<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXJzLnJlZGhhdC5jb20vcHJvZHVjdHMvb3Blbmpkay9kb3dubG9hZA==">Red Hat OpenJDK<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center">Yes</td></tr><tr><td style="text-align:center"><span class="exturl" data-url="aHR0cHM6Ly9zYXAuZ2l0aHViLmlvL1NhcE1hY2hpbmUv">SAP SAPMachine<i class="fa fa-external-link-alt"></i></span></td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center">Yes</td><td style="text-align:center"><font color=red>No</font></td><td style="text-align:center"><font color=red>No</font></td></tr></tbody></table><blockquote><p>TLS：long-term support，长期支持（LTS）是一种产品生命周期管理策略，在该策略中，与标准版相比，计算机软件的稳定版本可以维持更长的时间。该术语通常保留给开源软件，它描述的软件版本比该软件的标准版本支持数月或数年的支持。<br>TCK：Technology Compatibility Kit，技术兼容性套件（TCK）是一套测试套件，至少名义上检查Java规范请求（JSR）的特定声称实施是否符合要求</p></blockquote><h4 id="OralceJDK"><a class="header-anchor" href="#OralceJDK"></a>OralceJDK</h4><p>显而易见 OracleJDK 是在 Oracle 收购 SUN 公司之后，基于 OpenJDK 源码构建的 JDK 被命名了 OracleJDK，两则之间没有重大的技术差异</p><h4 id="两者的区别"><a class="header-anchor" href="#两者的区别"></a>两者的区别</h4><h4 id="问题"><a class="header-anchor" href="#问题"></a>问题</h4><p>有人会说了，这有啥好说的，我们在公司开发都是用 OracleJDK 的。曾经我也以为这两个区别不是很大，看公司的使用情况了，直到我使用了 CentOS 7 系统默认带的 OpenJDK 来编译 Gradle 项目，死活是编译不过，总是提醒我找不到 <code>tools.jar</code> 包。有图有真相</p><p><img src="https://res.cloudinary.com/incoder/image/upload/v1616514430/blog/gradle-openjre.png" alt=""></p><p>一开始，我把以为是环境配置的问题，但是经过一番折腾，卸载了自带的 OpenJDK，然后再用 <code>yum install java</code> 命令去安装 OpenJDK，发现并不是环境的问题，而是系统自带的这个 OpenJDK 是 JRE，所以并没有包含 <code>tools.jar</code> 文件。所以这个问题就是你系统 JDK 的问题了。建议卸载 JRE，重新安装 JDK</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 可以先查找 JDK，下面命令是我查找 java-1.8 的相关应用</span></span><br><span class="line">yum search java-1.8 | grep -i --color JDK</span><br><span class="line"><span class="comment"># 也可以直接安装 JDK，比如我这里提供的 java-1.8.0-openjdk-devel.x86_64</span></span><br><span class="line">yum install java-1.8.0-openjdk-devel.x86_64</span><br></pre></td></tr></table></figure><p><img src="https://res.cloudinary.com/incoder/image/upload/v1616515361/blog/centos-openjdk.png" alt=""></p><h3 id="Gradle-or-Maven"><a class="header-anchor" href="#Gradle-or-Maven"></a>Gradle or Maven</h3><p>关于如何使用 Gradle 构建项目，以及使用 Gradle 配置符合企业敏捷开发需求，可查看我的 <a href="https://incoder.org/tags/Gradle">Gradle</a> 系列的文章</p><h3 id="jar-与-bootJar"><a class="header-anchor" href="#jar-与-bootJar"></a>jar 与 bootJar</h3><p>之前在<a href="https://incoder.org/2019/07/05/springboot2/">《SpringBoot（二） 启动分析JarLauncher》</a>文章中进行对 SpringBoot 应用启动做了分析，提到了 <a href="https://incoder.org/2019/07/05/springboot2/#jar%E8%A7%84%E8%8C%83">jar 规范</a>，做了简单的介绍，那么本篇在此基础上进一步的完善这个知识点</p><blockquote><p>这里以 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL1Jvb3RDbHVzdGVyL3JjLW1pY3Jvc2VydmljZXMtYWxpYmFiYQ==">rc-microservices-alibaba<i class="fa fa-external-link-alt"></i></span> 项目的 <code>microservices-alibaba-gateway</code> 模块的编译为例</p></blockquote><h4 id="jar"><a class="header-anchor" href="#jar"></a>jar</h4><p>jar（Java Archive）可以看做是特殊文件压缩的一种，通常用于聚合大量的 Java 类文件，相关的元数据和资源文件到一个文件，以便分发 Java 平台应用软件或库。jar 文件是一种归档文件，以 ZIP 格式构建，以 <code>.jar</code> 为文件扩展名。包含一个可选的 <code>META-INF</code> 目录，可以通过命令行 jar 工具或使用 Java 平台中的 <code>java.util.jar</code> API 创建 jar 文件</p><p>可以看到，我们打包成 jar 的文件，仅仅是源码+资源文件，以及生成的 <code>META_INF</code> 文件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">microservices-alibaba-gateway-1.0-SNAPSHOT</span><br><span class="line">    ├── META-INF</span><br><span class="line">    ├── org</span><br><span class="line">    │   └── incoder</span><br><span class="line">    │       └── gateway</span><br><span class="line">    │           ├── config</span><br><span class="line">    │           ├── exception</span><br><span class="line">    │           └── filter</span><br><span class="line">    ├── static</span><br><span class="line">    └── templates</span><br></pre></td></tr></table></figure><h4 id="bootJar"><a class="header-anchor" href="#bootJar"></a>bootJar</h4><p>看名字就知道，这是 SpringBoot 的专属 jar。为什么会有这种 jar，原因是在 SpringBoot 出现之前，我们的 jar 应用想要运行，需要将应用放入到 Tomcat 中。而 SpringBoot 的出现改变了这层关系，是 SpringBoot 在打包成 bootJar 时，会内置 Tomcat，我们可以直接运行启动 jar 应用，可能有人会说，这怎么改变了，不都还是运行在 Tomcat 上么。没错它确实依然运行在 Tomcat 上，但是他们的加载方式改变了</p><p>我们可以看到，打成 bootJar 的文件，除了 <code>META-INF</code> 相关文件，并且包含了 <code>BOOT-INF</code> 的 lib 路径下存放项目所使用的所有第三方的 jar 包 ，同时在打包的根目录，生成了 SpringBoot 的 loader 相关的文件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">microservices-alibaba-gateway-1.0-SNAPSHOT</span><br><span class="line">    ├── BOOT-INF</span><br><span class="line">    │   ├── classes</span><br><span class="line">    │   │   ├── META-INF</span><br><span class="line">    │   │   ├── org</span><br><span class="line">    │   │   │   └── incoder</span><br><span class="line">    │   │   │       └── gateway</span><br><span class="line">    │   │   │           ├── config</span><br><span class="line">    │   │   │           ├── exception</span><br><span class="line">    │   │   │           └── filter</span><br><span class="line">    │   │   ├── static</span><br><span class="line">    │   │   └── templates</span><br><span class="line">    │   └── lib</span><br><span class="line">    ├── META-INF</span><br><span class="line">    └── org</span><br><span class="line">        └── springframework</span><br><span class="line">            └── boot</span><br><span class="line">                └── loader</span><br><span class="line">                    ├── archive</span><br><span class="line">                    ├── data</span><br><span class="line">                    ├── jar</span><br><span class="line">                    ├── jarmode</span><br><span class="line">                    └── util</span><br></pre></td></tr></table></figure><h3 id="Spring-生态"><a class="header-anchor" href="#Spring-生态"></a>Spring 生态</h3><ol><li>Spring：一个一站式轻量级Java 开发框架，核心是控制反转（IOC）和面向切面（AOP），针对开发 Web 层，业务层，持久层等提供了多种配置解决方案，也是整个微服务开发的基石</li><li>SpringMVC：是 Spring 基础之上的一个 MVC 框架，主要处理 Web 开发的路径映射和视图渲染，属于 Spring 框架中 Web 层开发的一部分（开发配置非常繁琐，复杂）</li><li>SpringBoot：专注于服务方面的接口开发，和前端解耦，默认优于配置，一定程度上取消了 XML 配置，是一套快速开发的脚手架，能快速开发单个微服务</li><li>SpringCloud：大部分功能组件基于 SpringBoot 去实现，提供了完整的微服务架构的技术生态，SpringCloud 专注于微服务的整合和管理</li></ol><h3 id="单工程-or-聚合工程"><a class="header-anchor" href="#单工程-or-聚合工程"></a>单工程 or 聚合工程</h3><blockquote><p>个人推荐单工程的方式，毕竟聚合工程最终会随着业务的发展推进，需要拆分为单项目开发管理，那还不如一开始就拆分</p></blockquote><h4 id="单工程"><a class="header-anchor" href="#单工程"></a>单工程</h4><p>这里的单工程是指，每一个模块都是一个项目，由一个仓库进行管理，特点及要求如下</p><ol><li>适合团队小组分工明确，开发人员多</li><li>适合项目迭代快</li><li>需要比较健全的基础设施，比如网关，公共基础工具包，消息管理，以及自动化部署相关服务设施</li></ol><blockquote><p>相关的构架过程可参考 <a href="https://incoder.org/2020/12/16/gradle3/">Gradle（三）SpringBoot 单工程</a> 文章</p></blockquote><h4 id="聚合工程"><a class="header-anchor" href="#聚合工程"></a>聚合工程</h4><p>这里的聚合工程是指，将整个系统开发的所有模块以及公共模块都放在一个项目工程中，也就是用同一个仓库来进行管理，特点如下</p><ol><li>适合项目初期，项目分工不是特别明确，开发人员少</li><li>项目需要集中管理</li></ol><blockquote><p>相关的构架过程可参考 <a href="https://incoder.org/2021/03/06/gradle4/">Gradle（四）SpringBoot 聚合工程</a> 文章</p></blockquote><h3 id="业务拆分"><a class="header-anchor" href="#业务拆分"></a>业务拆分</h3><p>对于服务的拆分是没有统一的标准，除了通过实际的业务场景，团队能力，人员组织架构等多种因素综合考虑。都根据实际的需求进行调整，对于拆分主要从以下原则去思考</p><ol><li>单一职责原则：保证每个服务只做好一件事，体现“高内聚，低耦合”，尽量减少对外界环境的依赖</li><li>服务依赖原则：避免服务间的循环依赖，在设计时就需要对服务进行分级，区分核心服务与非核心服务</li><li>Two Pizza Team原则：让团队保持在2 个披萨就能让队员吃饱的小规模概念</li></ol><h2 id="参考"><a class="header-anchor" href="#参考"></a>参考</h2><ul><li><span class="exturl" data-url="aHR0cHM6Ly96aHVhbmxhbi56aGlodS5jb20vcC81NzM4NTIzNA==">传统行业转型微服务的挖坑与填坑<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuemhpaHUuY29tL3F1ZXN0aW9uLzE5ODgyMzIw">Java官方（Oracle/Sun）发布的JDK，和开源项目OpenJDK，里面包含的JVM是否相同<i class="fa fa-external-link-alt"></i></span></li><li><span class="exturl" data-url="aHR0cHM6Ly9jbG91ZC50ZW5jZW50LmNvbS9kZXZlbG9wZXIvYXJ0aWNsZS8xNTk4Mjkx">OpenJDK和Oracle JDK有什么区别和联系？<i class="fa fa-external-link-alt"></i></span></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;曾几何时，市面上对于微服务，分两个派系，一个派系以阿里为主的 Dubbo 生态体系，还有一派以 Spring Cloud 生态为主的体系，这两个系列的讨论也一直没有停息过。但现在 Spring Cloud Alibaba 的出现，提供了一整套构建分布式应用开发的微服务组件，由于这些组件是构建在原生的 Spring Cloud 之上，因此其服务治理方面的能力可认为是 Spring Cloud Plus， 不仅完全覆盖 Spring Cloud 原生特性，而且提供更为稳定和成熟的实现。那么从本系列就开始跟着我一起用阿里系的应用搭建分布式微服务应用，满足企业级的应用需要，而不是停留在 Dome 级别的应用框架使用。废话不多说，我们一起开始这一系列的实践&lt;/p&gt;</summary>
    
    
    
    <category term="Microservices" scheme="https://incoder.org/categories/Microservices/"/>
    
    
    <category term="Microservices" scheme="https://incoder.org/tags/Microservices/"/>
    
    <category term="Alibaba" scheme="https://incoder.org/tags/Alibaba/"/>
    
  </entry>
  
</feed>
