<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>学少何Blog</title><link>https://study_less_shape.github.io/</link><description>Recent content on 学少何Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>© 2024 学少何</copyright><lastBuildDate>Thu, 05 Jun 2025 10:19:57 +0800</lastBuildDate><atom:link href="https://study_less_shape.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>使用 ConfuserEX2 加密混淆 C# 程序</title><link>https://study_less_shape.github.io/post/note/use-confuserex2/</link><pubDate>Thu, 05 Jun 2025 10:19:57 +0800</pubDate><guid>https://study_less_shape.github.io/post/note/use-confuserex2/</guid><description>&lt;h2 id="为什么需要对-c-程序进行混淆和加密">为什么需要对 C# 程序进行混淆和加密&lt;/h2>
&lt;p>通常需要这样做大多是因为商业项目。&lt;/p>
&lt;p>就我个人情况而言，目前是在为一个公司使用的软件编写插件，有外发需求，所以需要对插件进行代码加密。&lt;/p>
&lt;h2 id="快速使用">快速使用&lt;/h2>
&lt;p>目前我用得最舒适的方式是将 ConfuserEx 集成到项目中，并在编译的同时进行加密操作。&lt;/p>
&lt;blockquote>
&lt;p>ConfuserEx 的生成操作为，在项目构建完成之后，会将指定的文件进行加密，并根据配置来决定是否替换掉原文件。&lt;/p>&lt;/blockquote>
&lt;h3 id="使用前">使用前&lt;/h3>
&lt;p>ConfuserEx2 推荐去 Github 的 &lt;a href="https://github.com/mkaring/ConfuserEx/releases/latest"target="_blank" rel="noopener noreferrer">Release 界面&lt;/a>下载一个最新的 GUI 工具，方便管理和添加加密规则。&lt;/p>
&lt;h3 id="初始化加密">初始化加密&lt;/h3>
&lt;p>对于新建或者已有的 C# 项目，可以通过 Visual Studio 的包管理，或者通过 nuget 包管理器添加 ConfuserEx 的 MSBuild 包。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">dotnet add package Confuser.MSBuild --version 1.6.0
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后打开 ConfuserEx 的 GUI 工具，点击加号添加一个需要加密的文件，可以是 DLL 也可以是 EXE。当然，这个文件最好是单独的一个项目生成的文件。&lt;/p>
&lt;p>比如此处我的项目是 &lt;code>Less.Utils&lt;/code>，所以我添加了 &lt;code>Less.Utils.dll&lt;/code> 文件。&lt;/p>
&lt;p>&lt;img loading="lazy" src="01.add-dll-by-confuser.jpg"
alt="add-dll-by-confuser"/>&lt;/p>
&lt;p>然后在 &lt;code>Settings&lt;/code> 选项卡中，找到刚才添加的文件，然后点击右侧的加号添加一个加密规则。再点击编辑图标修改规则。&lt;/p>
&lt;p>&lt;code>Preset&lt;/code> 是预设，建议选择 &lt;code>None&lt;/code>，如果是加密应用程序可以按照需要选择。&lt;/p>
&lt;p>点击在 &lt;code>Protection&lt;/code> 下方右侧的加号即可添加一个加密规则。加密规则可以有很多个可以选择。&lt;/p>
&lt;p>目前 ConfuserEx 支持的规则有（描述都为机翻）：&lt;/p>
&lt;ul>
&lt;li>&lt;code>Anti Debug&lt;/code>：此保护可防止对程序集进行调试或分析。&lt;/li>
&lt;li>&lt;code>Anti Dump&lt;/code>：此保护可防止从内存中转储程序集。&lt;/li>
&lt;li>&lt;code>Anti IL Dasm&lt;/code>：此保护使用阻止 ILDasm 反汇编的属性标记模块。&lt;/li>
&lt;li>&lt;code>Anti Tamper&lt;/code>：此保护可确保应用程序的完整性。&lt;/li>
&lt;li>&lt;code>Constants&lt;/code>：此保护对代码中的常量进行编码和压缩。&lt;/li>
&lt;li>&lt;code>Control Flow&lt;/code>：此保护会破坏方法中的代码，以便反编译器无法反编译方法。&lt;/li>
&lt;li>&lt;code>Protection Hardening&lt;/code>：此组件改进了保护代码，使其更难绕过它。&lt;/li>
&lt;li>&lt;code>Invalid Metadata&lt;/code>：此保护将无效的元数据添加到模块中，以防止反汇编器/反编译器打开它们。&lt;/li>
&lt;li>&lt;code>Resources&lt;/code>：此保护对嵌入的资源进行编码和压缩。&lt;/li>
&lt;li>&lt;code>Type Scrambler&lt;/code>：将类型替换为泛型。&lt;/li>
&lt;li>&lt;code>Name&lt;/code>：此保护会混淆符号的名称，因此既不能编译也不能读取反编译的源代码。&lt;/li>
&lt;li>&lt;code>Watermarking&lt;/code>：这会将水印应用于程序集，表明 ConfuserEx 保护了程序集。所以人们试图扭转混淆，知道放弃。&lt;/li>
&lt;/ul>
&lt;p>对于类库的加密，目前个人添加的是 &lt;code>Anti Dump&lt;/code>、&lt;code>Anti IL Dasm&lt;/code>、&lt;code>Anti Tamper&lt;/code>、&lt;code>Constants&lt;/code>、&lt;code>Control Flow&lt;/code> 这 5 项。&lt;/p>
&lt;p>添加完成后，点击工具栏的 &lt;code>Save Project&lt;/code>，保存到项目的根目录下，文件命名和项目名需要一致。&lt;/p>
&lt;h3 id="项目设置">项目设置&lt;/h3>
&lt;p>加密会影响调试，所以首选需要设置在 Release 模式下才进行加密。&lt;/p>
&lt;p>双击项目打开项目文件，在文件中添加下面的内容：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;PropertyGroup&lt;/span> &lt;span class="na">Condition=&lt;/span>&lt;span class="s">&amp;#34;&amp;#39;$(Configuration)&amp;#39; == &amp;#39;Release&amp;#39;&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Obfuscate&amp;gt;&lt;/span>true&lt;span class="nt">&amp;lt;/Obfuscate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ConfuserReplaceOutput&amp;gt;&lt;/span>true&lt;span class="nt">&amp;lt;/ConfuserReplaceOutput&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这几行配置了在 Release 模式下启用加密，并替换掉原来的文件。&lt;/p>
&lt;p>然后打开刚才保存的加密配置文件，文件名为 &lt;code>&amp;lt;项目名&amp;gt;.crproj&lt;/code>。如果在 VS 中不存在，可以通过右键项目，添加现有项来添加。&lt;/p>
&lt;p>打开之后，将路径替换为 &lt;code>$(Output)&lt;/code>，修改后保存。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;project&lt;/span> &lt;span class="na">outputDir=&lt;/span>&lt;span class="s">&amp;#34;$(Output)\Confused&amp;#34;&lt;/span> &lt;span class="na">baseDir=&lt;/span>&lt;span class="s">&amp;#34;$(Output)&amp;#34;&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://confuser.codeplex.com&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;module&lt;/span> &lt;span class="na">path=&lt;/span>&lt;span class="s">&amp;#34;Less.Utils.dll&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;rule&lt;/span> &lt;span class="na">pattern=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span> &lt;span class="na">inherit=&lt;/span>&lt;span class="s">&amp;#34;false&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;protection&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;anti dump&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;protection&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;anti ildasm&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;protection&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;anti tamper&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;protection&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;constants&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;protection&lt;/span> &lt;span class="na">id=&lt;/span>&lt;span class="s">&amp;#34;ctrl flow&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/rule&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/module&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/project&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样，加密配置就完成了。现在在 Release 模式下编译的生成的文件，就会应用上面的加密规则。&lt;/p>
&lt;h2 id="市面上的加密工具">市面上的加密工具&lt;/h2>
&lt;h3 id="dotfuscator">Dotfuscator&lt;/h3>
&lt;p>可以直接使用 Visual Studio Installer，在其单个组件中找到并安装。安装之后在 Visual Studio 中使用功能搜索 dot，就能够使用了。相对来说使用方便，但是免费版加密程度有限，而且在我实际使用时，加密后的插件无法正常使用，所以不太推荐。&lt;/p>
&lt;p>不过如果是加密应用程序，应该还是没有问题的。&lt;/p>
&lt;h3 id="agilenet">Agile.NET&lt;/h3>
&lt;p>&lt;a href="https://www.secureteam.net/acode-features-detailed"target="_blank" rel="noopener noreferrer">Agile.NET&lt;/a> 是一个商业的加密工具，功能强大，但是需要付费，而且价格不便宜。&lt;/p>
&lt;p>不过效果确实看着不错，加密后的代码公开方法和类型都能够正常用 ILSpy 和 dnSpy 查看，只是方法体全被去掉了。&lt;/p>
&lt;h3 id="结界net">结界.NET&lt;/h3>
&lt;p>&lt;a href="https://github.com/dcsoft-yyf/JIEJIE.NET"target="_blank" rel="noopener noreferrer">结界.NET&lt;/a> 是开源的命令行加密工具，同时也有 GUI 界面。使用方法可以看一下这个博客 -&amp;gt; &lt;a href="https://www.cnblogs.com/dotnet-box/p/17360983.html"target="_blank" rel="noopener noreferrer">【JIEJIE.NET - 强大的 .NET 代码混淆工具】&lt;/a>&lt;/p>
&lt;p>不过这个加密工具同样是不推荐对类库进行加密，个人只推荐加密应用程序。&lt;/p>
&lt;h3 id="confuserex2">ConfuserEX2&lt;/h3>
&lt;p>&lt;a href="https://github.com/mkaring/ConfuserEx"target="_blank" rel="noopener noreferrer">ConfuserEx2&lt;/a>，也是原作者停止更新后，社区其他开发者接手更新的项目。目前没有继续发布新的稳定版本了，但是还在继续更新。&lt;/p>
&lt;p>这个工具除了 GUI 的界面，还有对应的 nuget 包 &lt;a href="https://www.nuget.org/packages/Confuser.MSBuild"target="_blank" rel="noopener noreferrer">Confuser.MSBuild&lt;/a>，能够在项目编译的同时进行加密操作。&lt;/p>
&lt;p>就个人目前使用体验而言，这个是用起来最无感且最方便的。&lt;/p>
&lt;h2 id="一些反编译工具">一些反编译工具&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/icsharpcode/ILSpy"target="_blank" rel="noopener noreferrer">ILSpy&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/dnSpyEx/dnSpy"target="_blank" rel="noopener noreferrer">dnSpy(Ex)&lt;/a> &lt;em>这个是原作者停止维护后，其他开发者接手并持续更新的项目&lt;/em>&lt;/li>
&lt;/ul>
&lt;p>dnSpy 可以修改并重新编译程序和 DLL 文件，相对来说功能更强大。&lt;/p></description></item><item><title>如何初始化 llvm-sys</title><link>https://study_less_shape.github.io/post/rust/how-to-init-llvm-sys/</link><pubDate>Sat, 23 Nov 2024 14:37:14 +0800</pubDate><guid>https://study_less_shape.github.io/post/rust/how-to-init-llvm-sys/</guid><description>&lt;h2 id="导入-llvm-sys库">导入 llvm-sys库&lt;/h2>
&lt;p>添加 crate &lt;a href="https://crates.io/crates/llvm-sys"target="_blank" rel="noopener noreferrer">llvm-sys&lt;/a> 到现有的项目中。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">cargo add llvm-sys
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果有指定 llvm 版本需求，可以指定版本，比如版本 &lt;code>18.1.x&lt;/code> 的 LLVM，可以指定 llmv-sys 的版本为 &lt;code>180&lt;/code>。&lt;/p>
&lt;p>目前最新版本支持到了 &lt;code>190&lt;/code>，即当前最新版本 &lt;code>19.1.0&lt;/code> 的 LLVM。&lt;/p>
&lt;h2 id="编译问题">编译问题&lt;/h2>
&lt;p>如果试用 rust-analyzer 或运行命令 &lt;code>cargo check&lt;/code>，会提示一个错误。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">D:&lt;span class="se">\P&lt;/span>rojects&lt;span class="se">\r&lt;/span>ust&lt;span class="se">\t&lt;/span>est-project&amp;gt;cargo check
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">warning: unused manifest key: worksapce
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Compiling llvm-sys v191.0.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">error: No suitable version of LLVM was found system-wide or pointed
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> to by LLVM_SYS_191_PREFIX.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Consider using &lt;span class="sb">`&lt;/span>llvmenv&lt;span class="sb">`&lt;/span> to compile an appropriate copy of LLVM, and
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> refer to the llvm-sys documentation &lt;span class="k">for&lt;/span> more information.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> llvm-sys: https://crates.io/crates/llvm-sys
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> llvmenv: https://crates.io/crates/llvmenv
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --&amp;gt; C:&lt;span class="se">\U&lt;/span>sers&lt;span class="se">\s&lt;/span>tudylessshape&lt;span class="se">\.&lt;/span>cargo&lt;span class="se">\r&lt;/span>egistry&lt;span class="se">\s&lt;/span>rc&lt;span class="se">\r&lt;/span>sproxy.cn-0dccff568467c15b&lt;span class="se">\l&lt;/span>lvm-sys-191.0.0&lt;span class="se">\s&lt;/span>rc/lib.rs:529:1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">529&lt;/span> &lt;span class="p">|&lt;/span> / std::compile_error!&lt;span class="o">(&lt;/span>concat!&lt;span class="o">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">530&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="s2">&amp;#34;No suitable version of LLVM was found system-wide or pointed
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">531 | | to by LLVM_SYS_&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">532&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> env!&lt;span class="o">(&lt;/span>&lt;span class="s2">&amp;#34;CARGO_PKG_VERSION_MAJOR&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">... &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="m">539&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> llvmenv: https://crates.io/crates/llvmenv&lt;span class="s2">&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">540 | | ));
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> | |__^
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">error: could not compile `llvm-sys` (lib) due to 1 previous error
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这是在提示需要为 &lt;code>LLVM_SYS_191_PREFIX&lt;/code> 指定正确的 LLVM 路径。&lt;/p>
&lt;h3 id="下载安装-llvm">下载安装 LLVM&lt;/h3>
&lt;style type="text/css">
.notice {
--title-color: #fff;
--title-background-color: #6be;
--content-color: #444;
--content-background-color: #e7f2fa;
}
.notice.info {
--title-background-color: #fb7;
--content-background-color: #fec;
}
.notice.tip {
--title-background-color: #5a5;
--content-background-color: #efe;
}
.notice.warning {
--title-background-color: #c33;
--content-background-color: #fee;
}
@media (prefers-color-scheme:dark) {
.notice {
--title-color: #fff;
--title-background-color: #069;
--content-color: #ddd;
--content-background-color: #023;
}
.notice.info {
--title-background-color: #a50;
--content-background-color: #420;
}
.notice.tip {
--title-background-color: #363;
--content-background-color: #121;
}
.notice.warning {
--title-background-color: #800;
--content-background-color: #400;
}
}
body.dark .notice {
--title-color: #fff;
--title-background-color: #069;
--content-color: #ddd;
--content-background-color: #023;
}
body.dark .notice.info {
--title-background-color: #a50;
--content-background-color: #420;
}
body.dark .notice.tip {
--title-background-color: #363;
--content-background-color: #121;
}
body.dark .notice.warning {
--title-background-color: #800;
--content-background-color: #400;
}
.notice {
padding: 18px;
line-height: 24px;
margin-bottom: 24px;
border-radius: 4px;
color: var(--content-color);
background: var(--content-background-color);
}
.notice p:last-child {
margin-bottom: 0
}
.notice-title {
margin: -18px -18px 12px;
padding: 4px 18px;
border-radius: 4px 4px 0 0;
font-weight: 700;
color: var(--title-color);
background: var(--title-background-color);
}
.icon-notice {
display: inline-flex;
align-self: center;
margin-right: 8px;
}
.icon-notice img,
.icon-notice svg {
height: 1em;
width: 1em;
fill: currentColor;
}
.icon-notice img,
.icon-notice.baseline svg {
top: .125em;
position: relative;
}
&lt;/style>&lt;div class="notice info" >
&lt;p class="notice-title">
&lt;span class="icon-notice baseline">
&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="92 59.5 300 300">
&lt;path d="M292 303.25V272c0-3.516-2.734-6.25-6.25-6.25H267v-100c0-3.516-2.734-6.25-6.25-6.25h-62.5c-3.516 0-6.25 2.734-6.25 6.25V197c0 3.516 2.734 6.25 6.25 6.25H217v62.5h-18.75c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h87.5c3.516 0 6.25-2.734 6.25-6.25Zm-25-175V97c0-3.516-2.734-6.25-6.25-6.25h-37.5c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h37.5c3.516 0 6.25-2.734 6.25-6.25Zm125 81.25c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z"/>
&lt;/svg>
&lt;/span>信息&lt;/p>&lt;p>Linux 直接使用包管理器安装 LLVM，在安装完成后，试着输入命令 &lt;code>llvm-config&lt;/code>，如果打印出了使用帮助信息，则可以不需要这一节的内容。&lt;/p>&lt;/div>
&lt;p>进入 LLVM 的官网 &lt;a href="https://llvm.org/"target="_blank" rel="noopener noreferrer">https://llvm.org/&lt;/a>，点击左侧的 &lt;code>All Releases&lt;/code>，或者直接进入 &lt;a href="https://releases.llvm.org/"target="_blank" rel="noopener noreferrer">https://releases.llvm.org/&lt;/a>。&lt;/p>
&lt;p>找到自己需要的版本，并点击 &lt;u>&lt;code>Download&lt;/code>&lt;/u>。&lt;/p>
&lt;p>&lt;img loading="lazy" src="download-llvm-page.png"
alt="download-llvm-page"/>&lt;/p>
&lt;p>目前的本人尝试下载的两个版本，都将下载地址放到了 github 上，而不是其官方网站了。&lt;/p>
&lt;p>在 github release 中下载需要注意，不要下载 &lt;code>win32.exe&lt;/code> 或 &lt;code>win64.exe&lt;/code>，因为这两个执行文件的是安装包，安装好的 bin 文件夹下没有 &lt;code>llvm-config&lt;/code> 程序。以 &lt;code>19.1.0&lt;/code> 为例，需要下载下方的 &lt;code>LLVM-19.1.0-Windows-X64.tar.xz&lt;/code> 文件。文件很大，所以如果下载不下来需要魔法才行，或者尝试去寻找并使用 github 文件下载加速站。&lt;/p>
&lt;p>&lt;img loading="lazy" src="select-file-from-github-release.png"
alt="select-file-from-github-release"/>&lt;/p>
&lt;p>下载好之后，将压缩包解压出来，建议路径中最好没有空格和非英文字母的字符。&lt;/p>
&lt;p>解压好之后，可以直接在 bin 文件夹下看一下有没有 &lt;code>llvm-config.exe&lt;/code>，确认好之后就需要配置环境变量了。&lt;/p>
&lt;h3 id="设置环境变量">设置环境变量&lt;/h3>
&lt;p>根据提示，可以在控制台中设置临时变量。设置好之后运行 &lt;code>cargo check&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">set&lt;/span> &lt;span class="nv">LLVM_SYS_191_PREFIX&lt;/span>&lt;span class="o">=&lt;/span>D:&lt;span class="se">\\&lt;/span>Programs&lt;span class="se">\\&lt;/span>LLVM
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cargo check
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果还出现了编译错误，并且与上面提到的问题一致，则可能需要重启一下控制台、IDE 或者系统。&lt;/p>
&lt;p>也可以设置到系统的环境变量里。&lt;/p>
&lt;p>&lt;img loading="lazy" src="set-env-for-system.png"
alt="set-env-for-system"/>&lt;/p>
&lt;p>没问题时，&lt;code>cargo check&lt;/code> 应该会有如下输出：&lt;/p>
&lt;p>&lt;img loading="lazy" src="cargo-check-success.png"
alt="cargo-check-success"/>&lt;/p>
&lt;div class="notice note" >
&lt;p class="notice-title">
&lt;span class="icon-notice baseline">
&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 128 300 300">
&lt;path d="M150 128c82.813 0 150 67.188 150 150 0 82.813-67.188 150-150 150C67.187 428 0 360.812 0 278c0-82.813 67.188-150 150-150Zm25 243.555v-37.11c0-3.515-2.734-6.445-6.055-6.445h-37.5c-3.515 0-6.445 2.93-6.445 6.445v37.11c0 3.515 2.93 6.445 6.445 6.445h37.5c3.32 0 6.055-2.93 6.055-6.445Zm-.39-67.188 3.515-121.289c0-1.367-.586-2.734-1.953-3.516-1.172-.976-2.93-1.562-4.688-1.562h-42.968c-1.758 0-3.516.586-4.688 1.563-1.367.78-1.953 2.148-1.953 3.515l3.32 121.29c0 2.734 2.93 4.882 6.64 4.882h36.134c3.515 0 6.445-2.148 6.64-4.883Z"/>
&lt;/svg>
&lt;/span>注释&lt;/p>&lt;p>如果出现了类似于 &lt;code>cl.exe did not execute successfully&lt;/code> 的错误，可能是使用了 LLVM 的安装包安装，并且直接将原来的安装目录下的文件，替换为了下载好的完整 LLVM 包里的文件，或者是路径中包含了空格。&lt;/p>&lt;/div></description></item><item><title>Archlinux 安装笔记</title><link>https://study_less_shape.github.io/post/note/archlinux-install-note/</link><pubDate>Thu, 06 Jun 2024 10:33:09 +0800</pubDate><guid>https://study_less_shape.github.io/post/note/archlinux-install-note/</guid><description>&lt;h2 id="前言">前言&lt;/h2>
&lt;p>在 618 期间买了台新电脑，因为很早之前就想试试直接在电脑上而不是虚拟机里使用 Linux 系统，所以尝试着给旧电脑安装 Archlinux，原因有两点：&lt;/p>
&lt;ol>
&lt;li>Archlinux 自由度高&lt;/li>
&lt;li>因为有 &lt;code>archinstall&lt;/code>，目前的安装 Archlinux 已经非常简单了。&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>另外这台旧电脑准备送给家人用，Linux 不太方便的安装软件方式，可以杜绝调很多安全问题。&lt;/p>&lt;/blockquote>
&lt;p>旧电脑为华硕（ASUS）的飞行堡垒5。&lt;/p>
&lt;ul>
&lt;li>CPU: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz&lt;/li>
&lt;li>显卡: NVIDIA GeForce GTX 1050Ti&lt;/li>
&lt;li>内存: 16GB Samsung 2667MHz （内存曾经自己加了一个 8GB 的）&lt;/li>
&lt;li>主板: ASUSTek COMPUTER INC. FX505GE&lt;/li>
&lt;/ul>
&lt;h2 id="用到的网站">用到的网站&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://archlinux.org/"target="_blank" rel="noopener noreferrer">Archlinux 官网&lt;/a>
&lt;blockquote>
&lt;p>下载 Archlinux 镜像&lt;/p>&lt;/blockquote>
&lt;/li>
&lt;li>&lt;a href="https://rufus.ie/zh/"target="_blank" rel="noopener noreferrer">rufus&lt;/a>
&lt;blockquote>
&lt;p>镜像安装引导盘制作&lt;/p>&lt;/blockquote>
&lt;/li>
&lt;li>&lt;a href="https://wiki.archlinuxcn.org/wiki/%E5%AE%89%E8%A3%85%E6%8C%87%E5%8D%97"target="_blank" rel="noopener noreferrer">官方安装教程（中文翻译）&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://arch.icekylin.online/guide/"target="_blank" rel="noopener noreferrer">Archlinux 简明指南&lt;/a>
&lt;blockquote>
&lt;p>包含安装教程，和一些常用软件、工具的安装&lt;/p>&lt;/blockquote>
&lt;/li>
&lt;li>&lt;a href="http://rsproxy.cn/"target="_blank" rel="noopener noreferrer">RsProxy&lt;/a>
&lt;blockquote>
&lt;p>目前使用过最快的国内 Rust 镜像&lt;/p>&lt;/blockquote>
&lt;/li>
&lt;li>&lt;a href="https://github.com/Morganamilo/paru"target="_blank" rel="noopener noreferrer">paru&lt;/a>
&lt;blockquote>
&lt;p>AUR 软件包安装助手&lt;/p>&lt;/blockquote>
&lt;/li>
&lt;li>&lt;a href="https://aur.archlinux.org/"target="_blank" rel="noopener noreferrer">AUR&lt;/a>
&lt;blockquote>
&lt;p>archlinux 的 aur 仓库网页，搜索需要的软件包很方便，官方直接支持的软件包可以点开 &lt;code>Home&lt;/code> 旁边的 &lt;code>packages&lt;/code> 查看&lt;/p>&lt;/blockquote>
&lt;/li>
&lt;/ul>
&lt;h2 id="下载镜像">下载镜像&lt;/h2>
&lt;p>进入 &lt;a href="https://archlinux.org/"target="_blank" rel="noopener noreferrer">Archlinux 官网&lt;/a>，点击导航栏最右侧的 Download，然后下载 torrent 文件，使用自己常用的种子下载工具下载镜像即可。&lt;/p>
&lt;blockquote>
&lt;p>直接使用磁力链接也可以，但是部分时候磁力链接可能加载不了下载列表。&lt;/p>&lt;/blockquote>
&lt;p>&lt;img loading="lazy" src="01.arhclinux-download.png"
alt="archlinux-download"/>&lt;/p>
&lt;p>Archlinux 的镜像文件相对来说比较小。写这篇博客时，下载的版本是 &lt;code>archlinux-2024.06.01-x86_64&lt;/code>。&lt;/p>
&lt;h3 id="启动盘制作">启动盘制作&lt;/h3>
&lt;p>此处我使用 &lt;a href="https://rufus.ie/zh/"target="_blank" rel="noopener noreferrer">rufus&lt;/a>，当然其他制作工具也是可以的。&lt;/p>
&lt;p>&lt;strong>！！！制作启动盘前，请将 U 盘内的重要数据进行备份！！！&lt;/strong>&lt;/p>
&lt;p>插入一个 U 盘，然后打开 rufus，分区类型选择 GPT，目标系统类型选择 UEFI，然后点击开始。&lt;/p>
&lt;p>&lt;img loading="lazy" src="02.arhclinux-ustart-make.png"
alt="rufus-make"/>&lt;/p>
&lt;p>U 盘会先被格式化，然后 rufus 会写入系统镜像。&lt;/p>
&lt;h2 id="bios-设置">BIOS 设置&lt;/h2>
&lt;p>在 BIOS 设置里关闭安全启动，不同的主板可能 BIOS 界面有所区别。&lt;/p>
&lt;h2 id="启动安装引导">启动安装引导&lt;/h2>
&lt;p>此处需要根据不同的主板来进行操作，比如华硕的主板，出现 ASUS Logo 时长按 ESC 键，然后选择 USB 启动即可。&lt;/p>
&lt;p>因为电脑已经寄走了，所以之后的安装画面都会使用虚拟机。&lt;/p>
&lt;p>安装选择版本选择 &lt;code>x86_64&lt;/code> 即可，也可根据自己电脑的实际情况选择。&lt;/p>
&lt;p>&lt;img loading="lazy" src="03.arhclinux-bios.png"
alt="archlinux-bios"/>&lt;/p>
&lt;p>&lt;img loading="lazy" src="04.arhclinux-bios-enter.png"
alt="archlinux-bios-enter"/>&lt;/p>
&lt;p>出现上图的样子，就是成功进入了安装界面了。&lt;/p>
&lt;h2 id="检查网络">检查网络&lt;/h2>
&lt;p>在进入安装界面的上面，有这样一段话：&lt;/p>
&lt;p>&lt;img loading="lazy" src="05.network-hint.png"
alt="network-hint"/>&lt;/p>
&lt;p>输入 &lt;code>iwctl&lt;/code> 可以进入无线网络的连接程序。输入 &lt;code>help&lt;/code> 指令可以获取帮助。&lt;/p>
&lt;p>Wlan 联网可以参考简明指南的&lt;a href="https://arch.icekylin.online/guide/rookie/basic-install.html#_3-%E8%BF%9E%E6%8E%A5%E7%BD%91%E7%BB%9C"target="_blank" rel="noopener noreferrer">连接网络章节&lt;/a>。&lt;/p>
&lt;p>因为有线网能直接检测到，所以我选择的方法是直连随身 WIFI 或者通过手机的 USB 来共享网络。&lt;/p>
&lt;h2 id="检查磁盘">检查磁盘&lt;/h2>
&lt;p>这一步的目的是为了清空磁盘。如果需要重装 Archlinux，这一步比较关键。&lt;/p>
&lt;blockquote>
&lt;p>在重装 Archlinux 时，如果不预先删掉分区，&lt;code>archinstall&lt;/code> 的磁盘选项里，通过建议创建的分区后，不会删掉原来的分区，安装报错并中断。&lt;/p>&lt;/blockquote>
&lt;p>！！！清空磁盘前，请确认磁盘的重要数据已经备份！！！&lt;/p>
&lt;p>输入 &lt;code>fdisk -l&lt;/code>，查看自己的磁盘。&lt;/p>
&lt;p>&lt;img loading="lazy" src="06.fdisk-list.png"
alt="fdisk-list"/>&lt;/p>
&lt;p>上面显示的有两个磁盘，其中 &lt;code>/dev/sda&lt;/code> 是我现在的磁盘。&lt;/p>
&lt;p>如果有分区，即上面的命令有 &lt;code>/dev/sda1&lt;/code>、&lt;code>/dev/sda2&lt;/code>，建议输入 &lt;code>fdisk /dev/sda&lt;/code>，进入磁盘管理控制台。然后输入 &lt;code>d&lt;/code> + 回车删除分区，多个分区需要多次 &lt;code>d&lt;/code> 指令。&lt;/p>
&lt;blockquote>
&lt;p>电脑如果有两个磁盘，或者只有一个磁盘但是已经安装了 Windows 系统，可能会导致 &lt;code>archinstall&lt;/code> 无法找到该磁盘，但是 &lt;code>fdisk&lt;/code> 能够列出该磁盘，所以可能需要同样的方式删除磁盘分区（待验证）。&lt;/p>&lt;/blockquote>
&lt;h2 id="安装">安装&lt;/h2>
&lt;p>网络和磁盘配置好之后，可以键入 &lt;code>archinstall&lt;/code> + 回车，来安装 Archlinux 了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="07.enter-archinstall.png"
alt="enter-archinstsall"/>&lt;/p>
&lt;blockquote>
&lt;p>如果一直卡在 &lt;code>Check version&lt;/code>，可以使用 &lt;code>Ctrl&lt;/code> + &lt;code>C&lt;/code> 先终止安装，再输入 &lt;code>archinstall --skip-version-check&lt;/code> 来禁止版本检查。&lt;/p>&lt;/blockquote>
&lt;p>进入 &lt;code>archinstall&lt;/code> 的页面如上所示。&lt;/p>
&lt;p>&lt;code>Archinstall language&lt;/code> 不作修改，点击 &lt;code>↓&lt;/code> 或 &lt;code>j&lt;/code> 选择 &lt;code>Mirrors&lt;/code>，回车进入后选择 &lt;code>Mirror region&lt;/code> 并回车。等待一小会儿之后，键入 &lt;code>/china&lt;/code>，搜索国内镜像，并回车选择。&lt;/p>
&lt;p>&lt;img loading="lazy" src="08.mirrors-select.gif"
alt="mirrors-select"/>&lt;/p>
&lt;p>按 &lt;code>j&lt;/code> 到 &lt;code>← Back&lt;/code> 并回车。进入 &lt;code>Locales&lt;/code> 设置，需要更改的是 &lt;code>Locale language&lt;/code>，进入后，选择 &lt;code>zh-cn.UTF8&lt;/code>。&lt;/p>
&lt;p>&lt;img loading="lazy" src="09.locale-select.gif"
alt="locale-select"/>&lt;/p>
&lt;p>然后是磁盘配置。直接使用建议配置就行，因为之前已经做了删除分区的操作，所以执行安装时应该不会出错了。磁盘格式可以按自己需要选择，此处选择 &lt;code>btrfs&lt;/code>。&lt;/p>
&lt;p>&lt;img loading="lazy" src="10.disk-configuration.gif"
alt="disk-configuration"/>&lt;/p>
&lt;p>主机名可以按需求更改，此处不变。&lt;/p>
&lt;p>&lt;code>Root Password&lt;/code> 即 root 用户密码，建议设置一个。&lt;code>User account&lt;/code> 里，设置一个用户，是否有 &lt;code>sudo&lt;/code> 权限可以根据自己需要来，为了方便我设置了 &lt;code>sudo&lt;/code> 权限。&lt;/p>
&lt;p>&lt;img loading="lazy" src="11.user-set.gif"
alt="user-set"/>&lt;/p>
&lt;p>&lt;code>Profiles&lt;/code> 选择桌面类型，当然如果是有其他需要，也可以不直接通过 &lt;code>archinstall&lt;/code> 安装桌面环境。桌面环境挺多的，具体可以在 &lt;a href="https://wiki.archlinuxcn.org/wiki/Category:%E6%A1%8C%E9%9D%A2%E7%8E%AF%E5%A2%83"target="_blank" rel="noopener noreferrer">分类:桌面环境&lt;/a> 查看，此处选择 Kde Plasma。&lt;/p>
&lt;p>&lt;img loading="lazy" src="12.profile-set.gif"
alt="profile-set"/>&lt;/p>
&lt;p>&lt;code>Audio&lt;/code> 可以 &lt;code>No audio server&lt;/code>，但还是建议随意选一个。&lt;/p>
&lt;p>&lt;code>Kernels&lt;/code> 即内核，建议多选择一个 &lt;code>linux-zen&lt;/code>，方便之后安装 &lt;code>waydroid&lt;/code> 并运行安卓应用。&lt;/p>
&lt;p>&lt;code>Network configuration&lt;/code> 建议如果选择安装了桌面环境，就使用 &lt;code>Use NetworkManager&lt;/code>，这样可以使有无线网卡的主板，连接 WIFI。&lt;/p>
&lt;p>&lt;code>Timezone&lt;/code> 选择上海。&lt;/p>
&lt;p>最终的配置如下所示。&lt;/p>
&lt;p>&lt;img loading="lazy" src="13.end-archinstall-configuration.png"
alt="end-archinstall-configuration"/>&lt;/p>
&lt;p>选择 &lt;code>Install&lt;/code>，然后回车，Archlinux 就正式开始安装了。安装过程中尽量保证网络畅通。&lt;/p>
&lt;p>经过一段时间的等待，安装便完成了，会提示是否进入控制界面，选择进入。&lt;/p>
&lt;p>&lt;img loading="lazy" src="14.install-end.png"
alt="install-end"/>&lt;/p>
&lt;p>Archlinux 没有自带中文字体，所以使用 pacman 安装一下中文字体：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pacman -S noto-fonts-cjk
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>安装完成后，输入 &lt;code>exit&lt;/code> 退出 chroot 模式，然后输入 &lt;code>poweroff&lt;/code> 关机，将 U 盘拔掉，再开机。&lt;/p>
&lt;h2 id="配置系统">配置系统&lt;/h2>
&lt;p>再次启动后，会进入到 ssdm 的启动页，如下所示，说明我们已经成功安装了 Archlinux，接下来就是配置系统的时候了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="15.enter-greeter.png"
alt="enter-ssdm"/>&lt;/p>
&lt;blockquote>
&lt;p>如果觉得当前的启动页与 Kde 的风格不符合，可以进入 &lt;code>系统设置-&amp;gt;颜色和主题-&amp;gt;登录屏幕(ssdm)&lt;/code>，选择 &lt;code>Breeze 微风&lt;/code>并应用即可。
&lt;img loading="lazy" src="15.1.modify-ssdm-theme.png"
alt="modify-ssdm-theme"/>&lt;/p>&lt;/blockquote>
&lt;p>输入密码进入桌面，我们依次打开 &lt;code>左下角菜单-&amp;gt;系统-&amp;gt;Konsole 终端&lt;/code>。&lt;/p>
&lt;blockquote>
&lt;p>因为在 archinstall 中，已经修改了镜像地区，所以后续使用 pacman 或 paru 时，并不需要修改地区。如果出现安装卡住，大部分时候是因为 aur 上软件包安装文件的指定服务器，国内无法访问或者访问过慢。&lt;/p>&lt;/blockquote>
&lt;h3 id="安装浏览器">安装浏览器&lt;/h3>
&lt;p>首先需要安装的是浏览器，火狐其实就很不错，输入下面的命令安装火狐浏览器：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo pacman -S firefox
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输入密码和 &lt;code>y&lt;/code> 并回车继续安装。&lt;/p>
&lt;p>&lt;img loading="lazy" src="16.install-firefox.png"
alt="install-firefox"/>&lt;/p>
&lt;h3 id="安装-paru">安装 paru&lt;/h3>
&lt;p>安装 &lt;code>git&lt;/code> 和 &lt;code>rustup&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo pacman -S git rustup
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>打开浏览器，在地址栏输入 &lt;code>http://rsproxy.cn/&lt;/code> 或者输入 &lt;code>rsproxy&lt;/code> 并搜索，进入 rsproxy 的网站。按照教程修改 &lt;code>.bashrc&lt;/code>，然后输入下面的命令安装 rust：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">rustup install stable
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>安装完成之后，按照 rsproxy 的教程，修改 &lt;code>~/.cargo/config&lt;/code> 或 &lt;code>~/.cargo/config.toml&lt;/code> 的内容。&lt;/p>
&lt;blockquote>
&lt;p>修改文件内容时，可能会出现找不到 &lt;code>vi&lt;/code> 指令，这是因为 Archlinux 不会默认将 &lt;code>vim&lt;/code> 指定 &lt;code>vi&lt;/code> 别称。&lt;/p>&lt;/blockquote>
&lt;p>打开 &lt;a href="https://github.com/Morganamilo/paru"target="_blank" rel="noopener noreferrer">paru 的 github 仓库&lt;/a>，找到 &lt;code>Install&lt;/code> 出，复制下面的命名开始安装 paru。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo pacman -S --needed base-devel
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git clone https://aur.archlinux.org/paru.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> paru
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">makepkg -si
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="安装输入法">安装输入法&lt;/h3>
&lt;p>输入法选择安装 fcitx5，即小企鹅 5。输入下面的命令安装输入法所需的所有软件包：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">paru -S fcitx5-im fcitx5-chinese-addons
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>除了 &lt;code>fcitx5-chinese-addons&lt;/code>，也可以查看 &lt;a href="https://wiki.archlinuxcn.org/wiki/Fcitx5"target="_blank" rel="noopener noreferrer">fcitx5 的 archlinux 页面&lt;/a>，安装其他中文输入法组件。
同时可以寻找想要安装的词库、皮肤或其他组件，也可以去搜索引擎中搜索。&lt;/p>&lt;/blockquote>
&lt;p>安装全部内容完成之后，进入系统设置。打开&lt;code>键盘-&amp;gt;虚拟键盘&lt;/code>，选择 &lt;code>Fcitx5&lt;/code> 并应用。应用之后会提示是否启用云拼音，可以根据自己需要选择。&lt;/p>
&lt;p>&lt;img loading="lazy" src="17.select-virsual-keyboard-fcitx5.png"
alt="select-virsual-keyboard-fcitx5"/>&lt;/p>
&lt;p>现在在可以输入的地方按下 &lt;code>Ctrl&lt;/code> + &lt;code>Space&lt;/code>，就可以看到光标旁的 &lt;code>en&lt;/code> 变成了 &lt;code>拼&lt;/code>，就可以输入中文了，同时也可以再次按下 &lt;code>Ctrl&lt;/code> + &lt;code>Space&lt;/code> 切换回英文输入。&lt;/p>
&lt;h4 id="配置输入法可选">配置输入法（可选）&lt;/h4>
&lt;p>返回系统设置，找到区域和语言，选择输入法。点击配置全局选项，将 &lt;code>切换启用/禁用输入法&lt;/code> 的快捷键修改为自己习惯的按键，此处我修改成了 &lt;code>Shift&lt;/code> 键。&lt;/p>
&lt;p>&lt;img loading="lazy" src="17.1.config-fcitx5.png"
alt="config-fcitx5"/>&lt;/p>
&lt;p>修改好后，应用并返回，找到 &lt;code>配置附加组件...-&amp;gt;经典用户界面&lt;/code>，往下翻找到 &lt;code>主题&lt;/code>，在这里可以修改输入法的主题。&lt;/p>
&lt;p>&lt;img loading="lazy" src="17.2.config-fcitx5-theme.png"
alt="config-fcitx5-theme"/>&lt;/p>
&lt;h3 id="安装-qq安装示例">安装 QQ（安装示例）&lt;/h3>
&lt;p>QQ 的安装比较典型，因为 QQ 在安装之后无法正常使用输入法。&lt;/p>
&lt;p>安装 QQ 可以使用下面的命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">paru -S linuxqq
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>执行时会提示有几个不同的软件包：&lt;/p>
&lt;p>&lt;img loading="lazy" src="18.execute-paru-s-linuxqq.png"
alt="execute-paru-s-linuxqq"/>&lt;/p>
&lt;p>不确定的话，可以上 &lt;a href="https://aur.archlinux.org/"target="_blank" rel="noopener noreferrer">Aur&lt;/a> 上搜索 &lt;code>linuxqq&lt;/code>，看看几个软件包有什么区别：&lt;/p>
&lt;p>&lt;img loading="lazy" src="19.search-linuxqq.png"
alt="search-linuxqq"/>&lt;/p>
&lt;p>QQ 在之前发布过 NT 版，支持多平台。而在 &lt;a href="https://aur.archlinux.org/"target="_blank" rel="noopener noreferrer">Aur&lt;/a> 上，&lt;code>linuxqq&lt;/code> 被标记过期，那么其实最好是选择其他的软件包安装。其中 &lt;code>linuxqq-nt-bwrap&lt;/code> 和 &lt;code>linuxqq-appimage&lt;/code> 任选其中一个安装即可。此处选择 2，即 &lt;code>linuxqq-appimage&lt;/code>。&lt;/p>
&lt;p>安装完成，打开并登录 QQ，此时如果想发消息，会发现切换不了输入法。&lt;/p>
&lt;p>退出 QQ，打开左下角菜单，找到 QQ，&lt;code>右键-&amp;gt;编辑应用程序...-&amp;gt;应用程序(A)&lt;/code>，看到参数一栏，在后面添加下面的参数：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">--enable-features&lt;span class="o">=&lt;/span>UseOzonePlatform --ozone-platform&lt;span class="o">=&lt;/span>wayland --enable-wayland-ime
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后就可以正常使用输入法了。&lt;/p>
&lt;h2 id="结束">结束&lt;/h2>
&lt;p>剩下的都可以自己慢慢摸索了。&lt;/p>
&lt;p>想要的软件包可以在 &lt;a href="https://www.archlinux.org/packages/"target="_blank" rel="noopener noreferrer">Packages - Archlinux&lt;/a> 和 &lt;a href="https://aur.archlinux.org/"target="_blank" rel="noopener noreferrer">Aur&lt;/a> 上安装，Kde 社区的软件可以上 &lt;a href="https://apps.kde.org/zh-cn/"target="_blank" rel="noopener noreferrer">Kde 应用软件官网&lt;/a> 上查找。&lt;/p>
&lt;p>也可以看看 &lt;a href="https://arch.icekylin.online/guide/"target="_blank" rel="noopener noreferrer">Archlinux 简明指南&lt;/a> 和 &lt;a href="https://wiki.archlinux.org/"target="_blank" rel="noopener noreferrer">Archlinux wiki&lt;/a>（&lt;a href="https://wiki.archlinuxcn.org/wiki/%E9%A6%96%E9%A1%B5"target="_blank" rel="noopener noreferrer">Archlinux 中文维基&lt;/a>）。&lt;/p></description></item><item><title>ItemsControl 组件的 Item 获取自己的 Index</title><link>https://study_less_shape.github.io/csharp/wpf/item-of-itemscontrol-get-self-index/</link><pubDate>Fri, 23 Feb 2024 20:47:28 +0800</pubDate><guid>https://study_less_shape.github.io/csharp/wpf/item-of-itemscontrol-get-self-index/</guid><description>&lt;h2 id="目的">目的&lt;/h2>
&lt;p>该功能的目的是为了获取 Item 自身的 Index，并可以绑定到数据模板中。&lt;/p>
&lt;h2 id="创建项目">创建项目&lt;/h2>
&lt;p>创建一个 WPF 应用程序项目，并添加下面的依赖&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit"target="_blank" rel="noopener noreferrer">MaterialDesignThemes&lt;/a>
&lt;blockquote>
&lt;p>注意按照 &lt;a href="https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/wiki/Getting-Started"target="_blank" rel="noopener noreferrer">Getting Start&lt;/a> 来配置主题&lt;/p>&lt;/blockquote>
&lt;/li>
&lt;/ul>
&lt;h2 id="添加-itemscontrol">添加 ItemsControl&lt;/h2>
&lt;p>将 &lt;code>Grid&lt;/code> 分为两列，左列添加一个 &lt;code>ItemsControl&lt;/code>，并将 &lt;code>ItemsControl.ItemsPanel&lt;/code> 中 &lt;code>ItemsPanelTemplate&lt;/code> 的内容设置为 &lt;code>WarpPanel&lt;/code>，这样，子元素就能够自己排列了。同时将 &lt;code>ItemsControl.ItemTemplate&lt;/code> 的模板设置为 &lt;code>DataTemplate&lt;/code>。&lt;/p>
&lt;p>现在的代码应该和下面一样，设计器中应该可以看见 &lt;code>Grid&lt;/code> 已经被分成两列了。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid.ColumnDefinitions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ColumnDefinition/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ColumnDefinition/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Grid.ColumnDefinitions&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl.ItemsPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsPanelTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;WrapPanel/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ItemsPanelTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ItemsControl.ItemsPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl.ItemTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;DataTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/DataTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ItemsControl.ItemTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ItemsControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个 &lt;code>ItemsControl&lt;/code> 所需要做的功能，除了显示每个 Item 自身的 Index，我还希望能够选中并显示到第二列中。&lt;/p>
&lt;p>在 &lt;a href="https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit"target="_blank" rel="noopener noreferrer">MaterialDesignThemes&lt;/a> 的 &lt;a href="https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/releases/tag/v4.9.0"target="_blank" rel="noopener noreferrer">DemoApp&lt;/a> 中，目录中有一栏叫 Chips，用来做为子控件感觉还不错，复制 Filter Chips 中的代码，添加到 &lt;code>DataTemplate&lt;/code> 中。&lt;/p>
&lt;p>把复制过来的代码中的 Content 删除，并将 &lt;code>TextBlock&lt;/code> 填入 Content 中，Text 的内容暂时保留。代码应该如下所示：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ItemsControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl.ItemsPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsPanelTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;WrapPanel&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ItemsPanelTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ItemsControl.ItemsPanel&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl.ItemTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;DataTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;CheckBox&lt;/span> &lt;span class="na">IsChecked=&lt;/span>&lt;span class="s">&amp;#34;True&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Style=&lt;/span>&lt;span class="s">&amp;#34;{StaticResource MaterialDesignFilterChipPrimaryOutlineCheckBox}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/CheckBox&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/DataTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ItemsControl.ItemTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ItemsControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>现在还没有东西显示出来，为了能够显示一点东西，先为 &lt;code>ItemsControl&lt;/code> 添加一个硬编码的 ItemSource。&lt;/p>
&lt;p>为了添加的内容较为简单，直接用数组即可，元素则使用字符串。为了使用基本类型，首先需要将 &lt;code>System&lt;/code> 命名空间添加到 XAML 当中，此处将其引入名称指定为 &lt;code>sys&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="na">x:Class=&lt;/span>&lt;span class="s">&amp;#34;IndexWithItemsControl.MainWindow&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">[...]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:sys=&lt;/span>&lt;span class="s">&amp;#34;clr-namespace:System;assembly=mscorlib&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接下来为 &lt;code>ItemsControl&lt;/code> 添加硬编码的 ItemSource。数组使用 &lt;code>x:Array&lt;/code> 标记，字符串我们使用 &lt;code>sys:String&lt;/code> 标记。&lt;/p>
&lt;p>随便添加一点元素就行。我的代码如下，简单的添加了三个 &lt;code>String&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ItemsControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl.ItemsSource&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;x:Array&lt;/span> &lt;span class="na">Type=&lt;/span>&lt;span class="s">&amp;#34;{x:Type sys:String}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:String&amp;gt;&lt;/span>a&lt;span class="nt">&amp;lt;/sys:String&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:String&amp;gt;&lt;/span>b&lt;span class="nt">&amp;lt;/sys:String&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;sys:String&amp;gt;&lt;/span>c&lt;span class="nt">&amp;lt;/sys:String&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/x:Array&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ItemsControl.ItemsSource&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl.ItemsPanel&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl.ItemTemplate&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ItemsControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这时候已经能看到设计器上面有三个 Chips 按钮了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="add-itemscontrol.png"
alt=""/>&lt;/p>
&lt;h2 id="添加-converter-来获取-index">添加 Converter 来获取 Index&lt;/h2>
&lt;p>为了获取 Index，我们需要找一下 &lt;code>ItemsControl&lt;/code> 的&lt;a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.controls.itemscontrol?view=windowsdesktop-8.0"target="_blank" rel="noopener noreferrer">文档&lt;/a>，但是里面好像并没有想要的东西。&lt;/p>
&lt;p>不过有一个属性比较令人在意，叫 &lt;a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.controls.itemscontrol.itemcontainergenerator?view=windowsdesktop-8.0"target="_blank" rel="noopener noreferrer">ItemContainerGenerator&lt;/a>。&lt;/p>
&lt;p>点开这个属性的类型 &lt;a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.controls.itemcontainergenerator?view=windowsdesktop-8.0"target="_blank" rel="noopener noreferrer">ItemContainerGenerator 类&lt;/a>看看，发现里面有一个函数叫做 &lt;a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.controls.itemcontainergenerator.indexfromcontainer?view=windowsdesktop-8.0#system-windows-controls-itemcontainergenerator-indexfromcontainer%28system-windows-dependencyobject%29"target="_blank" rel="noopener noreferrer">IndexFromContainer&lt;/a>。&lt;/p>
&lt;p>稍微理一下，首先是 &lt;code>ItemsControl&lt;/code> 中有一个 &lt;code>ItemContainerGenerator&lt;/code>，每当 &lt;code>ItemsControl&lt;/code> 的 ItemSource 更新时，应该会通过这个 Generator 来生成 UI。&lt;/p>
&lt;p>那么现在通过 &lt;code>IndexFromContainer&lt;/code> 这个函数，我们应该就可以拿到 Item 对应控件的 Index 了，这个 Index 同样也是 Item 的 Index。但是有一个问题是，这个传入的参数，即当前 Item 对应控件，该如何获取？&lt;/p>
&lt;p>回到 Visual Studio 的项目。我们为 &lt;code>TextBlock&lt;/code> 加一个绑定，但是特殊的是这个绑定只写一个语法，并不指定其他的东西，甚至是 &lt;code>Path&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;ItemsControl.ItemTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;DataTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;CheckBox&lt;/span> &lt;span class="na">IsChecked=&lt;/span>&lt;span class="s">&amp;#34;True&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Style=&lt;/span>&lt;span class="s">&amp;#34;{StaticResource MaterialDesignFilterChipPrimaryOutlineCheckBox}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&lt;/span> &lt;span class="na">Text=&lt;/span>&lt;span class="s">&amp;#34;{Binding}&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/CheckBox&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/DataTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/ItemsControl.ItemTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>看向设计器，一个令人兴奋的效果出现了，我们数组中的元素出现在了 Text 中。&lt;/p>
&lt;p>&lt;img loading="lazy" src="binding-show-textblock.png"
alt=""/>&lt;/p>
&lt;p>有这么个想法产生了，如果我们获取 &lt;code>DataTemplate&lt;/code> 或者 &lt;code>CheckBox&lt;/code> 控件，但是不指定其 Path 来绑定，那么传入的不就是控件自身了吗？&lt;/p>
&lt;p>为了调用 &lt;code>IndexFromContainer&lt;/code> 函数，还需要获得 &lt;code>ItemsControl&lt;/code> 的 &lt;code>ItemContainerGenerator&lt;/code>。所以需要用到 &lt;code>MultiBinding&lt;/code>，这个绑定和 &lt;code>Binding&lt;/code> 的区别在于，它可以绑定多个数据。&lt;/p>
&lt;p>那么事不宜迟，开始为 &lt;code>TextBlock&lt;/code> 来进行 &lt;code>MultiBinding&lt;/code>。其中的 &lt;code>Binding&lt;/code>，分别绑定 &lt;code>ItemsControl&lt;/code> 的 &lt;code>ItemContainerGenerator&lt;/code> 和当前控件。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock.Text&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;MultiBinding&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Binding&lt;/span> &lt;span class="na">RelativeSource=&lt;/span>&lt;span class="s">&amp;#34;{RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&amp;lt;/Binding&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Binding&lt;/span> &lt;span class="na">RelativeSource=&lt;/span>&lt;span class="s">&amp;#34;{RelativeSource Mode=FindAncestor,AncestorType=CheckBox}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&amp;lt;/Binding&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/MultiBinding&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/TextBlock.Text&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>不过绑定失败了，提示我们需要一个 MultiConverter，而且我们发现我们虽然拿到了控件，但是并不能运行需要的函数。&lt;/p>
&lt;p>为了先使绑定成功，先新建一个 Converter，类的名字命名为 &lt;code>IndexOfItemsControlConverter&lt;/code>。类的内容应如下所示。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Globalization&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="nn">System.Windows.Data&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="nn">IndexWithItemsControl.Converters&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">IndexOfItemsControlConverter&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">IMultiValueConverter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">NotImplementedException&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">ConvertBack&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">targetTypes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">NotImplementedException&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>为 Converter 实现了 &lt;code>IMultiValueConverter&lt;/code> 接口，这样就可以让 &lt;code>MultiBinding&lt;/code> 使用此 Converter。&lt;/p>
&lt;blockquote>
&lt;p>Converter 的使用可以查看 &lt;a href="https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/data/how-to-convert-bound-data?view=netframeworkdesktop-4.8"target="_blank" rel="noopener noreferrer">如何：转换绑定的数据&lt;/a>&lt;/p>&lt;/blockquote>
&lt;p>我们可以将返回值直接返回 &lt;code>values&lt;/code>，并在 &lt;code>Convert&lt;/code> 函数的 return 处打上断点。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">ConvertBack&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span> &lt;span class="k">value&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">targetTypes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">value&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="kt">object&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">list&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">list&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="kt">object&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="k">value&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后将该 Converter 应用到刚才 &lt;code>TextBlock.Text&lt;/code> 的绑定中。需要首先引入命名空间，然后将 Converter 添加到资源当中，并设置它的 Key。最后添加到绑定中。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Window&lt;/span> &lt;span class="err">[...]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:converter=&lt;/span>&lt;span class="s">&amp;#34;clr-namespace:IndexWithItemsControl.Converters&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;converter:IndexOfItemsControlConverter&lt;/span> &lt;span class="na">x:Key=&lt;/span>&lt;span class="s">&amp;#34;IndexOfItemsControlConverter&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Window.Resources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Grid.ColumnDefinitions&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl.ItemsSource&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl.ItemsPanel&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ItemsControl.ItemTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;DataTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;CheckBox&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TextBlock.Text&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;MultiBinding&lt;/span> &lt;span class="na">Converter=&lt;/span>&lt;span class="s">&amp;#34;{StaticResource IndexOfItemsControlConverter}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Binding&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Binding&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/MultiBinding&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/TextBlock.Text&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/TextBlock&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/CheckBox&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/DataTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ItemsControl.ItemTemplate&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/ItemsControl&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Grid&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Window&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>现在调试运行一下，看绑定的控件是否都传入了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="convert-values.png"
alt=""/>&lt;/p>
&lt;p>如果和上图一样，都是这 &lt;code>ItemsControl&lt;/code> 和 &lt;code>CheckBox&lt;/code> 类型，那么说明绑定成功了。如果调试步骤没有进入 Convert，或者 &lt;code>values&lt;/code> 的内容有区别，需要检查一下绑定是否是按照上面的来的。&lt;/p>
&lt;blockquote>
&lt;p>你可能注意到了有几个绑定失败的提示，这是因为在 Convert 中返回的 &lt;code>values&lt;/code> 在退出函数之后被销毁了，所以 &lt;code>TextBlock.Text&lt;/code> 拿到了一个空值。&lt;/p>
&lt;p>如果需要拿到非空值，需要使用 &lt;code>values.Clone()&lt;/code>。&lt;/p>&lt;/blockquote>
&lt;p>现在需要的内容都有了，开始获取 Index 吧！&lt;/p>
&lt;p>在 Convert 函数中，先把 &lt;code>ItemsControl&lt;/code> 和 &lt;code>CheckBox&lt;/code> 选出来。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ItemsControl&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">itemsControl&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CheckBox&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">checkBox&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">val&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">val&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="n">ItemsControl&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">itemsControl&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">val&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="n">CheckBox&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">checkBox&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后，调用方法来获取 Index。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">itemsControl&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">checkBox&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">index&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">itemsControl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ItemContainerGenerator&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexFromContainer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">checkBox&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">index&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>生成一下项目，回到设计器，会发现 Text 还是没有内容。&lt;/p>
&lt;p>在 &lt;code>var index = ...&lt;/code> 处打上断点，然后调试执行。看向 &lt;code>Index&lt;/code>，会发现其值为 -1。&lt;/p>
&lt;p>&lt;img loading="lazy" src="convert-error-index.png"
alt=""/>&lt;/p>
&lt;p>这显然不符合我们的需求，&lt;code>IndexFromContainer&lt;/code> 并没有找到我们需要的 Index，同时取消断点，会发现绑定又失败了，绑定的值又变为了 &lt;code>null&lt;/code>。&lt;/p>
&lt;p>看向官方文档 &lt;a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.data.relativesourcemode?view=windowsdesktop-8.0#fields"target="_blank" rel="noopener noreferrer">RelativeSourceMode Enum&lt;/a>，有一项叫做 &lt;code>TemplatedParent&lt;/code>。翻译看看描述。&lt;/p>
&lt;p>&lt;img loading="lazy" src="mode-templated-parent.png"
alt=""/>&lt;/p>
&lt;p>大概意思是能够获取到绑定的模板控件，那么将绑定的内容修改一下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;MultiBinding&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Binding&lt;/span>&lt;span class="err">[...]&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Binding&lt;/span> &lt;span class="na">RelativeSource=&lt;/span>&lt;span class="s">&amp;#34;{RelativeSource Mode=TemplatedParent}&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&amp;lt;/Binding&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/MultiBinding&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>同时在 Convert 方法中打一下断点，看看其内容。&lt;/p>
&lt;p>&lt;img loading="lazy" src="templated-parent-type.png"
alt=""/>&lt;/p>
&lt;p>发现它的类型是 &lt;code>ContentPresenter&lt;/code>。修改 Convert 函数的内容。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-csharp" data-lang="csharp">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">public&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">Convert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">object&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Type&lt;/span> &lt;span class="n">targetType&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">object&lt;/span> &lt;span class="n">parameter&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">CultureInfo&lt;/span> &lt;span class="n">culture&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ItemsControl&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">itemsControl&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// modify&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ContentPresenter&lt;/span>&lt;span class="p">?&lt;/span> &lt;span class="n">content&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">var&lt;/span> &lt;span class="n">val&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="n">values&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">val&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="n">ItemsControl&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">itemsControl&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// modify&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">val&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="n">ContentPresenter&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">c&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// modify&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">itemsControl&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span> &lt;span class="p">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">content&lt;/span> &lt;span class="p">!=&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// modify&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">var&lt;/span> &lt;span class="n">index&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">itemsControl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ItemContainerGenerator&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">IndexFromContainer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s">$&amp;#34;{index}&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>再次生成项目，回到设计器，应该可以看到 Text 的内容变成 Index 了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="item-index-in-view.png"
alt=""/>&lt;/p>
&lt;p>当然可以返回时 &lt;code>+1&lt;/code>，即 &lt;code>return $&amp;quot;{index + 1}&amp;quot;;&lt;/code> ，这样显示的起始 Index 就是 1 了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="after-index-add-one.png"
alt=""/>&lt;/p>
&lt;h2 id="附">附&lt;/h2>
&lt;p>仓库链接：&lt;a href="https://github.com/studylessshape/WPFUserRecord/tree/main/IndexWithItemsControl"target="_blank" rel="noopener noreferrer">IndexWithItemsControl&lt;/a>&lt;/p>
&lt;p>文档列表：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/wiki/Getting-Started"target="_blank" rel="noopener noreferrer">MaterialDesignThemes - Getting Start&lt;/a>&lt;/li>
&lt;li>&lt;a href="%28https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.controls.itemscontrol?view=windowsdesktop-8.0%29">ItemsControl&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.controls.itemcontainergenerator?view=windowsdesktop-8.0"target="_blank" rel="noopener noreferrer">ItemContainerGenerator 类&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/data/how-to-convert-bound-data?view=netframeworkdesktop-4.8"target="_blank" rel="noopener noreferrer">如何：转换绑定的数据&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.data.relativesourcemode?view=windowsdesktop-8.0"target="_blank" rel="noopener noreferrer">RelativeSourceMode Enum&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>WPF 使用记录目录</title><link>https://study_less_shape.github.io/post/note/wpf-use-record/</link><pubDate>Fri, 23 Feb 2024 20:42:48 +0800</pubDate><guid>https://study_less_shape.github.io/post/note/wpf-use-record/</guid><description>&lt;h2 id="说明">说明&lt;/h2>
&lt;p>最近正在做 WPF 的项目，制作的时候有些功能的实现比较难，但是实际上编写出来之后也没有特别多的代码。&lt;/p>
&lt;p>不过由于网络上能找到的资料很少，大部分都是网上找到的方法没有效果之后，通过一些其他较为复杂的方法实现了。&lt;/p>
&lt;p>所有用到的参考文档和资料除了在每篇博客文章中外，还会在此目录页做汇总。相关的项目有时间会上传。&lt;/p>
&lt;p>所有示例项目用的版本为 &lt;code>.net 6.0&lt;/code>。&lt;/p>
&lt;h3 id="用到的依赖">用到的依赖&lt;/h3>
&lt;p>为了方便快速开发，所有的项目基本都会用到下面的依赖：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/CommunityToolkit/dotnet"target="_blank" rel="noopener noreferrer">CommunityToolkit.Mvvm&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit"target="_blank" rel="noopener noreferrer">MaterialDesignThemes&lt;/a>
&lt;blockquote>
&lt;p>MaterialDesignThemes 需要根据 &lt;a href="https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/wiki/Getting-Started"target="_blank" rel="noopener noreferrer">Getting Start&lt;/a> 来进行配置。&lt;/p>
&lt;p>同时其 Demo 程序是重要的参考，如果和我一样使用 Release 版，可以直接去 Github 的 Release 中下载 DemoApp，如果想使用最新版的功能，可以克隆整个项目，并直接运行 Demo 项目。&lt;/p>&lt;/blockquote>
&lt;/li>
&lt;/ul>
&lt;h2 id="目录">目录&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="../../../csharp/wpf/item-of-itemscontrol-get-self-index">ItemsControl 组件的 Item 获取自己的 Index&lt;/a>&lt;/li>
&lt;li>&lt;a href="">（紧接上一篇）将多个参数传入 Command 中&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>使用条件格式设置介于最大最小值之间的单元格格式</title><link>https://study_less_shape.github.io/post/note/excel-condition-style-set-inner-value-block/</link><pubDate>Fri, 02 Feb 2024 20:20:45 +0800</pubDate><guid>https://study_less_shape.github.io/post/note/excel-condition-style-set-inner-value-block/</guid><description>&lt;p>公式参考&lt;a href="https://zhuanlan.zhihu.com/p/29887818"target="_blank" rel="noopener noreferrer">公式中如何固定获取相对单元格的值&lt;/a>&lt;/p>
&lt;h2 id="问题">问题&lt;/h2>
&lt;p>之前没怎么使用过 Excel，在遇到复杂的需求的时候就需要研究很久，所以这次记录下来。&lt;/p>
&lt;p>情况是这样的，给一个最小值和最大值，介于两者之间的值的单元格，统一设置一个填充颜色。&lt;/p>
&lt;h2 id="使用条件格式">使用条件格式&lt;/h2>
&lt;p>网上查找很容易就找到了条件格式。&lt;/p>
&lt;p>&lt;img loading="lazy" src="condition-style.png"
alt=""/>&lt;/p>
&lt;p>假设现在有以下表格，要做的就是将下面的满足条件的值设置一个颜色。（图片使用的是 WPS，但 LibreOffice 和 Microsoft Office 的操作方式基本一致）&lt;/p>
&lt;p>&lt;img loading="lazy" src="table-no-style.png"
alt=""/>&lt;/p>
&lt;p>首先选中下面 1-29 所有的单元格，然后点击&lt;strong>条件格式&lt;/strong> -&amp;gt; &lt;strong>新建规则&lt;/strong>。应该会出现下图所示的弹窗。&lt;/p>
&lt;p>&lt;img loading="lazy" src="new-condition.png"
alt=""/>&lt;/p>
&lt;p>然后选择第二项&lt;code>只为包含以下内容的单元格设置格式&lt;/code>，第一个输入框可以输入最小值，也可以如下图一样选择一个单元格的值，第二个输入框同理，输入的是最大值。输入框下的格式就是配置满足条件后，单元格会被设置的格式。此处设置了单元格的颜色为淡青色。&lt;/p>
&lt;p>&lt;img loading="lazy" src="select-block.png"
alt=""/>&lt;/p>
&lt;p>点击确定后，应该可以看到满足条件的单元格变色了，并且如果改变选择的最大最小值，可以看到单元格颜色的变化。&lt;/p>
&lt;p>&lt;img loading="lazy" src="condition-style-set.gif"
alt=""/>&lt;/p>
&lt;p>利用同样的方式，可以设置更多的值的范围，最终可能得到的效果如下图。&lt;/p>
&lt;p>&lt;img loading="lazy" src="set-result.png"
alt=""/>&lt;/p>
&lt;h2 id="另一种需求">另一种需求？&lt;/h2>
&lt;p>现在出现了另一种情况，并不是每个单元格都有一个值，如下图，但是我们需要根据有数值的那一行和有数值的那一列来设置中间没有数值的单元格格式。&lt;/p>
&lt;p>&lt;img loading="lazy" src="another-problem.png"
alt=""/>&lt;/p>
&lt;p>这时候就要用到四个公式（参考&lt;a href="https://zhuanlan.zhihu.com/p/29887818"target="_blank" rel="noopener noreferrer">公式中如何固定获取相对单元格的值&lt;/a>）：&lt;/p>
&lt;ul>
&lt;li>&lt;code>ROW()&lt;/code>：获取当前行号&lt;/li>
&lt;li>&lt;code>COLUMN()&lt;/code>：获取当前列号&lt;/li>
&lt;li>&lt;code>ADDRESS()&lt;/code>：根据行和列获取当前地址&lt;/li>
&lt;li>&lt;code>INDIRECT()&lt;/code>：从给定的地址取值&lt;/li>
&lt;/ul>
&lt;p>有了这四个公式，就可以拿到想要的单元格的值了。&lt;/p>
&lt;p>例如 &lt;code>INDIRECT(ADDRESS(ROW()-1,COLUMN()-1))&lt;/code>，就是查找自身上一行和上一列的单元格值。&lt;/p>
&lt;p>不过由于是根据自身单元格的行和列来查找的，所以需要一行一行或一列一列来设置。&lt;/p>
&lt;p>选择一行空行，然后新建规则，选择&lt;code>使用公式确定要设置的单元格&lt;/code>。将下面的公式复制到输入框中：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-excel" data-lang="excel">=AND(INDIRECT(ADDRESS(ROW()-1,COLUMN()))&amp;gt;=,INDIRECT(ADDRESS(ROW()-1,COLUMN()))&amp;lt;=)
&lt;/code>&lt;/pre>&lt;p>通过鼠标点击，将光标调整到大于等于号后面，然后选择最小值的单元格，同理后面的小于等于号，选择最大值的单元格，设置好格式后点击确定即可。&lt;/p>
&lt;p>整个过程可见下面的动图。&lt;/p>
&lt;p>&lt;img loading="lazy" src="condition-set-one-row.gif"
alt=""/>&lt;/p>
&lt;p>看起来效果还可以，但别忘了我们还有一列有数值的单元格。所以还需要一个 &lt;code>INDIRECT&lt;/code> 获取对应的值，并将两者加起来。获取第一列的值的公式如下：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-excel" data-lang="excel">=INDIRECT(ADDRESS(ROW(),1));
&lt;/code>&lt;/pre>&lt;p>如果不知道是第几列，&lt;code>1&lt;/code> 的位置可以替换成 &lt;code>COLUMN() - INDIRECT(ADDRESS(ROW()-1,COLUMN()))&lt;/code>，替换完的公式如下：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-excel" data-lang="excel">=INDIRECT(ADDRESS(ROW(),COLUMN() - INDIRECT(ADDRESS(ROW()-1,COLUMN()))));
&lt;/code>&lt;/pre>&lt;p>将获取第一列的值和上一行的值结合起来，可以得到下面的公式：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-excel" data-lang="excel">=AND(INDIRECT(ADDRESS(ROW()-1,COLUMN()))+INDIRECT(ADDRESS(ROW(),COLUMN() - INDIRECT(ADDRESS(ROW()-1,COLUMN()))))&amp;gt;=,INDIRECT(ADDRESS(ROW()-1,COLUMN()))+INDIRECT(ADDRESS(ROW(),COLUMN() - INDIRECT(ADDRESS(ROW()-1,COLUMN()))))&amp;lt;=)
&lt;/code>&lt;/pre>&lt;blockquote>
&lt;p>设置时，由于公式太长不太好将光标移动到想要的位置，可以用上面的 &lt;code>INDIRECT(ADDRESS(ROW(),1))&lt;/code> 替换 &lt;code>INDIRECT(ADDRESS(ROW(),COLUMN() - INDIRECT(ADDRESS(ROW()-1,COLUMN())))&lt;/code>。&lt;/p>&lt;/blockquote>
&lt;p>看起来很长，实际上就是将行值和列值加起来了。按照上面的方法设置条件格式，应该可以获得下面的效果：&lt;/p>
&lt;p>&lt;img loading="lazy" src="after-add-row-value.png"
alt=""/>&lt;/p>
&lt;p>同时应该注意到，每个单元格可以设置多个条件格式，所以在对每一行进行重复设置后，应该可以得到下面的效果。&lt;/p>
&lt;p>&lt;img loading="lazy" src="condition-set-row-col.gif"
alt=""/>&lt;/p>
&lt;h2 id="后记">后记&lt;/h2>
&lt;p>Excel 在使用的时候，无不感觉商业软件在易用性上的成功。但一旦需要一些奇怪或是复杂的需求，光靠 Excel 自身就非常困难。上面可以看到，其实就五个简单的公式，但是完成复杂需求时就会组合嵌套导致公式巨长无比。而且条件格式的公式输入框的光标移动键，被强行改成了单元格选择，这就导致修改更加不便，也不知道为什么这么设计，明明旁边已经有了一个切换单元格选择的按钮。&lt;/p>
&lt;p>当然这次后面的情况，实际上不太会出现，有的话应该就一行或一列，不太会出现列行同时的情况。&lt;/p>
&lt;p>Excel 示例表格可以&lt;a href="excel-condition-style.xlsx">点击此处获取&lt;/a>。&lt;/p></description></item><item><title>记一次使用 Github action 同步 Gitee 仓库到 Github</title><link>https://study_less_shape.github.io/post/note/record-github-action-for-gitee-to-github/</link><pubDate>Mon, 01 Jan 2024 11:07:34 +0800</pubDate><guid>https://study_less_shape.github.io/post/note/record-github-action-for-gitee-to-github/</guid><description>&lt;h1 id="原因">原因&lt;/h1>
&lt;p>开始尝试用 &lt;a href="https://vuepress.vuejs.org/zh/"target="_blank" rel="noopener noreferrer">vuepress&lt;/a> 编写文档，由于默认主题配置不是很方便，于是又尝试了 &lt;a href="https://theme-hope.vuejs.press/zh/"target="_blank" rel="noopener noreferrer">vuepress-theme-hope&lt;/a>，这个主题使用起来更舒服，配置更加方便快捷，顺带还提供了自动构建的 Github action。&lt;/p>
&lt;p>于是就想着把私有仓库中构建好的页面，提交到公开仓库去，这样就可以使用 Github page 来部署了。&lt;/p>
&lt;p>思路很简单，首先是将 Gitee 的仓库同步到 Github 中去，然后就可以尝试触发 &lt;a href="https://theme-hope.vuejs.press/zh/"target="_blank" rel="noopener noreferrer">vuepress-theme-hope&lt;/a> 的 Github action 来构建页面，构建好的页面存在另一个分支当中，把这个分支的提交同步给公开仓库就行了。&lt;/p>
&lt;h1 id="开始尝试同步">开始尝试同步&lt;/h1>
&lt;p>最简单的方式是先克隆 Gitee 的仓库，然后再提交到 Github。克隆 Gitee 仓库有两种方式，一种是通过 ssh，可以获取到提交记录；另一种是使用 Gitee 的 Open API，但这种方式不会有 Gitee 上的提交记录。&lt;/p>
&lt;p>首先要有一个 Github Token。在 Github 的设置里找到 &lt;code>Developer Settings&lt;/code>，选择 &lt;code>Personal access tokens&lt;/code> -&amp;gt; &lt;code>Tokens (classic)&lt;/code>，选择 &lt;code>Generate new token (classic)&lt;/code>。&lt;code>Expiration&lt;/code> 即有效期，根据自己的需求选择，我选择了无限期，然后下面的权限将 &lt;code>repo&lt;/code> 和 &lt;code>workflows&lt;/code> 全点上。&lt;/p>
&lt;p>&lt;img loading="lazy" src="git-token.png"
alt="git-token"/>&lt;/p>
&lt;p>&lt;strong>Token 一定不要泄漏！&lt;/strong>&lt;/p>
&lt;h2 id="ssh-克隆仓库">ssh 克隆仓库&lt;/h2>
&lt;p>首先需要生成一个 ssh 密钥。如何生成可以查看 Gitee 的文档&lt;a href="https://help.gitee.com/repository/ssh-key/generate-and-add-ssh-public-key"target="_blank" rel="noopener noreferrer">生成、添加 SSH 公钥&lt;/a>。使用的指令如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; ssh-keygen -t ed25519 -C &lt;span class="s2">&amp;#34;Gitee SSH Key&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>生成的密钥可以通过打印到控制台的信息查看。找到保存密钥的位置（windows 在 &lt;code>C:\Users\{user}\.ssh&lt;/code> 处，linux 位于 &lt;code>~/.ssh&lt;/code> 文件夹下）。带有 &lt;code>.pub&lt;/code> 后缀的为公钥，此处添加到了 Gitee 仓库的公钥中。设置为仓库的公钥可以保证这个公钥只用于克隆和同步仓库，可以增加安全性。&lt;/p>
&lt;p>&lt;img loading="lazy" src="public-ssh-key.png"
alt="公钥"/>&lt;/p>
&lt;p>在 Github 创建一个和 Gitee 仓库同名的仓库，并将私钥配置给对应的 Github 仓库。找到仓库设置，找到 &lt;code>Secrets and Variables&lt;/code>，点击 &lt;code>New repository secret&lt;/code>，名字随意，然后将私钥的内容复制进去。&lt;/p>
&lt;p>&lt;img loading="lazy" src="private-key-github.png"
alt="私钥"/>&lt;/p>
&lt;p>同理，之前生成的 Token 也要作为 secrets 配置进去。&lt;/p>
&lt;p>这一步完成之后，我们就可以尝试使用 ssh 来克隆仓库了。&lt;/p>
&lt;h3 id="测试-ssh">测试 ssh&lt;/h3>
&lt;p>按照 Gitee 的文档，接下来需要测试一下 ssh 连接是否能够联通。由于 Github action 的环境使用 linux 会更加简便，所以此处测试连接使用了 linux 环境。&lt;/p>
&lt;p>在 &lt;code>~/.ssh/id_ed25519&lt;/code> 中，将私钥复制进去。然后输入下面的命令测试连接：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; ssh -T git@gitee.com
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>回车执行之后，会有如下的提示内容：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Permissions &lt;span class="m">0777&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="s1">&amp;#39;/home/xueshihao/.ssh/id_ed25519&amp;#39;&lt;/span> are too open.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">It is required that your private key files are NOT accessible by others.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">This private key will be ignored.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Load key &lt;span class="s2">&amp;#34;/home/xueshihao/.ssh/id_ed25519&amp;#34;&lt;/span>: bad permissions
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git@gitee.com: Permission denied &lt;span class="o">(&lt;/span>publickey&lt;span class="o">)&lt;/span>.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>linux 下，将私钥文件的文件属性设置为只读就行。使用下面的指令设置权限：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&amp;gt; chmod &lt;span class="m">700&lt;/span> ~/.ssh/id_ed25519
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>再次尝试测试连接，会提示下面的内容：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">The authenticity of host &lt;span class="s1">&amp;#39;gitee.com (180.76.198.77)&amp;#39;&lt;/span> can&lt;span class="err">&amp;#39;&lt;/span>t be established.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ECDSA key fingerprint is SHA256:FQGC9Kn/eye1W8icdBgrQp+KkGYoFgbVr17bmjey0Wc.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Are you sure you want to &lt;span class="k">continue&lt;/span> connecting &lt;span class="o">(&lt;/span>yes/no/&lt;span class="o">[&lt;/span>fingerprint&lt;span class="o">])&lt;/span>?
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>为了模拟 Github action 的方式，此处直接回车，然后会发现无法连接，提示如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">Host key verification failed.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>具体解决方案可以查看这篇博客：&lt;a href="https://blog.csdn.net/xjp_xujiping/article/details/120012413"target="_blank" rel="noopener noreferrer">SSH 连接时出现 Host key verification failed 的原因及解决方法&lt;/a>。需要修改的配置的条目，可以在 ssh 的文档中找到：&lt;a href="https://www.man7.org/linux/man-pages/man5/ssh_config.5.html"target="_blank" rel="noopener noreferrer">ssh_config - StrictHostKeyChecking&lt;/a>&lt;/p>
&lt;p>&lt;img loading="lazy" src="ssh_config-StrictHostKeyChecking.png"
alt="ssh_config - StrictHostKeyChecking"/>&lt;/p>
&lt;p>打开或创建 &lt;code>~/.ssh/config&lt;/code> 文件，将下面的内容添加到文件当中：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-config" data-lang="config">Host gitee.com
StrictHostKeyChecking=no
&lt;/code>&lt;/pre>&lt;p>配置完成后，再次输入 &lt;code>ssh -T git@gitee.com&lt;/code> 并回车，就没有问题了。&lt;/p>
&lt;p>&lt;img loading="lazy" src="ssh-connection-test-result.png"
alt="ssh-connection-test-result"/>&lt;/p>
&lt;h3 id="github-action-使用-ssh">Github action 使用 ssh&lt;/h3>
&lt;p>完成了 ssh 的连接测试，接下来就可以先将上面的内容转存到 Github action 中了。在仓库的根目录创建 &lt;code>.github&lt;/code> 文件夹，并在 &lt;code>.github&lt;/code> 下创建 &lt;code>workflows&lt;/code> 文件夹。创建一个文件末尾格式为 &lt;code>.yml&lt;/code> 的文件，并在文件中输入下面的内容：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Mirror sync&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schedule&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">cron&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0 0 * * *&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">workflow_dispatch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">REPO_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">repo-name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>name&lt;/code> 是这个 action 的名字；&lt;code>on&lt;/code> 表示在什么情况下触发这个 action，&lt;code>schedule&lt;/code> 下设置了触发的时间，为每天的 0 点（北京时间 8 点），&lt;code>workflow_dispatch&lt;/code> 则是可以手动运行 action。&lt;/p>
&lt;p>&lt;code>env&lt;/code> 中的键值对，代表运行时，控制台的环境变量，可以自由设置。但这个变量是公开可见的，所以不建议将 token 之类的东西写在这里。&lt;/p>
&lt;p>关于 Github action 的其他内容可以查看&lt;a href="https://docs.github.com/zh/actions"target="_blank" rel="noopener noreferrer">官方文档&lt;/a>或者是使用到的 Github action 的示例。&lt;/p>
&lt;p>由于 Github action 的用户目录没有 &lt;code>.ssh&lt;/code> 文件夹，所以需要自己创建。然后将 ssh 的配置和私钥写入到对应的文件当中，注意私钥文件的文件权限，需要配置为只读。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">merge&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Down load gitee source&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> mkdir ~/.ssh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;Host gitee.com
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> StrictHostKeyChecking=no&amp;#34; &amp;gt; ~/.ssh/config
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> echo &amp;#34;${{ secrets.GITEE_SSH_PRIVATE_KEY }}&amp;#34; &amp;gt; ~/.ssh/id_ed25519
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> chmod 700 ~/.ssh/id_ed25519
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> eval $(ssh-agent)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> ssh -T git@gitee.com
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> git clone git@gitee.com:your_name/${{ env.REPO_NAME }}.git&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>通过 &lt;code>eval&lt;/code> 启用 ssh 认证，就可以克隆 Gitee 的私有仓库了。&lt;/p>
&lt;h3 id="提交到-github">提交到 Github&lt;/h3>
&lt;p>在上面的基础上，添加下面的指令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">merge&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Down load gitee source&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">|&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># .....&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">cd ${{ env.REPO_NAME }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">git remote add github https://${{ secrets.REPO_TOKEN }}@github.com/studylessshape/game-story-private&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">git push github master -f&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这三个指令的作用分别是：&lt;/p>
&lt;ol>
&lt;li>进入到克隆的目录&lt;/li>
&lt;li>为当前的本地仓库添加远程 remote&lt;/li>
&lt;li>提交到 Github&lt;/li>
&lt;/ol>
&lt;p>将配置好的 action 文件提交到 Gitee 和 Github 的仓库上。然后修改一些其他文件的内容并提交到 Gitee 仓库上。&lt;/p>
&lt;p>在 Github 的仓库页面，找到 Action，然后找到 Mirror sync。&lt;/p>
&lt;p>&lt;img loading="lazy" src="github-action.png"
alt="github action"/>&lt;/p>
&lt;p>点击旁边的 Run workflow，再点击绿色的 Run workflow。刷新一下就可以看见一个正在执行的工作流了。如果之前配置 Gitee 的 ssh 的公钥和私钥、以及 Github 的 Token 没有问题，那么运行结束后，所有的条目将都是正常完成的。&lt;/p>
&lt;p>&lt;img loading="lazy" src="run-success.png"
alt="run-success"/>&lt;/p>
&lt;h2 id="使用-gitee-open-api不推荐">使用 Gitee Open API（不推荐）&lt;/h2>
&lt;p>完整的 Github aciton 直接放在下面了：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Mirror sync&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">schedule&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">cron&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0 0 * * *&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">workflow_dispatch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">GITHUB_REPO_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">&amp;lt;your-github-repo&amp;gt;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">GITEE_DIR_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">&amp;lt;your giteee repo&amp;gt;-&amp;lt;branch&amp;gt;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">merge&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Down load gitee source&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">|&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> wget https://gitee.com/api/v5/repos/&amp;lt;your_name&amp;gt;/&amp;lt;your-repo&amp;gt;/zipball?access_token=${{ secrets.GITEE_ACCESS_TOKEN }} -O &amp;lt;branch&amp;gt;.zip
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> unzip &amp;lt;branch&amp;gt;.zip
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> cp $GITEE_DIR_NAME/* . -r
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> rm $GITEE_DIR_NAME &amp;lt;branch&amp;gt;.zip -rf&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">EndBug/add-and-commit@v9&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># You can change this to use a specific version.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">default_author&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">github_actor&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fetch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">committer_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">GitHub Actions&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">committer_email&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">41898282&lt;/span>&lt;span class="l">+github-actions[bot]@users.noreply.github.com&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pathspec_error_handling&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ignore&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>要使用 Gitee Open API，需要先生成一个私人令牌。&lt;/p>
&lt;h3 id="生成私人令牌">生成私人令牌&lt;/h3>
&lt;p>在 Gitee 的个人设置里，找到&lt;strong>私人令牌&lt;/strong>，点击&lt;strong>生成新令牌&lt;/strong>，建议只勾选 &lt;code>projects&lt;/code> 权限。&lt;/p>
&lt;p>&lt;img loading="lazy" src="gitee-open-api-key-gen.png"
alt="gitee-open-api-key-gen.png"/>&lt;/p>
&lt;p>点击生成，将生成好的内容复制。打开对应的 Github 仓库，在 &lt;code>Secrets and Variables&lt;/code> -&amp;gt; &lt;code>Actions&lt;/code> 中，新建一个仓库机密，命名为 &lt;code>GITEE_ACCESS_TOKEN&lt;/code>。这样，Github action 就能够使用这个私人令牌了。&lt;/p>
&lt;h3 id="action-中的命令解释">Action 中的命令解释&lt;/h3>
&lt;p>上面 Action 的命令逻辑很简单，首先是通过 Gitee Open API 将对应仓库的代码的 zip 下载下来，然后直接解压。关于 Gitee Open API 可以查看 Gitee 的 &lt;a href="https://gitee.com/api/v5/swagger#/"target="_blank" rel="noopener noreferrer">Swagger 文档&lt;/a>，只不过文档中并没有将所有的 API 列出来，比较可惜。&lt;/p>
&lt;p>因为 &lt;code>actions/checkout@v4&lt;/code> 以及将当前 Github 仓库的代码拉取下来，并将工作目录切换到了克隆的目录，所以直接将解压出来的所有的文件内容替换到当前文件夹下。同时记得需要删除解压出来的文件以及下载下来的安装包。&lt;/p>
&lt;p>最后使用 &lt;code>EndBug/add-and-commit@v9&lt;/code> 来执行提交操作。&lt;/p>
&lt;h1 id="后记">后记&lt;/h1>
&lt;p>其实之前准备尝试使用 &lt;a href="https://github.com/Yikun/hub-mirror-action"target="_blank" rel="noopener noreferrer">hub-mirror-action&lt;/a> 来同步的，但是这个 action 只能支持一段的私钥和 Token 的配置，所以只能够同步公开仓库。&lt;/p>
&lt;p>两种不同的方式的 action 都发布到这个仓库了：&lt;a href="https://github.com/studylessshape/sync-gitee-to-github"target="_blank" rel="noopener noreferrer">sync-gitee-to-github&lt;/a>&lt;/p></description></item><item><title>如何入门 Rust 的宏？</title><link>https://study_less_shape.github.io/post/rust/how-to-learn-rust-macro/</link><pubDate>Sun, 17 Dec 2023 22:16:02 +0800</pubDate><guid>https://study_less_shape.github.io/post/rust/how-to-learn-rust-macro/</guid><description>&lt;blockquote>
&lt;p>避免长篇大论，可以直接翻到最底下的&lt;a href="#%e6%95%b4%e7%90%86">整理&lt;/a>一栏。&lt;/p>&lt;/blockquote>
&lt;h2 id="rust-的宏是什么">Rust 的宏是什么？&lt;/h2>
&lt;p>Rust 的宏可以看作编译时期，将代码按照编写好的宏规则去生成的指令。换句话说就是用一定的规则将原来的代码替换掉。&lt;/p>
&lt;p>常见的宏有两种，一个是 &lt;code>macro_rules!&lt;/code> 声明的&lt;strong>声明式宏&lt;/strong>，另一种是和 Rust 函数一样的写法的&lt;strong>过程宏&lt;/strong>。&lt;/p>
&lt;h2 id="如何入门学习">如何入门/学习&lt;/h2>
&lt;p>之前我也是看见宏复杂的符号和奇怪的语法，而没有想法去学习。&lt;/p>
&lt;p>学习宏我觉得最重要的还是通过实例去学习，需要一定的积累才能够理解宏，首先要理解宏做了什么，然后才是实现它。&lt;/p>
&lt;h3 id="入门学习必备的-crate-和工具">入门学习必备的 crate 和工具&lt;/h3>
&lt;p>有几个很方便的 &lt;code>crate&lt;/code>，在宏小册中也提到了，学习过程宏时，使用这几个 &lt;code>crate&lt;/code> 会很方便。&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://docs.rs/syn"target="_blank" rel="noopener noreferrer">syn&lt;/a>：面向过程宏，解析语法树&lt;/li>
&lt;li>&lt;a href="https://docs.rs/quote"target="_blank" rel="noopener noreferrer">quote&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>必备的工具是 &lt;a href="https://github.com/dtolnay/cargo-expand"target="_blank" rel="noopener noreferrer">cargo-expand&lt;/a>，使用下面的指令安装和使用：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cargo install cargo-expand &lt;span class="c1"># install&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cargo expand &lt;span class="c1"># run&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="https://github.com/dtolnay/cargo-expand"target="_blank" rel="noopener noreferrer">cargo-expand&lt;/a> 可以展开宏代码，看到代码通过宏替换后的样子，非常方便，除了可以观察自己的宏有哪些问题，还可以观察其他 &lt;code>crate&lt;/code> 中，复杂的宏在使用时发生了什么。&lt;/p>
&lt;h3 id="入门学习的教程和网站">入门学习的教程和网站&lt;/h3>
&lt;p>宏到底是个什么，可以阅读一下 &lt;a href="https://rustwiki.org/zh-CN/reference/macros.html"target="_blank" rel="noopener noreferrer">Rust 参考手册（中文版非官方翻译）&lt;/a>，了解声明宏和过程宏。&lt;/p>
&lt;p>入门学习宏可以使用 &lt;a href="https://github.com/dtolnay/proc-macro-workshop"target="_blank" rel="noopener noreferrer">proc-macro-workshop&lt;/a> 进行学习。这个仓库中包含了几个比较常用的声明宏和过程宏的实现模板。并且同时可以阅读学习 &lt;a href="https://rustwiki.org/zh-CN/rust-by-example/macros.html"target="_blank" rel="noopener noreferrer">使用 macro_rules! 来创建宏（通过例子学 Rust 中文版）&lt;/a>。&lt;/p>
&lt;blockquote>
&lt;p>&lt;a href="https://docs.rs/syn"target="_blank" rel="noopener noreferrer">syn&lt;/a>、&lt;a href="https://docs.rs/quote"target="_blank" rel="noopener noreferrer">quote&lt;/a> 和 &lt;a href="https://github.com/dtolnay/proc-macro-workshop"target="_blank" rel="noopener noreferrer">proc-macro-workshop&lt;/a> 都是大佬 &lt;a href="https://github.com/dtolnay"target="_blank" rel="noopener noreferrer">dtolnay&lt;/a> 实现的，真的太强了！&lt;/p>&lt;/blockquote>
&lt;p>另外，宏小册，即 &lt;a href="https://veykril.github.io/tlborm/"target="_blank" rel="noopener noreferrer">The Little Book of Rust Macros&lt;/a>，可以当作工具书进行参阅。&lt;/p>
&lt;h2 id="闲话">闲话&lt;/h2>
&lt;p>一个朋友之前尝试写了个类似于 &lt;code>lisp&lt;/code> 的脚本语言的解析库，名叫 &lt;a href="https://gitee.com/ZerAx/dj-rs"target="_blank" rel="noopener noreferrer">dj&lt;/a>。我就尝试为其实现一个运行器，方便测试和运行。&lt;/p>
&lt;p>而最近突然萌生了写宏的想法，刚好又完成了 &lt;a href="https://gitee.com/study_less_shape/dj-runner"target="_blank" rel="noopener noreferrer">dj-runner&lt;/a> 的编程。在实现的过程中发现 &lt;a href="https://gitee.com/ZerAx/dj-rs"target="_blank" rel="noopener noreferrer">dj&lt;/a> 中的 &lt;code>Value::Builtin&lt;/code>，和 &lt;a href="https://actix.rs/"target="_blank" rel="noopener noreferrer">actix-web&lt;/a> 中的路由以及 &lt;a href="https://bevyengine.org/"target="_blank" rel="noopener noreferrer">bevy&lt;/a> 中的 &lt;code>System&lt;/code> 有着相似的形式，就想着能不能尝试将内建函数的形式简化。&lt;/p>
&lt;p>后来实际的实现与路由和 &lt;code>System&lt;/code> 差别挺大的，实现的方式也比较粗暴，但总的来说效果还不错，能够感觉到一些学习宏并实现一个的成就感。&lt;/p>
&lt;h2 id="整理">整理&lt;/h2>
&lt;ul>
&lt;li>&lt;code>crate&lt;/code>
&lt;ul>
&lt;li>&lt;a href="https://docs.rs/syn"target="_blank" rel="noopener noreferrer">syn&lt;/a>：面向过程宏，解析语法树&lt;/li>
&lt;li>&lt;a href="https://docs.rs/quote"target="_blank" rel="noopener noreferrer">quote&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>教程
&lt;ul>
&lt;li>&lt;a href="https://rustwiki.org/zh-CN/reference/macros.html"target="_blank" rel="noopener noreferrer">Rust 参考手册（中文版非官方翻译）&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/dtolnay/proc-macro-workshop"target="_blank" rel="noopener noreferrer">proc-macro-workshop&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://rustwiki.org/zh-CN/rust-by-example/macros.html"target="_blank" rel="noopener noreferrer">使用 macro_rules! 来创建宏（通过例子学 Rust 中文版）&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://veykril.github.io/tlborm/"target="_blank" rel="noopener noreferrer">The Little Book of Rust Macros&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description></item><item><title>有关 Axum 中 WebSocket 的使用</title><link>https://study_less_shape.github.io/post/rust/how-axum-start-with-websocket/</link><pubDate>Sun, 22 Oct 2023 13:34:59 +0800</pubDate><guid>https://study_less_shape.github.io/post/rust/how-axum-start-with-websocket/</guid><description>&lt;h1 id="开始">开始&lt;/h1>
&lt;h2 id="创建-axum-项目">创建 &lt;code>axum&lt;/code> 项目&lt;/h2>
&lt;p>创建一个项目，并将 &lt;code>axum&lt;/code> 添加到依赖中：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">cargo new axum-ws-test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> axum-ws-test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cargo add tokio -F full
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cargo add serde_json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cargo add axum -F ws
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cargo add rand
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后用自己喜欢的编辑器/IDE 打开整个项目，找到 &lt;code>Cargo.toml&lt;/code>，可以看到 &lt;code>Cargo.toml&lt;/code> 如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">dependencies&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">axum&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;0.6.20&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">features&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ws&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">rand&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;0.8.5&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">serde_json&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;1.0.107&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">tokio&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">version&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;1.33.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">features&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;full&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>以上依赖版本为本文编写时的最新稳定版，需要注意和自己的版本区别，&lt;code>axum&lt;/code> 的功能基本都有解释，可以查看 &lt;a href="https://docs.rs/axum/latest/axum/#feature-flags"target="_blank" rel="noopener noreferrer">&lt;code>axum&lt;/code> 文档&lt;/a>&lt;/p>&lt;/blockquote>
&lt;h3 id="axum-的程序基本结构">&lt;code>axum&lt;/code> 的程序基本结构&lt;/h3>
&lt;p>从官方文档可以看到，一个 &lt;code>axum&lt;/code> 程序，包含了程序入口、路由、路由服务和 &lt;code>axum&lt;/code> 服务端（即 &lt;code>axum::Server&lt;/code>），我们先将其基本结构写入到 &lt;code>main.rs&lt;/code> 的文件中（代码来自官方文档）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">axum&lt;/span>::&lt;span class="p">{&lt;/span>&lt;span class="n">routing&lt;/span>::&lt;span class="n">get&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Router&lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// 主函数入口
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#[tokio::main]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// 路由
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">app&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Router&lt;/span>::&lt;span class="n">new&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">||&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">}));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// axum 的 Server
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">axum&lt;/span>::&lt;span class="n">Server&lt;/span>::&lt;span class="n">bind&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="s">&amp;#34;0.0.0.0:8081&amp;#34;&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">parse&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">unwrap&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">serve&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">app&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">into_make_service&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">await&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">unwrap&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>运行程序后，打开 &lt;a href="localhost:8081">localhost:8081&lt;/a>，应该可以看见网页上有 &lt;code>Hello, World!&lt;/code>。&lt;/p>
&lt;h2 id="创建前端项目">创建前端项目&lt;/h2>
&lt;p>前端使用 &lt;code>Vue&lt;/code>，建议选择另一个文件夹来创建前端项目。输入下面的指令来创建前端项目，项目名称命名为 &lt;code>axum-test-front&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">npm create vue@latest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> axum-test-front
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">npm run dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="直接使用-http-请求">直接使用 Http 请求&lt;/h2>
&lt;p>我们模拟的情况试试，前端每次点击按钮都会获取后端的一个随机数。一开始我们先不使用 WebSocket，来测试一下效果。&lt;/p>
&lt;h3 id="后端代码">后端代码&lt;/h3>
&lt;p>添加一个函数，用于前端获取随机数：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">axum&lt;/span>::&lt;span class="p">{&lt;/span>&lt;span class="n">response&lt;/span>::&lt;span class="n">Json&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">routing&lt;/span>::&lt;span class="n">get&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Router&lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">use&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rand&lt;/span>::&lt;span class="n">Rng&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">use&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">serde_json&lt;/span>::&lt;span class="p">{&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="cp">#[tokio::main]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">app&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Router&lt;/span>::&lt;span class="n">new&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">||&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">}))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// add here
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/random&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">get_rand&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// Server
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1">// handler
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">get_rand&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>-&amp;gt; &lt;span class="nc">Json&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">mut&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rng&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rand&lt;/span>::&lt;span class="n">thread_rng&lt;/span>&lt;span class="p">();&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">Json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="fm">json!&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s">&amp;#34;num&amp;#34;&lt;/span>: &lt;span class="nc">rng&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">gen_range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="o">..=&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">)}))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>可以在浏览器中输入 &lt;a href="localhost:8081/random">localhost:8081/random&lt;/a>来测试，每个刷新应该都可以得到一个新的 &lt;code>num&lt;/code> 值。&lt;/p>
&lt;h3 id="前端代码">前端代码&lt;/h3>
&lt;p>前端在 &lt;code>App.vue&lt;/code> 中添加一个按钮和一个用于显示获取到的随机数的节点，代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">data&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">num&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">methods&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">get_random&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 从后端的对应地址获取随机数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;http://localhost:8081/random&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mode&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;cors&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">accpet&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;application/json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">num&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">num&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">main&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="err">@&lt;/span>&lt;span class="na">click&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;get_random()&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>click to get num&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>num is {{ num }}&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">main&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>执行的时候会发现无法从后端拿到数据，这是因为后端没有配置跨域请求。&lt;/p>
&lt;h3 id="为后端配置跨域">为后端配置跨域&lt;/h3>
&lt;p>首先需要添加一个依赖，输入下面的指令添加：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">cargo add tower-http -F cors
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后在创建路由之前，先新建一个跨域的许可：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">axum&lt;/span>::&lt;span class="p">{&lt;/span>&lt;span class="n">http&lt;/span>::&lt;span class="n">HeaderValue&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">response&lt;/span>::&lt;span class="n">Json&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">routing&lt;/span>::&lt;span class="n">get&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Router&lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">use&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">rand&lt;/span>::&lt;span class="n">Rng&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">use&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">serde_json&lt;/span>::&lt;span class="p">{&lt;/span>&lt;span class="n">json&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Value&lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">use&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">tower_http&lt;/span>::&lt;span class="n">cors&lt;/span>::&lt;span class="p">{&lt;/span>&lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">CorsLayer&lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="cp">#[tokio::main]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// 跨域配置
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">cors&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">CorsLayer&lt;/span>::&lt;span class="n">new&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">allow_methods&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">allow_headers&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Any&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">allow_origin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;http://localhost:5173&amp;#34;&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">parse&lt;/span>::&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">HeaderValue&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">unwrap&lt;/span>&lt;span class="p">());&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">app&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Router&lt;/span>::&lt;span class="n">new&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">||&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">}))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// 为路由方法处理添加跨域许可
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/random&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">get_rand&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">layer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cors&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// Server
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这时再运行后端和前端，打开前端的网页，点击按钮应该可以每次获取到不同的数字。打开开发者控制台，并选择网络（没有的话点加号或者 &lt;code>》&lt;/code> 可以找到），再多次点击按钮，可以看到每次点击按钮都发送了一次 Http 请求。&lt;/p>
&lt;blockquote>
&lt;p>如果前端开发者控制台报 &lt;code style="color: red;">Uncaught (in promise) ReferenceError: num is not defined&lt;/code> 这种错误，应该是在给 Vue 中 &lt;code>data&lt;/code> 里的字段赋值的时候没有加 &lt;code>this&lt;/code> 关键字，把 &lt;code>num&lt;/code> 改为 &lt;code>this.num&lt;/code> 即可&lt;/p>&lt;/blockquote>
&lt;h1 id="改为-websocket-传输随机数">改为 WebSocket 传输随机数&lt;/h1>
&lt;h2 id="后端代码修改">后端代码修改&lt;/h2>
&lt;p>添加一个新的函数，函数名为 &lt;code>handle_random&lt;/code>，再添加一个函数名为 &lt;code>handle_random_socket&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">handle_random&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ws_upgrade&lt;/span>: &lt;span class="nc">WebSocketUpgrade&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>-&amp;gt; &lt;span class="nc">Response&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">ws_upgrade&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">on_upgrade&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">handle_random_socket&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">handle_random_socket&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">mut&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">socket&lt;/span>: &lt;span class="nc">WebSocket&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">while&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">Some&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">recv&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="k">await&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">Ok&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">else&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="fm">println!&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Web Socket Closed&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在 &lt;code>handle_random&lt;/code> 中，调用 &lt;code>ws_upgrade&lt;/code> 的 &lt;code>on_upgrade&lt;/code> 函数可以建立 Web Socket 连接。&lt;code>handle_random_socket&lt;/code> 就是用于处理连接时的函数，此处先使用循环来接收来自连接另一端的消息，如果接收发生错误或者无法接收到，则视为连接关闭。&lt;code>WebSocket::recv()&lt;/code> 函数在连接关闭后，才会返回 &lt;code>None&lt;/code>。&lt;/p>
&lt;p>设想建立连接后，前端发送一个 &lt;code>get&lt;/code> 字符串，后端收到这个字符串，如果收到的确实是 &lt;code>get&lt;/code>，则返回给前端一个随机数。那么要做的事情就很简单了，首先要匹配发送过来的消息是否是字符串且内容是否为 &lt;code>get&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">handle_random_socket&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">mut&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">socket&lt;/span>: &lt;span class="nc">WebSocket&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">while&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">Some&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">recv&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="k">await&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">Ok&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="o">..&lt;/span>&lt;span class="p">.};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// 匹配字符串
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Message&lt;/span>::&lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">eq&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;get&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="fm">todo!&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在匹配成功后，将生成一个随机数，并返回给前端。这里会用到 &lt;code>WebSocket&lt;/code> 的 &lt;code>Send&lt;/code> 函数来返回响应，获取随机数可以用到之前写的函数。前端对数据的宽容性较大，所以可以考虑直接返回 JSON 格式的文本：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">handle_random_socket&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">mut&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">socket&lt;/span>: &lt;span class="nc">WebSocket&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">while&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">Some&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">recv&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="k">await&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">Ok&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="o">..&lt;/span>&lt;span class="p">.};&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Message&lt;/span>::&lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">eq&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;get&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">if&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">socket&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// 返回随机数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Message&lt;/span>::&lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">get_rand&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="k">await&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">to_string&lt;/span>&lt;span class="p">()))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">await&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">is_err&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// 如果出错了就关闭连接
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="fm">println!&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Web Socket Closed&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="fm">println!&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Web Socket Closed&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>编写完成，最后将 &lt;code>handle_random&lt;/code> 添加到路由中。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#[tokio::main]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// 跨域配置
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// 路由
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kd">let&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">app&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Router&lt;/span>::&lt;span class="n">new&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">||&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s">&amp;#34;Hello, World!&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">}))&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/random&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">get_rand&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="n">layer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cors&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">route&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;/ws/random&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">handle_random&lt;/span>&lt;span class="p">));&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// axum 的 Server
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c1">// ...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="前端代码修改">前端代码修改&lt;/h2>
&lt;p>在组件挂载时，会尝试创建一个 &lt;code>WebSocket&lt;/code> 的连接，并且绑定一个函数，用于在收到消息后，设置 &lt;code>num&lt;/code> 的值：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="nx">script&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">data&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">num&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ws&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">methods&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">build_connect&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 防止子域的 this 与 vue 的 this 冲突
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kd">var&lt;/span> &lt;span class="nx">that&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">that&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ws&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">WebSocket&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;ws://localhost:8081/ws/random&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 收到消息后，设置 num 的值
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">that&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ws&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">addEventListener&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kd">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">event&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">that&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">num&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">event&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">num&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mounted&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">build_connect&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">window&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">onclose&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ws&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">close&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>之后的每次点击都会变为通过连接来向后端发送消息。按照逻辑修改后的代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">get_random&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 通过连接向后端发送信息
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ws&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;get&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="运行">运行&lt;/h2>
&lt;p>首先启动后端：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">cargo run
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后启动前端：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">npm run dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>打开前端页面，点击按钮就可以看到每次都能够从后端获取到不同的随机数的效果了。&lt;/p>
&lt;h1 id="参考">参考&lt;/h1>
&lt;ol>
&lt;li>&lt;a href="https://docs.rs/axum"target="_blank" rel="noopener noreferrer">axum&lt;/a>
&lt;ul>
&lt;li>&lt;a href="https://docs.rs/axum/latest/axum/extract/ws/index.html"target="_blank" rel="noopener noreferrer">axum::extract::ws - Rust&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="https://cn.vuejs.org/"target="_blank" rel="noopener noreferrer">vue&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket"target="_blank" rel="noopener noreferrer">WebSocket - Web API 接口参考 | MDN&lt;/a>&lt;/li>
&lt;/ol></description></item><item><title>使用 Actix Web 和 Vue 在开发中遇到的问题</title><link>https://study_less_shape.github.io/post/rust/actix-web-with-vue/</link><pubDate>Tue, 02 May 2023 00:00:00 +0000</pubDate><guid>https://study_less_shape.github.io/post/rust/actix-web-with-vue/</guid><description>&lt;blockquote>
&lt;p>精力有限，不再更新了。&lt;/p>&lt;/blockquote>
&lt;h2 id="前言">前言&lt;/h2>
&lt;p>这篇博客会不定期记录我在使用 &lt;code>actix web&lt;/code> 和 &lt;code>vue&lt;/code> 时遇到的问题。两者放在一起是因为我在开发过程中，前端使用 &lt;code>vue&lt;/code>，后端使用 &lt;code>actix web&lt;/code>，一些地方可能两者结合起来才能说明的更加清楚。&lt;/p>
&lt;h2 id="遇到的问题">遇到的问题&lt;/h2>
&lt;h3 id="1-类型问题">1. 类型问题&lt;/h3>
&lt;p>浅浅了解 &lt;code>actix web&lt;/code> 和 类似与 &lt;code>Rust&lt;/code> 这种强类型语言的话，很容易知道类型匹配很多时候会令人头痛。&lt;/p>
&lt;p>在 &lt;code>actix web&lt;/code> 中，有一种工具叫做提取器（&lt;a href="https://actix.rs/docs/extractors"target="_blank" rel="noopener noreferrer">&lt;code>Extractor&lt;/code>&lt;/a>），提取器可以从请求的链接或者请求的数据中，提取中想要的数据，我常用的有 &lt;code>Json&lt;/code>、&lt;code>Path&lt;/code>、&lt;code>Query&lt;/code>、&lt;a href="https://docs.rs/actix-multipart/latest/actix_multipart/struct.Multipart.html"target="_blank" rel="noopener noreferrer">&lt;code>actix_multipart::Multipart&lt;/code>&lt;/a>。&lt;/p>
&lt;ol>
&lt;li>&lt;code>Json&lt;/code>：常用在 &lt;code>POST&lt;/code> 请求中，能够从请求的数据（即 &lt;code>fetch&lt;/code> 中的 &lt;code>body&lt;/code>）提取出 &lt;code>Json&lt;/code> 数据。&lt;/li>
&lt;li>&lt;code>Path&lt;/code>：常用于提取链接路径的参数。在 &lt;code>actix web&lt;/code> 配置路径及对应的服务和路由时，可以通过 &lt;code>{}&lt;/code> 来匹配对应路径的一段字符，如配置了路径 &lt;code>/path/to/{extractor}&lt;/code>，请求的路径为 &lt;code>/path/to/123&lt;/code>，那么通过 &lt;code>Path&lt;/code>，就可以提取出 &lt;code>{extractor}&lt;/code> 这一段的字符，也就是 &lt;code>123&lt;/code>。&lt;/li>
&lt;li>&lt;code>Query&lt;/code>：常用于 &lt;code>GET&lt;/code> 方法。&lt;code>GET&lt;/code> 方法的请求数据或者说参数，经常放在路径的 &lt;code>?&lt;/code> 号后，所以通过 &lt;code>Query&lt;/code> 就可以提取出所有参数。&lt;/li>
&lt;li>&lt;code>actix_multipart::Multipart&lt;/code>：适用于用于表单传输的结构体。目前只尝试通过其读取并保存请求传输过来的文件。&lt;/li>
&lt;/ol>
&lt;p>在上面 4 种常用的提取器中，&lt;code>Json&lt;/code> 和 &lt;code>Query&lt;/code> 是非常严格的。比如，对于 &lt;code>Json&lt;/code> 来说，得到了请求，请求的数据为 &lt;code>{&amp;quot;user_id&amp;quot;:&amp;quot;12&amp;quot;}&lt;/code>，那么在 &lt;code>actix web&lt;/code> 这里，需要新建一个结构体才能够提取出数据。比如下面的代码，结构体中的字段和请求的数据中的字段应该相对应，这时放入请求参数中，就没有问题。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-rust" data-lang="rust">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#[derive(Deserialize, Serialize)]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">pub&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="nc">UserIdRequest&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">pub&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">user_id&lt;/span>: &lt;span class="kt">i32&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">pub&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">async&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">fn&lt;/span> &lt;span class="nf">user_id_request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_id_re&lt;/span>: &lt;span class="nc">web&lt;/span>::&lt;span class="n">Json&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">UserIdRequest&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>-&amp;gt; &lt;span class="nc">impl&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">Responder&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c1">// ....
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但是上述请求会有比较蹊跷的地方，一旦尝试请求，会出现 &lt;code>400&lt;/code> 错误码，这又是为什么呢？&lt;/p>
&lt;p>仔细观察请求的 &lt;code>json&lt;/code> 数据：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>&lt;span class="nt">&amp;#34;user_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="s2">&amp;#34;12&amp;#34;&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>会发现 &lt;code>user_id&lt;/code> 的数据，实际上是字符串类型，所以在请求时，由于解析无法将字符串类型解析为整型，所以出错，返回了 &lt;code>400&lt;/code> 的错误码。这个错误出现次数非常多，多到每次排查错误排查很久，结果发现都是类型匹配错误。&lt;/p>
&lt;h4 id="为何会我经常出现这种错误">为何会我经常出现这种错误？&lt;/h4>
&lt;p>前端使用 &lt;code>vue&lt;/code> 编写。如果有这么一个场景，我需要输入数值，然后返回给后台，那么 &lt;code>vue&lt;/code> 代码可能是这样的：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">data&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">num&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sendRequest&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;localhost:8082/recive-number&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mode&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;cors&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;Content-type&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="s2">&amp;#34;application/json&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="nx">number&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">num&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">num&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">input&lt;/span> &lt;span class="na">v-model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;num&amp;#39;&lt;/span> &lt;span class="na">type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;text&amp;#39;&lt;/span> &lt;span class="na">placeholder&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;Please input number&amp;#39;&lt;/span>&lt;span class="p">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">button&lt;/span> &lt;span class="err">@&lt;/span>&lt;span class="na">click&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#39;sendRequest&amp;#39;&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>submit&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">button&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">template&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意这里，&lt;code>num&lt;/code> 在初始化时设定的值是 &lt;code>null&lt;/code>，而且在下面的 &lt;code>&amp;lt;input&amp;gt;&lt;/code> 框中，设定的 &lt;code>type&lt;/code> 为 &lt;code>text&lt;/code>，此时，如果直接将输入的数据发送给后台，那么就很有可能出现 &lt;code>400&lt;/code> 的错误。因此，&lt;code>body&lt;/code> 处的代码应该改为：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">data&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">num&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kc">null&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sendRequest&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;localhost:8082/recive-number&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">method&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;POST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">mode&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;cors&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;Content-type&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span>&lt;span class="s2">&amp;#34;application/json&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">body&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="nx">number&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">num&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nb">parseInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">num&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!-- template --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>当然也不一定是 &lt;code>parseInt&lt;/code>，也可以根据自己后台的数据类型决定使用哪种类型。&lt;/p>
&lt;h3 id="2-数据获取问题">2. 数据获取问题&lt;/h3>
&lt;p>以前使用过 &lt;code>JavaWeb&lt;/code>，前端可以直接获取到 Java 中的对象，前后端的数据同步基本上没有什么问题。而在我编写毕设项目时，发现 Http 中的 &lt;code>GET&lt;/code> 请求，在页面不刷新的时候，第二次请求同一个地址，会读取缓存，无论怎样修改都会出现这种情况。由于当时对前端和后端的通信了解的比较少，所以在需要重复获取数据的时候采用了跳转页面或者修改请求地址这种比较简单的方法，甚至很多地方直接使用其他不会读取缓存的方法。&lt;/p>
&lt;p>在之后的一段时间中了解到了 WebSocket，实际上就是为前端和后端建立一个 TCP 连接，这样的话想要获取需要的数据，数据可以从建立好的连接获取，就不需要考虑 &lt;code>GET&lt;/code> 请求的缓存问题了。&lt;/p></description></item></channel></rss>