<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://blog.gucrin.top/</id>
  <title type="text">liuyi</title>
  <subtitle type="text">让世界再萌一点</subtitle>
  <updated>2026-10-12T00:00:00.000Z</updated>
  <author><name>liuyi</name></author>
  <link rel="alternate" href="https://blog.gucrin.top/"/>
  <link rel="self" href="https://blog.gucrin.top/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://blog.gucrin.top/posts/boc-2233msc/%E5%93%94%E5%8D%A1%E7%BD%91%E7%94%B3%E5%92%8C%E7%94%B3%E8%AF%B7%E8%BF%9B%E5%BA%A6%E6%9F%A5%E8%AF%A2%E6%94%BB%E7%95%A5/</id>
      <title type="text">哔卡网申和申请进度查询攻略</title>
      <published>2026-10-12T00:00:00.000Z</published>
      <updated>2026-10-12T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/boc-2233msc/%E5%93%94%E5%8D%A1%E7%BD%91%E7%94%B3%E5%92%8C%E7%94%B3%E8%AF%B7%E8%BF%9B%E5%BA%A6%E6%9F%A5%E8%AF%A2%E6%94%BB%E7%95%A5/"/>
      <summary type="text">新的哔卡已经出现</summary>
      <content type="html"><![CDATA[<section><h2>预约、进度查询<a href="#预约进度查询"><span>#</span></a></h2><p>🔗 网申预约入口：<a href="https://open.boc.cn/bankcard/cardReserve/index.html#/?appId=8AFAF8C546EB63E9AE3202BC13DB46FC&amp;prodId=P260802MYZGWK2025060515073600001&amp;subProdId=JJKYY&amp;funcId=cardIntroduce&amp;chanId=bili_bzapp" target="_blank">“中国银行卡片预约页面”</a></p><p>🔍 进度查询入口： 进入 <a href="https://open.boc.cn/bankcard/cardReserve/index.html#/?appId=8AFAF8C546EB63E9AE3202BC13DB46FC&amp;prodId=P260802MYZGWK2025060515073600001&amp;subProdId=JJKYY&amp;funcId=cardIntroduce&amp;chanId=bili_bzapp" target="_blank">预约页面</a> 点击底部的 <a href="https://open.boc.cn/bankcard/cardReserve/index.html#/?appId=8AFAF8C546EB63E9AE3202BC13DB46FC&amp;prodId=P260802MYZGWK2025060515073600001&amp;subProdId=JJKYY&amp;funcId=cardIntroduce&amp;chanId=bili_bzapp" target="_blank">“申请进度查询”</a></p></section>
<section><h2>高级控制台代码和 JS 脚本<a href="#高级控制台代码和-js-脚本"><span>#</span></a></h2><p>进入申请进度查询页面，输入你的信息查询，点击你要具体查询的卡，按 F12 打开浏览器控制台（Console），直接粘贴输入以下单行表达式，即可实时返回完整卡号与对应制卡状态：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>document.getElementsByClassName("progress-result-wrap")[0].__vue__.$parent.recordInfo.cardNo.replace(/\s+/g, '') + " | " + document.getElementsByClassName("progress-result-wrap")[0].__vue__.$parent.recordInfo.mkcrdProgreSts + " (" + ({'00':'待制卡','01':'制卡中','03':'寄送中','04':'待领卡','06':'已领卡','02':'流程异常','05':'流程异常','07':'流程异常'}[document.getElementsByClassName("progress-result-wrap")[0].__vue__.$parent.recordInfo.mkcrdProgreSts] || "未知") + ")"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者你也可以使用我使用 DeepSeek V4.1 Flash 制作的 JS 脚本：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// ==UserScript==</span></div></div><div><div><div>2</div></div><div><span>// @name         中银制卡进度提取</span></div></div><div><div><div>3</div></div><div><span>// @namespace    https://viayoo.com/</span></div></div><div><div><div>4</div></div><div><span>// @version      1.2.0</span></div></div><div><div><div>5</div></div><div><span>// @description  从 open.boc.cn 预约开户进度页读取 Vue recordInfo，输出卡号+状态，并自动枚举其余字段</span></div></div><div><div><div>6</div></div><div><span>// @author       lianlian</span></div></div><div><div><div>7</div></div><div><span>// @run-at       document-idle</span></div></div><div><div><div>8</div></div><div><span>// @noframes</span></div></div><div><div><div>9</div></div><div><span>// @match        https://open.boc.cn/bankcard/cardReserve/*</span></div></div><div><div><div>10</div></div><div><span>// @grant        none</span></div></div><div><div><div>11</div></div><div><span>// ==/UserScript==</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>(function () {</span></div></div><div><div><div>14</div></div><div><span><span>  </span></span><span>'use strict';</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>if (window.top !== window.self) return;</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span><span>  </span></span><span>/* ================= 可调参数 ================= */</span></div></div><div><div><div>18</div></div><div><span><span>  </span></span><span>const CFG = {</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>selector: '.progress-result-wrap',</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>autoCopy: false,                   // 首次拿到结果自动复制</span></div></div><div><div><div>21</div></div><div><span><span>    </span></span><span>showPanel: true,                   // false = 只往 console 打</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>fields: 'auto',                    // 'none' 只卡号+状态 | 'auto' 自动附加全部字段 | 'json' 附完整 JSON</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>includeEmpty: false,               // auto 模式是否输出空值字段</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>maxValueLen: 80,                   // auto 模式单个值超长截断</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>skipKeys: ['cardNo', 'mkcrdProgreSts'], // 已经单独输出了，不再重复</span></div></div><div><div><div>26</div></div><div><span><span>    </span></span><span>recheckDelay: 400,</span></div></div><div><div><div>27</div></div><div><span><span>    </span></span><span>idleInterval: 1500,</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>giveUpAfterMs: 3 * 60 * 1000</span></div></div><div><div><div>29</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span><span>  </span></span><span>const STS = {</span></div></div><div><div><div>32</div></div><div><span><span>    </span></span><span>'00': '待制卡', '01': '制卡中', '03': '寄送中', '04': '待领卡',</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>'06': '已领卡', '02': '流程异常', '05': '流程异常', '07': '流程异常'</span></div></div><div><div><div>34</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div><span><span>  </span></span><span>const padCode = (c) =&gt; {</span></div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>const s = String(c == null ? '' : c).trim();</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>return /^\d$/.test(s) ? '0' + s : s;</span></div></div><div><div><div>39</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span><span>  </span></span><span>/* ============ 1. 找元素所属的 Vue 实例 ============ */</span></div></div><div><div><div>42</div></div><div><span><span>  </span></span><span>function findVm(elm) {</span></div></div><div><div><div>43</div></div><div><span><span>    </span></span><span>if (elm.__vue__) return { v2: elm.__vue__ };</span></div></div><div><div><div>44</div></div><div><span><span>    </span></span><span>let p = elm.parentElement, d = 0;</span></div></div><div><div><div>45</div></div><div><span><span>    </span></span><span>while (p &amp;&amp; d++ &lt; 60) {</span></div></div><div><div><div>46</div></div><div><span><span>      </span></span><span>if (p.__vue__) return { v2: p.__vue__ };</span></div></div><div><div><div>47</div></div><div><span><span>      </span></span><span>p = p.parentElement;</span></div></div><div><div><div>48</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>49</div></div><div><span><span>    </span></span><span>if (elm.__vueParentComponent) return { v3: elm.__vueParentComponent };</span></div></div><div><div><div>50</div></div><div><span><span>    </span></span><span>p = elm.parentElement; d = 0;</span></div></div><div><div><div>51</div></div><div><span><span>    </span></span><span>while (p &amp;&amp; d++ &lt; 60) {</span></div></div><div><div><div>52</div></div><div><span><span>      </span></span><span>if (p.__vueParentComponent) return { v3: p.__vueParentComponent };</span></div></div><div><div><div>53</div></div><div><span><span>      </span></span><span>p = p.parentElement;</span></div></div><div><div><div>54</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>return null;</span></div></div><div><div><div>56</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>57</div></div><div>
</div></div><div><div><div>58</div></div><div><span><span>  </span></span><span>/* ============ 2. 取 recordInfo ============ */</span></div></div><div><div><div>59</div></div><div><span><span>  </span></span><span>const pickInfo = (o) =&gt; !o ? null :</span></div></div><div><div><div>60</div></div><div><span><span>    </span></span><span>o.recordInfo || (o.$data &amp;&amp; o.$data.recordInfo) || (o.$props &amp;&amp; o.$props.recordInfo) || null;</span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div><span><span>  </span></span><span>function infoFromV2(vm) {</span></div></div><div><div><div>63</div></div><div><span><span>    </span></span><span>let cur = vm;</span></div></div><div><div><div>64</div></div><div><span><span>    </span></span><span>for (let i = 0; i &lt; 8 &amp;&amp; cur; i++) {</span></div></div><div><div><div>65</div></div><div><span><span>      </span></span><span>const info = pickInfo(cur);</span></div></div><div><div><div>66</div></div><div><span><span>      </span></span><span>if (info &amp;&amp; info.cardNo) return info;</span></div></div><div><div><div>67</div></div><div><span><span>      </span></span><span>cur = cur.$parent;</span></div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>69</div></div><div><span><span>    </span></span><span>return searchV2(vm.$root, 0);</span></div></div><div><div><div>70</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>71</div></div><div><span><span>  </span></span><span>function searchV2(vm, d) {</span></div></div><div><div><div>72</div></div><div><span><span>    </span></span><span>if (!vm || d &gt; 12) return null;</span></div></div><div><div><div>73</div></div><div><span><span>    </span></span><span>const info = pickInfo(vm);</span></div></div><div><div><div>74</div></div><div><span><span>    </span></span><span>if (info &amp;&amp; info.cardNo) return info;</span></div></div><div><div><div>75</div></div><div><span><span>    </span></span><span>const kids = vm.$children || [];</span></div></div><div><div><div>76</div></div><div><span><span>    </span></span><span>for (let i = 0; i &lt; kids.length; i++) {</span></div></div><div><div><div>77</div></div><div><span><span>      </span></span><span>const r = searchV2(kids[i], d + 1);</span></div></div><div><div><div>78</div></div><div><span><span>      </span></span><span>if (r) return r;</span></div></div><div><div><div>79</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>80</div></div><div><span><span>    </span></span><span>return null;</span></div></div><div><div><div>81</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>82</div></div><div><span><span>  </span></span><span>function infoFromV3(inst) {</span></div></div><div><div><div>83</div></div><div><span><span>    </span></span><span>let cur = inst;</span></div></div><div><div><div>84</div></div><div><span><span>    </span></span><span>for (let i = 0; i &lt; 8 &amp;&amp; cur; i++) {</span></div></div><div><div><div>85</div></div><div><span><span>      </span></span><span>const info = pickInfo(cur.ctx || cur.proxy || cur);</span></div></div><div><div><div>86</div></div><div><span><span>      </span></span><span>if (info &amp;&amp; info.cardNo) return info;</span></div></div><div><div><div>87</div></div><div><span><span>      </span></span><span>cur = cur.parent;</span></div></div><div><div><div>88</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>89</div></div><div><span><span>    </span></span><span>return null;</span></div></div><div><div><div>90</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>91</div></div><div>
</div></div><div><div><div>92</div></div><div><span><span>  </span></span><span>/* ============ 3. 自动枚举字段 ============ */</span></div></div><div><div><div>93</div></div><div><span><span>  </span></span><span>function dumpKeys(info) {</span></div></div><div><div><div>94</div></div><div><span><span>    </span></span><span>const set = new Set(Object.keys(info));</span></div></div><div><div><div>95</div></div><div><span><span>    </span></span><span>// 兜底：有些字段是 getter / 挂在 $data 上，不在 Object.keys 里</span></div></div><div><div><div>96</div></div><div><span><span>    </span></span><span>try {</span></div></div><div><div><div>97</div></div><div><span><span>      </span></span><span>Object.getOwnPropertyNames(info).forEach((k) =&gt; set.add(k));</span></div></div><div><div><div>98</div></div><div><span><span>      </span></span><span>if (info.$data) Object.keys(info.$data).forEach((k) =&gt; set.add(k));</span></div></div><div><div><div>99</div></div><div><span><span>    </span></span><span>} catch (e) { /* ignore */ }</span></div></div><div><div><div>100</div></div><div>
</div></div><div><div><div>101</div></div><div><span><span>    </span></span><span>return Array.from(set).filter((k) =&gt;</span></div></div><div><div><div>102</div></div><div><span><span>      </span></span><span>k !== '__ob__' &amp;&amp;</span></div></div><div><div><div>103</div></div><div><span><span>      </span></span><span>!/^(_|\$)/.test(k) &amp;&amp;                 // 过滤 __ob__ / _isVue / $data 等</span></div></div><div><div><div>104</div></div><div><span><span>      </span></span><span>CFG.skipKeys.indexOf(k) === -1 &amp;&amp;</span></div></div><div><div><div>105</div></div><div><span><span>      </span></span><span>typeof readVal(info, k) !== 'function'</span></div></div><div><div><div>106</div></div><div><span><span>    </span></span><span>);</span></div></div><div><div><div>107</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>108</div></div><div>
</div></div><div><div><div>109</div></div><div><span><span>  </span></span><span>function readVal(info, k) {</span></div></div><div><div><div>110</div></div><div><span><span>    </span></span><span>try { return info[k]; } catch (e) { return undefined; } // getter 抛错就跳过</span></div></div><div><div><div>111</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>112</div></div><div>
</div></div><div><div><div>113</div></div><div><span><span>  </span></span><span>function fmtValue(v, maxLen) {</span></div></div><div><div><div>114</div></div><div><span><span>    </span></span><span>let s;</span></div></div><div><div><div>115</div></div><div><span><span>    </span></span><span>if (v === null || v === undefined) s = '';</span></div></div><div><div><div>116</div></div><div><span><span>    </span></span><span>else if (typeof v === 'object') {</span></div></div><div><div><div>117</div></div><div><span><span>      </span></span><span>try { s = JSON.stringify(v); } catch (e) { s = String(v); }</span></div></div><div><div><div>118</div></div><div><span><span>      </span></span><span>if (s &amp;&amp; s.length &gt; maxLen) s = s.slice(0, maxLen) + '…';</span></div></div><div><div><div>119</div></div><div><span><span>    </span></span><span>} else s = String(v);</span></div></div><div><div><div>120</div></div><div><span><span>    </span></span><span>return s.replace(/\s+/g, ' ').trim();</span></div></div><div><div><div>121</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>122</div></div><div>
</div></div><div><div><div>123</div></div><div><span><span>  </span></span><span>function autoPairs(info) {</span></div></div><div><div><div>124</div></div><div><span><span>    </span></span><span>const out = [];</span></div></div><div><div><div>125</div></div><div><span><span>    </span></span><span>dumpKeys(info).forEach((k) =&gt; {</span></div></div><div><div><div>126</div></div><div><span><span>      </span></span><span>const s = fmtValue(readVal(info, k), CFG.maxValueLen);</span></div></div><div><div><div>127</div></div><div><span><span>      </span></span><span>if (!s &amp;&amp; !CFG.includeEmpty) return;</span></div></div><div><div><div>128</div></div><div><span><span>      </span></span><span>out.push(k + ':' + (s || '-'));</span></div></div><div><div><div>129</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>130</div></div><div><span><span>    </span></span><span>return out;</span></div></div><div><div><div>131</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>132</div></div><div>
</div></div><div><div><div>133</div></div><div><span><span>  </span></span><span>/* ============ 4. 扫描 + 格式化 ============ */</span></div></div><div><div><div>134</div></div><div><span><span>  </span></span><span>function formatOne(info) {</span></div></div><div><div><div>135</div></div><div><span><span>    </span></span><span>const cardNo = String(info.cardNo).replace(/\s+/g, '');</span></div></div><div><div><div>136</div></div><div><span><span>    </span></span><span>const raw = info.mkcrdProgreSts;</span></div></div><div><div><div>137</div></div><div><span><span>    </span></span><span>const text = STS[padCode(raw)] || '未知';</span></div></div><div><div><div>138</div></div><div><span><span>    </span></span><span>let line = cardNo + ' | ' + raw + ' (' + text + ')';</span></div></div><div><div><div>139</div></div><div>
</div></div><div><div><div>140</div></div><div><span><span>    </span></span><span>if (CFG.fields === 'json') {</span></div></div><div><div><div>141</div></div><div><span><span>      </span></span><span>let j;</span></div></div><div><div><div>142</div></div><div><span><span>      </span></span><span>try { j = JSON.stringify(info); } catch (e) { j = String(info); }</span></div></div><div><div><div>143</div></div><div><span><span>      </span></span><span>return line + ' | ' + j;</span></div></div><div><div><div>144</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>145</div></div><div><span><span>    </span></span><span>if (CFG.fields === 'auto') {</span></div></div><div><div><div>146</div></div><div><span><span>      </span></span><span>autoPairs(info).forEach((p) =&gt; { line += ' | ' + p; });</span></div></div><div><div><div>147</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>148</div></div><div><span><span>    </span></span><span>return line;</span></div></div><div><div><div>149</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>150</div></div><div>
</div></div><div><div><div>151</div></div><div><span><span>  </span></span><span>function scan() {</span></div></div><div><div><div>152</div></div><div><span><span>    </span></span><span>const wraps = document.querySelectorAll(CFG.selector);</span></div></div><div><div><div>153</div></div><div><span><span>    </span></span><span>const lines = [], infos = [];</span></div></div><div><div><div>154</div></div><div><span><span>    </span></span><span>for (let i = 0; i &lt; wraps.length; i++) {</span></div></div><div><div><div>155</div></div><div><span><span>      </span></span><span>const vm = findVm(wraps[i]);</span></div></div><div><div><div>156</div></div><div><span><span>      </span></span><span>if (!vm) continue;</span></div></div><div><div><div>157</div></div><div><span><span>      </span></span><span>const info = vm.v2 ? infoFromV2(vm.v2) : infoFromV3(vm.v3);</span></div></div><div><div><div>158</div></div><div><span><span>      </span></span><span>if (!info || !info.cardNo) continue;</span></div></div><div><div><div>159</div></div><div><span><span>      </span></span><span>lines.push(formatOne(info));</span></div></div><div><div><div>160</div></div><div><span><span>      </span></span><span>infos.push(info);</span></div></div><div><div><div>161</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>162</div></div><div><span><span>    </span></span><span>// 去重（保留首次出现顺序）</span></div></div><div><div><div>163</div></div><div><span><span>    </span></span><span>const seen = {}, uLines = [], uInfos = [];</span></div></div><div><div><div>164</div></div><div><span><span>    </span></span><span>lines.forEach((l, i) =&gt; {</span></div></div><div><div><div>165</div></div><div><span><span>      </span></span><span>if (seen[l]) return;</span></div></div><div><div><div>166</div></div><div><span><span>      </span></span><span>seen[l] = 1;</span></div></div><div><div><div>167</div></div><div><span><span>      </span></span><span>uLines.push(l);</span></div></div><div><div><div>168</div></div><div><span><span>      </span></span><span>uInfos.push(infos[i]);</span></div></div><div><div><div>169</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>170</div></div><div><span><span>    </span></span><span>return { lines: uLines, infos: uInfos };</span></div></div><div><div><div>171</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>172</div></div><div>
</div></div><div><div><div>173</div></div><div><span><span>  </span></span><span>/* ============ 5. 复制 ============ */</span></div></div><div><div><div>174</div></div><div><span><span>  </span></span><span>function fallbackCopy(text) {</span></div></div><div><div><div>175</div></div><div><span><span>    </span></span><span>try {</span></div></div><div><div><div>176</div></div><div><span><span>      </span></span><span>const ta = document.createElement('textarea');</span></div></div><div><div><div>177</div></div><div><span><span>      </span></span><span>ta.value = text;</span></div></div><div><div><div>178</div></div><div><span><span>      </span></span><span>ta.setAttribute('readonly', '');</span></div></div><div><div><div>179</div></div><div><span><span>      </span></span><span>ta.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;';</span></div></div><div><div><div>180</div></div><div><span><span>      </span></span><span>document.body.appendChild(ta);</span></div></div><div><div><div>181</div></div><div><span><span>      </span></span><span>ta.focus(); ta.select();</span></div></div><div><div><div>182</div></div><div><span><span>      </span></span><span>ta.setSelectionRange(0, text.length);</span></div></div><div><div><div>183</div></div><div><span><span>      </span></span><span>const ok = document.execCommand('copy');</span></div></div><div><div><div>184</div></div><div><span><span>      </span></span><span>ta.remove();</span></div></div><div><div><div>185</div></div><div><span><span>      </span></span><span>return ok;</span></div></div><div><div><div>186</div></div><div><span><span>    </span></span><span>} catch (e) { return false; }</span></div></div><div><div><div>187</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>188</div></div><div><span><span>  </span></span><span>function copy(text) {</span></div></div><div><div><div>189</div></div><div><span><span>    </span></span><span>return new Promise((res) =&gt; {</span></div></div><div><div><div>190</div></div><div><span><span>      </span></span><span>if (navigator.clipboard &amp;&amp; window.isSecureContext) {</span></div></div><div><div><div>191</div></div><div><span><span>        </span></span><span>navigator.clipboard.writeText(text).then(() =&gt; res(true), () =&gt; res(fallbackCopy(text)));</span></div></div><div><div><div>192</div></div><div><span><span>      </span></span><span>} else res(fallbackCopy(text));</span></div></div><div><div><div>193</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>194</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>195</div></div><div>
</div></div><div><div><div>196</div></div><div><span><span>  </span></span><span>/* ============ 6. 悬浮面板 ============ */</span></div></div><div><div><div>197</div></div><div><span><span>  </span></span><span>let panel, titleEl, bodyEl, tipEl, tipTimer, fieldBtn;</span></div></div><div><div><div>198</div></div><div>
</div></div><div><div><div>199</div></div><div><span><span>  </span></span><span>function mkBtn(label, onclick) {</span></div></div><div><div><div>200</div></div><div><span><span>    </span></span><span>const b = document.createElement('button');</span></div></div><div><div><div>201</div></div><div><span><span>    </span></span><span>b.textContent = label;</span></div></div><div><div><div>202</div></div><div><span><span>    </span></span><span>b.style.cssText = 'all:unset;cursor:pointer;padding:2px 7px;border-radius:6px;' +</span></div></div><div><div><div>203</div></div><div><span><span>      </span></span><span>'background:rgba(255,255,255,.12);font-size:12px;line-height:1.5';</span></div></div><div><div><div>204</div></div><div><span><span>    </span></span><span>const stop = (e) =&gt; e.stopPropagation();</span></div></div><div><div><div>205</div></div><div><span><span>    </span></span><span>b.addEventListener('mousedown', stop);</span></div></div><div><div><div>206</div></div><div><span><span>    </span></span><span>b.addEventListener('touchstart', stop, { passive: true });</span></div></div><div><div><div>207</div></div><div><span><span>    </span></span><span>b.addEventListener('click', (e) =&gt; { e.stopPropagation(); onclick(b); });</span></div></div><div><div><div>208</div></div><div><span><span>    </span></span><span>return b;</span></div></div><div><div><div>209</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>210</div></div><div>
</div></div><div><div><div>211</div></div><div><span><span>  </span></span><span>function flash(msg, ok) {</span></div></div><div><div><div>212</div></div><div><span><span>    </span></span><span>if (!tipEl) return;</span></div></div><div><div><div>213</div></div><div><span><span>    </span></span><span>tipEl.textContent = msg;</span></div></div><div><div><div>214</div></div><div><span><span>    </span></span><span>tipEl.style.color = ok ? '#8fd18f' : '#ff9a9a';</span></div></div><div><div><div>215</div></div><div><span><span>    </span></span><span>clearTimeout(tipTimer);</span></div></div><div><div><div>216</div></div><div><span><span>    </span></span><span>tipTimer = setTimeout(() =&gt; { tipEl.textContent = ''; }, 1500);</span></div></div><div><div><div>217</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>218</div></div><div>
</div></div><div><div><div>219</div></div><div><span><span>  </span></span><span>const FIELD_LABEL = { none: '简', auto: '全', json: 'JSON' };</span></div></div><div><div><div>220</div></div><div>
</div></div><div><div><div>221</div></div><div><span><span>  </span></span><span>function setFieldMode(mode) {</span></div></div><div><div><div>222</div></div><div><span><span>    </span></span><span>CFG.fields = mode;</span></div></div><div><div><div>223</div></div><div><span><span>    </span></span><span>fieldBtn.textContent = '字段:' + FIELD_LABEL[mode];</span></div></div><div><div><div>224</div></div><div><span><span>    </span></span><span>lastText = '';</span></div></div><div><div><div>225</div></div><div><span><span>    </span></span><span>tick();</span></div></div><div><div><div>226</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>227</div></div><div>
</div></div><div><div><div>228</div></div><div><span><span>  </span></span><span>function ensurePanel() {</span></div></div><div><div><div>229</div></div><div><span><span>    </span></span><span>if (panel || !CFG.showPanel) return;</span></div></div><div><div><div>230</div></div><div><span><span>    </span></span><span>panel = document.createElement('div');</span></div></div><div><div><div>231</div></div><div><span><span>    </span></span><span>panel.style.cssText = 'position:fixed;left:8px;bottom:8px;z-index:2147483647;max-width:min(92vw,400px);' +</span></div></div><div><div><div>232</div></div><div><span><span>      </span></span><span>'background:rgba(18,20,24,.93);color:#e9eaed;border:1px solid rgba(255,255,255,.16);border-radius:10px;' +</span></div></div><div><div><div>233</div></div><div><span><span>      </span></span><span>'font:13px/1.55 -apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;' +</span></div></div><div><div><div>234</div></div><div><span><span>      </span></span><span>'box-shadow:0 8px 28px rgba(0,0,0,.4);overflow:hidden;' +</span></div></div><div><div><div>235</div></div><div><span><span>      </span></span><span>'-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)';</span></div></div><div><div><div>236</div></div><div>
</div></div><div><div><div>237</div></div><div><span><span>    </span></span><span>const head = document.createElement('div');</span></div></div><div><div><div>238</div></div><div><span><span>    </span></span><span>head.style.cssText = 'display:flex;align-items:center;gap:5px;padding:6px 8px;' +</span></div></div><div><div><div>239</div></div><div><span><span>      </span></span><span>'background:rgba(255,255,255,.07);cursor:move;touch-action:none';</span></div></div><div><div><div>240</div></div><div>
</div></div><div><div><div>241</div></div><div><span><span>    </span></span><span>titleEl = document.createElement('div');</span></div></div><div><div><div>242</div></div><div><span><span>    </span></span><span>titleEl.textContent = '制卡进度';</span></div></div><div><div><div>243</div></div><div><span><span>    </span></span><span>titleEl.style.cssText = 'flex:1;min-width:0;font-weight:600;white-space:nowrap;overflow:hidden;' +</span></div></div><div><div><div>244</div></div><div><span><span>      </span></span><span>'text-overflow:ellipsis;-webkit-user-select:none;user-select:none';</span></div></div><div><div><div>245</div></div><div>
</div></div><div><div><div>246</div></div><div><span><span>    </span></span><span>tipEl = document.createElement('span');</span></div></div><div><div><div>247</div></div><div><span><span>    </span></span><span>tipEl.style.cssText = 'font-size:11px;color:#8fd18f;white-space:nowrap';</span></div></div><div><div><div>248</div></div><div>
</div></div><div><div><div>249</div></div><div><span><span>    </span></span><span>fieldBtn = mkBtn('字段:' + FIELD_LABEL[CFG.fields], () =&gt; {</span></div></div><div><div><div>250</div></div><div><span><span>      </span></span><span>const order = ['none', 'auto', 'json'];</span></div></div><div><div><div>251</div></div><div><span><span>      </span></span><span>setFieldMode(order[(order.indexOf(CFG.fields) + 1) % order.length]);</span></div></div><div><div><div>252</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>253</div></div><div>
</div></div><div><div><div>254</div></div><div><span><span>    </span></span><span>head.appendChild(titleEl);</span></div></div><div><div><div>255</div></div><div><span><span>    </span></span><span>head.appendChild(tipEl);</span></div></div><div><div><div>256</div></div><div><span><span>    </span></span><span>head.appendChild(mkBtn('复制', () =&gt; copyAll()));</span></div></div><div><div><div>257</div></div><div><span><span>    </span></span><span>head.appendChild(fieldBtn);</span></div></div><div><div><div>258</div></div><div><span><span>    </span></span><span>head.appendChild(mkBtn('↻', () =&gt; { lastText = ''; tick(); }));</span></div></div><div><div><div>259</div></div><div><span><span>    </span></span><span>const collapse = mkBtn('－', () =&gt; {</span></div></div><div><div><div>260</div></div><div><span><span>      </span></span><span>const hidden = bodyEl.style.display === 'none';</span></div></div><div><div><div>261</div></div><div><span><span>      </span></span><span>bodyEl.style.display = hidden ? '' : 'none';</span></div></div><div><div><div>262</div></div><div><span><span>      </span></span><span>collapse.textContent = hidden ? '－' : '＋';</span></div></div><div><div><div>263</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>264</div></div><div><span><span>    </span></span><span>head.appendChild(collapse);</span></div></div><div><div><div>265</div></div><div>
</div></div><div><div><div>266</div></div><div><span><span>    </span></span><span>bodyEl = document.createElement('div');</span></div></div><div><div><div>267</div></div><div><span><span>    </span></span><span>bodyEl.style.cssText = 'max-height:45vh;overflow:auto;-webkit-overflow-scrolling:touch';</span></div></div><div><div><div>268</div></div><div>
</div></div><div><div><div>269</div></div><div><span><span>    </span></span><span>panel.appendChild(head);</span></div></div><div><div><div>270</div></div><div><span><span>    </span></span><span>panel.appendChild(bodyEl);</span></div></div><div><div><div>271</div></div><div><span><span>    </span></span><span>document.body.appendChild(panel);</span></div></div><div><div><div>272</div></div><div>
</div></div><div><div><div>273</div></div><div><span><span>    </span></span><span>let dragging = false, sx = 0, sy = 0, ox = 0, oy = 0;</span></div></div><div><div><div>274</div></div><div><span><span>    </span></span><span>const onDown = (e) =&gt; {</span></div></div><div><div><div>275</div></div><div><span><span>      </span></span><span>const t = e.touches ? e.touches[0] : e;</span></div></div><div><div><div>276</div></div><div><span><span>      </span></span><span>const r = panel.getBoundingClientRect();</span></div></div><div><div><div>277</div></div><div><span><span>      </span></span><span>dragging = true; sx = t.clientX; sy = t.clientY; ox = r.left; oy = r.top;</span></div></div><div><div><div>278</div></div><div><span><span>      </span></span><span>e.preventDefault();</span></div></div><div><div><div>279</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>280</div></div><div><span><span>    </span></span><span>const onMove = (e) =&gt; {</span></div></div><div><div><div>281</div></div><div><span><span>      </span></span><span>if (!dragging) return;</span></div></div><div><div><div>282</div></div><div><span><span>      </span></span><span>const t = e.touches ? e.touches[0] : e;</span></div></div><div><div><div>283</div></div><div><span><span>      </span></span><span>const x = Math.min(Math.max(0, ox + t.clientX - sx), innerWidth - panel.offsetWidth);</span></div></div><div><div><div>284</div></div><div><span><span>      </span></span><span>const y = Math.min(Math.max(0, oy + t.clientY - sy), innerHeight - panel.offsetHeight);</span></div></div><div><div><div>285</div></div><div><span><span>      </span></span><span>panel.style.left = x + 'px';</span></div></div><div><div><div>286</div></div><div><span><span>      </span></span><span>panel.style.top = y + 'px';</span></div></div><div><div><div>287</div></div><div><span><span>      </span></span><span>panel.style.right = 'auto';</span></div></div><div><div><div>288</div></div><div><span><span>      </span></span><span>panel.style.bottom = 'auto';</span></div></div><div><div><div>289</div></div><div><span><span>      </span></span><span>e.preventDefault();</span></div></div><div><div><div>290</div></div><div><span><span>    </span></span><span>};</span></div></div><div><div><div>291</div></div><div><span><span>    </span></span><span>const onUp = () =&gt; { dragging = false; };</span></div></div><div><div><div>292</div></div><div><span><span>    </span></span><span>head.addEventListener('mousedown', onDown);</span></div></div><div><div><div>293</div></div><div><span><span>    </span></span><span>head.addEventListener('touchstart', onDown, { passive: false });</span></div></div><div><div><div>294</div></div><div><span><span>    </span></span><span>document.addEventListener('mousemove', onMove);</span></div></div><div><div><div>295</div></div><div><span><span>    </span></span><span>document.addEventListener('touchmove', onMove, { passive: false });</span></div></div><div><div><div>296</div></div><div><span><span>    </span></span><span>document.addEventListener('mouseup', onUp);</span></div></div><div><div><div>297</div></div><div><span><span>    </span></span><span>document.addEventListener('touchend', onUp);</span></div></div><div><div><div>298</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>299</div></div><div>
</div></div><div><div><div>300</div></div><div><span><span>  </span></span><span>function render(lines) {</span></div></div><div><div><div>301</div></div><div><span><span>    </span></span><span>if (!CFG.showPanel) return;</span></div></div><div><div><div>302</div></div><div><span><span>    </span></span><span>ensurePanel();</span></div></div><div><div><div>303</div></div><div><span><span>    </span></span><span>titleEl.textContent = '制卡进度 · ' + lines.length + ' 条';</span></div></div><div><div><div>304</div></div><div><span><span>    </span></span><span>bodyEl.innerHTML = '';</span></div></div><div><div><div>305</div></div><div><span><span>    </span></span><span>lines.forEach((line) =&gt; {</span></div></div><div><div><div>306</div></div><div><span><span>      </span></span><span>const row = document.createElement('div');</span></div></div><div><div><div>307</div></div><div><span><span>      </span></span><span>row.textContent = line;</span></div></div><div><div><div>308</div></div><div><span><span>      </span></span><span>row.style.cssText = 'padding:6px 9px;border-top:1px solid rgba(255,255,255,.09);' +</span></div></div><div><div><div>309</div></div><div><span><span>        </span></span><span>'cursor:pointer;word-break:break-all;-webkit-user-select:text;user-select:text';</span></div></div><div><div><div>310</div></div><div><span><span>      </span></span><span>row.addEventListener('click', () =&gt; {</span></div></div><div><div><div>311</div></div><div><span><span>        </span></span><span>copy(line).then((ok) =&gt; flash(ok ? '已复制' : '复制失败', ok));</span></div></div><div><div><div>312</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>313</div></div><div><span><span>      </span></span><span>bodyEl.appendChild(row);</span></div></div><div><div><div>314</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>315</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>316</div></div><div>
</div></div><div><div><div>317</div></div><div><span><span>  </span></span><span>function copyAll() {</span></div></div><div><div><div>318</div></div><div><span><span>    </span></span><span>if (!lastText) return flash('暂无数据', false);</span></div></div><div><div><div>319</div></div><div><span><span>    </span></span><span>copy(lastText).then((ok) =&gt; flash(ok ? '已复制全部' : '复制失败', ok));</span></div></div><div><div><div>320</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>321</div></div><div>
</div></div><div><div><div>322</div></div><div><span><span>  </span></span><span>/* ============ 7. 调度 ============ */</span></div></div><div><div><div>323</div></div><div><span><span>  </span></span><span>let lastText = '', found = false, autoCopied = false, queued = null, lastActive = Date.now(), loggedOnce = false;</span></div></div><div><div><div>324</div></div><div>
</div></div><div><div><div>325</div></div><div><span><span>  </span></span><span>function tick() {</span></div></div><div><div><div>326</div></div><div><span><span>    </span></span><span>const res = scan();</span></div></div><div><div><div>327</div></div><div><span><span>    </span></span><span>const lines = res.lines;</span></div></div><div><div><div>328</div></div><div><span><span>    </span></span><span>if (lines.length) {</span></div></div><div><div><div>329</div></div><div><span><span>      </span></span><span>found = true;</span></div></div><div><div><div>330</div></div><div><span><span>      </span></span><span>const text = lines.join('\n');</span></div></div><div><div><div>331</div></div><div><span><span>      </span></span><span>if (text !== lastText) {</span></div></div><div><div><div>332</div></div><div><span><span>        </span></span><span>lastText = text;</span></div></div><div><div><div>333</div></div><div><span><span>        </span></span><span>console.log('[制卡进度]\n' + text);</span></div></div><div><div><div>334</div></div><div><span><span>        </span></span><span>if (!loggedOnce) {                  // 顺手把原始对象也丢进控制台，方便核对字段</span></div></div><div><div><div>335</div></div><div><span><span>          </span></span><span>loggedOnce = true;</span></div></div><div><div><div>336</div></div><div><span><span>          </span></span><span>console.log('[制卡进度] 原始 recordInfo:', res.infos);</span></div></div><div><div><div>337</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>338</div></div><div><span><span>        </span></span><span>render(lines);</span></div></div><div><div><div>339</div></div><div><span><span>        </span></span><span>if (CFG.autoCopy &amp;&amp; !autoCopied) { autoCopied = true; copy(text); }</span></div></div><div><div><div>340</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>341</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>342</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>343</div></div><div>
</div></div><div><div><div>344</div></div><div><span><span>  </span></span><span>function schedule(delay) {</span></div></div><div><div><div>345</div></div><div><span><span>    </span></span><span>if (queued) return;</span></div></div><div><div><div>346</div></div><div><span><span>    </span></span><span>queued = setTimeout(() =&gt; { queued = null; tick(); }, delay == null ? CFG.recheckDelay : delay);</span></div></div><div><div><div>347</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>348</div></div><div>
</div></div><div><div><div>349</div></div><div><span><span>  </span></span><span>const mo = new MutationObserver(() =&gt; schedule());</span></div></div><div><div><div>350</div></div><div>
</div></div><div><div><div>351</div></div><div><span><span>  </span></span><span>const boot = () =&gt; {</span></div></div><div><div><div>352</div></div><div><span><span>    </span></span><span>if (!document.documentElement) return setTimeout(boot, 200);</span></div></div><div><div><div>353</div></div><div><span><span>    </span></span><span>mo.observe(document.documentElement, { childList: true, subtree: true, characterData: true });</span></div></div><div><div><div>354</div></div><div><span><span>    </span></span><span>tick();</span></div></div><div><div><div>355</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>356</div></div><div><span><span>  </span></span><span>boot();</span></div></div><div><div><div>357</div></div><div>
</div></div><div><div><div>358</div></div><div><span><span>  </span></span><span>window.addEventListener('hashchange', () =&gt; {</span></div></div><div><div><div>359</div></div><div><span><span>    </span></span><span>lastActive = Date.now();</span></div></div><div><div><div>360</div></div><div><span><span>    </span></span><span>lastText = '';</span></div></div><div><div><div>361</div></div><div><span><span>    </span></span><span>found = false;</span></div></div><div><div><div>362</div></div><div><span><span>    </span></span><span>schedule(600);</span></div></div><div><div><div>363</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>364</div></div><div>
</div></div><div><div><div>365</div></div><div><span><span>  </span></span><span>const iv = setInterval(() =&gt; {</span></div></div><div><div><div>366</div></div><div><span><span>    </span></span><span>if (Date.now() - lastActive &gt; CFG.giveUpAfterMs) {</span></div></div><div><div><div>367</div></div><div><span><span>      </span></span><span>clearInterval(iv);</span></div></div><div><div><div>368</div></div><div><span><span>      </span></span><span>console.log('[制卡进度] 长时间未找到 .progress-result-wrap，已停止轮询。');</span></div></div><div><div><div>369</div></div><div><span><span>      </span></span><span>return;</span></div></div><div><div><div>370</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>371</div></div><div><span><span>    </span></span><span>tick();</span></div></div><div><div><div>372</div></div><div><span><span>  </span></span><span>}, CFG.idleInterval);</span></div></div><div><div><div>373</div></div><div><span>})();</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/fluxer/%E8%87%AA%E5%BB%BA-discord-%E6%9D%A5%E4%BA%86fluxer-%E9%83%A8%E7%BD%B2%E6%95%99%E7%A8%8B/</id>
      <title type="text">自建 Discord 来了！Fluxer 部署教程</title>
      <published>2026-08-07T00:00:00.000Z</published>
      <updated>2026-08-07T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/fluxer/%E8%87%AA%E5%BB%BA-discord-%E6%9D%A5%E4%BA%86fluxer-%E9%83%A8%E7%BD%B2%E6%95%99%E7%A8%8B/"/>
      <summary type="text">带你了解最新的语音软件</summary>
      <content type="html"><![CDATA[<section><h1>开始使用<a href="#开始使用"><span>#</span></a></h1><p>使用 Docker Compose 运行你自己的 Fluxer 实例。本指南将带你从一台全新的服务器开始，部署一个完整可用的自托管实例，包括：Web 应用、API 服务、网关服务（Gateway）、管理后台、媒体文件上传、搜索功能、存储系统、语音信令服务，所有服务都会通过一个公网域名统一访问。</p></section>
<section><h1>你将运行的内容<a href="#你将运行的内容"><span>#</span></a></h1><p>该自托管技术栈由一个 Docker Compose 项目组成：</p><ul>
<li><strong>Caddy</strong> 负责终止公共 HTTP(S) 流量，或接收来自 Cloudflare Tunnel 的流量。</li>
<li><strong>App proxy（应用代理）</strong> 用于提供 Fluxer Web 客户端服务，并注入实例的引导数据（bootstrap data）。</li>
<li><strong>API</strong> 处理账户、身份验证、社区、消息、文件上传、管理员 API 以及实例发现。</li>
<li><strong>Admin dashboard（管理员仪表盘）</strong> 是必需的，并在 <code>/admin</code> 路径下提供服务。</li>
<li><strong>Gateway（网关）</strong> 处理 WebSocket 会话、在线状态、事件调度、推送分发（push fanout）以及实时事件。</li>
<li><strong>Messages service（消息服务）</strong> 构建消息响应并提供历史消息服务。</li>
<li><strong>Media proxy（媒体代理）</strong> 处理附件上传的中继、媒体元数据、缩略图以及对象读取。</li>
<li><strong>Static proxy（静态代理）</strong> 在同一主机名下提供 Fluxer 字体、图标、表情符号、徽章、默认头像和语音客户端资源。</li>
<li><strong>LiveKit</strong> 处理语音与视频信令以及 WebRTC 媒体。</li>
<li><strong>Postgres、Valkey、NATS、Meilisearch</strong> 和 <strong>SeaweedFS</strong> 提供数据、缓存、事件、搜索以及兼容 S3 的对象存储。
应用包由自托管的 app-proxy 镜像提供；共享静态资源由独立的 <code>static-proxy</code> 容器提供。该技术栈不依赖 Fluxer 的公共静态资源主机。</li>
</ul></section>
<section><h1>环境要求<a href="#环境要求"><span>#</span></a></h1><ul>
<li>一台可以运行 Docker Engine 的 Linux 服务器或虚拟机。</li>
<li>Docker Engine 以及 Docker Compose 插件。</li>
<li>实例的主机名，例如 <code>chat.example.com</code>。</li>
<li>需开放公共入站端口 <code>80/tcp</code> 和 <code>443/tcp</code>，或者使用 Cloudflare Tunnel 将主机名路由至 Caddy 容器。</li>
<li>生产环境下的语音与视频媒体服务，需要开放通往 <code>7881/tcp</code> 和 <code>7882/udp</code> 的公共路径。</li>
<li>至少 2 个 vCPU、4 GB 内存和 20 GB 磁盘空间。对于小型活跃社区，建议使用 4 个 vCPU 和 8 GB 及以上内存。</li>
<li>该技术栈在空闲时占用几 GB 内存，启动阶段是负载最高的时刻，因为所有服务镜像会同时进行初始化。</li>
</ul><section><h2>步骤 1：安装 Docker<a href="#步骤-1安装-docker"><span>#</span></a></h2><p>根据适用于你 Linux 发行版的 Docker 官方指南安装 Docker Engine：</p><ul>
<li><a href="https://docs.docker.com/engine/install/" target="_blank">安装 Docker Engine</a></li>
<li><a href="https://docs.docker.com/compose/install/linux/" target="_blank">安装 Compose 插件</a></li>
<li><a href="https://docs.docker.com/engine/install/linux-postinstall/" target="_blank">Linux 安装后续步骤</a></li>
</ul><p>确认所安装的 Docker 版本：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>--version</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>version</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>请使用 Docker Engine 24 或更高版本以及 Compose v2 插件。</p></section><section><h2>步骤 2：下载技术栈<a href="#步骤-2下载技术栈"><span>#</span></a></h2><p>创建一个工作目录并下载技术栈文件：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>mkdir</span><span> </span><span>fluxer</span></div></div><div><div><div>2</div></div><div><span>cd</span><span> </span><span>fluxer</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>base</span><span>=</span><span>https://raw.githubusercontent.com/fluxerapp/fluxer/main/deploy/self-hosting</span></div></div><div><div><div>5</div></div><div><span>curl</span><span> </span><span>-fsSLO</span><span> </span><span>"</span><span>$base</span><span>/docker-compose.yml"</span></div></div><div><div><div>6</div></div><div><span>curl</span><span> </span><span>-fsSLO</span><span> </span><span>"</span><span>$base</span><span>/Caddyfile"</span></div></div><div><div><div>7</div></div><div><span>curl</span><span> </span><span>-fsSLO</span><span> </span><span>"</span><span>$base</span><span>/livekit.yaml"</span></div></div><div><div><div>8</div></div><div><span>curl</span><span> </span><span>-fsSL</span><span> </span><span>"</span><span>$base</span><span>/.env.example"</span><span> </span><span>-o</span><span> </span><span>.env</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你现在应该包含以下文件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Caddyfile</span></div></div><div><div><div>2</div></div><div><span>docker-compose.yml</span></div></div><div><div><div>3</div></div><div><span>livekit.yaml</span></div></div><div><div><div>4</div></div><div><span>.env</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>步骤 3：配置 .env<a href="#步骤-3配置-env"><span>#</span></a></h2><p>在 <code>.env</code> 文件顶部设置公共主机名。
对于由 Caddy 直接获取证书的普通公共服务器：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>FLUXER_DOMAIN</span><span>=</span><span>chat.example.com</span></div></div><div><div><div>2</div></div><div><span>FLUXER_PUBLIC_SCHEME</span><span>=</span><span>https</span></div></div><div><div><div>3</div></div><div><span>FLUXER_PUBLIC_PORT</span><span>=</span><span>443</span></div></div><div><div><div>4</div></div><div><span>FLUXER_CADDY_SITE_ADDRESS</span><span>=</span><span>chat.example.com</span></div></div><div><div><div>5</div></div><div><span>FLUXER_VAPID_EMAIL</span><span>=</span><span>admin@example.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于由 Cloudflare 终止 HTTPS 并将 HTTP 转发至 Caddy 的 Cloudflare Tunnel 配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>FLUXER_DOMAIN</span><span>=</span><span>chat.example.com</span></div></div><div><div><div>2</div></div><div><span>FLUXER_PUBLIC_SCHEME</span><span>=</span><span>https</span></div></div><div><div><div>3</div></div><div><span>FLUXER_PUBLIC_PORT</span><span>=</span><span>443</span></div></div><div><div><div>4</div></div><div><span>FLUXER_CADDY_SITE_ADDRESS</span><span>=</span><span>:80</span></div></div><div><div><div>5</div></div><div><span>FLUXER_VAPID_EMAIL</span><span>=</span><span>admin@example.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>FLUXER_PUBLIC_SCHEME</code> 和 <code>FLUXER_PUBLIC_PORT</code> 描述用户在浏览器中看到的协议与端口。<code>FLUXER_CADDY_SITE_ADDRESS</code> 描述 Caddy 在技术栈内部监听的地址。</p><p>生成所需的密钥：</p><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> </span><span>key</span><span> </span><span>in</span><span> </span><span>POSTGRES_PASSWORD</span><span> </span><span>MEILI_MASTER_KEY</span><span> </span><span>FLUXER_S3_SECRET_KEY</span><span> \</span></div></div><div><div><div>2</div></div><div><span>  </span><span>FLUXER_SUDO_MODE_SECRET</span><span> </span><span>FLUXER_CONNECTION_INITIATION_SECRET</span><span> \</span></div></div><div><div><div>3</div></div><div><span>  </span><span>FLUXER_GATEWAY_RPC_AUTH_TOKEN</span><span> </span><span>FLUXER_MEDIA_PROXY_SECRET_KEY</span><span> \</span></div></div><div><div><div>4</div></div><div><span>  </span><span>FLUXER_ADMIN_SECRET_KEY_BASE</span><span> </span><span>FLUXER_ADMIN_OAUTH_CLIENT_SECRET</span><span> \</span></div></div><div><div><div>5</div></div><div><span>  </span><span>LIVEKIT_API_SECRET</span><span>; </span><span>do</span></div></div><div><div><div>6</div></div><div><span>  </span><span>sed</span><span> </span><span>-i</span><span> </span><span>"s|^</span><span>$key</span><span>=.*|</span><span>$key</span><span>=$(</span><span>openssl</span><span> rand </span><span>-hex</span><span> </span><span>32</span><span>)|"</span><span> </span><span>.env</span></div></div><div><div><div>7</div></div><div><span>done</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>sed</span><span> </span><span>-i</span><span> </span><span>"s|^FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=.*|FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=$(</span><span>openssl</span><span> rand </span><span>-base64</span><span> </span><span>32</span><span>)|"</span><span> </span><span>.env</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>VAPID</span><span><span>=</span><span>$(</span></span><span>docker</span><span> </span><span>run</span><span> </span><span>--rm</span><span> </span><span>node:24-alpine</span><span> </span><span>npx</span><span> </span><span>--yes</span><span> </span><span>web-push</span><span> </span><span>generate-vapid-keys</span><span> </span><span>--json</span><span>)</span></div></div><div><div><div>12</div></div><div><span>pub</span><span><span>=</span><span>$(</span></span><span>printf</span><span> </span><span>'%s'</span><span> </span><span>"</span><span>$VAPID</span><span>"</span><span> | </span><span>grep</span><span> </span><span>-o</span><span> </span><span>'"publicKey":"[^"]*"'</span><span> | </span><span>cut</span><span> </span><span>-d</span><span>'"'</span><span> </span><span>-f4</span><span>)</span></div></div><div><div><div>13</div></div><div><span>priv</span><span><span>=</span><span>$(</span></span><span>printf</span><span> </span><span>'%s'</span><span> </span><span>"</span><span>$VAPID</span><span>"</span><span> | </span><span>grep</span><span> </span><span>-o</span><span> </span><span>'"privateKey":"[^"]*"'</span><span> | </span><span>cut</span><span> </span><span>-d</span><span>'"'</span><span> </span><span>-f4</span><span>)</span></div></div><div><div><div>14</div></div><div><span>sed</span><span> </span><span>-i</span><span> </span><span>"s|^FLUXER_VAPID_PUBLIC_KEY=.*|FLUXER_VAPID_PUBLIC_KEY=</span><span>$pub</span><span>|"</span><span> </span><span>.env</span></div></div><div><div><div>15</div></div><div><span>sed</span><span> </span><span>-i</span><span> </span><span>"s|^FLUXER_VAPID_PRIVATE_KEY=.*|FLUXER_VAPID_PRIVATE_KEY=</span><span>$priv</span><span>|"</span><span> </span><span>.env</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>除非明确知道需要修改，否则请保持以下默认设置：</p><ul>
<li><code>LIVEKIT_API_KEY=fluxer</code>；密钥为 <code>LIVEKIT_API_SECRET</code>。</li>
<li><code>FLUXER_S3_ACCESS_KEY=fluxer</code>；密钥为 <code>FLUXER_S3_SECRET_KEY</code>。</li>
<li>电子邮件功能初始状态为禁用。后续可通过 <code>.env</code> 和管理员仪表盘启用 SMTP。</li>
</ul><div><div><div></div><div>保持 <code>.env</code> 的私密性</div></div><div><p><code>​.env</code> 包含该实例的所有密钥。请勿将其提交到代码库、粘贴到技术支持工单中或放入截图内。</p></div></div></section><section><h2>步骤 4：发布主机名<a href="#步骤-4发布主机名"><span>#</span></a></h2><p>为主机名创建 DNS 记录：</p><ul>
<li>从 <code>chat.example.com</code> 指向服务器 IPv4 地址的 <code>A</code> 记录。</li>
<li>（可选）从 <code>chat.example.com</code> 指向服务器 IPv6 地址的 <code>AAAA</code> 记录。
保持 <code>FLUXER_CADDY_SITE_ADDRESS=chat.example.com</code>。当端口 <code>80/tcp</code> 和 <code>443/tcp</code> 可以访问服务器时，Caddy 将自动申请并续订证书。</li>
</ul></section><section><h2>步骤 5：开放防火墙<a href="#步骤-5开放防火墙"><span>#</span></a></h2><p>如果你使用的是直接暴露在公网上的服务器，请允许以下入站流量：</p><ul>
<li><code>22/tcp</code> 或你的 SSH 端口。</li>
<li>用于 Caddy 的 <code>80/tcp</code> 和 <code>443/tcp</code>。</li>
<li>用于 LiveKit 媒体的 <code>7881/tcp</code> 和 <code>7882/udp</code>。
如果你对 Web 流量使用了 Cloudflare Tunnel，可以在云服务商的防火墙处拦截入站的 <code>80/tcp</code> 和 <code>443/tcp</code>。同时保持 LiveKit 媒体端口关闭，除非你意图开放语音/视频媒体或正在使用 TURN 服务器。</li>
</ul><div><div><div></div><div>优先使用云服务商防火墙</div></div><div><p>Docker 发布的端口可能会绕过 UFW 等主机防火墙，因为 Docker 会安装自己的数据包过滤规则。请优先使用云服务商的防火墙或安全组来制定面向互联网的安全策略。</p></div></div></section><section><h2>步骤 6：启动技术栈<a href="#步骤-6启动技术栈"><span>#</span></a></h2><p>启动 Fluxer：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>up</span><span> </span><span>-d</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果你使用了上述的 Cloudflare 覆盖配置，请同时启动这两个文件：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>-f</span><span> </span><span>docker-compose.yml</span><span> </span><span>-f</span><span> </span><span>cloudflared.compose.yml</span><span> </span><span>up</span><span> </span><span>-d</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>观察启动状态：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>ps</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>logs</span><span> </span><span>-f</span><span> </span><span>api</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>首次启动可能需要几分钟时间，因为需要下载镜像并初始化服务。<code>seaweedfs-init</code> 在创建对象存储桶后会退出，这是正常现象。</p></section><section><h2>步骤 7：验证实例<a href="#步骤-7验证实例"><span>#</span></a></h2><p>在 Shell 中设置你的域名：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export FLUXER_DOMAIN=chat.example.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>检查每一个公共 HTTP 入口点：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for path in /_health /api/_health /gateway/_health /media/_health /admin/_health; do</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>curl -fsS -o /tmp/fluxer-check -w "$path %{http_code}\n" "https://$FLUXER_DOMAIN$path"</span></div></div><div><div><div>3</div></div><div><span>done</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>预期结果：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/_health 200</span></div></div><div><div><div>2</div></div><div><span>/api/_health 200</span></div></div><div><div><div>3</div></div><div><span>/gateway/_health 200</span></div></div><div><div><div>4</div></div><div><span>/media/_health 200</span></div></div><div><div><div>5</div></div><div><span>/admin/_health 200</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>检查实例发现（instance discovery）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl -fsS "https://$FLUXER_DOMAIN/api/.well-known/fluxer" | jq '.features.self_hosted, .endpoints.admin, .endpoints.gateway, .endpoints.media, .endpoints.static_cdn'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你应该看到 <code>true</code>、以 <code>/admin</code> 结尾的管理员 URL、以 <code>/gateway</code> 结尾的网关 URL、以 <code>/media</code> 结尾的媒体 URL，以及与实例源（origin）相同的静态资源 URL。
检查 Web 应用、管理员登录页面、应用包以及静态资源容器：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl -fsSI "https://$FLUXER_DOMAIN" | sed -n '1,8p'</span></div></div><div><div><div>2</div></div><div><span>curl -fsSI "https://$FLUXER_DOMAIN/admin/login" | sed -n '1,8p'</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>asset=$(curl -fsS "https://$FLUXER_DOMAIN" | grep -o 'src="[^"]*/assets/[^"]*"' | head -n1 | cut -d'"' -f2)</span></div></div><div><div><div>5</div></div><div><span>case "$asset" in</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>http*) curl -fsSI "$asset" | sed -n '1,8p' ;;</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>/*) curl -fsSI "https://$FLUXER_DOMAIN$asset" | sed -n '1,8p' ;;</span></div></div><div><div><div>8</div></div><div><span>esac</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>curl -fsSI "https://$FLUXER_DOMAIN/fonts/ibm-plex.css?v=3" | sed -n '1,8p'</span></div></div><div><div><div>11</div></div><div><span>curl -fsSI "https://$FLUXER_DOMAIN/web/favicon-32x32.png" | sed -n '1,8p'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果你使用了 Cloudflare Tunnel 并遇到了 HTTP 530 错误，这说明 Tunnel 连接器当前未建立连接，或者公共主机名路由指向了错误的服务。</p></section><section><h2>步骤 8：创建所有者账户<a href="#步骤-8创建所有者账户"><span>#</span></a></h2><p>打开 Web 应用：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>https://chat.example.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注册第一个账户。在自托管实例上，第一个被接受注册的账户将获得通配符管理员权限（wildcard admin access）。使用该账户进行首次管理员登录：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>https://chat.example.com/admin</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在管理员仪表盘中完成初始设置。至少需要审查以下内容：</p><ul>
<li>品牌标识与实例名称。</li>
<li>注册模式：开放、审核或关闭。</li>
<li>电子邮件发送。</li>
<li>验证码策略（如果你开放了公共注册）。</li>
<li>单一社区模式（如果你希望使用一个默认社区，而非由用户创建多个社区）。</li>
<li>语音区域与 LiveKit 可达性（如果你启用了语音功能）。</li>
</ul></section><section><h2>电子邮件<a href="#电子邮件"><span>#</span></a></h2><p>电子邮件默认处于禁用状态。要启用 SMTP，请在 <code>.env</code> 中设置以下变量，并重启 <code>api</code>、<code>worker</code> 和 <code>admin</code> 服务：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>FLUXER_EMAIL_ENABLED</span><span>=</span><span>true</span></div></div><div><div><div>2</div></div><div><span>FLUXER_EMAIL_PROVIDER</span><span>=</span><span>smtp</span></div></div><div><div><div>3</div></div><div><span>FLUXER_EMAIL_FROM_EMAIL</span><span>=</span><span>noreply@example.com</span></div></div><div><div><div>4</div></div><div><span>FLUXER_EMAIL_FROM_NAME</span><span>=</span><span>Fluxer</span></div></div><div><div><div>5</div></div><div><span>FLUXER_EMAIL_SMTP_HOST</span><span>=</span><span>smtp.example.com</span></div></div><div><div><div>6</div></div><div><span>FLUXER_EMAIL_SMTP_PORT</span><span>=</span><span>587</span></div></div><div><div><div>7</div></div><div><span>FLUXER_EMAIL_SMTP_USERNAME</span><span>=</span><span>example</span></div></div><div><div><div>8</div></div><div><span>FLUXER_EMAIL_SMTP_PASSWORD</span><span>=</span><span>example-secret</span></div></div><div><div><div>9</div></div><div><span>FLUXER_EMAIL_SMTP_SECURE</span><span>=</span><span>true</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>随后在 <code>/admin/instance-config</code> 页面测试 SMTP 配置。</p></section><section><h2>语音与视频<a href="#语音与视频"><span>#</span></a></h2><p>Fluxer 使用 LiveKit 处理语音和视频。Caddy 将 /livekit 路由至 LiveKit 的 HTTP/WebSocket 信令端口，但浏览器的媒体流通过 WebRTC 传输：</p><ul>
<li><code>7882/udp</code> 是正常的媒体路径。</li>
<li><code>7881/tcp</code> 是 TCP 回退路径。</li>
<li><code>7880/tcp</code> 保持私有，位于 Caddy 后端用于信令传输。
在开放了 <code>7881/tc</code> 和 <code>7882/udp</code> 的 VPS 上，LiveKit 通常可以自动检测公网 IP。在 NAT、Cloudflare Tunnel 或受限网络环境下，请添加 TURN 服务器并为其配置 LiveKit。</li>
</ul></section><section><h2>备份<a href="#备份"><span>#</span></a></h2><p>在升级前和定期计划中备份以下项目：</p><ul>
<li><code>.env</code></li>
<li><code>postgres-data</code></li>
<li><code>seaweedfs-data</code></li>
</ul><p>如需进行冷备份：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>stop</span><span> </span><span>api</span><span> </span><span>worker</span><span> </span><span>gateway</span><span> </span><span>admin</span><span> </span><span>app-proxy</span><span> </span><span>media-proxy</span><span> </span><span>static-proxy</span><span> </span><span>livekit</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>--rm</span><span> </span><span>-v</span><span> </span><span>fluxer_postgres-data:/data</span><span> </span><span>-v</span><span> </span><span>"</span><span>$PWD</span><span>/backups:/backup"</span><span> </span><span>alpine</span><span> </span><span>tar</span><span> </span><span>czf</span><span> </span><span>/backup/postgres-data.tgz</span><span> </span><span>-C</span><span> </span><span>/data</span><span> </span><span>.</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>--rm</span><span> </span><span>-v</span><span> </span><span>fluxer_seaweedfs-data:/data</span><span> </span><span>-v</span><span> </span><span>"</span><span>$PWD</span><span>/backups:/backup"</span><span> </span><span>alpine</span><span> </span><span>tar</span><span> </span><span>czf</span><span> </span><span>/backup/seaweedfs-data.tgz</span><span> </span><span>-C</span><span> </span><span>/data</span><span> </span><span>.</span></div></div><div><div><div>4</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>up</span><span> </span><span>-d</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对于生产环境，优先使用 Postgres 原生转储（dump）加上对象存储备份，这样无需停止实例运行。</p></section><section><h2>升级<a href="#升级"><span>#</span></a></h2><p>默认的镜像标签为 <code>v1</code>，它会跟踪最新的兼容版本：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>pull</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>up</span><span> </span><span>-d</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>fluxer-static</code> 镜像属于默认技术栈的一部分，因此静态资源的更新会通过相同的拉取和重启流程同步完成。
要锁定特定版本，请将 <code>.env</code> 中的 <code>FLUXER_IMAGE_TAG</code> 设置为你希望使用的版本标签，然后执行拉取并重启。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/classwork/%E5%8A%AA%E5%8A%9B%E8%B0%83%E9%80%82%E5%B0%B1%E4%B8%9A%E5%BF%83%E7%90%86%E5%85%85%E5%88%86%E5%81%9A%E5%A5%BD%E5%B0%B1%E4%B8%9A%E5%87%86%E5%A4%87/</id>
      <title type="text">努力调适就业心理，充分做好就业准备</title>
      <published>2026-06-11T00:00:00.000Z</published>
      <updated>2026-06-11T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/classwork/%E5%8A%AA%E5%8A%9B%E8%B0%83%E9%80%82%E5%B0%B1%E4%B8%9A%E5%BF%83%E7%90%86%E5%85%85%E5%88%86%E5%81%9A%E5%A5%BD%E5%B0%B1%E4%B8%9A%E5%87%86%E5%A4%87/"/>
      <summary type="text">非毕业班的《就业指导》期末考核是每位学生提交一篇 2000 字左右的手写论文...</summary>
      <content type="html"><![CDATA[本文已加密保护，请访问网站查看。]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/tanchaung/no/</id>
      <title type="text">去除 获取打开此&apos;ms-gamebar&apos;链接的应用 的弹窗</title>
      <published>2026-04-19T00:00:00.000Z</published>
      <updated>2026-04-19T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/tanchaung/no/"/>
      <summary type="text">这个好烦啊</summary>
      <content type="html"><![CDATA[<section><h2>前言<a href="#前言"><span>#</span></a></h2><p>如果你删过了系统自带的原生xbox组件，那你很大概率见过这个弹窗<br />
为什么会出现这个弹窗？<br />
我受够了 Win11 自带的 Xbox 组件。它的本意是好的，在接入手柄的时候，它会在后台默默启动，然后你可以使用手柄上的按键快捷截图。但有些时候，我们需要安静的游戏时间，或者要顾及电脑性能，所以决定删掉 Xbox 组件。<br />
这就是噩梦的开端。你一定会发现，每次接入 Xbox 手柄时，系统都会弹出一个弹窗，进程名叫 OpenWith.exe。它的功能就是弹出“你要如何打开这个文件 / 选取应用”的对话框。<br />
问题不是这个进程本身带来的，它只是 Windows 自带的兜底机制。背后的原因就是你删掉的 Xbox 组件：底层硬件服务在手柄接入时，依然试图在后台默默通过协议唤醒组件；因为它找不到被删除的软件，无法处理这个协议，所以弹窗出现了。</p></section>
<section><h2>极简 Debug 过程<a href="#极简-debug-过程"><span>#</span></a></h2><p>为了找出具体是哪条协议在作祟，常规的系统事件查看器（Event Viewer）是无效的，因为在系统底层逻辑里，唤醒操作本身没有报错，只是应用层没有响应。<br />
通过使用 Process Monitor (Procmon) 抓取手柄接入瞬间的底层日志，得出了以下结论：<br />
手柄接入时，svchost.exe (即插即用服务) 会向 Explorer.exe 发出指令，疯狂查询当前用户注册表下的 HKCU\Software\Classes\ms-gamebar 协议。很多人（包括我）最初的直觉是进注册表把 ms-gamebar 彻底删掉。但这是一个陷阱——你删得越干净，系统越是找不到处理程序，就越会触发防呆机制，强制弹出 OpenWith.exe 让你手动选应用。</p></section>
<section><h2>结论与解决方案<a href="#结论与解决方案"><span>#</span></a></h2><p>既然系统的底层逻辑是“必须找到一个程序来处理这个协议”，堵不如疏，我们直接给它造一个伪装的空协议。
将系统的呼叫重定向到 %SystemRoot%\System32\systray.exe。这是一个极其小巧的 Windows 古董内置组件，不带参数运行时，它会瞬间静默退出，不会有任何界面和后台残留。系统以为任务完成了，弹窗也就彻底消失了。<br />
新建一个文本文档，将以下代码复制进去，保存并重命名为 FakeGameBar.bat。直接双击运行一次，即可永久解决。</p><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>@</span><span>echo</span><span> </span><span>off</span></div></div><div><div><div>2</div></div><div><span>title</span><span> Final Fix </span><span>for</span><span> Gamepad Popup - Black Hole Protocol</span></div></div><div><div><div>3</div></div><div><span>color</span><span> 0A</span></div></div><div><div><div>4</div></div><div><span>echo</span><span> Injecting fake protocol handler...</span></div></div><div><div><div>5</div></div><div><span>:: Create a black hole for ms-gamebar protocol</span></div></div><div><div><div>6</div></div><div><span>reg</span><span> add </span><span>"HKCU\Software\Classes\ms-gamebar"</span><span> /f /ve /d </span><span>"URL:ms-gamebar"</span><span> &gt;</span><span>nul</span></div></div><div><div><div>7</div></div><div><span>reg</span><span> add </span><span>"HKCU\Software\Classes\ms-gamebar"</span><span> /f /v </span><span>"URL Protocol"</span><span> /d </span><span>""</span><span> &gt;</span><span>nul</span></div></div><div><div><div>8</div></div><div><span>reg</span><span> add </span><span>"HKCU\Software\Classes\ms-gamebar"</span><span> /f /v </span><span>"NoOpenWith"</span><span> /d </span><span>""</span><span> &gt;</span><span>nul</span></div></div><div><div><div>9</div></div><div><span>reg</span><span> add </span><span>"HKCU\Software\Classes\ms-gamebar\shell\open\command"</span><span> /f /ve /d </span><span>"\"</span><span>%SystemRoot%</span><span>\System32\systray.exe\</span><span>""</span><span> &gt;</span><span>nul</span></div></div><div><div><div>10</div></div><div><span>:: Create a black hole for ms-gamingoverlay protocol</span></div></div><div><div><div>11</div></div><div><span>reg</span><span> add </span><span>"HKCU\Software\Classes\ms-gamingoverlay"</span><span> /f /ve /d </span><span>"URL:ms-gamingoverlay"</span><span> &gt;</span><span>nul</span></div></div><div><div><div>12</div></div><div><span>reg</span><span> add </span><span>"HKCU\Software\Classes\ms-gamingoverlay"</span><span> /f /v </span><span>"URL Protocol"</span><span> /d </span><span>""</span><span> &gt;</span><span>nul</span></div></div><div><div><div>13</div></div><div><span>reg</span><span> add </span><span>"HKCU\Software\Classes\ms-gamingoverlay"</span><span> /f /v </span><span>"NoOpenWith"</span><span> /d </span><span>""</span><span> &gt;</span><span>nul</span></div></div><div><div><div>14</div></div><div><span>reg</span><span> add </span><span>"HKCU\Software\Classes\ms-gamingoverlay\shell\open\command"</span><span> /f /ve /d </span><span>"\"</span><span>%SystemRoot%</span><span>\System32\systray.exe\</span><span>""</span><span> &gt;</span><span>nul</span></div></div><div><div><div>15</div></div><div><span>echo</span><span> Injection complete! Protocol routed to void.</span></div></div><div><div><div>16</div></div><div><span>echo</span><span> Please plug in your controller to test.</span></div></div><div><div><div>17</div></div><div><span>pause</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/cyrjtj/1/</id>
      <title type="text">Windows 常用软件</title>
      <published>2026-04-06T00:00:00.000Z</published>
      <updated>2026-04-06T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/cyrjtj/1/"/>
      <summary type="text">运行库一类的</summary>
      <content type="html"><![CDATA[<section><h2>前言<a href="#前言"><span>#</span></a></h2><p>为了方便在干净安装 Windows 后快速查找运行库等驱动，特写本期博客分享</p></section>
<section><h2>驱动<a href="#驱动"><span>#</span></a></h2><p><a href="https://www.nvidia.cn/software/nvidia-app/" target="_blank">NVIDIA app</a><br />
<a href="https://www.nvidia.cn/drivers/" target="_blank">特定版本的 NVIDIA 驱动</a></p></section>
<section><h2>运行库<a href="#运行库"><span>#</span></a></h2><p><a href="https://developer.microsoft.com/zh-cn/microsoft-edge/webview2/" target="_blank">Microsoft Edge Webview 2</a><br />
<a href="https://zhangyue667.lanzouh.com/DirectXRepairEnhanced" target="_blank">DirectX Repair 增强版</a><br />
<a href="https://423down.lanzouo.com/ibFny3c118de" target="_blank">Microsoft 常用运行库合集</a></p></section>
<section><h2>常用软件<a href="#常用软件"><span>#</span></a></h2><p><a href="https://www.azul.com/downloads/" target="_blank">Zulu JDK</a><br />
<a href="https://www.python.org/downloads/windows/" target="_blank">Python</a><br />
<a href="https://abdownloadmanager.com/" target="_blank">AB Download Manager</a></p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/wifi-pay/%E7%94%A8-openwrt-%E5%AE%9E%E7%8E%B0%E5%90%8C-wifi-%E5%8F%8C%E5%AF%86%E7%A0%81%E4%B8%8D%E5%90%8C%E5%93%8D%E5%BA%94/</id>
      <title type="text">用 OpenWrt 实现同 Wifi 双密码不同响应</title>
      <published>2026-04-04T00:00:00.000Z</published>
      <updated>2026-04-04T00:00:00.000Z</updated>
      <author><name>Asaki</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/wifi-pay/%E7%94%A8-openwrt-%E5%AE%9E%E7%8E%B0%E5%90%8C-wifi-%E5%8F%8C%E5%AF%86%E7%A0%81%E4%B8%8D%E5%90%8C%E5%93%8D%E5%BA%94/"/>
      <summary type="text">其实也可以用来做 Pay Wall</summary>
      <content type="html"><![CDATA[<section><h2>引言<a href="#引言"><span>#</span></a></h2><p>最近入手了个路由器，用来接校园网的宽带（神秘校园网只允许一个设备同时接入..）<br />
正当我在想Wifi名的时候，突然想整个活，于是便设置成了Ciallo～ (∠・ω )⌒☆，密码也是喜闻乐见的0d000721。
但设置好后突然感觉不对：这不是相当于公开了我花钱办的宽带了吗🤔于是便想改<br />
但最后思来想去还是不想放弃这个Wifi名称，于是便设法实现了使用两个不同密码连接会有不同响应的Wifi。具体来说，同一个名为Ciallo～ (∠・ω )⌒☆的网络，使用0d000721连接就只会弹一个带图片的网址，而使用另外一个密码连接才能正常上网。</p></section>
<section><h2>准备工作<a href="#准备工作"><span>#</span></a></h2><p>想要实现上述效果，需要：
一台已刷OpenWrt的路由器（关于路由器的选购与刷机这里不赘述）<br />
基础Shell操作知识<br />
正常的网络连接</p><div><div><div></div><div>搞机有风险，操作需谨慎🥹</div></div><div><p>本教程使用的OpenWrt版本为25.XX，不建议在更早的版本上使用本教程，因为不同版本间的差异较大，可能会无法使用或造成故障。</p></div></div><section><h3>创建Wifi<a href="#创建wifi"><span>#</span></a></h3><p>我们创建需要使用不同密码连接的Wifi。
点击任意radio的添加按钮打开网络扫描，在弹出的框中翻到最下面，
在ESSID框中输入要使用的Wifi名称。
切换到无线安全，将加密改为WPA2-PSK，之后在密钥中输入真实的密码（之后能够正常联网的密码）
点击保存，最后点击最下面的保存并应用，等待设置更新。</p></section><section><h3>升级dnsmasq-full<a href="#升级dnsmasq-full"><span>#</span></a></h3><p>首先由于我使用的是OpenNDS来做弹出的页面，所以需要将dnsmasq包升级为完整版。<br />
在Shell中输入以下命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apk update &amp;&amp; apk add dnsmasq-full</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>看到形如 OK: XXX MiB in XXX packages 的结果即可</p></section><section><h3>配置Multi-PSK<a href="#配置multi-psk"><span>#</span></a></h3><p>使用Shell编辑/etc/config/wireless，以下是一个示例：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>config wifi-device 'radio0'</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>......</span></div></div><div><div><div>3</div></div><div><span>config wifi-iface 'default_radio0'</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>......</span></div></div><div><div><div>5</div></div><div><span>config wifi-device 'radio1'</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>......</span></div></div><div><div><div>7</div></div><div><span>config wifi-iface 'wifinet3'</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>option device 'radio1'</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>option network 'lan'</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>option mode 'ap'</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>option ssid 'hyw'</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>option encryption 'psk2'</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>option key 'ThisIsTheTruePassword'</span></div></div><div><div><div>14</div></div><div><span>config wifi-iface 'wifinet2'</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>option device 'radio1'</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>option network 'wwan'</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>option ssid 'ExampleWifi'</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>......</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>可以看到，在config wifi-iface ‘default_radio1’配置块中有我们刚刚创建的Wifi，在config wifi-iface ‘wifinet2’配置块中有我们刚刚连接的Wifi。<br />
接下来对配置文件做以下更改：</p><section><h4>启用MultiPSK<a href="#启用multipsk"><span>#</span></a></h4><p>在创建的Wifi的配置块中加入option multi_psk ‘1’
之后你的配置应该像这样：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>config wifi-iface 'wifinet3'</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>option device 'radio1'</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>option network 'lan'</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>option mode 'ap'</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>option ssid 'hyw'</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>option encryption 'psk2'</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>option key 'ThisIsTheTruePassword'</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>option multi_psk '1'</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>添加wifi-vlan块<a href="#添加wifi-vlan块"><span>#</span></a></h4><p>在文件底部添加以下配置：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>config wifi-vlan</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>option name 'g_v'</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>option network 'guest'</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>option vid '10'</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>option iface 'wifinet3'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注意：
这里的wifinet3为我们创建的wifi的wifi-iface的名字，见上面的示例配置。
这里的name可以任取但是不能太长，具体限制在不同设备上
可能不同，但建议只使用3~4个字符。
vid的10和network的guest均可自定义，不过之后需要保证配置能够对的上。</p></section><section><h4>添加wifi-station块<a href="#添加wifi-station块"><span>#</span></a></h4><p>继续在文件底部添加以下配置：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>config wifi-station</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>option key '0d000721'</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>option vid '10'</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>option iface 'wifinet3'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注意：
这里的key即为假密码，即无法正常上网的密码。
这里的vid和iface要和上面匹配。
编辑完成，保存文件进行下一步。</p></section></section><section><h3>添加设备与接口<a href="#添加设备与接口"><span>#</span></a></h3><p>打开OpenWrt管理面板，进入网络&gt;接口，切换到设备页面，点击最下面的添加设备配置。
在弹出的窗口中，将设备类型选为网桥设备,
设备名称设为br-guest（可以自己改），选中允许启动空网桥，然后点击保存。
接着切换回接口页面，点击下面的添加新接口，
将名称设置为guest（注意和上面的匹配），将协议设置为静态地址，将设备选为br-guest（和上面创建的匹配）,然后点击创建接口。
接着在弹出的窗口中，将IPv4 地址设置为192.168.72.1（可以任意设置，但要和你正常使用的主网络不同），将IPv4 子网掩码设置为255.255.255.0。
然后点击上面的防火墙设置，点击创建/分配防火墙区域，在最下面的自定义框中输入guest_zone（可以自己起名）然后按回车。
最后点击上面的DHCP 服务器，然后点击配置 DHCP 服务器按钮，不用变更配置。
最后点击保存关闭配置窗口，点击最下面的保存并应用，等待设置更新。</p></section><section><h3>安装OpenNDS<a href="#安装opennds"><span>#</span></a></h3><p>OpenNDS的作用本来一般是提供上网认证界面，但这里我们使用它来做弹出图片的效果。
打开Shell，输入以下命令：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apk update &amp;&amp; apk add opennds</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>之后编辑/etc/config/opennds，找到option gatewayinterface行
（我设备上是第308行，可以使用查找功能查找），
将其取消注释并把后面的值换成上面配置的设备的名称。
更换之后配置应该如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>...</span></div></div><div><div><div>2</div></div><div><span># GateWayInterface</span></div></div><div><div><div>3</div></div><div><span># Default br-lan</span></div></div><div><div><div>4</div></div><div><span># Use this option to set the device opennds will bind to.</span></div></div><div><div><div>5</div></div><div><span># The value may be an interface section in /etc/config/network or adevice name such as br-lan.</span></div></div><div><div><div>6</div></div><div><span># The selected interface must be allocated an IPv4 address.</span></div></div><div><div><div>7</div></div><div><span># In OpenWrt this is normally br-lan, in generic Linux it might besomething else.</span></div></div><div><div><div>8</div></div><div><span>#</span></div></div><div><div><div>9</div></div><div><span>option gatewayinterface 'br-guest'</span></div></div><div><div><div>10</div></div><div><span>##################################################################</span></div></div><div><div><div>11</div></div><div><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>保存文件，进行下一步。</p></section><section><h3>配置弹出页面<a href="#配置弹出页面"><span>#</span></a></h3><p>编辑/usr/lib/opennds/theme_click-to-continue-basic.sh，
将里面的内容全部删除，替换成下面的：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/bin/sh</span></div></div><div><div><div>2</div></div><div><span>cat /etc/opennds/htdocs/splash.html</span></div></div><div><div><div>3</div></div><div><span>exit 0</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>之后保存文件。
然后编辑/etc/opennds/htdocs/splash.html（即上面配置的文件），将原有内容（如果有）全部删除，替换成你想要的内容。
在使用假密码登录时，OpenNDS会拦截链接并把/etc/opennds/htdocs/splash.html里面的HTML提供给登录者作为登录界面，所以这里可以自由发挥。
值得注意的是，由于使用假密码连接的时候没有网络连接，所以如果想要插入图片的话不能使用图床，因为无法访问。这里我为了方便直接使用了Base64硬编码图片到splash.html里。
另外，由于存在缓冲区大小限制，splash.html不能太长，所以如果要使用Base64添加图片的话需要对图片进行充分压缩。
以下是我的配置：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!DOCTYPE html&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;html&gt;</span></div></div><div><div><div>3</div></div><div><span>&lt;head&gt;</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>&lt;title&gt;Ciallo～ (∠・ω )⌒☆&lt;/title&gt;</span></div></div><div><div><div>5</div></div><div><span>&lt;/head&gt;</span></div></div><div><div><div>6</div></div><div><span>&lt;body&gt;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>&lt;h1&gt;Ciallo～ (∠・ω )⌒☆&lt;/h1&gt;&lt;br&gt;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>&lt;img width="300px" id="ciallo" alt="ciallo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAgCAIAAADvz6...(太长了省略)" /&gt;</span></div></div><div><div><div>9</div></div><div><span>&lt;/body&gt;</span></div></div><div><div><div>10</div></div><div><span>&lt;/html&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>保存文件，至此配置已经全部完成。</p></section><section><h3>重启服务<a href="#重启服务"><span>#</span></a></h3><p>使用以下命令重启服务：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/etc/init.d/network restart</span></div></div><div><div><div>2</div></div><div><span>wifi reload</span></div></div><div><div><div>3</div></div><div><span>/etc/init.d/dnsmasq restart</span></div></div><div><div><div>4</div></div><div><span>/etc/init.d/opennds restart</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>之后应该就可以看到创建的Ciallo～ (∠・ω )⌒☆Wifi了。</p></section><section><h3>遇到问题？<a href="#遇到问题"><span>#</span></a></h3><p>由于本人之前也没有接触过OpenWrt，在尝试实现这个效果的过程中也是在摸索，
所以如果各位遇到了一些问题我可能无法进行有效的解答。不过这里有些我
遇到过的问题，供大家参考。</p><p>在配置完OpenNDS并重启后无法访问LuCI？
这一般是因为OpenNDS没有被正确配置（即gatewayinterface没有与前面
创建的设备匹配），导致它将所有网络连接全部拦截。</p><p>遇到这种情况时，使用Shell登录，并执行/etc/init.d/opennds stop
来停止OpenNDS，之后应该就能恢复LuCI访问。
为了解决问题，请参照上面的步骤正确配置之后，再运行/etc/init.d/opennds start
恢复OpenNDS，看看是否正常工作。</p><p>重启服务后找不到添加的Wifi？
这可能是因为你的wifi-vlan块中的name过长，
请参照上面的步骤将其改短后再执行wifi reload看看。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/win-icon/%E8%AE%A9-windows-%E7%94%A8%E4%B8%8A-mac-os-26-%E5%90%8C%E6%AC%BE%E5%9B%BE%E6%A0%87/</id>
      <title type="text">让 Windows 用上 mac OS 26 同款图标</title>
      <published>2026-03-30T00:00:00.000Z</published>
      <updated>2026-03-30T00:00:00.000Z</updated>
      <author><name>Asaki</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/win-icon/%E8%AE%A9-windows-%E7%94%A8%E4%B8%8A-mac-os-26-%E5%90%8C%E6%AC%BE%E5%9B%BE%E6%A0%87/"/>
      <summary type="text">哎呦我大玻璃图标</summary>
      <content type="html"><![CDATA[<section><h2>前言<a href="#前言"><span>#</span></a></h2><p>我很喜欢 mac OS 的图标设计，于是研究出了一个方法可以把 mac OS 的图标引入到 Windows 使用。</p></section>
<section><h2>工具网站<a href="#工具网站"><span>#</span></a></h2><section><h3>HQ ICON<a href="#hq-icon"><span>#</span></a></h3><a href="https://github.com/YuKongA/HQ-ICON" target="_blank"><div><div><div><div></div><div>YuKongA</div></div><div>/</div><div>HQ-ICON</div></div><div></div></div><div>[Web] Get app icons from App Store</div><div><div>—</div><div>—</div><div>MIT</div><span>JavaScript</span></div></a><p><a href="https://icon.yukonga.top/" target="_blank">作者自己域名</a> 或 <a href="https://yukonga.github.io/HQ-ICON/" target="_blank">Github Page</a><br />
这个网站可以从 App Store 获取高清应用图标</p><div><div><div></div><div>注意</div></div><div><p>由于 Apple 有时会对 Android UA 进行限制，Android 设备查询时可能只会返回一个名称完全匹配的结果。如遇此问题，可以尝试使用 <a href="https://github.com/YuKongA/HQ-ICON_Compose/releases" target="_blank">App</a> 版本绕过这个限制。</p></div></div></section><section><h3>PNG 转 ICO<a href="#png-转-ico"><span>#</span></a></h3><p><a href="https://www.aconvert.com/cn/icon/png-to-ico/" target="_blank">PNG转ICO - 在线转换图标文件</a>
简单的图片转换网站，支持多分辨率 (最高512×512) 的 ICO 格式转换</p></section></section>
<section><h2>操作过程<a href="#操作过程"><span>#</span></a></h2><section><h3>下载图标<a href="#下载图标"><span>#</span></a></h3><p>在 HQ ICON 中搜索对应软件的图标，点击图标即可下载</p><section><h4>筛选条件<a href="#筛选条件"><span>#</span></a></h4><p>查询类型  一般选 iOS<br />
查询数量  按个人喜好选择<br />
国家地区  CN 搜不到的切 US 或其他国家地区<br />
裁切方式  推荐官方圆角<br />
图像格式  PNG<br />
图像尺寸  1024px</p></section></section><section><h3>转换图标文件<a href="#转换图标文件"><span>#</span></a></h3><p>打开 PNG 转 ICO 网站<br />
图标类型：多分辨率图标<br />
图标尺寸：16x16 至 256x256 全部勾选<br />
然后将转换好的文件下载下来</p></section></section>
<section><h2>修改桌面图标<a href="#修改桌面图标"><span>#</span></a></h2><p>右键桌面上的任意图标，点击属性 - 快捷方式 - 更改图标<br />
然后选择对应转换好的图标文件即可</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/obs-replay/%E6%AF%94-n-%E5%8D%A1%E5%9B%9E%E6%94%BE%E8%BF%98%E5%A5%BD%E7%94%A8obs-studio-%E5%9B%9E%E6%94%BE%E9%85%8D%E7%BD%AE%E6%95%99%E7%A8%8B/</id>
      <title type="text">比 N 卡回放还好用？OBS Studio 回放配置教程</title>
      <published>2026-03-29T00:00:00.000Z</published>
      <updated>2026-03-29T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/obs-replay/%E6%AF%94-n-%E5%8D%A1%E5%9B%9E%E6%94%BE%E8%BF%98%E5%A5%BD%E7%94%A8obs-studio-%E5%9B%9E%E6%94%BE%E9%85%8D%E7%BD%AE%E6%95%99%E7%A8%8B/"/>
      <summary type="text">OBS 真不错</summary>
      <content type="html"><![CDATA[<section><h2>前言<a href="#前言"><span>#</span></a></h2><p>我在玩游戏的过程中总会打出精彩操作，为了能够保存下来我选择使用 OBS Studio 的回放缓存功能，并配合插件来满足我的使用需求~<br />
我会在接下来的部分介绍我的 OBS 设置以及插件设置方法，希望能够帮到你</p></section>
<section><h2>软件及插件下载<a href="#软件及插件下载"><span>#</span></a></h2><section><h3>OBS Studio<a href="#obs-studio"><span>#</span></a></h3><a href="https://github.com/obsproject/obs-studio" target="_blank"><div><div><div><div></div><div>obsproject</div></div><div>/</div><div>obs-studio</div></div><div></div></div><div>OBS Studio - Free and open source software for live streaming and screen recording</div><div><div>—</div><div>—</div><div>GPL-2.0</div><span>C</span></div></a><p>也可以前往 <a href="https://obsproject.com/download" target="_blank">OBS 官网</a> 下载</p><p>用于直播和屏幕录制的免费开源软件</p></section><section><h3>OBS Notifier<a href="#obs-notifier"><span>#</span></a></h3><a href="https://github.com/DmitriySalnikov/OBSNotifier" target="_blank"><div><div><div><div></div><div>DmitriySalnikov</div></div><div>/</div><div>OBSNotifier</div></div><div></div></div><div>Simple program for displaying notifications from OBS on your desktop.</div><div><div>—</div><div>—</div><div>MIT</div><span>C#</span></div></a><p>也可以前往 <a href="https://obsproject.com/forum/resources/obs-notifier.1625/" target="_blank">OBS 论坛</a> 下载</p><p>一款简单的插件，用于在桌面上显示 OBS 的通知</p></section><section><h3>Replay Buffer - move to folder<a href="#replay-buffer---move-to-folder"><span>#</span></a></h3><a href="https://github.com/YelovSK/replay-buffer-move-to-folder" target="_blank"><div><div><div><div></div><div>YelovSK</div></div><div>/</div><div>replay-buffer-move-to-folder</div></div><div></div></div><div>Moves OBS replay buffer recordings into folders based on the active window.</div><div><div>—</div><div>—</div><div>GPL-2.0</div><span>C</span></div></a><p>也可以前往 <a href="https://obsproject.com/forum/resources/replay-buffer-move-to-folder.2055/" target="_blank">OBS 论坛</a> 下载</p><p>一款简单的插件，会根据当前活动窗口，将 OBS 回放缓存录制的内容移动到相应的文件夹中</p></section><section><h3>CoreAudio - AAC<a href="#coreaudio---aac"><span>#</span></a></h3><a href="https://github.com/kiki-kiko/iTunes-12.3.1.23" target="_blank"><div><div><div><div></div><div>kiki-kiko</div></div><div>/</div><div>iTunes-12.3.1.23</div></div><div></div></div><div>iTunes.exe Bundle</div><div><div>—</div><div>—</div><div>no-license</div><span>Unavailable</span></div></a><p>只需要下载 AppleApplicationSupport64.msi 和 AppleMobileDeviceSupport6464.msi 即可</p><p>来自 Apple 的 ACC 320 Kbps 高音质编码器</p></section></section>
<section><h2>OBS Studio 配置<a href="#obs-studio-配置"><span>#</span></a></h2><section><h3>直播设置<a href="#直播设置"><span>#</span></a></h3><p>音频编码器：CoreAudio AAC<br />
视频编码器：NVIDIA NVENC H.264<br />
重新缩放输出：已禁用</p></section><section><h3>录制设置<a href="#录制设置"><span>#</span></a></h3><p>录像路径：自定义或保持默认<br />
录像格式：Matroska 视频 (.mkv)<br />
视频编码器：NVIDIA NVENC H.264<br />
音频编码器：CoreAudio AAC<br />
音轨：1 2 3 4 5 6 全选<br />
重新缩放输出：已禁用</p></section><section><h3>编码器设置<a href="#编码器设置"><span>#</span></a></h3><p>比特率控制：恒定比特率 ( CBR )<br />
比特率：10000 Kbps<br />
关键帧间隔 (秒，0 ＝ 自动)：0 s<br />
预设： P4<br />
调节：高质量<br />
多次编码模式：二次编码 (1/4 分辨率)<br />
配置文件：high<br />
前向考虑：关闭<br />
自适应量化：开启<br />
B 帧：2</p></section><section><h3>快捷键设置<a href="#快捷键设置"><span>#</span></a></h3><p>在 OBS Studio 的设置 - 快捷键中，可以修改回放缓存的快捷键<br />
我设置的是 Ctrl + Alt + Shift</p></section><section><h3>快捷方式设置<a href="#快捷方式设置"><span>#</span></a></h3><section><h4>属性<a href="#属性"><span>#</span></a></h4><p>在桌面右键 OBS Studio 图标，选择属性，在目标栏的 obs64.exe 后加入下方的启动参数</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>--startreplaybuffer --minimize-to-tray</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>第一条参数是打开软件时启动回放缓存，第二条参数是启动时立即最小化到任务托盘</p></section><section><h4>兼容性<a href="#兼容性"><span>#</span></a></h4><p>转到兼容性面板，勾选 以管理员身份运行此程序</p><p>这是为了OBS快捷键能够在游戏内正常生效</p></section></section></section>
<section><h2>OBS Notifier 设置<a href="#obs-notifier-设置"><span>#</span></a></h2><section><h3>连接 WebSocket 服务器<a href="#连接-websocket-服务器"><span>#</span></a></h3><p>打开 OBS Studio，在上方工具栏中选择 工具 - WebSocket 服务器设置<br />
选择 开启 WebSocket 服务器
然后选择 显示连接信息，复制服务器密码</p><p>回到 OBS Notifier, WebSocket 服务器地址如下填写</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>localhost:4455</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>服务器密码则是刚刚在 OBS Studio 复制的服务器密码，复制粘贴即可连接到 OBS Studio 了</p></section><section><h3>开机启动和跟随关闭<a href="#开机启动和跟随关闭"><span>#</span></a></h3><p>选择 开机启动，并点击右边的 从OBS开始，OBS Notifier 会生成一个自动启动脚本，并将该脚本的路径自动复制到你的剪贴板</p><p>回到 OBS Studio，在上方工具栏中选择 工具 - 脚本<br />
点击 + 号，在新弹出的窗口中点击文件地址栏并粘贴进去，删除 obs_notifier_autostart.lua 再回车，即可成功添加脚本<br />
跟随 OBS Studio 关闭只需要选择 退出OBS时关闭此程序 (设置窗口关闭时)</p></section><section><h3>附加参数<a href="#附加参数"><span>#</span></a></h3><p>这里给一个我正在用的附加参数</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>BackgroundColor = #DD0031</span></div></div><div><div><div>2</div></div><div><span>ForegroundColor = #000000</span></div></div><div><div><div>3</div></div><div><span>TextColor = #E4E4E4</span></div></div><div><div><div>4</div></div><div><span>SlideDuration = 400</span></div></div><div><div><div>5</div></div><div><span>SlideOffset = 180</span></div></div><div><div><div>6</div></div><div><span>LineWidth = 6.0</span></div></div><div><div><div>7</div></div><div><span>Scale = 1.0</span></div></div><div><div><div>8</div></div><div><span>MaxPathChars = 32</span></div></div><div><div><div>9</div></div><div><span>ClickThrough = False</span></div></div><div><div><div>10</div></div><div><span>ShowQuickActions = false</span></div></div><div><div><div>11</div></div><div><span>ShowQuickActionsColoredLine = false</span></div></div><div><div><div>12</div></div><div><span>QuickActionsOffset = 8.0</span></div></div><div><div><div>13</div></div><div><span>IconHeight = 64.0</span></div></div><div><div><div>14</div></div><div><span>IconPath = branding_rsx.ico</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>Replay Buffer - move to folder 设置<a href="#replay-buffer---move-to-folder-设置"><span>#</span></a></h2><p>安装后自动启用，可以在 OBS Studio 的工具 - 插件中查看启用情况</p></section>
<section><h2>结语<a href="#结语"><span>#</span></a></h2><p>现在你的 OBS Studio 应该可以承担游戏录制回放的任务了~</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/amdl/amdl/</id>
      <title type="text">Apple Music 也被破解了！音质最好的音乐平台免费下载？</title>
      <published>2026-03-14T00:00:00.000Z</published>
      <updated>2026-03-14T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/amdl/amdl/"/>
      <summary type="text">Apple Music 音源下载与解密</summary>
      <content type="html"><![CDATA[<div><div><div></div><div>提示</div></div><div><p>本教程仅供技术交流与学习研究，请勿用于商业用途或侵犯版权。请在下载后 24 小时内删除。</p></div></div>
<section><h2>前言<a href="#前言"><span>#</span></a></h2><p>本教程利用 Termux 在 Android 上运行 Linux 环境，并部署 AppleMusicDecrypt 项目。</p><p>特点：</p><ul>
<li><strong>支持无损 (ALAC)、杜比全景声 (Atmos) 下载</strong></li>
<li><strong>无需 Apple Music 订阅即可解密 （利用公共实例)</strong></li>
<li><strong>支持简体中文元数据，自动保存歌词（LRC)</strong></li>
</ul></section>
<section><h2>准备工作<a href="#准备工作"><span>#</span></a></h2><section><h3>下载 Termux<a href="#下载-termux"><span>#</span></a></h3><a href="https://github.com/termux/termux-app" target="_blank"><div><div><div><div></div><div>termux</div></div><div>/</div><div>termux-app</div></div><div></div></div><div>Termux - a terminal emulator application for Android OS extendible by variety of packages.</div><div><div>—</div><div>—</div><div>NOASSERTION</div><span>Java</span></div></a><p>或者：<a href="https://f-droid.org/en/packages/com.termux/" target="_blank">F-Droid 下载</a></p><section><h4>授予存储权限<a href="#授予存储权限"><span>#</span></a></h4><p>打开 Termux，输入以下命令并允许权限：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>termux-setup-storage</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h3>安装 Debian<a href="#安装-debian"><span>#</span></a></h3><section><h4>更新软件源<a href="#更新软件源"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>pkg update -y</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>安装 Proot-distro<a href="#安装-proot-distro"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>pkg install proot-distro -y</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>安装 Debian<a href="#安装-debian-1"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>pd i debian</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section></section>
<section><h2>安装基础环境<a href="#安装基础环境"><span>#</span></a></h2><section><h3>进入 Debian 环境<a href="#进入-debian-环境"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>pd login debian</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h4>安装 git 和 Python 环境管理器<a href="#安装-git-和-python-环境管理器"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>apt</span><span> </span><span>update</span><span> &amp;&amp; </span><span>apt</span><span> </span><span>install</span><span> </span><span>pipx</span><span> </span><span>git</span><span> </span><span>-y</span><span> &amp;&amp; </span><span>pipx</span><span> </span><span>install</span><span> </span><span>poetry</span><span> &amp;&amp; </span><span>pipx</span><span> </span><span>ensurepath</span><span> &amp;&amp; </span><span>source</span><span> </span><span>~/.bashrc</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section></section>
<section><h2>部署 AppleMusicDecrypt<a href="#部署-applemusicdecrypt"><span>#</span></a></h2><section><h3>克隆项目源码<a href="#克隆项目源码"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>git clone https://github.com/WorldObservationLog/AppleMusicDecrypt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h4>切换到项目目录<a href="#切换到项目目录"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>cd AppleMusicDecrypt</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>编译安装依赖<a href="#编译安装依赖"><span>#</span></a></h4><div><div><div></div><div>重要</div></div><div><p>执行此命令后，屏幕可能会长时间没有输出，或者滚动大量代码。这是正在从源码编译 MP4Box，非常耗时，通常需要 10 到 20 分钟。请保持手机常亮，千万不要关闭 Termux。如果长时间完全无反应，可以考虑 Ctrl+C 停止后重试。</p></div></div><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bash</span><span> </span><span>./tools/install-deps.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>安装 Python 依赖<a href="#安装-python-依赖"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>poetry env use /usr/bin/python3 &amp;&amp; poetry install</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h3>生成配置文件<a href="#生成配置文件"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>cp config.example.toml config.toml</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h4>修改配置文件<a href="#修改配置文件"><span>#</span></a></h4><div><figure><figcaption><span>使用公共解密服务器</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sed -i 's/url = "127.0.0.1:8080"/url = "wm.wol.moe"/g' config.toml</span></div></div><div><div><div>2</div></div><div><span>sed -i 's/secure = false/secure = true/g' config.toml</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption><span>设置保存路径</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sed -i 's|dirPathFormat = "downloads/{album_artist}/{album}"|dirPathFormat = "/sdcard/Music/AppleMusic"|g' config.toml</span></div></div><div><div><div>2</div></div><div><span>sed -i 's|playlistDirPathFormat = "/sdcard/Music/playlists/{playlistName}"|playlistDirPathFormat = "/sdcard/Music/AppleMusic"|g' config.toml</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption><span>设置文件名格式</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sed -i 's|songNameFormat = "{disk}-{tracknum:02d} {title}"|songNameFormat = "{title} - {artist}"|g' config.toml</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption><span>设置元数据语言为简体中文</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sed -i 's/language = "zh-Hant-HK"/language = "zh-Hans-CN"/g' config.toml</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h3>设置 “一键启动” 捷径<a href="#设置-一键启动-捷径"><span>#</span></a></h3><p>为了以后使用方便，我们设置一个别名，以后只需要输入 AppleMusic 就能启动程序。</p><section><h4>退出 Debian 环境<a href="#退出-debian-环境"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>exit</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>写入启动命令到配置文件<a href="#写入启动命令到配置文件"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>echo "alias am='pd login debian -- bash -c \"cd ~/AppleMusicDecrypt &amp;&amp; poetry run python main.py\"'" &gt;&gt; ~/.bashrc</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>刷新配置<a href="#刷新配置"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>source</span><span> </span><span>~/.bashrc</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section></section>
<section><h2>启动项目<a href="#启动项目"><span>#</span></a></h2><p>打开 Termux，输入：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>am</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>看到出现 &gt; 符号，就说明启动成功了！</p><p>常用下载命令：</p><ol>
<li>下载歌曲/专辑/歌单 （默认 ALAC 无损) ：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>dl https://music.apple.com/example</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>下载指定音质 （如杜比全景声) ：</li>
</ol><div><figure><figcaption><span>alac=无损, aac=有损, ec3=杜比全景声</span></figcaption><pre><code><div><div><div>1</div></div><div><span>dl -c ec3 https://music.apple.com/example</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>强制指定语言 （防止乱码) ：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>dl -l zh-Hans-CN https://music.apple.com/example</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>查询音质信息 （不下载，只看有哪些格式) ：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>qa https://music.apple.com/example</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>退出程序：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>exit</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>对歌曲的后期处理<a href="#对歌曲的后期处理"><span>#</span></a></h2><p>下载器会自动生成 .lrc 歌词文件，但很多播放器不自动识别。我们可以使用 “音乐标签” 这款 App 将歌词写入音乐文件内部。</p><ol>
<li>下载并打开“音乐标签”App。</li>
<li>进入 /sdcard/Music/AppleMusic 目录。</li>
<li>选中需要处理的歌曲（可全选）。</li>
<li>点击“编辑” -&gt; 找到“歌词”栏。</li>
<li>点击导入图标，选择同名的 .lrc 文件。</li>
<li>保存。</li>
</ol><p>这样处理后，即使你删除了 .lrc 文件，Poweramp、foobar2000 等播放器也能正常显示歌词。</p></section>
<section><h2>常见问题 (FAQ)<a href="#常见问题-faq"><span>#</span></a></h2><p>Q1: 执行 bash ./tools/install-deps.sh 时一直没反应，是不是卡死了？</p><p>A: 不一定是卡死。这是在编译 MP4Box，在 Android 手机上非常慢。请保持耐心等待 10-20 分钟。如果 CPU 没有占用或者 Termux 闪退，才需要重来。</p><p>Q2: 下载时卡在 Decrypting… 不动？</p><p>A: 这通常是公共服务器 拥堵或短暂故障。按 Ctrl+C 停止，重启 Termux 再次输入 am 重试即可。如果终端下方显示的 Decrypt Speed: xx.xx 数字在跳动，那就是正常的，请耐心等待。</p><p>Q3: 为什么有的歌没有歌词？</p><p>A: 取决于 Apple Music 官方数据。如果在 Apple Music App 里播放这首歌也没有歌词，那下载器自然也抓不到。可以尝试用 -l en-US 或 -l ja 指定其他地区语言试一试。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/steam-extension/steam-extension/</id>
      <title type="text">给 Steam 加点得劲魔法❤️ — Steam 优化小技巧</title>
      <published>2026-03-09T00:00:00.000Z</published>
      <updated>2026-03-09T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/steam-extension/steam-extension/"/>
      <summary type="text">这样舒服多了</summary>
      <content type="html"><![CDATA[<section><h1>前言<a href="#前言"><span>#</span></a></h1><p>Steam 作为全球最大的游戏平台，可以说是必装软件之一了。但是它的客户端缺失的一些功能却又恰恰是玩家们需要的，而且在中国大陆地区 Steam 的访问速度也是不尽如人意… 那么在本期文章中，我将给你的 Steam 施展得劲の魔法❤️让它变得更加好用(＾.＾)✌️</p></section>
<section><h1>优化与 Steam 服务器的连接<a href="#优化与-steam-服务器的连接"><span>#</span></a></h1><p>首先来解决一下访问速度的问题，大家可能首先想到的是 Steam++，也就是现在的 Watt Toolkit</p><a href="https://github.com/BeyondDimension/SteamTools" target="_blank"><div><div><div><div></div><div>BeyondDimension</div></div><div>/</div><div>SteamTools</div></div><div></div></div><div>🛠「Watt Toolkit」是一个开源跨平台的多功能 Steam 工具箱。</div><div><div>—</div><div>—</div><div>GPL-3.0</div><span>C#</span></div></a><p>是的，它很好用，但是现在的它广告多，启动速度很慢。
因此，我推荐使用另一位大佬 <a href="https://www.dogfight360.com/blog/author/wu360463231/" target="_blank">羽翼城 | Dogfight360</a> 开发的软件：Steamcommunity 302
<a href="https://www.dogfight360.com/blog/18682/" target="_blank">Steamcommunity 302 下载</a>
软件界面简洁，无广，且支持多种平台加速</p></section>
<section><h1>给 Steam 安装扩展<a href="#给-steam-安装扩展"><span>#</span></a></h1><p>Steam 客户端是基于 Chromium 的，某种意义上 Steam 客户端就是一个浏览器套壳，基于此我们可以加装扩展，来拓展 Steam 的使用体验~</p><section><h4>查看史低价格<a href="#查看史低价格"><span>#</span></a></h4><p>安装 SteamDB，首先正常启动 Steam，并打开 商店 页面
</p><figure><img alt="商店 页面" loading="lazy" width="1920" height="1032" src="/_astro/steam-1.8zaqeSmT_ZL6kmP.webp" /><figcaption>商店 页面</figcaption></figure><p></p><p>然后，用鼠标滚轮（中键）点击任意一款游戏，Steam 会弹出新窗口
</p><figure><img alt="新窗口" loading="lazy" width="1920" height="1032" src="/_astro/steam-2.DJ8oBem0_2g1p9e.webp" /><figcaption>新窗口</figcaption></figure><p></p><p>在地址栏输入 “chrome://extensions”，然后敲击键盘 回车（Enter）
</p><figure><img alt="chrome://extensions" loading="lazy" width="1280" height="774" src="/_astro/steam-3.D-GIcPDU_2rlBMB.webp" /><figcaption>chrome://extensions</figcaption></figure><p></p><p>点击左侧 “Chrome 应用商店”，进入 Chrome 网上应用商店
</p><figure><img alt="Chrome 网上应用商店" loading="lazy" width="1280" height="774" src="/_astro/steam-4.KyhjEptm_Z1wX2ue.webp" /><figcaption>Chrome 网上应用商店</figcaption></figure><p></p><p>搜索 SteamDB，点击 添加至 Chrome，新弹出的窗口会询问你保存位置，直接点击保存即可
</p><figure><img alt="保存" loading="lazy" width="1281" height="774" src="/_astro/steam-5.C1KxucgG_X4iKr.webp" /><figcaption>保存</figcaption></figure><p></p><p>此时 SteamDB 扩展已安装完成，回到 商店 页面，随意点击一款游戏，能看到 SteamDB 数据，安装完成~
</p><figure><img alt="安装完成" loading="lazy" width="1920" height="1032" src="/_astro/steam-6.Crxs_l1a_1VM8Fg.webp" /><figcaption>安装完成</figcaption></figure><p></p></section><section><h4>市场比价、批量上架，批量购买、一键徽章合成等<a href="#市场比价批量上架批量购买一键徽章合成等"><span>#</span></a></h4><p>安装 Steam Inventory Helper，操作同上</p></section><section><h4>Steam 崩溃了怎么办？<a href="#steam-崩溃了怎么办"><span>#</span></a></h4><p>首先在 文件资源管理器 中，打开 显示隐藏文件，然后进入以下位置</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>C:\Users\你的用户名\Appdata\Local\Steam\htmlcache\Default\Extensions</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>删除拓展后重启 Steam 即可</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/fedora-setup/fedora-setup/</id>
      <title type="text">Fedora 新手进！必看教程来了</title>
      <published>2026-03-02T00:00:00.000Z</published>
      <updated>2026-03-02T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/fedora-setup/fedora-setup/"/>
      <summary type="text">一起加入 Fedora 的世界吧❤️</summary>
      <content type="html"><![CDATA[<section><h1>前言<a href="#前言"><span>#</span></a></h1><p>👋 嘿，你好！
既然你来到了这里，想必你刚刚安装完 Fedora，正盯着全新的桌面发愁：“接下来该干嘛？” 我也经历过这个阶段。Fedora 在刚安装完后确实需要“推一把”才能达到最佳状态 :)。这就是我编写这份指南的原因，它汇总了我从多个渠道收集的 Fedora 切换经验。</p><blockquote><p>[!ABSTRACT] 摘要
这不是官方指南。这只是分享我个人的配置方案和偏好，你可以根据需要选择性跳过。在复制粘贴任何命令之前，请务必先阅读说明。</p></blockquote><div><div><div></div><div>关于命令的小提示</div></div><div><ul>
<li>看到开头的 sudo，意味着“以管理员身份运行”，它会要求输入密码。</li>
<li>-y 标签意味着“对所有确认提示都回答 Yes”，这样你就不用一直按回车了。</li>
<li>复制粘贴是你的好伙伴，但 运行前请务必确认命令内容。</li>
</ul></div></div></section>
<section><h1>🔥 核心基础设置<a href="#-核心基础设置"><span>#</span></a></h1><section><h4>RPM Fusion - 软件源大补帖<a href="#rpm-fusion---软件源大补帖"><span>#</span></a></h4><p>由于法律原因，Fedora 预装的软件非常精简。RPM Fusion 是存放那些“真正有用”的东西的地方（如编解码器、驱动等）。你肯定需要它。</p><p>获取 free 仓库 (包含大部分你需要的东西)</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>\ </span><span>https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-</span><span>$(</span><span>rpm</span><span> </span><span>-E</span><span> </span><span>%fedora</span><span>)</span><span>.noarch.rpm</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>获取 nonfree 仓库 (包含 NVIDIA 驱动和部分专有编解码器)</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>\ </span><span>https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-</span><span>$(</span><span>rpm</span><span> </span><span>-E</span><span> </span><span>%fedora</span><span>)</span><span>.noarch.rpm</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>更新所有内容以确保兼容性</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>group</span><span> </span><span>upgrade</span><span> </span><span>core</span><span> </span><span>-y</span></div></div><div><div><div>2</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>check-update</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>[!FAQ] Free 和 Nonfree 有什么区别？</p><ul>
<li>Free: 法律上允许自由分发的开源软件。</li>
<li>Nonfree: 专有软件（如 NVIDIA 驱动、部分音视频格式），由于授权原因 Fedora 官方无法直接携带。</li>
</ul></blockquote></section><section><h4>系统更新 (枯燥但重要)<a href="#系统更新-枯燥但重要"><span>#</span></a></h4><p>我知道更新很无聊，但真的请先做这一步。刚安装完的系统通常有很多过时的软件包。</p></section><section><h4>更新所有内容<a href="#更新所有内容"><span>#</span></a></h4><p>sudo dnf update -y</p></section><section><h4>如果更新了内核，请重启<a href="#如果更新了内核请重启"><span>#</span></a></h4><p>sudo reboot</p><div><div><div></div><div>建议</div></div><div><p>养成每周或每月运行一次 sudo dnf update 的习惯。</p></div></div></section><section><h4>固件更新<a href="#固件更新"><span>#</span></a></h4><p>你的硬件可能有更新的固件可用，这对于改善 WiFi 稳定性和电池续航很有帮助。</p><p>查看有哪些设备可更新</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>fwupdmgr</span><span> </span><span>get-devices</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>强制刷新固件数据库</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>fwupdmgr</span><span> </span><span>refresh</span><span> </span><span>--force</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>检查更新</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>fwupdmgr</span><span> </span><span>get-updates</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>应用更新</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>fwupdmgr</span><span> </span><span>update</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>注意</div></div><div><p>固件更新后必须重启系统才能生效。</p></div></div></section><section><h4>给你的电脑起个名<a href="#给你的电脑起个名"><span>#</span></a></h4><p>这纯粹是为了好看，让你更有归属感。挑个酷一点的名字吧！</p><p>把 hungry-beast 换成你喜欢的名字</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>hostnamectl</span><span> </span><span>set-hostname</span><span> </span><span>hungry-beast</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>📦 获取更多软件<a href="#-获取更多软件"><span>#</span></a></h1><section><h4>Flathub 设置<a href="#flathub-设置"><span>#</span></a></h4><p>Fedora 默认只开启了一个“阉割版”的 Flatpak 仓库。Flathub 才是真正的宝库。</p><p>移除限制版的 Fedora Flatpak 仓库</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>flatpak</span><span> </span><span>remote-delete</span><span> </span><span>fedora</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>选项 1：完整仓库（最推荐）
可以访问 Flathub 上的所有应用（包括那些非开发者官方维护的社区版应用）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>flatpak</span><span> </span><span>remote-add</span><span> </span><span>--if-not-exists</span><span> </span><span>flathub</span><span> </span><span>https://flathub.org/repo/flathub.flatpakrepo</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>选项 2：仅限验证过的应用
如果你只信任由软件原作者维护的应用：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>flatpak</span><span> </span><span>remote-add</span><span> </span><span>--if-not-exists</span><span> </span><span>--subset=verified</span><span> </span><span>flathub</span><span> </span><span>https://flathub.org/repo/flathub.flatpakrepo</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>警告</div></div><div><p>很多流行的社区打包应用将不会显示。</p></div></div></section></section>
<section><h1>🎮 显卡驱动<a href="#-显卡驱动"><span>#</span></a></h1><p>NVIDIA (比较棘手)</p><div><div><div></div><div>警告</div></div><div><p>NVIDIA 在 Linux 上比较复杂。以下方法通常有效，但如果遇到问题，欢迎加入“受害者联盟”。</p></div></div><p>📋 开始之前：</p><ul>
<li>在 BIOS 中关闭 Secure Boot（或者学习如何给内核模块签名，由你决定 ;)）。</li>
<li>确保系统已 更新并重启过。</li>
<li>准备好备份方案（认真的，搞砸了我不负责）。</li>
</ul><section><h4>安装内核头文件和开发工具<a href="#安装内核头文件和开发工具"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>kernel-devel</span><span> </span><span>kernel-headers</span><span> </span><span>gcc</span><span> </span><span>make</span><span> </span><span>dkms</span><span> </span><span>acpid</span><span> </span><span>\</span></div></div><div><div><div>2</div></div><div><span>  </span><span>libglvnd-glx</span><span> </span><span>libglvnd-opengl</span><span> </span><span>libglvnd-devel</span><span> </span><span>pkgconfig</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>RTX 4000 及更新系列显卡特别说明</div></div><div><p>如果你使用的是 4000 系列或更新的 GPU，Fedora 需要在安装驱动前设置一个宏，以启用开源内核模块。</p></div></div></section><section><h4>设置开源内核模块宏 (一次性步骤)<a href="#设置开源内核模块宏-一次性步骤"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>sh</span><span> </span><span>-c</span><span> </span><span>'echo "%_with_kmod_nvidia_open 1" &gt; /etc/rpm/macros.nvidia-kmod'</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>安装驱动程序<a href="#安装驱动程序"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>akmod-nvidia</span><span> </span><span>xorg-x11-drv-nvidia-cuda</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>现在请耐心等待。认真的，构建模块需要 5–15 分钟。你可以通过此命令查看进度：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>journalctl</span><span> </span><span>-f</span><span> </span><span>-u</span><span> </span><span>akmods</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>完成后请执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>reboot</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>检查是否生效</p><p>运行 nvidia-smi 如果你看到了 GPU 信息，那就成功了！</p></section></section>
<section><h1>🎵 多媒体支持<a href="#-多媒体支持"><span>#</span></a></h1><p>视频编解码器 (解决视频无法播放)
Fedora 由于专利问题基本不带编解码器。以下命令可以修复它。</p><section><h4>用功能完整的 ffmpeg 替换“阉割版”<a href="#用功能完整的-ffmpeg-替换阉割版"><span>#</span></a></h4><p>sudo dnf swap -y ffmpeg-free ffmpeg —allowerasing</p></section><section><h4>安装所有 GStreamer 插件<a href="#安装所有-gstreamer-插件"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>gstreamer1-plugins-{bad-</span><span>\*</span><span>,good-</span><span>\*</span><span>,base}</span><span> </span><span>\ </span><span>gstreamer1-plugin-openh264</span><span> </span><span>gstreamer1-libav</span><span> </span><span>lame</span><span>\*</span><span> </span><span>\ </span><span>--exclude=gstreamer1-plugins-bad-free-devel</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>安装多媒体组<a href="#安装多媒体组"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>group</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>multimedia</span></div></div><div><div><div>2</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>group</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>sound-and-video</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>🔧 实用工具<a href="#-实用工具"><span>#</span></a></h1><p>微软字体 (无奈但必须)
没有这些字体，网页和文档看起来会很奇怪。</p><section><h4>安装依赖<a href="#安装依赖"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>curl</span><span> </span><span>cabextract</span><span> </span><span>xorg-x11-font-utils</span><span> </span><span>fontconfig</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>安装字体<a href="#安装字体"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>rpm</span><span> </span><span>-i</span><span> </span><span>--nodigest</span><span> </span><span>--nosignature</span><span> </span><span>https://downloads.sourceforge.net/project/mscorefonts2/rpms/msttcore-fonts-installer-2.6-1.noarch.rpm</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>更新字体缓存<a href="#更新字体缓存"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>fc-cache</span><span> </span><span>-fv</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>[!QUOTE] 碎碎念
是的，我们仍然需要 Arial 和 Times New Roman 字体。这得怪微软)</p></blockquote></section><section><h4>Flatpak 自动更新<a href="#flatpak-自动更新"><span>#</span></a></h4><p>你可以让 Flatpak 应用自动保持最新。</p><p>[Unit]
Description=Update Flatpak apps automatically</p><p>[Service]
Type=oneshot
ExecStart=/usr/bin/flatpak update -y —noninteractive</p></section></section>
<section><h1>⚡ 性能优化<a href="#-性能优化"><span>#</span></a></h1><p>加快开机速度
这个改动很简单，效果很明显。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>systemctl</span><span> </span><span>disable</span><span> </span><span>NetworkManager-wait-online.service</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>[!QUESTION] 为什么？
这个服务在开机时等待网络连接。对于大多数个人用户来说，不需要这种开机时的强制延迟。</p></blockquote></section>
<section><h1>🧹 保持系统整洁<a href="#-保持系统整洁"><span>#</span></a></h1><section><h4>清理软件包缓存<a href="#清理软件包缓存"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>clean</span><span> </span><span>all</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>移除不再需要的孤立软件包<a href="#移除不再需要的孤立软件包"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>dnf</span><span> </span><span>autoremove</span><span> </span><span>-y</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/immich/%E8%87%AA%E5%BB%BA%E7%A7%81%E4%BA%BA%E4%BA%91%E7%9B%B8%E5%86%8C--immich-%E9%83%A8%E7%BD%B2%E6%95%99%E7%A8%8B/</id>
      <title type="text">自建私人云相册 — Immich 部署教程</title>
      <published>2026-02-27T00:00:00.000Z</published>
      <updated>2026-02-27T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/immich/%E8%87%AA%E5%BB%BA%E7%A7%81%E4%BA%BA%E4%BA%91%E7%9B%B8%E5%86%8C--immich-%E9%83%A8%E7%BD%B2%E6%95%99%E7%A8%8B/"/>
      <summary type="text">Google 相册的表弟来了说是</summary>
      <content type="html"><![CDATA[<section><h1>前言<a href="#前言"><span>#</span></a></h1><p>最近给我的小主机 HP Elite 800 G SFF 重装了 Fedora 43 WorkStation 系统，在使用 Docker Compose 安装 Immich 的时候发现了一点问题，不过顺利解决。于是决定顺便把部署过程写出来😎</p></section>
<section><h1>部署过程<a href="#部署过程"><span>#</span></a></h1><section><h3>下载配置文件<a href="#下载配置文件"><span>#</span></a></h3><p>首先新建文件夹用于存放配置文件，例如
“/home/Docker/immich</p><p>然后在文件夹中打开终端：</p><p>下载 docker-compose.yml</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>下载 .env file</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>当然，也可以手动下载好文件再移动到文件夹中，但是要记得把<code>example.env</code> 重命名为 <code>.env</code>哦！</p></section><section><h3>自定义 .env 文件配置<a href="#自定义-env-文件配置"><span>#</span></a></h3><p>下面是默认的 .env 文件配置</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># You can find documentation for all the supported env variables at https://docs.immich.app/install/environment-variables</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span># The location where your uploaded files are stored</span></div></div><div><div><div>4</div></div><div><span>UPLOAD_LOCATION=./library</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># The location where your database files are stored. Network shares are not supported for the database</span></div></div><div><div><div>7</div></div><div><span>DB_DATA_LOCATION=./postgres</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List</span></div></div><div><div><div>10</div></div><div><span># TZ=Etc/UTC</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># The Immich version to use. You can pin this to a specific version like "v2.1.0"</span></div></div><div><div><div>13</div></div><div><span>IMMICH_VERSION=v2</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span># Connection secret for postgres. You should change it to a random password</span></div></div><div><div><div>16</div></div><div><span># Please use only the characters `A-Za-z0-9`, without special characters or spaces</span></div></div><div><div><div>17</div></div><div><span>DB_PASSWORD=postgres</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span># The values below this line do not need to be changed</span></div></div><div><div><div>20</div></div><div><span>###################################################################################</span></div></div><div><div><div>21</div></div><div><span>DB_USERNAME=postgres</span></div></div><div><div><div>22</div></div><div><span>DB_DATABASE_NAME=immich</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li>可以替换 <code>UPLOAD_LOCATION</code> 的值为你想要用于存放备份数据的位置，请填写绝对路径，并且需要是一个新目录，有充足的空间。</li>
<li>可以考虑修改 <code>DB_PASSWORD</code> 值，Postgres并不会公开暴露，因此该密码仅用于本地验证。为避免与 Docker 冲突，密码最好仅使用<code>A-Za-z0-9</code>的这些字符。</li>
<li>取消  <code>TZ=</code> 行的注释即可自定义你的时区。</li>
</ul></section><section><h3>修改 docker -compose .yml 文件<a href="#修改-docker--compose-yml-文件"><span>#</span></a></h3><p>由于 Fedora 的 SELinux 限制，我们还需要额外修改一下 <code>docker-compose.yml</code> 文件，即在每个 <code>volume</code> 值的后方加上 <code>:Z</code>，这一步是为了让 Fedora 的 SELinux 不再拦截。</p><p>注意：timezone 不需要添加 <code>:Z</code></p></section><section><h3>启动 Immich<a href="#启动-immich"><span>#</span></a></h3><p>在文件夹中打开终端，运行下方命令即可以后台服务的形式启动 Immich！</p><p>启动容器</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>podman-compose up -d</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h1>结语<a href="#结语"><span>#</span></a></h1><p>如果一切正常，那么恭喜你，已经成功的在 Fedora 上运行了 Immich，接下来在浏览器中访问 localhost&lt;2283&gt; 即可开始配置 Immich，快去体验吧！</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/cmccacc/%E5%B0%8F%E4%BC%97%E5%8A%A0%E9%80%9F%E5%99%A8%E6%8E%A8%E8%8D%90--%E4%B8%AD%E5%9B%BD%E7%A7%BB%E5%8A%A8%E5%8A%A0%E9%80%9F%E5%99%A8/</id>
      <title type="text">神了，中国移动也做加速器！</title>
      <published>2026-02-22T00:00:00.000Z</published>
      <updated>2026-02-22T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/cmccacc/%E5%B0%8F%E4%BC%97%E5%8A%A0%E9%80%9F%E5%99%A8%E6%8E%A8%E8%8D%90--%E4%B8%AD%E5%9B%BD%E7%A7%BB%E5%8A%A8%E5%8A%A0%E9%80%9F%E5%99%A8/"/>
      <summary type="text">真的是很小众的加速器了</summary>
      <content type="html"><![CDATA[<section><h1>引言<a href="#引言"><span>#</span></a></h1><p>大家好呀ヾ(＾-＾)ﾉ 这里是 Gucrin，今天打算给大家推荐一下我自己一直在用的加速器~</p><p>铛铛铛铛~ 它就是 —— 中国移动加速器！
</p><figure><img alt="中国移动场景化宽带" loading="lazy" width="1898" height="1038" src="/_astro/guanwang.DFjyqOP3_ZjyKQ4.webp" /><figcaption>中国移动场景化宽带</figcaption></figure>
这里把官网链接贴一下：<a href="https://aifast.komect.com/" target="_blank">中国移动场景化宽带</a><p></p><p>可以看到官方起的名字还是挺高大上的，叫做场景化宽带，其实功能上和市面上常见的加速器产品是一样的。</p></section>
<section><h1>推荐理由<a href="#推荐理由"><span>#</span></a></h1><p>这时候就有人问了，既然差不多为啥要推荐呢？请听我细细道来：</p><ol>
<li>
<p>UI
</p><figure><img alt="主界面" loading="lazy" width="1232" height="704" src="/_astro/ui.C1IKwank_ZavlOW.webp" /><figcaption>主界面</figcaption></figure>
可以看到软件 UI 还是挺简洁的，没有广告。对我来说下方的 Banner 是公告区，不算广告 不过缺点是有点老气，而且没有很多动画，用起来有点卡顿。<p></p>
</li>
<li>
<p>加速效果稳定
</p><figure><img alt="节点列表" loading="lazy" width="684" height="422" src="/_astro/jiedian.O0bsHIDf_Z2nYtRt.webp" /><figcaption>节点列表</figcaption></figure>
中国移动作为三大运营商之一，线路质量是很稳定有保障的，我平时主玩 PUBG，延迟能稳定在 50ms 以下，游戏体验很不错。而且线路是三网都有的，也就是移动联通电信都有加速线路~<p></p>
</li>
<li>
<p>价格便宜
这里需要重点提一下了，也是我推荐的前提，你需要有一条中国移动的宽带，才能办理 3 元/月的游戏加速服务，不然原价订阅的话是 20 元/月，挺贵的。
</p><figure><img alt="套餐价格" loading="lazy" width="808" height="507" src="/_astro/jiage.Db6IcNdo_Z1bbyIH.webp" /><figcaption>套餐价格</figcaption></figure><p></p>
</li>
</ol></section>
<section><h1>总结<a href="#总结"><span>#</span></a></h1><p>总结下来，还是因为便宜我才推荐的 hhh，只要 3 元每月，加速时长不限，而且加速游戏支持的也很多，总之有移动宽带的值得推荐🤓</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/new-year/happynewyear/</id>
      <title type="text">新年快乐🎉</title>
      <published>2026-02-17T00:00:00.000Z</published>
      <updated>2026-02-17T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/new-year/happynewyear/"/>
      <summary type="text">这是本博客创建以来过的第一次新年！</summary>
      <content type="html"><![CDATA[<p>liuyi 在这里祝大家新年快乐，天天开心，吃嘛嘛香，身体健康~
</p><figure><img alt="新年好！" loading="lazy" width="2160" height="1620" src="/_astro/asaki1.BFL3rDFa_2uHYqW.webp" /><figcaption>新年好！</figcaption></figure><p></p>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/ts3-audio-bot/ts3-audio-bot/</id>
      <title type="text">给你的语音服务器加点乐趣ฅ՞•ﻌ•՞ฅ — 部署 TS3 音乐机器人</title>
      <published>2026-02-16T00:00:00.000Z</published>
      <updated>2026-02-16T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/ts3-audio-bot/ts3-audio-bot/"/>
      <summary type="text">这里也能网抑云😵‍💫</summary>
      <content type="html"><![CDATA[<section><h1>引言<a href="#引言"><span>#</span></a></h1><p>部署好了 TeamSpeak 服务器，本着折腾精神，同时也可以为服务器增添点乐趣，我找到了这个项目</p><a href="https://github.com/ZHANGTIANYAO1/TS3AudioBot-NetEaseCloudmusic-plugin" target="_blank"><div><div><div><div></div><div>ZHANGTIANYAO1</div></div><div>/</div><div>TS3AudioBot-NetEaseCloudmusic-plugin</div></div><div></div></div><div>这是一个用C#给TS3AudioBot编写网易云插件,让你的TS可以有一个音乐机器人。如果觉得好的话，还请给个星星支持一下</div><div><div>—</div><div>—</div><div>OSL-3.0</div><span>C#</span></div></a><p>这是 <a href="https://github.com/Splamy/TS3AudioBot" target="_blank">TS3AudioBot</a> 的网易云插件版本，提供了对网易云音乐的支持，也就是说服务器内的成员可以点歌♫(/^^)/♫  只要网易有版权的话</p><p>还需要部署这个项目 <a href="https://www.npmjs.com/package/NeteaseCloudMusicApi" target="_blank">NeteaseCloudMusicApi</a> ，这是为了给机器人提供 API 支持，当然你也可以使用公用 API，但我更加推荐自建 API 服务*(・ω・)👉*</p></section>
<section><h1>部署 API<a href="#部署-api"><span>#</span></a></h1><div><div><div></div><div>NOTE</div></div><div><p>本文基于 Windows 平台撰写，实际部署时请注意操作系统差异！</p></div></div><section><h3>环境依赖<a href="#环境依赖"><span>#</span></a></h3><p><strong>在开始部署 API 之前，您需要确保系统满足以下要求：</strong></p><ul>
<li><strong>Node.js &gt;= 20</strong></li>
<li><strong>pnpm &gt;= 9</strong></li>
</ul></section><section><h3><a href="https://docs-firefly.cuteleaf.cn/guide/get-started/#%E5%AE%89%E8%A3%85-node-js" target="_blank">安装 Node.js</a><a href="#安装-nodejs"><span>#</span></a></h3><p>访问 <a href="https://nodejs.org/" target="_blank">Node.js 官网</a> 下载并安装最新版本的 Node.js。建议使用 LTS 版本。</p><p>安装完成后，打开终端或命令提示符，运行以下命令验证 Node.js 是否安装成功：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>node</span><span> </span><span>-v</span></div></div><div><div><div>2</div></div><div><span>npm</span><span> </span><span>-v</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果显示版本号，则表示安装成功。</p></section><section><h3><a href="https://docs-firefly.cuteleaf.cn/guide/get-started/#%E5%AE%89%E8%A3%85-pnpm" target="_blank">安装 pnpm</a><a href="#安装-pnpm"><span>#</span></a></h3><p>如果您尚未安装 pnpm，可以通过 npm 安装：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>npm</span><span> </span><span>install</span><span> </span><span>-g</span><span> </span><span>pnpm</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>安装完成后，打开终端或命令提示符，运行以下命令验证 pnpm 是否安装成功：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pnpm</span><span> </span><span>-v</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果显示版本号，则表示安装成功。</p></section><section><h3>防火墙放行<a href="#防火墙放行"><span>#</span></a></h3><p>在防火墙开放以下端口以便服务顺利运行</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>9987</span><span> </span><span>UDP</span></div></div><div><div><div>2</div></div><div><span>30033</span><span> </span><span>TCP</span></div></div><div><div><div>3</div></div><div><span>10011</span><span> </span><span>TCP</span></div></div><div><div><div>4</div></div><div><span>41144</span><span> </span><span>TCP</span></div></div><div><div><div>5</div></div><div><span>3000</span><span> </span><span>TCP+UDP</span></div></div><div><div><div>6</div></div><div><span>58913</span><span> </span><span>TCP</span></div></div><div><div><div>7</div></div><div><span>8000</span><span> </span><span>TCP+UDP</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>部署<a href="#部署"><span>#</span></a></h3><p>新建一个项目文件夹，然后在文件夹里打开 CMD，输入下方命令，开始安装</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>npm</span><span> </span><span>i</span><span> </span><span>NeteaseCloudMusicApi</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>等待安装完成后，在文件夹里编写一个 .bat 脚本，方便后续启动 API 服务</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>npx NeteaseCloudMusicApi</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>运行一下 bat 文件，正常情况下应该可以看到如下输出：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>Services</span><span> </span><span>starts</span><span> </span><span>at</span><span> </span><span>localhost:3000</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样我们的网易云 API 就搭建好啦🎉</p></section></section>
<section><h1>部署 Bot<a href="#部署-bot"><span>#</span></a></h1><div><div><div></div><div>NOTE</div></div><div><p>本文基于 Windows 平台撰写，实际部署时请注意操作系统差异！</p></div></div><a href="https://github.com/ZHANGTIANYAO1/TS3AudioBot-NetEaseCloudmusic-plugin" target="_blank"><div><div><div><div></div><div>ZHANGTIANYAO1</div></div><div>/</div><div>TS3AudioBot-NetEaseCloudmusic-plugin</div></div><div></div></div><div>这是一个用C#给TS3AudioBot编写网易云插件,让你的TS可以有一个音乐机器人。如果觉得好的话，还请给个星星支持一下</div><div><div>—</div><div>—</div><div>OSL-3.0</div><span>C#</span></div></a><p>点击项目卡片跳转到仓库页面，然后在 releases 页面按照说明下载对应文件，本文使用 3.0.2测试版 - TS3AudioBot_WIN_64.zip 作为演示。</p><p>解压文件，然后运行 TS3AudioBot.exe</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>INFO</span><span>|</span><span>Core</span><span><span>| [</span><span>============</span><span> TS3AudioBot started </span><span>=============</span><span>]</span></span></div></div><div><div><div>2</div></div><div><span>INFO</span><span>|</span><span>Core</span><span>| [ Date: Monday, </span><span>16</span><span> February </span><span>2026</span><span> 09:31:44</span></div></div><div><div><div>3</div></div><div><span>INFO|Core| [ Version: 0.12.2/master/4c6be036</span></div></div><div><div><div>4</div></div><div><span>INFO|Core| [ Build: Release</span></div></div><div><div><div>5</div></div><div><span>INFO|Core| [ Platform: Windows </span><span>6.2.9200.0</span><span> (</span><span>64</span><span>bit)</span></div></div><div><div><div>6</div></div><div><span>INFO|Core| [ Runtime: .NET Framework </span><span>3.1.32</span><span> ServerGC:True GC:SustainedLowLatency</span></div></div><div><div><div>7</div></div><div><span>INFO|Core| [ Opus: libopus 1.2.1-fixed (</span><span>x64</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>INFO|Core| [</span><span>==============================================</span><span>]</span></span></div></div><div><div><div>9</div></div><div><span>INFO|Core| Starting Webserver on port </span><span>58913</span></div></div><div><div><div>10</div></div><div><span>It seems like there are no bots configured.</span></div></div><div><div><div>11</div></div><div><span>Fill out this quick setup to get started.</span></div></div><div><div><div>12</div></div><div><span>Please enter the ip, domain or nickname (</span><span>with</span><span> </span><span>port</span><span>; </span><span>default</span><span><span>:</span><span> </span></span><span>9987</span><span>) where to connect to:</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里是让我们输入 TeamSpeak 服务器的 IP 地址，也支持域名以及昵称，默认连接 9987 端口，输入好之后回车</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>Please</span><span> </span><span>enter</span><span> </span><span>the</span><span> </span><span>server</span><span> </span><span>password</span><span> (or </span><span>leave</span><span> </span><span>empty</span><span> </span><span>for</span><span> </span><span>none</span><span>):</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这里是填写 TeamSpeak 服务器密码，没设置的直接回车即可，设置了的输入好之后回车</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>09:37:58.7697</span><span>| </span><span>INFO</span><span>|</span><span>Core</span><span>| </span><span>Bot</span><span> </span><span>"default"</span><span> </span><span>connecting</span><span> </span><span>to</span><span> </span><span>"localhost:9987"</span></div></div><div><div><div>2</div></div><div><span>09:37:59.2042</span><span>| </span><span>INFO</span><span>|</span><span>Core</span><span>| </span><span>Client</span><span> </span><span>connected.</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>看到如上输入就代表着 Bot 已经加入你的服务器了🎉</p><section><h3>接入网易云 API<a href="#接入网易云-api"><span>#</span></a></h3><p>打开 Bot 文件夹，找到 plugins 文件夹，修改 neteaseApi 参数</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>version</span><span>: </span><span>1</span></div></div><div><div><div>2</div></div><div><span>playMode</span><span>: </span><span>SeqPlay</span></div></div><div><div><div>3</div></div><div><span>neteaseApi</span><span>: </span><span>http://localhost:3000</span></div></div><div><div><div>4</div></div><div><span>isPrivateFMMode</span><span>: </span><span>false</span></div></div><div><div><div>5</div></div><div><span>isQrlogin</span><span>: </span><span>false</span></div></div><div><div><div>6</div></div><div><span>cookieUpdateIntervalMin</span><span>: </span><span>30</span></div></div><div><div><div>7</div></div><div><span>autoPause</span><span>: </span><span>true</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>前面我们本地部署的 API 运行在 <a href="http://localhost:3000%EF%BC%8C%E5%9B%A0%E6%AD%A4%E6%88%91%E4%BB%AC%E5%B0%B1%E4%BF%AE%E6%94%B9%E4%B8%BA%E6%AD%A4%E5%9C%B0%E5%9D%80%E3%80%82" target="_blank">http://localhost:3000，因此我们就修改为此地址。</a></p></section></section>
<section><h1>配置 Bot<a href="#配置-bot"><span>#</span></a></h1><p>Bot 默认启动在端口 58913 上，使用云服务器的宝子可以用你服务器的公网 IP 访问这个端口即可看到 Web 配置页面~
</p><figure><img alt="Web 配置页面示例" loading="lazy" width="1903" height="910" src="/_astro/web.DyFzIQw5_7Blli.webp" /><figcaption>Web 配置页面示例</figcaption></figure>
如图所示我们需要填写 Client UID，获取它的方式也很简单，打开 TeamSpeak 客户端，连接到你的服务器，找到刚刚加入频道的 Bot 并发起文字聊天，输入下方指令：<p></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>!</span><span>api</span><span> </span><span>token</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后机器人就会返回一个 Client UID，将这个 UID 填入即可开始配置 Bot 了。</p><p>点击 Jump to your Bots overview，然后点击空白图标 其实是设置
</p><figure><img alt="示例图" loading="lazy" width="252" height="117" src="/_astro/overview.BypUtsii_Z2cBojl.webp" /><figcaption>示例图</figcaption></figure><p></p><p>在 Server 选项卡里可以修改 Bot 所在频道以及名称。
</p><figure><img alt="Server 选项卡示例" loading="lazy" width="887" height="380" src="/_astro/server.tp3vaERY_87xF7.webp" /><figcaption>Server 选项卡示例</figcaption></figure><p></p><p>Settings 选项卡可以修改服务器地址 密码 频道密码以及机器人音量。
</p><figure><img alt="Settings 选项卡示例" loading="lazy" width="890" height="506" src="/_astro/settings.BDChRtdu_p5MzT.webp" /><figcaption>Settings 选项卡示例</figcaption></figure><p></p><p>Playlist 选项卡可以播放本地音乐，如图所示 File Name 处只要填写音频文件的绝对路径即可~
</p><figure><img alt="Playlist 选项卡示例" loading="lazy" width="958" height="329" src="/_astro/playlist._Co0WxWz_ZFnHlo.webp" /><figcaption>Playlist 选项卡示例</figcaption></figure>
例如：C:\Users\Gucrin\Music\NURTURE MEGAMIX.mp3<p></p><section><h3>登录网易云<a href="#登录网易云"><span>#</span></a></h3><p>有三种方式可以登录网易云：</p><p>二维码登录：(输入指令后扫描机器人头像二维码登录)</p><ul>
<li><code>!yun login netease qr</code></li>
</ul><p>验证码登录：</p><ul>
<li>
<p><code>!yun login netease sms &lt;手机号&gt; [验证码]</code></p>
</li>
<li>
<p>先使用 <code>!yun login netease sms &lt;手机号&gt;</code> 获取验证码</p>
</li>
<li>
<p>在使用 <code>!yun login netease sms &lt;手机号&gt; &lt;验证码&gt;</code> 登录</p>
</li>
</ul><p>Cookie登录:</p><ul>
<li><code>!yun login netease cookie &lt;Cookie&gt;</code></li>
</ul></section><section><h3>Bot 命令<a href="#bot-命令"><span>#</span></a></h3><p>先来学习一下命令用法吧</p>

<table><thead><tr><th>命令</th><th>命令格式</th><th>注释</th></tr></thead><tbody><tr><td>login</td><td><code>!yun login &lt;登录方式&gt;</code></td><td>登录</td></tr><tr><td>play</td><td><code>!yun play &lt;歌单/专辑/歌曲/歌名/歌曲ID&gt;</code></td><td>播放</td></tr><tr><td>add</td><td><code>!yun add &lt;歌单/专辑/歌曲/歌名/歌曲ID&gt;</code></td><td>下一首播放</td></tr><tr><td>next</td><td><code>!yun next</code></td><td>下一首</td></tr><tr><td>gedan</td><td><code>!yun gedan &lt;歌单/歌单名/歌单ID&gt; [长度(max/1-9999)]</code></td><td>歌单</td></tr><tr><td>zhuanji</td><td><code>!yun gedan &lt;专辑/专辑名/专辑ID&gt; [长度(max/1-9999)]</code></td><td>专辑</td></tr><tr><td>mode</td><td><code>!yun mode &lt;播放模式&gt;</code></td><td>播放模式</td></tr><tr><td>list</td><td><code>!yun list</code></td><td>查看播放列表</td></tr><tr><td>clear</td><td><code>!yun clear</code></td><td>清空播放列表</td></tr><tr><td>stop</td><td><code>!yun stop</code></td><td>停止播放</td></tr><tr><td>start</td><td><code>!yun start</code></td><td>开始播放</td></tr><tr><td>status</td><td><code>!yun status</code></td><td>查看登录状态</td></tr><tr><td>reload</td><td><code>!yun reload</code></td><td>重载插件配置</td></tr></tbody></table><p>播放模式</p>

<table><thead><tr><th>模式</th><th>注释</th></tr></thead><tbody><tr><td><code>0</code></td><td>顺序播放</td></tr><tr><td><code>1</code></td><td>顺序循环</td></tr><tr><td><code>2</code></td><td>随机播放</td></tr><tr><td><code>3</code></td><td>随机循环</td></tr></tbody></table><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>!yun play Shelter</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>即可播放名为 Shelter 的歌曲</p></section></section>
<section><h1>总结<a href="#总结"><span>#</span></a></h1><p></p><figure><img alt="点歌成功啦~" loading="lazy" width="759" height="601" src="/_astro/shelter.2VL5jqCb_Z287UUa.webp" /><figcaption>点歌成功啦~</figcaption></figure><p></p><p>现在你的点歌机器人可以正常工作啦~ 这次我们使用了两个项目以完成目标，感谢二位项目作者的付出！</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/teamspeak/teamspeak/</id>
      <title type="text">真的是最好用的语音软件吗 — TeamSpeak 从部署到使用</title>
      <published>2026-02-15T00:00:00.000Z</published>
      <updated>2026-02-15T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/teamspeak/teamspeak/"/>
      <summary type="text">带你了解最好用的语音软件</summary>
      <content type="html"><![CDATA[<section><h2>前言<a href="#前言"><span>#</span></a></h2><p>前段时间把 MC 小服开起来之后，我和朋友们就开始了愉快的开荒时期。而玩游戏时语音软件是必不可少的，而我，一个喜欢折腾的小女孩，是肯定不会错过这次机会滴，而本次折腾的目标就是… TeamSpeak！</p></section>
<section><h2>介绍<a href="#介绍"><span>#</span></a></h2><p>TeamSpeak 是一款硬核的实时语音工具。于 2001 年发布。它以<strong>极低延迟</strong>、<strong>超高语音质量</strong>和<strong>极低系统占用</strong>闻名。相较于 Discord （国内的YY、Kook、Oopz 等），TS <strong>无广</strong>而且<strong>界面简洁</strong>，是和朋友开黑的不二之选。</p></section>
<section><h2>部署服务端<a href="#部署服务端"><span>#</span></a></h2><div><div><div></div><div>TIP</div></div><div><p>请根据您服务器实际运行的系统选择对应的安装包，本文章基于 Windows 系统讲解！</p></div></div><p>首先前往 <a href="https://www.teamspeak.com/en/downloads/" target="_blank">官网</a> 下载 Server 端。例如 Windows 平台的 Server 64-bit 3.13.7，下载完成后进行安装即可。</p><p>首次启动服务器，应有一个如下图所示的窗口!
</p><figure><img alt="服务器密钥" loading="lazy" width="982" height="857" src="/_astro/IMG_20260216_000037.uKnTNkvJ_Z2bhrVq.webp" /><figcaption>服务器密钥</figcaption></figure>
请 ==一定一定一定保存好 Server Admin Privilege Key== ，这个密钥 ==只会出现这一次== ，后面我们还需要使用它！<p></p><p>接下来回到 TeamSpeak 的客户端，连接上你的服务器，首次连接客户端会提示你输入 Priviledge Key，那我们就把刚刚保存好的 Key 给输入进去，这样你就是这个服务器唯一的管理员了乀(ˉεˉ乀)</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>teamspeak</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>image</span><span>: </span><span>teamspeak</span></div></div><div><div><div>4</div></div><div><span>    </span><span>restart</span><span>: </span><span>always</span></div></div><div><div><div>5</div></div><div><span>    </span><span>ports</span><span>:</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>- </span><span>9987:9987/udp</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>- </span><span>10011:10011</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>- </span><span>30033:30033</span></div></div><div><div><div>9</div></div><div><span>    </span><span>environment</span><span>:</span></div></div><div><div><div>10</div></div><div><span>      </span><span>TS3SERVER_DB_PLUGIN</span><span>: </span><span>ts3db_mariadb</span></div></div><div><div><div>11</div></div><div><span>      </span><span>TS3SERVER_DB_SQLCREATEPATH</span><span>: </span><span>create_mariadb</span></div></div><div><div><div>12</div></div><div><span>      </span><span>TS3SERVER_DB_HOST</span><span>: </span><span>db</span></div></div><div><div><div>13</div></div><div><span>      </span><span>TS3SERVER_DB_USER</span><span>: </span><span>root</span></div></div><div><div><div>14</div></div><div><span>      </span><span>TS3SERVER_DB_PASSWORD</span><span>: </span><span>example</span></div></div><div><div><div>15</div></div><div><span>      </span><span>TS3SERVER_DB_NAME</span><span>: </span><span>teamspeak</span></div></div><div><div><div>16</div></div><div><span>      </span><span>TS3SERVER_DB_WAITUNTILREADY</span><span>: </span><span>30</span></div></div><div><div><div>17</div></div><div><span>      </span><span>TS3SERVER_LICENSE</span><span>: </span><span>accept</span></div></div><div><div><div>18</div></div><div><span>  </span><span>db</span><span>:</span></div></div><div><div><div>19</div></div><div><span>    </span><span>image</span><span>: </span><span>mariadb</span></div></div><div><div><div>20</div></div><div><span>    </span><span>restart</span><span>: </span><span>always</span></div></div><div><div><div>21</div></div><div><span>    </span><span>environment</span><span>:</span></div></div><div><div><div>22</div></div><div><span>      </span><span>MYSQL_ROOT_PASSWORD</span><span>: </span><span>example</span></div></div><div><div><div>23</div></div><div><span>      </span><span>MYSQL_DATABASE</span><span>: </span><span>teamspeak</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>🎉恭喜你，属于你自己的 TeamSpeak 服务器已经部署完成！</p></section>
<section><h2>使用<a href="#使用"><span>#</span></a></h2><p>当然是先告诉你的朋友你的 TeamSpeak 服务器的 IP 地址啊，个人推荐绑个域名更好记也更好看一些(<em>/∇＼</em>)
然后就是根据使用习惯调整软件设置了，TS 的语音设置十分详细各位可以自行摸索一下～</p><p>一些小技巧：</p><ol>
<li>创建分割线可以使用 cspacer0，这样分割线的文本就会居中了</li>
<li>服务器横幅可以改成固定比例调整大小，这样图片就会根据你的窗口大小来自适应了</li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.gucrin.top/posts/mc-server/how-to-fix-ipv6-1/</id>
      <title type="text">MC 服务器启动脚本的编写是如何影响玩家联机的</title>
      <published>2026-02-09T00:00:00.000Z</published>
      <updated>2026-02-09T00:00:00.000Z</updated>
      <author><name>liuyi</name></author>
      <link rel="alternate" href="https://blog.gucrin.top/posts/mc-server/how-to-fix-ipv6-1/"/>
      <summary type="text">解决了玩家无法通过 IPv6 公网访问服务器</summary>
      <content type="html"><![CDATA[<p>大家好我是 Gucrin，这是本博客的第一篇文章，打算写个简单点的先试试水~</p>
<section><h4>事件起因<a href="#事件起因"><span>#</span></a></h4><p>我用自己组的 NAS 开了个 MC 服务器，邀请了朋友来玩，我们互相之间直接使用游戏客户端开的存档里 <strong>对局域网开放</strong> 是可以正常联机的。但是朋友无法通过 IPv6 连接到我的服务器，令我百思不得其解🤔</p></section>
<section><h4>排查过程<a href="#排查过程"><span>#</span></a></h4><p>很明显，和网络环境是没有关系的，因为我们之间是可以正常联机，说明问题出在服务器配置方面。于是我询问了 Gemini，得到的回复是:</p><blockquote><p><strong>即使你系统支持 IPv6，Java 在运行时有时会由于默认设置，优先使用 IPv4 栈（IPv4 Preferred），从而导致它虽然启动了，但根本不理会从 IPv6 进来的握手请求。</strong></p></blockquote><p>所以问题就很明显了😉</p></section>
<section><h4>解决方案<a href="#解决方案"><span>#</span></a></h4><p>只需要略微修改服务器的启动脚本即可，这里给大家贴一下我的启动脚本。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>@</span><span>echo</span><span> </span><span>off</span></span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>:: </span><span>==========================================</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>:: </span><span>Purpur</span><span> </span><span>Server</span><span> </span><span>Startup</span><span> </span></span><span>Script</span><span><span> (</span><span>Modular</span><span>) </span></span><span>-</span><span><span> </span><span>Fixed</span></span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>:: </span><span>==========================================</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>:: </span><span>1.</span><span> </span><span>JVM</span><span><span> </span><span>参数</span><span>：</span><span>内存</span><span> </span></span><span>+</span><span> </span><span>GC</span><span><span> </span><span>优化</span><span> </span></span><span>+</span><span> </span><span>D</span><span><span> </span><span>属性</span></span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span><span>set</span><span> </span></span><span>JAVA_OPTS</span><span>=-</span><span><span>Xmx4G</span><span> </span></span><span>-</span><span><span>Xms4G</span><span> </span></span><span>^</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span> </span><span>-</span><span><span>XX</span><span>:</span></span><span>+</span><span><span>UseG1GC</span><span> </span></span><span>-</span><span><span>XX</span><span>:</span></span><span>+</span><span><span>ParallelRefProcEnabled</span><span> </span></span><span>-</span><span><span>XX</span><span>:</span><span>MaxGCPauseMillis</span></span><span>=</span><span>200</span><span> </span><span>^</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span> </span><span>-</span><span><span>XX</span><span>:</span></span><span>+</span><span><span>UnlockExperimentalVMOptions</span><span> </span></span><span>-</span><span><span>XX</span><span>:</span></span><span>+</span><span><span>DisableExplicitGC</span><span> </span></span><span>-</span><span><span>XX</span><span>:</span></span><span>+</span><span><span>AlwaysPreTouch</span><span> </span></span><span>^</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span> </span><span>-</span><span><span>XX</span><span>:</span><span>G1NewSizePercent</span></span><span>=</span><span>30</span><span> </span><span>-</span><span><span>XX</span><span>:</span><span>G1MaxNewSizePercent</span></span><span>=</span><span>40</span><span> </span><span>-</span><span><span>XX</span><span>:</span><span>G1HeapRegionSize</span></span><span>=</span><span>8</span><span>M</span><span> </span><span>^</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span> </span><span>-</span><span><span>XX</span><span>:</span><span>G1ReservePercent</span></span><span>=</span><span>20</span><span> </span><span>-</span><span><span>XX</span><span>:</span><span>G1HeapWastePercent</span></span><span>=</span><span>5</span><span> </span><span>-</span><span><span>XX</span><span>:</span><span>G1MixedGCCountTarget</span></span><span>=</span><span>4</span><span> </span><span>^</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span> </span><span>-</span><span><span>XX</span><span>:</span><span>InitiatingHeapOccupancyPercent</span></span><span>=</span><span>15</span><span> </span><span>-</span><span><span>XX</span><span>:</span><span>G1MixedGCLiveThresholdPercent</span></span><span>=</span><span>90</span><span> </span><span>^</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span> </span><span>-</span><span><span>XX</span><span>:</span><span>G1RSetUpdatingPauseTimePercent</span></span><span>=</span><span>5</span><span> </span><span>-</span><span><span>XX</span><span>:</span><span>SurvivorRatio</span></span><span>=</span><span>32</span><span> </span><span>^</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span> </span><span>-</span><span><span>XX</span><span>:</span></span><span>+</span><span><span>PerfDisableSharedMem</span><span> </span></span><span>-</span><span><span>XX</span><span>:</span><span>MaxTenuringThreshold</span></span><span>=</span><span>1</span><span> </span><span>^</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span> </span><span>-</span><span><span>Djava</span><span>.</span></span><span>net</span><span>.</span><span>preferIPv4Stack</span><span>=</span><span>false</span><span> </span><span>-</span><span><span>Djava</span><span>.</span></span><span>net</span><span>.</span><span>preferIPv6Addresses</span><span>=</span><span>true</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>:: </span><span>2.</span><span><span> </span><span>核心文件</span></span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span><span>set</span><span> </span></span><span>SERVER_JAR</span><span>=</span><span>purpur</span><span>-</span><span>1.21</span><span>.</span><span>11</span><span>-</span><span>2560.</span><span>jar</span></div></div><div><div><div>35</div></div><div>
</div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span>:: </span><span>3.</span><span><span> </span><span>服务器参数</span></span></div></div><div><div><div>39</div></div><div>
</div></div><div><div><div>40</div></div><div><span><span>set</span><span> </span></span><span>SERVER_ARGS</span><span>=</span><span>nogui</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>:: </span><span>4.</span><span><span> </span><span>执行启动</span></span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span><span>chcp</span><span> </span></span><span>65001</span></div></div><div><div><div>46</div></div><div>
</div></div><div><div><div>47</div></div><div><span><span>echo</span><span> </span><span>服务器启动中</span></span><span>...</span></div></div><div><div><div>48</div></div><div>
</div></div><div><div><div>49</div></div><div><span><span>java</span><span> </span></span><span>%</span><span>JAVA_OPTS</span><span>%</span><span> </span><span>-</span><span><span>jar</span><span> </span></span><span>%</span><span>SERVER_JAR</span><span>%</span><span> </span><span>%</span><span>SERVER_ARGS</span><span>%</span></div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span>pause</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>原理： 强制 Java 允许并优先处理 IPv6 连接，使其行为与客户端联机模块保持一致。</p></section>]]></content>
    </entry>
</feed>
