普通视图

发现新文章,点击刷新页面。
昨天以前一个工匠

AI 发展临界点 - 快上车

作者 海驴
2026年2月8日 13:32
<p>最近做了 Coding Copilot 的一些分享,这里脱敏 + 剪裁后做下分享。核心内容:</p><ol><li>当前这一刻是进入 AI 的最后时间<ul><li>AI Transformers 基建能力已经被榨干,当前这一刻 AI 能力达到巅峰,且将持续很久。</li><li>之前进入的,与现在进入的,没有差距 (AI 内核基建快速迭代、AI ABI 不稳定、没有现象级产品)。</li><li>现在进入的,与往后进入的,存在代沟 (应用层推进、协议层被 AI 重塑、时代变化飞跃)。</li></ul></li><li>Vibe 辅助协作式 已经非常成熟, 完全托管方式也已经成型。</li><li>所有 AI CLI / App 的工作方式都是一致的 (普适性),通过 Prompts 做任务编排。</li><li>人类和 AI 的交互目前已经被限制在 Prompts 里,这有可能限制 AI 多年的发展。<ul><li>Thinking 可以提升质量,无法规避用户原始 prompts 的输入。</li><li>Prompt 提示词在往后相当长的时间里,都是 AI 与人交互的唯一方式。</li></ul></li></ol><h2 id="ToC"><a href="#ToC" class="headerlink" title="ToC"></a>ToC</h2><!-- prettier-ignore --><table><thead><tr><th>1. 为什么现在是拥抱 AI 的最好时刻</th><th>2. Anthropic 产品矩阵</th><th>3. Vibe Coding: Copilot &amp; Autopilot</th></tr></thead><tbody><tr><td>4. AI CLI 工作方式:任务编排 (Prompts)</td><td>5. Prompts e.g.:Legal Skill</td><td>6. Prompts e.g.:One Prompt, One App</td></tr></tbody></table><span id="more"></span><h2 id="缩略图预览"><a href="#缩略图预览" class="headerlink" title="缩略图预览"></a>缩略图预览</h2><!-- prettier-ignore --><table><thead><tr><th><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140357438.png" alt="拥抱 AI 最好的时刻"></th><th><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140500166.png" alt="Anthropic 产品矩阵"></th><th><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140618965.png" alt="Vibe Coding 的两种方式"></th></tr></thead><tbody><tr><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140720340.png" alt="C-C 工作方式(普适性)"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140745216.png" alt="Prompts 与 AI 沟通:Legal Skill"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140819771.png" alt="Prompts 与 AI 沟通:One Prompt, One App"></td></tr></tbody></table><h2 id="大图预览"><a href="#大图预览" class="headerlink" title="大图预览"></a>大图预览</h2><p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140357438.png" alt="拥抱 AI 最好的时刻"></p><p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140500166.png" alt="Anthropic 产品矩阵"></p><p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140618965.png" alt="Vibe Coding: Copilot &amp; Autopilot"></p><p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140720340.png" alt="AI CLI 工作方式:任务编排(Prompts)"></p><p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140745216.png" alt="Prompts e.g.:Legal Skill"></p><p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20260306140819771.png" alt="Prompts e.g.:One Prompt, One App"></p>

iPhone 截屏高效翻译

作者 海驴
2025年12月14日 23:48
<p>工作生活中可能有以下多语言的困扰:</p><p>a. 在 iPhone 小屏幕上工作,临时切不到 Mac 等多效率平台。<br>b. 多国语言的文本需要阅读、处理,如 email、app 使用、社交媒体等。<br>c. 语言学习能力很渣。</p><p>AI 在文本领域已经足够强大,翻译领域更是和 code 领域一起,是第一批被攻下神坛的大山。<br>以下提供一些 iPhone 设备上能够『较快、较高质量』的完成文本翻译的方案。</p><h2 id="系统-Translate-翻译-app"><a href="#系统-Translate-翻译-app" class="headerlink" title="系统 Translate(翻译) app"></a>系统 Translate (翻译) app</h2><blockquote><p>Version: iOS 26+</p></blockquote><p>iPhone 系统自带的 Translate 功能,目前已经提供了较完备的翻译能力。<br>打开 app 后甚至可以和其他人面对面语音输入并翻译沟通。除了需要打开 app 外,iPhone 还提供了以下翻译的隐藏入口:</p><p>a. 网页内容,长按文本,弹出框中选择『翻译』<br>b. 截屏,文本 OCR 识别后,旁边显示『翻译』<br>c. 拍照,文本 OCR 识别后,旁边显示『翻译』(对准文本区块即可,不用点击拍摄)</p><span id="more"></span><p>以上三个隐藏入口,会唤起系统级别的翻译弹窗面板,可以快速的解决近乎所有需要翻译的场景(尤其 b- 截屏)。特别说明:</p><ol><li>如果是 Safari 浏览器阅读文本,通过配置广为流传的「沉浸式翻译」,搭配自定义 ai 模型后,效果会很好。</li><li>如果是 app 中内嵌的 web 浏览器,通过 a 方案,效率就已经很高了。</li><li>有些网页实在是烂,长按选择文本很吃力,就切到 b 方案。</li><li>对于 c 方案,如果有大片文本在不同区域,可能只识别一个区块。可以直接截屏切到 b 方案。</li></ol><p>截屏方案是非常好用的大杀器,对任何文本、app 都生效。还可以自定义很多快捷方式来启动截屏。<br>有一个显著的优化建议:有时候「截屏 - 文本识别 - 翻译」后,文本会很小,看不清。这个时候可以双指捏合来放大截屏内容,然后再翻译。</p><h2 id="Translate-app"><a href="#Translate-app" class="headerlink" title="Translate app"></a>Translate app</h2><p>iOS 26+ 系统对非官方 app 开放了系统级的翻译入口。有不少 ai app 都接入了这个入口。好处是:</p><p>a. 普通 app 可以直接通过上面系统 Translate 的入口(截屏、长按等)来唤起普通 app 的翻译面板。<br>b. 普通 app 可以使用 ai 模型进行翻译,提升质量。</p><p>走到这一步,还是 Apple 太邋遢。它掌握着翻译的入口、但翻译能力有时候真不太行。</p><p>操作:在系统设置中,将翻译的默认 app 设置成自己正在使用的 ai app 就可以了(如 OpenCat、FlowDown)。</p><h2 id="其他建议"><a href="#其他建议" class="headerlink" title="其他建议"></a>其他建议</h2><p>a. AI 模型推荐使用 Gemini-2.5-flash-lite。速度贼快、质量极好、价格低廉。在文本处理上,不可能三角被它打下来了。<br>b. 如果有条件,一定切到 AI 模型。系统 Translate 的质量有时候真的堪忧。尤其很多工作生活场景,文本会出现换行,它处理的就不好。<br>c. 若使用 iPhone 的同时也正在使用 Mac,通过「iPhone Mirror」可以投屏。Mac 上翻译的工具就太多了(如 Bob)。</p><hr>

Xcode Symbolic Debug

作者 海驴
2025年8月22日 18:57
<p>Xcode 的 Symbolic Breakpoint(符号断点)在排查问题的时候非常好用,尤其在三方闭源库联调的时候。</p><blockquote><p>在闭源三方库中,如果能根据公开的 Api 找到一些有用的信息,还是非常 nice 的。</p></blockquote><p>不知道什么时候开始,符号断点的内容格式非常严格,不然无法被断点。虽然 Xcode 给了如下提示,但那么多符号,很难短时间处理好:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Xcode won't pause at this breakpoint because it has not been resolved.</span><br><span class="line">Resolving it requires that:</span><br><span class="line">• The symbolic name is spelled correctly.</span><br><span class="line">• The symbol actually exists in its library.</span><br><span class="line">• The library for the breakpoint is loaded.</span><br></pre></td></tr></tbody></table></figure><span id="more"></span><p>简单来说,之前通过快捷键可以将当前指针所在的 Symbolic 快速录入到搜索框中进行搜索(xcode 支持符号检索),然后把输入框内容复制到 Breakpoint 就能进行符号断点了。现在死活断不到。<br>需要:绝对准确的符号签名,包括 static、参数、返回值 等等全量信息,少一点就断不成。</p><p>快速的方案,是在 Debug 中执行 <code>image lookup -rn xxx</code> 来查找整个工程中特定符号的内容,在其中找到准确的符号签名后,再设置到 Breakpoint 中。这里使用的 xcode lldb 命令,输出内容一不小心就闪瞎眼。</p><p>还有一个比较快捷的方案是使用 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL0RlcmVrU2VsYW5kZXIvTExEQg==">https://github.com/DerekSelander/LLDB</span>,通过其 <code>lookup xxx</code> 命令,就可以非常整洁的整理出来所需符号的完整签名信息。copy 一下就能使用了。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">// e.g.</span><br><span class="line"></span><br><span class="line">(lldb) lookup initApp</span><br><span class="line">****************************************************</span><br><span class="line">1 hits in: EleSDK</span><br><span class="line">****************************************************</span><br><span class="line">static EleSDK.Ele.initApp(key: Swift.String, apiURLString: Swift.Optional&lt;Swift.String&gt;) -&gt; ()</span><br><span class="line">****************************************************</span><br><span class="line">4 hits in: ManagedConfiguration</span><br><span class="line">****************************************************</span><br><span class="line">+[MCLazyInitializationUtilities initAppleIDSSOAuthentication]</span><br><span class="line">__61+[MCLazyInitializationUtilities initAppleIDSSOAuthentication]_block_invoke</span><br><span class="line">__61+[MCLazyInitializationUtilities initAppleIDSSOAuthentication]_block_invoke_2</span><br><span class="line">objc_msgSend$initAppleIDSSOAuthentication</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>最后,也比较一下 <code>image lookup -rn xxx</code> 的结果:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">(lldb) image lookup -rn initApp</span><br><span class="line">1 match found in /xxx/Ele-ios-demo-swift-gfeqjptpbrkqcrborvnyugpagjfl/Build/Products/Debug-iphoneos/Ele-ios-demo-swift.app/Frameworks/EleSDK.framework/EleSDK:</span><br><span class="line"> Address: EleSDK[0x000000041efc] (EleSDK.__TEXT.__text + 237308)</span><br><span class="line"> Summary: EleSDK`static EleSDK.Ele.initApp(key: Swift.String, apiURLString: Swift.Optional&lt;Swift.String&gt;) -&gt; ()</span><br><span class="line">4 matches found in /Users/hailv/Library/Developer/Xcode/iOS DeviceSupport/iPhone17,3 18.6.2 (22G100)/Symbols/System/Library/PrivateFrameworks/ManagedConfiguration.framework/ManagedConfiguration:</span><br><span class="line"> Address: ManagedConfiguration[0x00000001a1bc9a2c] (ManagedConfiguration.__TEXT.__text + 225740)</span><br><span class="line"> Summary: ManagedConfiguration`+[MCLazyInitializationUtilities initAppleIDSSOAuthentication]</span><br><span class="line"> Address: ManagedConfiguration[0x00000001a1bc9ab4] (ManagedConfiguration.__TEXT.__text + 225876)</span><br><span class="line"> Summary: ManagedConfiguration`__61+[MCLazyInitializationUtilities initAppleIDSSOAuthentication]_block_invoke</span><br><span class="line"> Address: ManagedConfiguration[0x00000001a1bc9b18] (ManagedConfiguration.__TEXT.__text + 225976)</span><br><span class="line"> Summary: ManagedConfiguration`__61+[MCLazyInitializationUtilities initAppleIDSSOAuthentication]_block_invoke_2</span><br><span class="line"> Address: ManagedConfiguration[0x00000001a1cc8a20] (ManagedConfiguration.__TEXT.__objc_stubs + 22656)</span><br><span class="line"> Summary: ManagedConfiguration`objc_msgSend$initAppleIDSSOAuthentication</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>找到 func 签名,挺费事儿的。</p><hr>

投资风险收益评估

作者 海驴
2025年5月25日 13:58
<p>风险等级说明:</p><ul><li>R1: 极低风险 (年化回报率: 3.0-5.5%)</li><li>R2: 低风险 (年化回报率: 3.5-6.0%)</li><li>R3: 中低风险 (年化回报率: 4.5-7.5%)</li><li>R4: 中等风险 (年化回报率: 5.0-10.0%)</li><li>R5: 中高风险 (年化回报率: 7.0-13.0%)</li><li>R6: 高风险 (年化回报率: 5.0-20.0%, 损失风险: -50% 至 - 70%)</li><li>R7: 极高风险 (年化回报率: 15% 以上 (部分品种远超此范围), 损失风险: -50% 至 - 100%)</li></ul><p>金融投资产品(按风险从低到高排列):</p><ul><li><p>R1 年化回报率: 3.0-5.5%</p><ul><li>储蓄账户 (Savings Accounts) <em>年化回报: 0.5-4.5%</em></li><li>国库券 (Treasury Bills) <em>年化回报: 4-5.5%</em></li><li>存款证 (CDs - Certificates of Deposit) <em>年化回报: 3.5-5.0%</em></li><li>货币市场基金 (Money Market Funds) <em>年化回报: 4.0-5.5%</em> (通常属于共同基金的一种)</li></ul></li></ul><span id="more"></span><ul><li><p>R2 年化回报率: 3.5-6.0%</p><ul><li>政府债券 (Government Bonds) <em>年化回报: 4.0-5.0%</em></li><li>市政债券 (Municipal Bonds) <em>年化回报: 3.0-5.5%</em> (风险通常低于公司债券,但高于国债,税前回报)</li><li>年金 (Annuities) <em>年化回报: 4.0-7.0%</em> (固定年金部分参考: 4.5-5.8%,变额年金风险和回报波动大)</li></ul></li><li><p>R3 年化回报率: 4.5-7.5%</p><ul><li>债券 (Bonds) <em>年化回报: 4-7%</em> (泛指,风险高于政府债券)<ul><li>公司债券 (Corporate Bonds) <em>年化回报: 5.0-6.5%</em> (投资级公司债)</li></ul></li><li>债券基金 (Bond Funds) <em>年化回报: 3.5-6.0%</em> (投资于上述债券的基金,回报取决于具体配置)</li><li>优先股 (Preferred Stock) <em>年化回报: 5.5-8.0%</em> (风险通常介于债券和普通股之间)</li></ul></li><li><p>R4 年化回报率: 5.0-10.0%</p><ul><li>共同基金 (Mutual Funds) <em>年化回报: 6-12%</em> (这是一个大类,其下具体基金风险各异,回报取决于投资标的)</li><li>混合基金 (Balanced Funds) <em>年化回报: 6.0-9.0%</em> (同时投资于股票和债券)</li><li>指数基金 (Index Funds) <em>年化回报: 4.0-7.0%</em> (主要指债券指数基金及其他低风险指数基金;股票指数基金回报见 R5)</li><li>交易所交易基金 (ETFs - Exchange Traded Funds) <em>年化回报: 4.0-7.0%</em> (主要指债券 ETF 及其他低风险 ETF;股票 ETF 回报见 R5)</li><li>可转换证券 (Convertible Securities) <em>年化回报: 5.0-9.0%</em></li><li>外汇 (现汇交易/非杠杆) (Foreign Exchange - Spot/Non-leveraged) <em>年化回报: 1-8%</em> (高度不确定,含利率差和汇率波动)</li></ul></li><li><p>R5 年化回报率: 7.0-13.0% (损失风险: -20% 至 - 40%)</p><ul><li>股票 (Stocks) <em>年化回报: 8-12%</em> (蓝筹股或大型稳定公司股票)</li><li>股票基金 (Stock Funds) <em>年化回报: 9-13%</em> (投资于股票的基金,主动管理型)</li><li>股票指数基金 (Stock Index Funds) <em>年化回报: 8-12%</em> (追踪股票指数的基金)</li><li>股票类 ETFs (Stock ETFs) <em>年化回报: 8-12%</em> (追踪股票指数的交易所基金)</li><li>房地产投资信托 (REITs - Real Estate Investment Trusts) <em>年化回报: 7-11%</em> (总回报,含股息和资本增值)</li><li>房地产 (Real Estate) <em>年化回报: 6-10%</em> (直接投资,总回报,注意高持有成本和低流动性)<ul><li>住宅房地产 (Residential Real Estate) <em>年化回报: 5-8%</em></li><li>商业房地产 (Commercial Real Estate) <em>年化回报: 7-12%</em> (近年面临挑战)</li></ul></li><li>企业年金/退休计划 (Pension Plans/Retirement Plans) <em>年化回报: 6-10%</em> (风险和回报高度取决于计划内的具体投资组合)</li></ul></li><li><p>R6 年化回报率: 5.0-20.0% (损失风险: -50% 至 - 70%)</p><ul><li>P2P 借贷 (Peer-to-Peer Lending) <em>年化回报: 5-12%</em> (净回报预期,高违约风险)</li><li>高收益债券/垃圾债券 (High-yield Bonds/Junk Bonds) <em>年化回报: 6-10%</em> (总回报预期,或 Yield to Worst 7-9%)</li><li>小盘股 (Small-cap Stocks) <em>年化回报: 9-15%</em> (成长型小公司股票,高波动性)</li><li>新兴市场股票 (Emerging Market Stocks) <em>年化回报: 5-15%</em> (长期平均回报,高波动性)</li><li>大宗商品 (Commodities) <em>年化回报: 0-10%</em> (长期平均回报,波动极大,可能负回报)<ul><li>贵金属 (Precious Metals - e.g., Gold, Silver) <em>年化回报: 2-10%</em> (黄金波动相对较低,白银波动大)</li><li>能源 (Energy - e.g., Oil, Natural Gas) <em>年化回报: -5-15%</em> (波动极大,高风险)</li><li>农产品 (Agricultural Products - e.g., Corn, Wheat) <em>年化回报: 0-12%</em> (波动极大,高风险)</li></ul></li><li>众筹投资 (Crowdfunding Investments) <em>年化回报: -50% 至 + 50%</em> (通常是初创企业股权,风险极高,高失败率)</li><li>结构性产品 (Structured Products) <em>年化回报: 5-20%</em> (风险复杂且不透明,回报高度依赖具体结构)</li><li>收藏品 (Collectibles) <em>年化回报: 2-10%</em> (流动性极差,价值波动大,持有成本高)</li></ul></li><li><p>R7 年化回报率: 15% 以上 (部分品种远超此范围) (损失风险: -50% 至 - 100%)</p><ul><li>私募股权 (Private Equity) <em>年化回报: 12-25%</em> (净 IRR 预期,高风险、高门槛、长周期、低流动性)</li><li>风险投资基金 (Venture Capital) _年化回报: 15-30%+ _ (早期高成长性投资预期,整体回报差异极大,高失败率)</li><li>对冲基金 (Hedge Funds) <em>年化回报: 5-20%</em> (策略多样,大类资产平均预期,少数可能更高)</li><li>期权 (Options) <em>年化回报: -100% 至 + 数倍</em> (极高风险,专业性要求高)</li><li>期货 (Futures) <em>年化回报: -100% 至 + 数倍</em> (极高风险,专业性要求高)</li><li>杠杆 ETF (Leveraged ETFs) <em>年化回报: 高度波动 (-90% 至 + 数倍可能), 不宜长期持有</em></li><li>外汇 (Forex - Foreign Exchange) <em>年化回报: -100% 至 + 数倍</em> (杠杆交易风险极高,专业性要求高)</li><li>二元期权 (Binary Options) <em>年化回报: 高投机,回报范围极大,通常不被视为正规投资</em></li><li>加密货币 (Cryptocurrencies) <em>年化回报: 极高波动 (-90% 至 + 数千百分比可能), 投机性强,风险巨大</em></li><li>微盘股 (Penny Stocks) <em>年化回报: 极高风险 (-95% 至 + 数倍可能,大概率亏损)</em></li></ul></li></ul><hr>

Mac 快速修改系统快捷键

作者 海驴
2025年5月19日 14:14
<p>Macos 系统快速调整快捷键方案:</p><ol><li>在 <code>system settings</code> 中清理不需要的快捷键,把核心快捷键用来自定义</li><li><code>Raycast</code> - 最方便的快速创建自定义快捷键解决方案</li><li><code>Hammerspoon</code> - 复杂疑难病症的解决方案</li></ol><p>操作说明和快捷键建议指南如下。</p><span id="more"></span><h2 id="系统快捷键设置"><a href="#系统快捷键设置" class="headerlink" title="系统快捷键设置"></a>系统快捷键设置</h2><p>入口:<code>system settings</code> - <code>keyboard</code></p><ul><li><code>Press 地球 key to</code> 调整为 nothing<ul><li>候选项有<code>Change Input Source</code>、<code>Show Emoji &amp; Symbols</code>、<code>Start Dictation</code></li><li>Input Source 通过 <code>Input Source Pro</code> app 可以快速设置(支持快捷键)</li><li>Emoji &amp; Symbols 通过系统全局 <code>Control + CMD + Space</code> 呼出,不需要快捷键</li><li>Start Dictation 如果不需要这个功能,不用开启</li></ul></li><li><code>Dictation</code> - <code>Shortcut</code>: 改成不需要常用快捷键的方式</li><li><code>Keyboard Shortcuts</code> - 这里面是系统提供的很多没必要的快捷键,不需要的都可以取消掉</li></ul><p>上面三个步骤设置完成后,核心的快捷键都可以留给我们自己来分配了。</p><h2 id="通过-Raycast-修改"><a href="#通过-Raycast-修改" class="headerlink" title="通过 Raycast 修改"></a>通过 Raycast 修改</h2><p>Raycast 非常好用,通过 <code>Hotkey</code> 可以快速设置系统级快捷键,非常方便。</p><ol><li>打开 app:一般不需要,通过 Raycast 启动入口打开 app 已经很快了</li><li>Script Command:这个非常好用,很多系统级别的能力,可以通过 sh / applescript 的形式执行,分配一个快捷键后非常方便。<ul><li>e.g. 写一个【当前最前置 window】移动到下一个屏幕上(多屏幕场景),增加 [Double Click CMD] 快捷键。</li></ul></li></ol><h2 id="通过-Hammerspoon-修改"><a href="#通过-Hammerspoon-修改" class="headerlink" title="通过 Hammerspoon 修改"></a>通过 Hammerspoon 修改</h2><p>Hammerspoon 是非常强大的快捷键执行器, 解决 any 疑难杂症。</p><p>在多屏幕场景下,很难实现【移动鼠标到指定屏幕】,Hammerspoon 可以实现。</p><h2 id="快捷键建议"><a href="#快捷键建议" class="headerlink" title="快捷键建议"></a>快捷键建议</h2><ul><li>1 只手能操作的快捷键,绝不留给 2 只手</li><li><code>Ctrl</code>、<code>Option</code>、<code>Cmd</code> 和 <code>q/w/e/r/a/s/d/f/z/x/c/v</code> 组合<ul><li>尽量少用 <code>Shift</code></li></ul></li><li><code>Double Click Ctrl</code>、<code>Double Click Option</code>、<code>Double Click Cmd</code> 这三个最方便,要留给最常用的操作</li><li>很多时候快捷键操作的都是【文件 / 文件夹】,Mac Finder 支持太弱了,上 <code>QSpace Pro</code> 就非常方便(使用 Raycast Script Cammand 也很方便)。</li><li>快捷键的脚本怎么写,别自己写,让 AI 写。</li></ul><hr>

AI 指北

作者 海驴
2025年6月30日 22:44
<br><p>整理 &amp; 学习了 AI 相关的知识点。这里把 PPT 内容放一下,详细内容可查阅 via <span class="exturl" data-url="aHR0cHM6Ly95aWdlZ29uZ2ppYW5nLm5vdGlvbi5zaXRlL0FJLTJlNzJmYmRhZDViYzRlZTRiOGYyYzc5Y2ZhZjkyN2QyP3B2cz00">https://yigegongjiang.notion.site/AI</span></p><h2 id="主题"><a href="#主题" class="headerlink" title="主题"></a>主题</h2><table><colgroup><col width="30%"><col width="70%"></colgroup><tbody><tr> <td style="text-align: center; vertical-align: middle; font-size: 1.8em; font-weight: bold;">神经网络抽象了现实世界</td> <td style="text-align: center; vertical-align: middle;"> <img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010329.png" width="50%"> </td></tr></tbody></table><h2 id="ToC"><a href="#ToC" class="headerlink" title="ToC"></a>ToC</h2><table><tbody><tr><td>1. 数学对现实的抽象</td><td> 2. 二元感知机 - 1957</td><td>3. 感知机升维困境 - AI 寒冬</td></tr><tr><td>4. 正向传播 &amp; 反向传播 - 1974</td><td>5. 梯度下降 - 古老的算法</td><td> 6. 多层感知机 MLP - 1986</td></tr><tr><td>7. 卷积神经 CNN - 1998</td><td>8. 自注意力机制 - 2017</td><td>9. Transformer 流程解析</td></tr><tr><td>10. AI - 缸中大脑</td><td> 11. Prompts 是 AI 入场券</td><td> 12. 调参 - 进一步掌控 AI</td></tr><tr><td>13. AI 悬停点 - 2025.06</td><td></td><td></td></tr></tbody></table><span id="more"></span><h2 id="缩略图预览"><a href="#缩略图预览" class="headerlink" title="缩略图预览"></a>缩略图预览</h2><table><tbody><tr><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010329.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010346.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010354.png" width="100%"></td></tr><tr><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010400.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010408.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010416.png" width="100%"></td></tr><tr><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010426.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010435.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010445.png" width="100%"></td></tr><tr><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010451.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010455.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010500.png" width="100%"></td></tr><tr><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010506.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010510.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010514.png" width="100%"></td></tr><tr><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010518.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010522.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010529.png" width="100%"></td></tr><tr><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010537.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010544.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010548.png" width="100%"></td></tr><tr><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010552.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010557.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010603.png" width="100%"></td></tr><tr><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010609.png" width="100%"></td><td><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010617.png" width="100%"></td><td></td></tr></tbody></table><h2 id="大图预览"><a href="#大图预览" class="headerlink" title="大图预览"></a>大图预览</h2><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010329.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010346.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010354.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010400.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010408.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010416.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010426.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010435.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010445.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010451.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010455.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010500.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010506.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010510.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010514.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010518.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010522.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010529.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010537.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010544.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010548.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010552.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010557.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010603.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010609.png" width="100%"><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/20250724010617.png" width="100%"><hr>

Git Record

作者 海驴
2025年5月13日 14:20
<blockquote><p>SSH / Personal Access Tokens / GPG Keys / Signing Key / ssh-agent<br>裸中央仓库 / worktree<br>…</p></blockquote><span id="more"></span><h1 id="Email"><a href="#Email" class="headerlink" title="Email"></a>Email</h1><p>通过 ssh key 等方式操作 github 等仓库平台时,只要 ssh 验权通过就可以进行仓库操作。不过对于 git commit 等提交操作,git 会强制要求配置 username 和 email。<br>不过,email 一定要配置好,和账号的 email 一致。github 虽然不对 email 进行操作验证,但是在显示 verified 等标记的时候,还是会校验 email 的。如果 email 不对,则标记 <code>Unverified</code>。</p><p>整体来说,虽然不强求,但尽量配置好。</p><h1 id="SSH-Key"><a href="#SSH-Key" class="headerlink" title="SSH Key"></a>SSH Key</h1><p>github 这些 git 平台,除了 https 之外,也支持 git 协议的 ssh 操作。<br>对于公私钥,本机主要使用私钥,公钥在 github 远程平台上设置。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">tree ~/.ssh/</span><br><span class="line">/Users/example/.ssh/</span><br><span class="line">├── config</span><br><span class="line">├── id_ed25519_personal</span><br><span class="line">├── id_ed25519_personal.pub</span><br><span class="line">├── id_ed25519_work</span><br><span class="line">├── id_ed25519_work.pub</span><br><span class="line">├── known_hosts</span><br><span class="line">├── company-key</span><br><span class="line">└── company-key.pub</span><br></pre></td></tr></tbody></table></figure><p>当需要在一台主机上管理多个 github 账号的时候,需要在 config 文件中进行如下配置:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">&gt; cat ~/.ssh/config</span><br><span class="line"></span><br><span class="line">Host github-personal</span><br><span class="line"> HostName github.com</span><br><span class="line"> User git</span><br><span class="line"> IdentityFile ~/.ssh/id_ed25519_personal</span><br><span class="line"></span><br><span class="line">Host github-work</span><br><span class="line"> HostName github.com</span><br><span class="line"> User git</span><br><span class="line"> IdentityFile ~/.ssh/id_ed25519_work</span><br><span class="line"></span><br><span class="line">Host company-github</span><br><span class="line"> HostName github.company.com</span><br><span class="line"> User git</span><br><span class="line"> IdentityFile ~/.ssh/company-key</span><br></pre></td></tr></tbody></table></figure><p>对应的 git clone 地址也需要做改变:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git clone git@github.com:octocat/test.git</span><br><span class="line"></span><br><span class="line">-&gt;</span><br><span class="line"></span><br><span class="line">git clone git@github-personal:octocat/test.git</span><br></pre></td></tr></tbody></table></figure><h2 id="SSH-与-Personal-Access-Tokens-的关系"><a href="#SSH-与-Personal-Access-Tokens-的关系" class="headerlink" title="SSH 与 Personal Access Tokens 的关系"></a>SSH 与 Personal Access Tokens 的关系</h2><blockquote><p>一般操作,使用 SSH Key 的情况下,不需要设置 PAT。</p></blockquote><h3 id="核心区别"><a href="#核心区别" class="headerlink" title="核心区别"></a>核心区别</h3><ul><li><p>SSH 认证</p><ul><li>使用 <code>git@github.com:user/repo.git</code> 格式地址</li><li>依赖本地 <code>~/.ssh/</code> 目录的密钥对</li><li>通过 <code>ssh -T git@github.com</code> 验证连接</li></ul></li><li><p>HTTPS 认证</p><ul><li>使用 <code>https://github.com/user/repo.git</code> 格式地址</li><li>需要配置 PAT 替代密码(GitHub 已禁用密码认证)</li></ul></li></ul><h3 id="协议检查方法"><a href="#协议检查方法" class="headerlink" title="协议检查方法"></a>协议检查方法</h3><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git remote -v</span><br><span class="line"><span class="comment"># 显示 git@github.com → 使用 SSH</span></span><br><span class="line"><span class="comment"># 显示 https://github.com → 需要 PAT</span></span><br></pre></td></tr></tbody></table></figure><h3 id="需要-PAT-的场景"><a href="#需要-PAT-的场景" class="headerlink" title="需要 PAT 的场景"></a>需要 PAT 的场景</h3><ol><li>调用 GitHub REST API</li><li>使用 GitHub CLI (<code>gh</code>) 操作敏感资源</li><li>访问 GitHub Packages 服务(npm / Docker 等)</li><li>操作其他 HTTPS 协议的仓库时</li><li>第三方 CI / CD 工具集成</li><li>使用 GitHub Actions 需要访问仓库时</li><li>使用双重认证 (2FA) 的账户通过 HTTPS 协议操作仓库</li></ol><h3 id="mac-钥匙串存储-PAT"><a href="#mac-钥匙串存储-PAT" class="headerlink" title="mac 钥匙串存储 PAT"></a>mac 钥匙串存储 PAT</h3><p>git 客户端(命令)会在用户输入 PAT 后,将 PAT 存储到 mac 的 keychain 中,并和 github domain 绑定。<br>这个时候,如果有多个 github 账号,就会发生冲突,导致其中一个 github 账号下面的 pat 验证失败。<br>所以,需要使用 <code>https://github.company.com/xxx/xx.git</code> 或 <code>https://alias.github.com/xxx/xx.git</code> 这样的格式,来对不同的 github 账号进行区分。</p><h3 id="URL-使用-PAT"><a href="#URL-使用-PAT" class="headerlink" title="URL 使用 PAT"></a>URL 使用 PAT</h3><p>URL 协议本身是支持增加 username 和 password 的,即 <code>https://username:password@github.com/xxx/xx.git</code>。</p><p>这个时候,可以把 username 换成 github 账号,password 换成 PAT,就可以在通过 github 授权的情况下操作 xx 仓库了。<br>原理:</p><ol><li>git 客户端会解析 URL,将 username 和 password 提取成 a:b 的格式,进行 base64 编码,放到 “Authorization: Basic ???” 头中。</li><li>服务器端接收到请求后,会进行 base64 解码,获取到 username 和 password,然后进行验证。</li></ol><blockquote><p>不推荐,简单实用一下还是可以的。</p></blockquote><h1 id="Signing-Key"><a href="#Signing-Key" class="headerlink" title="Signing Key"></a>Signing Key</h1><p>github 推出的 commit / tag 等操作的签名认证。被签名的 commit 会在历史记录中显示一个绿色的 <code>verified</code> 标记。<br>GPG Keys 是专门做这个事情的,其中,github 也推出了自己的 Signing Key,可以复用 SSH Keys 的设置(操作入口在同一个地方)。</p><p>具体配置如下,通过在 <code>~/.gitconfig</code> 中增加 url 分流配置,可以为不同的 host 组设置不同的 signing 规则。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// ~/.gitconfig-github-personal</span><br><span class="line"></span><br><span class="line">[user]</span><br><span class="line"> signingkey = ~/.ssh/id_ed25519_personal</span><br><span class="line">[commit]</span><br><span class="line">gpgsign = true</span><br><span class="line">[gpg]</span><br><span class="line">format = ssh</span><br><span class="line">[tag]</span><br><span class="line">gpgsign = true</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// ~/.gitconfig-github-work</span><br><span class="line"></span><br><span class="line">[user]</span><br><span class="line"> signingkey = ~/.ssh/id_ed25519_work</span><br><span class="line">[commit]</span><br><span class="line">gpgsign = true</span><br><span class="line">[gpg]</span><br><span class="line">format = ssh</span><br><span class="line">[tag]</span><br><span class="line">gpgsign = true</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// ~/.gitconfig</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">[includeIf "hasconfig:remote.*.url:git@github-personal:*/**"]</span><br><span class="line"> path = ~/.gitconfig-github-personal</span><br><span class="line">[includeIf "hasconfig:remote.*.url:git@github-work:*/**"]</span><br><span class="line"> path = ~/.gitconfig-github-work</span><br><span class="line"></span><br><span class="line">...</span><br></pre></td></tr></tbody></table></figure><h1 id="ssh-agent"><a href="#ssh-agent" class="headerlink" title="ssh-agent"></a>ssh-agent</h1><p>ssh 和 ssh-agent 都是 openssh 的一部分。对于 ssh 而言,公私钥中,私钥是可以被加密的,加密后,私钥就无法被直接使用,需要使用 passphrase 进行解密后才能使用。<br>这个时候操作流程就比较复杂,每次使用 ssh 的时候,ssh 命令通过操作系统弹窗,让用户输入 passphrase,然后对私钥原始内容进行解密获取私钥再使用。</p><p>ssh-agent 就是一个内存服务,ssh 拿到解密后的私钥后,直接放到 ssh-agent 中,后续直接从 ssh-agent 中进行读取。</p><p>这个时候又会遇到一个问题,就是 macos 系统重启了,ssh-agent 内存数据消失了。所以 ssh 会把 passphrase 存储到 keychain 中,下次启动时,会自动从 keychain 中读取 passphrase。<br>具体流程是:</p><ol><li>macos 重启,ssh-agent 数据清空</li><li>用户操作了 ssh 命令</li><li>ssh 客户端,通过 secret api 读取 keychain 中的 passphrase</li><li>对密钥原始数据进行解密后,把密钥放到 ssh-agent 中</li><li>后续直接从 ssh-agent 中读取</li></ol><p>配置 ssh-agent 示例 (UseKeychain &amp; AddKeysToAgent):</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&gt; <span class="built_in">cat</span> ~/.ssh/config</span><br><span class="line"></span><br><span class="line">Host example.com</span><br><span class="line"> HostName example.com</span><br><span class="line"> User git</span><br><span class="line"> PreferredAuthentications publickey</span><br><span class="line"> IdentityFile ~/.ssh/id_ed25519_personal</span><br><span class="line"> UseKeychain <span class="built_in">yes</span></span><br><span class="line"> AddKeysToAgent <span class="built_in">yes</span></span><br></pre></td></tr></tbody></table></figure><h1 id="git-如何存储文件的修改"><a href="#git-如何存储文件的修改" class="headerlink" title="git 如何存储文件的修改"></a>git 如何存储文件的修改</h1><p>via <span class="exturl" data-url="aHR0cHM6Ly9zd2lmdHJvY2tzLmNvbS93aGF0LWhhcHBlbnMtd2hlbi15b3UtbW92ZS1hLWZpbGUtaW4tZ2l0">https://swiftrocks.com/what-happens-when-you-move-a-file-in-git</span></p><h1 id="裸仓库"><a href="#裸仓库" class="headerlink" title="裸仓库"></a>裸仓库</h1><p>【裸仓库】是一个不包含工作区(working tree)的 Git 仓库,只包含 Git 版本库数据(.git 目录中的内容)。在 github 等平台上,我们通过 push、pull 操作的远程仓库,都是【裸仓库】,因为它们只需要存储版本数据,不需要工作区。</p><p>裸仓库 一般使用 xx.git 命名文件夹,内部没有工程目录,完全是 git 操作目录,如下:</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">tree</span><br><span class="line">.</span><br><span class="line">├── HEAD</span><br><span class="line">├── config</span><br><span class="line">├── description</span><br><span class="line">├── hooks</span><br><span class="line">│ ├── applypatch-msg.sample</span><br><span class="line">│ ├── commit-msg.sample</span><br><span class="line">│ ├── fsmonitor-watchman.sample</span><br><span class="line">│ ├── post-update.sample</span><br><span class="line">│ ├── pre-applypatch.sample</span><br><span class="line">│ ├── pre-commit.sample</span><br><span class="line">│ ├── pre-merge-commit.sample</span><br><span class="line">│ ├── pre-push.sample</span><br><span class="line">│ ├── pre-rebase.sample</span><br><span class="line">│ ├── pre-receive.sample</span><br><span class="line">│ ├── prepare-commit-msg.sample</span><br><span class="line">│ ├── push-to-checkout.sample</span><br><span class="line">│ └── update.sample</span><br><span class="line">├── info</span><br><span class="line">│ └── exclude</span><br><span class="line">├── objects</span><br><span class="line">│ ├── info</span><br><span class="line">│ └── pack</span><br><span class="line">└── refs</span><br><span class="line"> ├── heads</span><br><span class="line"> └── tags</span><br></pre></td></tr></tbody></table></figure><ol><li>创建 裸仓库:<code>git init --bare example.git</code></li><li>clone:<code>git clone ./path/to/example.git example</code></li></ol><h1 id="worktree"><a href="#worktree" class="headerlink" title="worktree"></a>worktree</h1><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">NAME</span><br><span class="line"> git-worktree - 管理多个工作目录</span><br><span class="line"></span><br><span class="line">SYNOPSIS</span><br><span class="line"> git worktree add [-f] [--detach] [--checkout] [--lock [--reason &lt;string&gt;]]</span><br><span class="line"> [--orphan] [(-b | -B) &lt;new-branch&gt;] &lt;path&gt; [&lt;commit-ish&gt;]</span><br><span class="line"> git worktree list [-v | --porcelain [-z]]</span><br><span class="line"> git worktree lock [--reason &lt;string&gt;] &lt;worktree&gt;</span><br><span class="line"> git worktree move &lt;worktree&gt; &lt;new-path&gt;</span><br><span class="line"> git worktree prune [-n] [-v] [--expire &lt;expire&gt;]</span><br><span class="line"> git worktree remove [-f] &lt;worktree&gt;</span><br><span class="line"> git worktree repair [&lt;path&gt;...]</span><br><span class="line"> git worktree unlock &lt;worktree&gt;</span><br></pre></td></tr></tbody></table></figure><p><code>git worktree</code> - 管理多个工作目录</p><p>核心功能: 允许你从一个 Git 仓库创建多个独立的工作目录,方便同时处理不同分支或任务。</p><p>常用命令:</p><ol><li><code>git worktree add &lt;path&gt; [&lt;branch&gt;]</code> - 创建新的工作目录<ul><li>作用: 在 <code>&lt;path&gt;</code> 创建新的工作目录,并检出 <code>[&lt;branch&gt;]</code> 分支 (默认当前分支)。</li><li>常用选项:<ul><li><code>f</code>: 强制创建,即使目录已存在 (小心数据丢失)。</li><li><code>-detach</code>: 创建分离 HEAD 的工作目录 (不关联分支)。</li><li><code>b &lt;new-branch&gt;</code>: 创建并检出新分支。</li></ul></li><li>示例:<ul><li><code>git worktree add -b feature-branch ../feature-branch</code>: 创建新分支 <code>feature-branch</code> 并检出到新工作目录。</li><li><code>git worktree add ../working-dir feature-branch</code>: 检出已存在的 <code>feature-branch</code> 分支到新工作目录。</li><li><code>git worktree add ../hotfix origin/main</code>: 创建 <code>hotfix</code> 工作目录 (基于 <code>origin/main</code>)。</li></ul></li></ul></li><li><code>git worktree list</code> - 列出工作目录<ul><li>作用: 显示所有已创建的工作目录及其路径和分支信息。</li><li>常用选项:<ul><li><code>v</code>: 显示更详细的信息。</li></ul></li></ul></li><li><code>git worktree remove &lt;path&gt;</code> - 删除工作目录<ul><li>作用: 删除 <code>&lt;path&gt;</code> 指定的工作目录。</li><li>常用选项:<ul><li><code>f</code>: 强制删除,即使工作目录不干净 (小心数据丢失)。</li></ul></li><li>注意: 仅删除工作目录,不影响仓库本身。</li></ul></li><li><code>git worktree lock &lt;worktree&gt;</code> - 锁定工作目录<ul><li>作用: 锁定 <code>&lt;worktree&gt;</code>,防止被 <code>git worktree remove</code> 删除。</li><li>常用选项:<ul><li><code>-reason &lt;string&gt;</code>: 添加锁定原因。</li></ul></li></ul></li><li><code>git worktree unlock &lt;worktree&gt;</code> - 解锁工作目录<ul><li>作用: 解锁 <code>&lt;worktree&gt;</code>,使其可以被 <code>git worktree remove</code> 删除。</li></ul></li><li><code>git worktree move &lt;worktree&gt; &lt;new-path&gt;</code> - 移动工作目录<ul><li>作用: 将 <code>&lt;worktree&gt;</code> 移动到 <code>&lt;new-path&gt;</code>。</li></ul></li><li><code>git worktree prune</code> - 清理无效信息<ul><li>作用: 清理已手动删除的工作目录在 Git 仓库中残留的记录。</li><li>常用选项:<ul><li><code>n</code>: 模拟运行,不实际删除。</li></ul></li></ul></li><li><code>git worktree repair</code> - 修复工作目录<ul><li>作用: 修复工作目录的元数据 (通常自动维护,极少手动使用)。</li></ul></li></ol><h1 id="文件夹大小写"><a href="#文件夹大小写" class="headerlink" title="文件夹大小写"></a>文件夹大小写</h1><p>Git 对文件夹和文件的大小写,是不敏感的。有些 IDE 会默认做很多事情,让用户无感知。当不通过 IDE 操作 Git 的时候,开发过程中有可能遇到【修改文件夹 / 文件名】无效的场景。</p><h1 id="orphan-branch"><a href="#orphan-branch" class="headerlink" title="orphan branch"></a>orphan branch</h1><p>孤儿分支,是指没有父分支的分支。场景:</p><ul><li>希望从当前 git 工程独立一份完全没有 git 记录的 new-branch,又需要保留当前 git old-branch 所有的 git 状态</li><li>还希望 new-branch 在当前 git 仓库中被管理。</li></ul><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git switch --orphan new-branch</span><br><span class="line">git checkout old-branch -- .</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>new-branch 将成为独立 commit 节点,没有任何父节点,并且 git 状态和 old-branch 一致。<br>后面可以新增 remote url,将 new-branch 推送到远程仓库。</p><hr>

How to pay

作者 海驴
2024年12月9日 01:18
<h1 id="All-pays"><a href="#All-pays" class="headerlink" title="All pays"></a>All pays</h1><p><span class="exturl" data-url="aHR0cHM6Ly9zdHJpcGUuY29tL3poLXNnL3BheW1lbnRzL3BheW1lbnQtbWV0aG9kcw==">https://stripe.com/zh-sg/payments/payment-methods</span></p><p>Stripe 支持了很多支付方式,可以窥见一些。Stripe 主要对接线上支付,对于很多线下支付的渠道如【nanaco】等电子货币,就看不到了。</p><h2 id="japan"><a href="#japan" class="headerlink" title="japan"></a>japan</h2><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090122548.png" width="60%"><span id="more"></span><h1 id="货币"><a href="#货币" class="headerlink" title="货币"></a>货币</h1><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090139321.png" width="60%"><h1 id="电子货币-支付链路"><a href="#电子货币-支付链路" class="headerlink" title="电子货币 - 支付链路"></a>电子货币 - 支付链路</h1><p>Suica、PASMO、nanaco、WAON、Edy(Rakuten Edy)、QUICPay、iD</p><p>如上文【货币】-【电子货币】中所描述,E-money(电子货币)是法定货币的 1:n 等值替换,即电子货币 = 法定货币。</p><p>电子货币无法凭空产生,需要基于预付费模式工作的,这意味着用户需要先充值然后才能消费。</p><p>用户将一定金额的资金存入电子钱包或智能卡中,然后在各种接受 E-money 支付的地点使用这些资金进行消费。</p><p>消费方式:实体卡刷卡、Apple / Google Pay、App 出示识别码等等。</p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090124537.png" width="60%"><h1 id="钱包-支付链路"><a href="#钱包-支付链路" class="headerlink" title="钱包 - 支付链路"></a>钱包 - 支付链路</h1><p>市场上有各种 X Pay,均为钱包。除了 Apple / Google Pay 这种专门为 信用卡、预付卡、借记卡、电子货币 提供聚合服务的产品外,其他钱包均有实际公司主体。</p><p>比如常见的 Paypay、wechat、alipay 等等,它们提供各式各样的终端消费能力。</p><p>但用户在钱包中的钱并不能凭空产生。</p><p>一种方式是依靠【信用卡】,信用卡 的链路最为复杂,下一章会单独讲诉。</p><p>一种方式是依靠【预充值】,用户将法定货币预先充值到平台侧。形式有:预付卡、借记卡、平台账户余额。</p><p>通过 预充值 的形式,钱包相当于中间平台,为用户的【纸币、硬币】这些法定货币,提供了中间放置平台,以进一步通过该平台向外部商家进行支付。</p><blockquote><p>当用户把钱充值到钱包后,钱包就需要一系列的管理功能。一来确保用户账户数额不能有差错,二来确保用户可以随意把钱花出去。 所以平台需要做一个大管家,提供各种安全保障能力。 甚至与,哪个商户若需要对平台用户进行收款,哪个商户就也要在该平台进行商家注册并绑定收款银行账号。 这样,商户的账户也被平台管控,平台就很容易进行交易清算。</p></blockquote><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090125351.png" width="60%"><h1 id="信用卡-预付卡-借记卡"><a href="#信用卡-预付卡-借记卡" class="headerlink" title="信用卡 - 预付卡 - 借记卡"></a>信用卡 - 预付卡 - 借记卡</h1><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p>信用卡关键的能力有两点:</p><ol><li>提供个人信用背书,个人没有钱,也能够通过信用卡消费。</li><li>有稳定的支付通道,为世界各地的信用卡提供清算能力。</li></ol><p>信用卡普及且应用广泛后,整个体系就非常成熟,商家已经对信用卡支付形成依赖。此时,一些不符合 1 条件的用户就没有信用卡,但自身也有钱 / 能力进行支付,就衍生出了预付卡、借记卡。</p><p>预付卡、借记卡 借用 2 支付通道,不提供信用背书,直接用现金支付,从而完成交易。</p><blockquote><p>预付卡、借记卡 也包括电子货币。只是在信用卡体系里,预付卡、借记卡 一定依赖 visa、mastercard、… 等支付网络,一定属于 visa、mastercard、… 卡。<br>若商户对 个人信用 十分在意,那么只能使用 信用卡 完成交易,预付卡、借记卡 无法支付。</p></blockquote><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090126692.png" width="60%"><h2 id="品牌贴牌"><a href="#品牌贴牌" class="headerlink" title="品牌贴牌"></a>品牌贴牌</h2><p>目前大范围使用的信用卡支付网络,只有 Visa、MasterCard、American Express (AmEx)、Discover、UnionPay、JCB。其中,大部分不发行卡,只提供【支付网络】。支付网络将是下一个章节重点说明的内容。</p><blockquote><p>American Express 和 Discover 提供发卡服务。即提供支付网络,也下场作为发卡行。&lt;这两位,拥有银行执照&gt;</p></blockquote><p>大众最常见的信用卡,是从银行处申请获取。发卡行一定是具有【银行执照】的金融机构,银行天然具有该优势。基本上,所有的信用卡,都是银行发行的。</p><p>但还有一种常见的信用卡,是从【钱包】公司处获取,如 Paypay、MerPay 等。这些公司并不具备银行执照,即没有发卡能力。</p><p>这是另一种常见的【品牌贴牌】信用卡,即 公司 对接发卡行。公司对用户进行信用评估等审核,并提供品牌福利,而发卡行承担用户的消费风险及盈利。</p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090127218.png" width="60%"><h2 id="信用卡支付网络"><a href="#信用卡支付网络" class="headerlink" title="信用卡支付网络"></a>信用卡支付网络</h2><p>【支付网络】是信用卡交易的核心,也是连接全球所有信用卡的枢纽。</p><p>所有的发卡行、收单行、有能力对接支付网络 的机构、单位、环节,其【资质、技术、安全】等都需要满足【支付网络】提供商的要求。</p><p>【支付网络】决定了整条交易链路的【规则、安全保障、清算、结算】等。</p><p>所有的信用卡,不分地区,通过【支付网络】都可以进行联通、交易。发卡行承担不同币种的汇率、消费额度等工作。</p><blockquote><p>发卡行会在交易前进行验卡,防止卡滥用。如有些卡不允许国际支付,会拒付。</p></blockquote><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090128277.png" width="60%"><h2 id="著名的国际支付服务提供商"><a href="#著名的国际支付服务提供商" class="headerlink" title="著名的国际支付服务提供商"></a>著名的国际支付服务提供商</h2><p>上文中,很多商业产品都会通过【支付服务提供商】来对接【支付网络】最终完成交易。以下是一些有名的提供商列表:</p><p>via <span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXIuYXBwbGUuY29tL3RhcC10by1wYXkvcmVnaW9ucy8=">https://developer.apple.com/tap-to-pay/regions/</span></p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090128854.png" width="60%"><h2 id="Apple-Pay-如何支持-EMV-FaliCa"><a href="#Apple-Pay-如何支持-EMV-FaliCa" class="headerlink" title="Apple Pay 如何支持 EMV &amp; FaliCa"></a>Apple Pay 如何支持 EMV &amp; FaliCa</h2><p>EMV 是 Europay MasterCard Visa 的缩写,是信用卡的协议标准,不同支付网络的信用卡均遵守该协议。</p><p>所以,信用卡在世界各地支持 EMV 读卡器的机子上均能【插卡支付】并消费。</p><p>但是在 Touch (contactless) 方面,日本提前广泛使用了 FaliCa 标准,没能很好的支持 EMV,导致前期不管国内还是国外的信用卡,都不支持【非触控支付】。</p><p>在技术专区里,会重点说明 FaliCa 的这段历史,以及 Quicpay / iD 如何解决这个问题。</p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090129489.png" width="60%"><h2 id="信用卡的不同支付链路"><a href="#信用卡的不同支付链路" class="headerlink" title="信用卡的不同支付链路"></a>信用卡的不同支付链路</h2><p>信用卡最终需要【发卡行】通过【支付网络】转账到【收单行】。</p><p>发卡行 依靠哪些信息来判定一张信用卡的合法性,是十分关键的。</p><p>在不同的 信用卡 使用场景中,发卡行 从【支付网络】侧获取到的卡信息是不一样的。</p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090129298.png" width="60%"><h1 id="特别支付场景介绍"><a href="#特别支付场景介绍" class="headerlink" title="特别支付场景介绍"></a>特别支付场景介绍</h1><h2 id="Tap-to-pay"><a href="#Tap-to-pay" class="headerlink" title="Tap to pay"></a>Tap to pay</h2><blockquote><p>基于 NFC 技术,可以【技术专区】中详细了解实现过程。</p></blockquote><p>通过<strong>移动设备终端</strong> (iPhone / Android) 识别 visa 等实体卡或者 apple pay 等<strong>物理硬件</strong>,<strong>获取到卡片的【代理卡号 / 虚拟码】,并完成支付</strong>的方式。</p><ol><li>通过 NFC 协议,完成数据读取</li><li>移动设备会有一个 app,用来对接 nfc api,完成数据读取、服务器交互等工作</li><li>Android 对于 NFC 较开放,可以方便实施。</li><li>iPhone 使用了 Core NFC 中【卡读取】的能力。对个别国家和供应商有开放。</li></ol><h3 id="example"><a href="#example" class="headerlink" title="example"></a>example</h3><p>via <span class="exturl" data-url="aHR0cHM6Ly93d3cuYXBwbGUuY29tL2J1c2luZXNzL3RhcC10by1wYXktb24taXBob25lLw==">https://www.apple.com/business/tap-to-pay-on-iphone/</span></p><h3 id="apple-供应地区说明"><a href="#apple-供应地区说明" class="headerlink" title="apple 供应地区说明"></a><strong>apple 供应地区说明</strong></h3><p>via <span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXIuYXBwbGUuY29tL3RhcC10by1wYXkv">https://developer.apple.com/tap-to-pay/</span></p><h3 id="Stripe-支持地区说明"><a href="#Stripe-支持地区说明" class="headerlink" title="Stripe 支持地区说明"></a><strong>Stripe 支持地区说明</strong></h3><p>via <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLnN0cmlwZS5jb20vdGVybWluYWwvcGF5bWVudHMvc2V0dXAtcmVhZGVyL3RhcC10by1wYXk/cGxhdGZvcm09aW9z">https://docs.stripe.com/terminal/payments/setup-reader/tap-to-pay?platform=ios</span></p><h2 id="Alipay-碰一下"><a href="#Alipay-碰一下" class="headerlink" title="Alipay 碰一下"></a>Alipay 碰一下</h2><blockquote><p>基于 NFC 技术,可以【技术专区】中详细了解实现过程。</p></blockquote><ol><li>支付宝给商家提供 NFC 卡模拟芯片。用户侧的支付宝 app 充当读卡器,读取商家的 NFC 芯片信息。app 获取商家信息后,进行网络处理,完成支付。</li><li>以往的支付,读卡器处于商家侧,如 Tap to pay、POS 机等。只要商家具有稳定的网络,就可以完成支付。</li><li>alipay 碰一碰方案,读卡器处于用户侧 app 中。这就需要用户侧具有稳定的网络,以完成支付。</li></ol><h1 id="技术专区"><a href="#技术专区" class="headerlink" title="技术专区"></a>技术专区</h1><h2 id="Visa-支付网关和清算平台-跨国际交易"><a href="#Visa-支付网关和清算平台-跨国际交易" class="headerlink" title="Visa - 支付网关和清算平台 - 跨国际交易"></a>Visa - 支付网关和清算平台 - 跨国际交易</h2><ol><li>Visa 不发行卡,不对接个人用户。它只对接银行、大企业主。</li><li>Visa 负责两个银行之间的资金流动,它会使用一天的固定汇率 (高于实时汇率) 加上自身的手续费进行计价。</li><li>因为 Visa 自身做的比较大、有信任、打广告,所以只要牵涉到跨国之间的终端用户级别的资金流动,都会有 Visa 的影子。</li><li>如果仅仅是本地企业、同一货币,那么不需要使用 Visa,就可以省去手续费。如银联。</li><li>所有的 Visa 信用卡交易,都需要过 Visa 的网关进行清算。最终也不是实时扣款,visa 会在结算时间到来后,统一将所有银行的账单进行清算。</li></ol><blockquote><p>扩展:</p></blockquote><ol><li>Visa 和 SWIFT 是两条赛道。SWIFT 主要处理两个国家之间银行界资金的流动。是直接流动,金额更大、级别更高。SWFIT 处理的金钱要比 Visa 多的多。</li><li>SWIFT 一日处理 5W 亿美元的交易,Visa 一年处理 10W 亿美元的交易。交易量不在一个量级。</li></ol><p>Visa 等一众【支付网络】的具体运转,详见上文中的【信用卡支付网络】章节。</p><h3 id="非银行如何对接【支付网络】"><a href="#非银行如何对接【支付网络】" class="headerlink" title="非银行如何对接【支付网络】"></a>非银行如何对接【支付网络】</h3><p>Stripe 对 Visa 的一些解释:<span class="exturl" data-url="aHR0cHM6Ly9zdHJpcGUuY29tL3poLXNnL3Jlc291cmNlcy9tb3JlL3doYXQtaXMtdmlzYSNzaHVpLXphaS1zaGkteW9uZy12aXNh">https://stripe.com/zh-sg/resources/more/what-is-visa#shui-zai-shi-yong-visa</span></p><p>Stripe 在 2015 年开始直接对接 Visa,不在走【收单行】对接:<span class="exturl" data-url="aHR0cHM6Ly93d3cuYnVzaW5lc3N3aXJlLmNvbS9uZXdzL2hvbWUvMjAxNTA3MjkwMDU4NDEvemgtQ04v">https://www.businesswire.com/news/home/20150729005841/zh-CN/</span></p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090134169.png" width="60%"><h2 id="Apple-Pay"><a href="#Apple-Pay" class="headerlink" title="Apple Pay"></a>Apple Pay</h2><h3 id="原理概要"><a href="#原理概要" class="headerlink" title="原理概要"></a>原理概要</h3><p>Apple 提供的【apple pay】方案,是将用户的信用卡信息,使用 “令牌化” 方案,通过【代理卡号】的方式存储于 iPhone 设备,后续将 DAN 提供给读取者,读取者使用 DAN 完成后续的支付链路。</p><blockquote><p>代理卡号(Device Account Number, DAN) 不存储卡号、有效期、cvs,是银行侧生成的一个 code。银行会根据这个 code 在结算的时候进行卡账户校验。</p><p>每次进行 apple wallet 录入的时候,都会生成一个新的 DAN。</p></blockquote><p>使用 DAN 的业务方:</p><ol><li>Apple Wallet:iPhone 作为 NFC 卡模拟,供系统级别的 Wallet app 使用。<ol><li>wallet app<ol><li>将银行卡信息存储到 iPhone 中(录入 DAN)</li><li>有外部 NFC 读卡器的时候,wallet 被激活,完成用户身份认证,并读取 DAN 并提供给外部 NFC 读卡器。</li><li>外部读卡器获取到 DAN 后,对接 Stripe 或者卡服务商。最终在发卡行完成 DAN 的校验,完成扣款和资金转移。</li></ol></li></ol></li><li>Native app:通过 passkit sdk,swift / oc 对接 sdk 在 app 内部完成【支付信息】的读取<ol><li>native app 绑定 【Merchant ID】出口,未绑定的 id 不允许支付。</li><li>native app 弹出 apple pay 弹窗,获取 pay token(公钥加密)</li><li>将 token 给到 self server 或者 stripe 等平台,这些服务平台需要对 token 进行私钥解密。<ol><li>私钥的来源有一套比较复杂的流程</li></ol></li><li>server 将解密后的信息给到【发卡行】,发卡行完成校验及扣款事宜。</li></ol></li><li>Web app:<ol><li>整体和 native app 一致</li><li>因为不在 app 内部,缺少必要的 Merchant ID 信息。而是在 safari 中唤醒 apple pay 支付,所有多了一个【Identity】认证<ol><li>通过公私钥证书来完成</li><li>该认证,主要用于对服务器进行确认,进而获取商户信息。后续流程和 native app 一致。</li></ol></li></ol></li></ol><h3 id="技术流程"><a href="#技术流程" class="headerlink" title="技术流程"></a>技术流程</h3><h3 id="native-self-server"><a href="#native-self-server" class="headerlink" title="native - self server"></a>native - self server</h3><ol><li>Apple Developer 申请 Merchant ID(id)</li><li>dev 申请 CRS 文件(本机绑定私钥 private key),在 developer 后台绑定 id 生成 cer 证书。<ol><li>dev 将 cer 证书安装到本机后,导出 p12 文件(包含公私钥 public + private key)</li><li>将 p12 给到 self server</li></ol></li><li>native app 在 xcode 中绑定 id<ol><li>该行为使得 app 可以确定允许的商户,可绑定多个 id</li><li>web app 没有这一步,所以需要 【identity】认证</li></ol></li><li>native app 完成 apple pay 弹窗并获取 payment token</li><li>token 给到 server,server 通过 p12 拿到 private key,对 token 进行解密后,提交发卡行进行验证扣款。</li></ol><h3 id="native-stripe"><a href="#native-stripe" class="headerlink" title="native - stripe"></a>native - stripe</h3><ol><li>Apple Developer 申请 Merchant ID(id)</li><li>在 stripe 后台申请 CRS 文件后,在 developer 绑定 id 生成 cer 证书,再将 cer 证书上传到 stripe 后台。<ol><li>stripe 这里就拥有了 p12 (private key + public key)</li></ol></li><li>native app 调用 stripe api,完成 apple pay 弹窗 等所有操作,直接获取支付结果<ol><li>stripe sdk 将 payment token 给到 stripe server 完成 token 解密</li><li>stripe server 进行发卡行提交</li><li>stripe 处理支付结果并返回给业务</li></ol></li></ol><h3 id="web-self-server"><a href="#web-self-server" class="headerlink" title="web - self server"></a>web - self server</h3><p>和 native 基本流程一致,在 id 绑定证书环节,除了 【Apple Pay Payment Processing Certificate】cer 证书外,还需要以下:</p><ol><li><p>Apple Pay Merchant Identity Certificate</p><ol><li>商户需要通过该证书向 apple 获取 Merchant ID 的详细信息</li></ol><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"> // example</span><br><span class="line"></span><br><span class="line">const https = require('https');</span><br><span class="line">const fs = require('fs');</span><br><span class="line"></span><br><span class="line">function getApplePaySession(validationURL, callback) {</span><br><span class="line"> const options = {</span><br><span class="line"> url: validationURL,</span><br><span class="line"> method: 'POST',</span><br><span class="line"> cert: fs.readFileSync('path/to/merchant_identity_certificate.pem'),</span><br><span class="line"> key: fs.readFileSync('path/to/merchant_identity_private_key.pem'),</span><br><span class="line"> headers: {</span><br><span class="line"> 'Content-Type': 'application/json'</span><br><span class="line"> },</span><br><span class="line"> body: JSON.stringify({</span><br><span class="line"> merchantIdentifier: 'merchant.com.yourdomain.yourmerchantname',</span><br><span class="line"> domainName: 'yourdomain.com',</span><br><span class="line"> displayName: '您的商户名称'</span><br><span class="line"> })</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> const req = https.request(options, (res) =&gt; {</span><br><span class="line"> let data = '';</span><br><span class="line"> res.on('data', (chunk) =&gt; { data += chunk; });</span><br><span class="line"> res.on('end', () =&gt; { callback(null, JSON.parse(data)); });</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> req.on('error', (e) =&gt; { callback(e); });</span><br><span class="line"> req.end();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure></li><li><p>Merchant Domains</p><ol><li>需要将必要的 apple 文件放置于 domain 服务器上,供 apple 对 domain 进行验证。未验证通过的 domain 无法进行 apple pay 支付。</li></ol></li></ol><h2 id="NFC"><a href="#NFC" class="headerlink" title="NFC"></a>NFC</h2><p>NFC(近场通信)的技术原理基于无线电频率识别(RFID)技术,使用磁场感应来实现在设备间的通信。NFC 设备在 13.56 MHz 频率上操作,通常用于非接触式数据传输,距离范围非常短,通常在几厘米内,传输速率慢,在 400kbps (50kb / s) 左右。</p><h3 id="技术原理"><a href="#技术原理" class="headerlink" title="技术原理"></a>技术原理</h3><p>识卡器发出信号(电磁感应),激活了终端(手机自动点亮),然后进行数据交互,并可能需要机主进行身份验证,最后完成信息的交互(支付、上公交车、开门等)。</p><p>NFC 有三种工作模式:点对点通信模式、读卡器模式、卡模拟模式。又分为【主动模式】和【被动模式】,其中一个设备提供射频场,另一个设备利用这个射频场进行通信。</p><p>使用 NFC,需要两个终端,一个做控制器,用于发射磁场来识别信息。一个做无电源的数据芯片,通过接收到的磁场来感应并传输信息。</p><p>对于 NFC 设备来说:</p><ol><li>点对点通信:两个 NFC 设备相互交换信息。</li><li>读卡器模式:一个 NFC 作为控制器,读取其他 NFC 芯片中的信息。</li><li>卡模拟模式:一个 NFC 作为数据芯片,其他读卡器可以读取其中的信息。</li></ol><p>现代电子产品中,Android 和 iPhone 都支持 NFC 技术,手机作为 NFC 设备,使用读卡器模式和卡模拟模式,已经可以完成很多事情。</p><ol><li>当作为 NFC 控制器的时候,手机可以主动的读取外部 NFC 芯片中的信息,也可以将必要的信息写入到外部 NFC 芯片。(物流中,可以通过手机对商品挂载的 NFC 芯片进行记录)</li><li>当作为 NFC 芯片的时候,手机可以模拟一个 NFC 芯片,通过软件将信息提前写入手机中,其他读卡器就可以直接读取手机中的信息。(可以实现门禁卡等)</li></ol><p>Android 对 NFC 的 API 开放较多,app 可通过 api 来控制 NFC 进行 读取、写入、模拟 的操作,来实现快捷的智能家居、门禁卡等场景。</p><p>iPhone 上则比较保守,在【卡模拟】、【卡读取】方面,都有不少限制。</p><h3 id="门禁卡"><a href="#门禁卡" class="headerlink" title="门禁卡"></a>门禁卡</h3><p>普通门禁卡:</p><ol><li>门禁卡中有 【微芯片】(存储卡片的识别信息和其他数据)和 【天线】(用于接收和发送无线信号)。</li><li>门禁卡靠近门禁系统的读卡器 → 读卡器会发出一个射频信号 → 信号通过天线供电给门禁卡上的微芯片 → 微芯片被激活, 通过天线将存储在芯片上的识别信息发送回读卡器 → 读卡器接收到信息后,将数据传输给后端的门禁控制系统。</li></ol><p>NFC 门禁卡:</p><p>原理基本和普通门禁卡一样,不过,NFC 提供了更高的安全性。它支持双向通信,卡和识卡器之间可以通信。它们之间会进行密钥交换,通过对称、非对称加密来完成数据的安全传输。相比普通门禁卡,NFC 门禁卡会更加的安全。</p><blockquote><p>NFC 是一种普适性的技术方案,手机也可以作为 NFC 终端。这里就可以把 NFC 门禁卡的信息保存在 手机中,使得手机可以充当 NFC 门禁卡的功能。</p></blockquote><p>蓝牙门锁:</p><p>有些 app 会通过 蓝牙的方式,和门锁连接。这在智能门锁中非常常见。因为距离很远,就可以连接上。而 NFC 需要非常短的距离 (4cm) 才能通信。</p><h3 id="移动支付"><a href="#移动支付" class="headerlink" title="移动支付"></a>移动支付</h3><p>通过 NFC 进行移动支付,主要有三种方案:</p><ol><li>卡模拟方案。mobile 录入支付卡信息,被外部读取器识别<ol><li>系统级别的支持,如 apple pay。开发人员没有掌控能力。用户只能通过 apple wallet 录入银行卡信息,然后通过 apple pay 进行支付。</li><li>应用级别的支持。Android 支持的较好,iPhone 限制很多。<ol><li>iPhone 在 iOS 18.1 放开了该限制,app 可以将支付卡信息写入 app 中,支付的时候调用 app 完成支付。</li><li>不对普通开发者开放,需要和 apple 签订商务协议,支付费用,一般都是支付中间商如 Stripe。并且只对个别国家开放。</li></ol></li></ol></li><li>读卡器方案。mobile 作为读取器识别外部实体卡(信用卡等)。Android 支持的叫好,iPhone 限制很多。<ol><li>Apple Tap to pay。商家可以在自己的手机中,打开 m app,然后用户把信用卡、iWatch 靠近手机,即可完成支付。<ol><li>普通开发人员没有太多的控制能力。也需要签订商务协议,一般都是支付中间商如 Stripe。它们提供 SDK 并和 Apple Api 交互完成支付。</li><li>Apple 和 Stripe 中间商会对地区等有限制。只在少有的地区开放了 Tap To Pay 能力。</li></ol></li><li>alipay 碰一碰。非常聪明的通过 NFC 实现支付的方案。<ol><li>支付宝给商家提供 NFC 卡模拟芯片。用户侧的支付宝 app 充当读卡器,读取商家的 NFC 芯片信息。app 获取商家信息后,进行网络处理,完成支付。</li><li>以往的支付,读卡器处于商家侧,如 Tap to pay、POS 机等。只要商家具有稳定的网络,就可以完成支付。</li><li>alipay 碰一碰方案,读卡器处于用户侧 app 中。这就需要用户侧具有稳定的网络,以完成支付。</li></ol></li></ol></li></ol><h3 id="Apple-iPhone-NFC"><a href="#Apple-iPhone-NFC" class="headerlink" title="Apple iPhone NFC"></a>Apple iPhone NFC</h3><p>简单介绍一下 iPhone 对 NFC 支持的历史:</p><ul><li>WWDC 2017:引入 Core NFC,并具备 NDEF 标签【<strong>读取</strong>】 功能。</li><li>WWDC 2018:在较新设备上对 NDEF 消息进行后台标签读取。</li><li>WWDC 2019:重大扩展,允许 NDEF【<strong>写入</strong>】,支持 ISO 7816、ISO 15693 和 MIFARE 标签,并支持自定义命令。</li><li>WWDC 2020:多标签检测,VAS 协议支持和 ISO 15693 标签的后台读取。</li><li>WWDC 2021-2023:专注于稳定性、性能提升和小幅增强,没有重大 API 更改。</li><li>WWDC 2024:支持【<strong>卡模拟</strong>】。</li></ul><h3 id="EMV-vs-FeliCa"><a href="#EMV-vs-FeliCa" class="headerlink" title="EMV vs FeliCa"></a>EMV vs FeliCa</h3><p>EMV:基于 NFC Type A / B 设计的通信标准。NFC 的【读卡器】和【卡芯片】均使用 EMV 协议。</p><p>FeliCa:Sony 开发,基于 NFC Type F 设计的通信标准,速度快,成本高,仅在日本大范围使用。NFC 的【读卡器】和【卡芯片】均使用 FeliCa 协议。</p><p>这两者,是使用 NFC 实现的两套不兼容的通信协议标准。详细技术解释可参考【Quicpay】章节。</p><h2 id="Quicpay-iD-为什么称为【电子货币】"><a href="#Quicpay-iD-为什么称为【电子货币】" class="headerlink" title="Quicpay - iD 为什么称为【电子货币】"></a>Quicpay - iD 为什么称为【电子货币】</h2><p>Quicpay 官网: <a href="https://www.quicpay.jp/"><span class="exturl" data-url="aHR0cHM6Ly93d3cucXVpY3BheS5qcC8=">https://www.quicpay.jp</span></a></p><p>日本很早期,地铁等交通就很繁华,对于人流量大的场景,过关卡排队就需要很长时间。</p><p>One day,Sony 基于 NFC Type F 研发了 FeliCa 协议的高频无线通信技术。</p><p>FeliCa 主要用于 Touch / Contactless【非触控】场景,Pasmo、Suica 都是基于 FeliCa 实现的刷卡,特点是:速度快。</p><p>很快,FeliCa 成为日本在【非触控】刷卡领域的事实标准,所有能刷卡的地方,都是基于 FeliCa 实现。</p><p>前面介绍 NFC 的时候,说到 NFC 需要两个终端(读卡器、卡模拟)才能工作,日本所有的读卡器也都是基于 FeliCa 实现的。</p><p>而卡模拟一侧,包括 Visa 信用卡、Pasmo 公交卡、电子货币实体卡 等,都是支持 FeliCa 的。</p><p>前面介绍 Apple Wallet 对接 Quicpay 的时候,流程图中有提到 Quicpay DAN。在 Apple Pay 还没有进入日本之前,Quicpay、iD 就已经发行实体卡进行消费,同时 Visa 等信用卡也都是支持 Quicpay / iD 支付。</p><p>交易的链路和现在相比没有改变,依旧是读卡器通过 FeliCa 读取卡信息后提交到 Quicpay 后台,后台通过 收单行 对接 Visa 支付网络,完成交易。</p><p>只是这个时候没有 DAN,DAN 安全码是 Apple Wallet 特有的产物。</p><p>但是 FeliCa 的硬件成本比普通的 NFC Type A/B 协议高,国际上普遍使用的都是 EMV 协议。EMV 是基于 NFC Type A/B 实现的通信协议。</p><p>在刷卡过程中,数据是需要加密的,而这套加密规则,也是和 FeliCa / EMV 的设计绑定的。</p><p>此时,日本基于 FeliCa 的读卡器,在【非触控】刷卡的时候,就无法读取基于 EMV 协议设计的国际信用卡。</p><blockquote><p>但是对于插卡消费是没有影响的。 Visa、masterCard 等官方平台,制定的通信规则就是【信用卡基于 EMV 协议】。所以日本的信用卡本身绝对是符合 EMV 协议的。 所以日本的信用卡在插卡消费的时候,也同样使用 EMV 协议。因为 FeliCa 协议只在【非触控】场景下使用。 So,这个时期,外国游客在日消费,可以使用信用卡插卡消费,但无法使用 Touch / Contactless【非触控】刷卡消费。</p></blockquote><blockquote><p>对于在日本申请的信用卡实体卡,本身就是支持 EMV 和 FeliCa 两种通信协议。在日本的时候,读卡器可以插卡识别 EMV,也可以【非触控】识别 FeliCa。 当日本人出国在境外刷卡,外国的读卡器都是支持 EMV 插卡 &amp;【非触控】识别,所以日本信用卡在国外使用完全不受影响。</p></blockquote><p>对于国外信用卡在日本不能很好使用【非触控】的体验问题,并没有好的解决办法。FeliCa 历史已久,在速度方面很优秀,Pasmo 等众多卡都使用这个协议,根子是不能替换的。</p><p>所以日本开始升级读卡器,截至目前,很多读卡器也都同时支持 EMV 和 FeliCa 两种协议的 NFC 识别了。</p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090137924.png" width="60%"><p>同时,随着 Apple/Google Pay 的发展,现在 Apple Wallet 也支持 Quicpay/iD 支付了,在日本称为【电子货币】。</p><p>在 Apple Wallet 中见到 Quicpay / iD 的场景,都是信用卡场景。即 wallet 中,一张卡的右下角,同时具有 Quicpay + Visa 或者 iD + masterCard Logo。</p><p>有些钱包公司发行的卡,仅仅支持 Quicpay 或者 iD,就没有 Visa 或者 masterCard Logo 了,即这个卡就不能在国外使用啦。</p><blockquote><p>但从 Apple Wallet 中 Visa、masterCard 支持 Quicpay/iD,被称为【电子货币】或许有些奇怪。 若从 Quicpay/iD 自身的功能场景出发,它们本身就是提供预充值的实体卡而后刷卡消费,的确属于【电子货币】。 只是 信用卡 虽然走了 Quicpay/iD 的支付通道,又的确和【电子货币】没关联,其实依旧属于【信用卡支付】。 so,Quicpay/iD 被称为【电子支付】,完全是根据其自身的原始功能,下的定义。</p></blockquote><p>最终在说明一点,在日本银行发行的信用卡,基本上都是和 Quicpay 或者 iD 合作的。有些钱包公司借壳发卡行发行的卡,有可能没有 Quicpay / iD。</p><p>这里有一份列表,via <span class="exturl" data-url="aHR0cHM6Ly9hdGFkaXN0YW5jZS5uZXQvYXBwbGUtcGF5LWphcGFuLWNyZWRpdC1jYXJkcy8=">https://atadistance.net/apple-pay-japan-credit-cards/</span></p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202412090138969.png" width="60%"><hr>

apple frameworks

作者 海驴
2024年11月14日 13:16
<p>动态库 &amp; 静态库,若单独输出并接入主工程,逻辑倒很清晰。如果牵涉到多个 framework 不同类型并且相互依赖,会增加不少复杂度。<br>以下说明中,动态库使用 DFramework 表示,静态库使用 SFramework 表示。</p><h1 id="1-framework"><a href="#1-framework" class="headerlink" title="1 framework"></a>1 framework</h1><p>仅需要提供 1 个 framework,直接提供 DFramework 即可。DFramework 优先,必要的时候再提供 SFramework</p><h1 id="n-framework"><a href="#n-framework" class="headerlink" title="n framework"></a>n framework</h1><p>因为代码结构分组,可能需要提供多个 framework:</p><ol><li>DFramework 优先,能不提供 SFramework 就不要提供<ul><li>有 n 个 DFramework 就提供 n 个。相互之间做好依赖文档,App 需要根据依赖文档 <strong>flat</strong> 平铺接入所有需要的 DFramework。</li><li>如果 b DFramework 仅仅被 a DFramework 依赖,可以将 b 打入 a DFramework 的 <code>frameworks</code>文件夹中,通过 <code>embed</code> 实现。<ul><li>这样就可以不再文档中说明 b DFramework 的存在,对外界透明。</li><li>这种方案,最终的 app 中 framework 不是 flat 平铺的,而是具有层级结构,如 :App - a DFramework - b DFramework</li></ul></li><li>如果 b DFramework 同时被 m DFramework 和 n DFramework 依赖,不能将 b 隐藏(打入 m 和 n 中)。<ul><li>否则,App 集成 m 和 n 的时候,b 会出现多份。如果 b 的版本不一样,将按照打包顺序只使用其中一个。</li></ul></li></ul></li></ol><span id="more"></span><ol start="2"><li>如果提供 SFramework,则将所有的 framework 做成依赖,输出一份 SFramework 即可。<ul><li>如果有按需接入的场景,按按照场景输出 n 个 SFramework。</li></ul></li></ol><h1 id="依赖开源库"><a href="#依赖开源库" class="headerlink" title="依赖开源库"></a>依赖开源库</h1><p>一般这种场景是因为开发 framework 的同时,又使用了开源的库。这个时候如何处理开源库是个问题(开源库一般都是 源码 提供的,不讨论 闭源包 提供方式,这个和上面的场景一致)。<br>由于 apple 的 Swift Package Manager 三方库管理的介入,一个 module 在最终的 app 中到底是 dymanic 还是 static,已经不可控了。SPM 会自行管理,不像 cocoapods 的时候我们可以自行控制。</p><blockquote><p>如果是源码提供者,可以增加 dymanic / static 约束。但很多源码都不会提供这个选项,而是让 SPM 自行管理采用哪一个。</p></blockquote><p>在这种场景下,开发的 framework 如果对开源库有依赖,就必须考虑是否【去开源】事宜了。<br>另外,除了自己使用开源库,App 主工程开发者,可能也需要使用同一个开源库,这时候还会出现【版本不一致问题】。<br>以下场景,假设有 T 开源代码需要使用:</p><h2 id="不封装,让外界接入"><a href="#不封装,让外界接入" class="headerlink" title="不封装,让外界接入"></a>不封装,让外界接入</h2><p>framework 需要依赖 T</p><ul><li>如果不是强依赖,在 xcode 中使用 <code>-weak_framework</code>进行标记,这样业务也可以不接入,相当于默认不使用这个功能。</li><li>如果是强依赖,并且在 文档 中说明业务需要接入 x 的具体版本范围。不接入无法编译或者 app 启动闪退。</li></ul><h2 id="封装,外界无感"><a href="#封装,外界无感" class="headerlink" title="封装,外界无感"></a>封装,外界无感</h2><p>第一步就是需要对 T 进行【模块隔离】,否则外界也接入 T 的时候,App 主工程和我们提供的 framework 同时有 T 代码,会有问题。<br>虽然 xcode 在编译连接的时候,会处理好这个问题,使得同一份代码尽量在 app 中仅保留一份。<br>但如果开发人员使用不恰当或者配置错误,很可能导致出现两份 T 源码在项目中。<br>这个时候如果版本还不兼容,就会有非常大的调试和异常隐患。<br>模块隔离方案:<strong>修改 Product Name,将 T 变成 TT 模块</strong><br>然后,还需要考虑将 TT 变成静态库还是动态库:</p><h3 id="静态库:"><a href="#静态库:" class="headerlink" title="静态库:"></a>静态库:</h3><p>这是完全去开源的方案。变成静态库后,TT 源码将打入 framework 二进制中一起提供。因为模块隔离,外部也无法调用和感知。</p><ul><li>通过对 framework 二进制字符表进行分析,还是能够看到 T 的踪迹。</li><li>如果不做模块隔离,也能成功。主 app 也使用 T 模块的时候,链接的时候将自动保留 1 份静态代码。有版本差异的话,出现异常问题将非常难以排查。</li></ul><h3 id="动态库:"><a href="#动态库:" class="headerlink" title="动态库:"></a>动态库:</h3><p>使用动态库的话,如前面【需要提供多个 framework】所描述的,有两种方案:</p><ul><li>TT 可以提供给业务,让业务自行接入。是 flat 平铺 结构。</li><li>也可以内嵌到 m framework 的 frameworks 文件夹中专门供 m framework 使用。是 内嵌 结构。<ul><li>如果不做模块隔离,也可以内嵌。但主 app 可能也使用了 T 模块,使得同一个工程包含 2 个版本的 T 模块,运行的时候按照 xcode 编译顺序,只有一份被使用。有版本差异的话,出现异常问题将非常难以排查。</li></ul></li></ul><hr>

Mac - Command Line Tools

作者 海驴
2024年11月11日 11:38
<p>Mac 系统上,默认没有提供 git、xcodebuild 这些开发者命令。所以当一些终端命令 (brew 等) 被触发后,macos 系统会弹窗提醒用户进行【开发者工具】的安装,即【Command Lines Tools】(下面简称 tools)。<br>如果安装了不同版本的 xcode,则每个版本都会携带各自 xcode 版本的【tools】。<br>上面提到的系统弹窗,是不需要安装 xcode 也可以安装【tools】。但这个 tools 版本会比较低,内部的命令数量也会少于 xcode 携带的。即:最新版本的 xcode,携带的 tools 也是最新的。<br>这里就可以发现有多个 tools 目录了,如下:</p><ol><li>非 xcode 携带:<code>/Library/Developer/CommandLineTools</code></li><li>xcode:<code>/Applications/Xcode.app/Contents/Developer</code></li><li>xcode beta: <code>/Applications/Xcode-16.2.0-Beta.2.app/Contents/Developer</code></li></ol><p>在终端执行 <code>git</code> 、<code>xcodebuild</code> 的时候,一定会使用某一个 tools 中的命令,可具体使用哪一个呢?</p><span id="more"></span><h1 id="xcode-select"><a href="#xcode-select" class="headerlink" title="xcode-select"></a>xcode-select</h1><p>通过 <code>xcode-select</code> 可以方便的切换 main tools,默认也只有 main tools 会生效。可以通过 <code>man xcode-select</code> 查看简介。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">xcode-select --install // 安装非 xcode 版本的 tools</span><br><span class="line">xcode-select --switch &lt;path&gt; // 切换不同的 tools 作为 main tools</span><br><span class="line">xcode-select -p // 打印当前 main tools path</span><br></pre></td></tr></tbody></table></figure><p>当 tools 被指定为 <code>xcode-xx-beta.app/xx/Develop</code> 目录的时候:<br>在终端中执行 git 命令,最终就会使用:<code>/Applications/Xcode-16.2.0-Beta.2.app/Contents/Developer/usr/bin/git</code><br>xcode-select 默认只有 1 个 tools 被激活,是操作系统级别的全局有效。<br>如果在 a tools 激活的同时,希望使用 b tools 该怎么办呢?在多版本 xcode 共存的时候,可能需要使用不同的 tools 命令进行开发。<br>这个时候在当前的命令行环境中设置<code>DEVELOPER_DIR</code> 就可以临时切换 tools 目录:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// 1</span><br><span class="line">&gt; xcrun --find git</span><br><span class="line">/Applications/Xcode-16.2.0-Beta.2.app/Contents/Developer/usr/bin/git</span><br><span class="line">// 2</span><br><span class="line">export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"</span><br><span class="line">// 3</span><br><span class="line">&gt; xcrun --find git</span><br><span class="line">/Applications/Xcode.app/Contents/Developer/usr/bin/git</span><br></pre></td></tr></tbody></table></figure><h1 id="xcrun"><a href="#xcrun" class="headerlink" title="xcrun"></a>xcrun</h1><p>当在终端中执行 <code>git</code> 命令的时候,使用的是 tools 中的 git,这里需要探究一下。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">// 1</span><br><span class="line">&gt; where git</span><br><span class="line">/usr/bin/git</span><br><span class="line">// 2</span><br><span class="line">&gt; xcrun --find git</span><br><span class="line">/Applications/Xcode.app/Contents/Developer/usr/bin/git</span><br></pre></td></tr></tbody></table></figure><p>通过 where 发现,<code>/usr/bin</code> 目录下是存在 git 二进制可执行文件的。为什么说执行的是 tools 中的 git 可执行文件呢?<br>这就要说到 xcode-select 维护多个 tools 的意义了。<br><code>/usr/bin</code> 是 path 路径,可以直接找到命令。但是像 git 这些命令依托不同的 tools 环境提供,总不能在切换 main tools 的时候,把这些命令 copy 到 <code>/usr/bin</code> 中。所以对于 <code>/usr/bin</code> 中的 git、xcodebuild 等命令,在具体执行的时候都执行的是 main tools 中的可执行文件。<br>实现这个能力的,就是 【xcrun】。</p><p>xcrun 的目标只有一个,就是根据 xcode-select 设定的 mail tools 目录,在其下面寻找命令。可以通过 <code>man xcrun</code> 查看详细:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Options:</span><br><span class="line"> -h, --help show this help message and exit</span><br><span class="line"> --version show the xcrun version</span><br><span class="line"> -v, --verbose show verbose logging output</span><br><span class="line"> --sdk &lt;sdk name&gt; find the tool for the given SDK name</span><br><span class="line"> --toolchain &lt;name&gt; find the tool for the given toolchain</span><br><span class="line"> -l, --log show commands to be executed (with --run)</span><br><span class="line"> -f, --find only find and print the tool path</span><br><span class="line"> -r, --run find and execute the tool (the default behavior)</span><br><span class="line"> -n, --no-cache do not use the lookup cache</span><br><span class="line"> -k, --kill-cache invalidate all existing cache entries</span><br><span class="line"> --show-sdk-path show selected SDK install path</span><br><span class="line"> --show-sdk-version show selected SDK version</span><br><span class="line"> --show-sdk-build-version show selected SDK build version</span><br><span class="line"> --show-sdk-platform-path show selected SDK platform path</span><br><span class="line"> --show-sdk-platform-version show selected SDK platform version</span><br></pre></td></tr></tbody></table></figure><p>上面示例中的 <code>xcrun --find git</code> 就是用来找 git 二进制可执行文件路径的。<br>当 git 在 tools 中的路径找到后,原先执行的 <code>/usr/bin/git</code> 命令,也就通过这个新的执行文件来实现了。</p><hr>

DNS 的 CNAME 是如何工作的

作者 海驴
2024年11月10日 22:54
<p>今天配置 cloudflare 中站点的 dns,遇到一些关于 cname、tls、cloudflare 代理相关的问题,做了下梳理。重点有下:</p><ol><li>cname 负责提供目标 ip,在机制上类似【权威域名服务器】</li><li>目标 ip 所在的服务如果非自行掌控(比如强行设置一个目标域名),很可能无法工作。</li><li>cloudflare 设置 cname 的时候有一个【代理】功能。这个设计非常糟糕,完全脱离了 cname 的本意。</li></ol><p>以下介绍中,使用 cloudflare 配置 <code>test.yigegongjiang.com</code> 的 cname 为 <code>httpbin.org</code>,默认不配置【代理】。</p><span id="more"></span><h1 id="CNAME-是做什么用的"><a href="#CNAME-是做什么用的" class="headerlink" title="CNAME 是做什么用的"></a>CNAME 是做什么用的</h1><p>DNS 解析中,通过 <code>dig A test.yigegongjiang.com</code>,<code>dig A httpbin.org</code> 会有如下返回(参数 A 表示返回目标域名的 ipv4):</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"> // dig A test.yigegongjiang.com</span><br><span class="line"> </span><br><span class="line"> ;; ANSWER SECTION:</span><br><span class="line">-&gt; test.yigegongjiang.com. 300 IN CNAME httpbin.org.</span><br><span class="line"> httpbin.org. 60 IN A 54.243.34.18</span><br><span class="line"> httpbin.org. 60 IN A 34.206.181.91</span><br><span class="line"> httpbin.org. 60 IN A 3.222.34.231</span><br><span class="line"> httpbin.org. 60 IN A 35.172.59.156</span><br><span class="line"> httpbin.org. 60 IN A 54.237.204.19</span><br><span class="line"> httpbin.org. 60 IN A 184.73.239.81</span><br><span class="line"> </span><br><span class="line"> // dig A httpbin.org</span><br><span class="line"> ;; ANSWER SECTION:</span><br><span class="line"> httpbin.org. 60 IN A 3.222.34.231</span><br><span class="line"> httpbin.org. 60 IN A 34.206.181.91</span><br><span class="line"> httpbin.org. 60 IN A 54.243.34.18</span><br><span class="line"> httpbin.org. 60 IN A 184.73.239.81</span><br><span class="line"> httpbin.org. 60 IN A 35.172.59.156</span><br><span class="line"> httpbin.org. 60 IN A 54.237.204.19</span><br></pre></td></tr></tbody></table></figure><p>可以发现,如果需要找到 <code>test.yigegongjiang.com</code> 的 A 记录,一定需要先找到 CNAME 记录,再通过 CNAME 指向的域名继续寻找目标 ip。</p><p>所以这里提出 <strong>CNAME</strong> 的第一个作用,就是【设定 IP】。使用 github pages 搭建博客的时候:</p><ol><li>购买了域名 m ,希望将 m 域名映射到 n.github.io Blog 域名上。</li><li>后续直接访问 m,ip 被重定向到 n.github.io,从而完成 Blog 的访问。</li></ol><p>其次,<strong>CNAME</strong> 还有一个作用是【负载均衡】。如使用 xx 云平台部署服务:</p><ol><li>在 xx 云平台上的多个边缘节点部署了服务,并统一使用域名 m 向用户提供服务。</li><li>无需自行搭建【权威域名服务器】,通过 xx 云平台提供的 n.xx.com 做域名 m 的 cname 指向。</li><li>用户请求域名 m 的时候,DNS 解析会进入 n.xx.com,xx 云平台负责根据用户的地理位置通过 n.xx.com 提供动态的 ip,实现【边缘访问】和【负载均衡】。</li></ol><p>Anyway,不管 CNAME 通过中继域名实现【设定 IP】,还是做【负载均衡】,CNAME 本质上都是为初始域名提供 ip。<br>不像 A / AAAA 记录,强制设定了一个固定 ip。CNAME 提供了一个【缓冲层】,可以做更多的事情。</p><p>这里也会出现一些问题,通过缓冲层获取到的 ip 所在的服务器,可能无法很好的处理我们的 m 域名请求。</p><h1 id="CNAME-目标服务器注意事项"><a href="#CNAME-目标服务器注意事项" class="headerlink" title="CNAME 目标服务器注意事项"></a>CNAME 目标服务器注意事项</h1><p>假设我们回到了没有 tls / ssl 安全验证的 http 场景,将 <code>test.yigegongjiang.com</code> 的 cname 指向 <code>www.google.com</code> 或者 <code>www.facebook.com</code>,可以访问 Google 和 facebook 吗?<br>理论上可以,实际并不行。如果把 cname 指向 <code>httpbin.org</code>,却又可以正常访问。<br>进行 http 请求的时候,主机域名 <code>test.yigegongjiang.com</code> 会通过 【host】字段携带到目标服务器,此时目标服务器完全可以主动拒绝服务,因为不是它们自己的域名。<br>而<code>httpbin.org</code>能正常访问,是它没有限制请求中的 host。</p><p>回到 https 场景,有了 <a href="https://www.yigegongjiang.com/2023/signature/#%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF%EF%BC%9ASSL">tls / ssl 安全证书</a>,又变得不一样了。<br>同样进行上面的设定,不管 cname 指向哪个域名,浏览器都会弹窗告诉我们不安全。因为 <code>www.google.com</code> 返回的 tls 证书,验证的域名是 <code>*.google.com</code>,而用户访问的域名是 <code>test.yigeogngjiang.com</code>,不匹配!证书验证不通过!<br>甚至于有些目标域名,连安全弹窗都不会弹起。</p><blockquote><p>服务器如何返回证书,是通过 tls 认证中的【SNI】标记识别具体服务的(一个服务器可以开 n 个服务,有 n 个证书)。<br>如何服务器都不认识 <code>test.yigegongjiang.com</code>这个 SNI 标记,那么它可能连 tls 证书都不会返回。</p></blockquote><p>上面提到的 google、facebook、httpbin 服务,虽然不认识 <code>test.yigegongjiang.com</code> 这个域名,但还是返回了他们各自默认的 tls 证书。此时,如果用户选择强制信任证书:</p><ol><li>google、facebook 回到了 http 时候一样,因为 host 不匹配,主动拒绝服务了。</li><li>httpbin 也回到了 http 时候一样,可以正常访问。因为 httpbin 默认没有限制 host。</li></ol><p>对于一台 Nginx 配置的目标服务器(假设默认域名是 a.com),如果需要完美兼容 <code>test.yigegongjiang.com</code> 这个域名的 cname 映射,需要做两件事:</p><ol><li>识别到 SNI 是 <code>test.yigegongjiang.com</code> 后,需要返回 <code>yigegongjinag.com</code> 的 tls 证书,供浏览器进行安全链验证。<br> a. 或者返回一个 tls 证书,该证书同时包含 <code>a.com</code> 和 <code>yigegongjiang.com</code> 的认证。</li><li>将 <code>a.com</code> 和 <code>test.yigegongjiang.com</code> 这两个 host 同时指向同一个内部的 port 服务。<br> a. 或者不识别 host,所有 host 都指向同一个内部的 port 服务。</li></ol><p>上面的两个方案,如果是特殊的服务场景倒还能实现,如 github page 提供了 static blog 服务,可以根据我们配置的域名来进行设置,从而完成访问。<br>对于 <code>fly.io</code>、<code>koyeb.com</code> 这些云平台,自定义域名就是它们的收费项,那肯定不会放开这个口子。</p><h2 id="通过-代理-实现-koyeb-com-云平台自定义域名?"><a href="#通过-代理-实现-koyeb-com-云平台自定义域名?" class="headerlink" title="通过 代理 实现 koyeb.com 云平台自定义域名?"></a>通过 代理 实现 koyeb.com 云平台自定义域名?</h2><p>如果有一个免费的中间代理,我们访问 <code>test.yigegongjiang.com</code> 的所有请求,这个中间代理帮我们将所有流量正向代理到 koyeb.com 平台上,不就可以既不买 koyeb 的服务,又能实现自定义域名了吗?<br>cloudflare 提供了这个能力,也是它的安全、缓存等一系列功能的起点。<br>cloudflare 配置 dns cname 的时候,有一个【代理】选项。选中后,当前配置虽然看起来是 cname,但完全脱离了 cname 本意。</p><ol><li>此时不是 cname 了,通过 dig 工具会发现此时返回的是 cloudflare 的 ip</li><li>访问域名的流量,会被 cloudflare 转发到 cname 指向的域名上,这里的 cname 充当【代理】的角色。</li></ol><p>实际上,依旧访问不通。显然 koyeb 这些云平台不会留下这么大的空子给别人钻。<br>因为 cloudflare、aws 这些正规的代理服务,在请求目标服务的时候,都会提供完善的信息已告知目标服务:我是代理。<br>它不仅把原始的域名等信息提供给了 koyeb,还会把自己的信息都提供给 koyeb。所以,koyeb 直接拒绝服务就行了。</p><p>当然,我们可以自己搭建一个【猥琐】的【三级代理】,将 cloudflare 那边的代理流量打过来,然后再去请求 koyeb,此时【猥琐代理】完全装作正常用户的浏览器,那 koyeb 的确是察觉不出来的。<br>此时,cloudflare 又能够捕获到完整的数据,实现缓存、边缘节点、cdn 等等能力。</p><hr><pre><code></code></pre>

SSE 指南

作者 海驴
2024年11月1日 12:16
<p>Server-Sent Events(SSE)是一种允许服务器通过 HTTP 连接主动向客户端发送数据的技术。它主要被用于创建实时应用,如消息推送和实时通知。SSE 使用简单的文本格式发送消息,这种格式使得其易于在浏览器中实现和使用。</p><p>SSE 注意事项:</p><ol><li>通过 HTTP 协议通道 建立单向长连接,即 Client 连接 Server 后,Server 不断开连接,并持续的通过 socket 套接字发送 data 给 Client。</li><li>网关等设备会主动关闭 tcp 通道,需要 SSE Server 端增加心跳。很多种场景都会导致 tcp 连接中断,和 IM 心跳一致。这里需要 SSE Server 增加应用层心跳,非 TCP 层心跳。</li></ol><span id="more"></span><h1 id="数据格式"><a href="#数据格式" class="headerlink" title="数据格式"></a>数据格式</h1><p>通过具有明显分割线的消息体,来分割数据字段:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// 以下文本消息体,最终通过编译成二进制的形式被传递和解析,通过 \n 等标记符号进行【行】分割。</span><br><span class="line"></span><br><span class="line">event: message/userupdate/custom...\n</span><br><span class="line">data: {xxx}\n</span><br><span class="line">id: 98769879675\n</span><br><span class="line">retry: 10000\n</span><br><span class="line">\n</span><br><span class="line"></span><br><span class="line">// 一个完整的消息体如下,通过 \n 分割行,末尾通过 \n\n 分割单个消息体</span><br><span class="line">id: event-id-1\ndata:event-data-first\n\n</span><br></pre></td></tr></tbody></table></figure><p>以上 event/data/id/retry 四个字段中,data 是必须字段,其他三个是可选字段。每个消息体,必须以 \n\n 作为末尾标记。</p><p>在 ts 中,可行的生成消息体的 code 如下:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">private encodeMessage(message: ChatMessage) {</span><br><span class="line"> const data = {</span><br><span class="line"> type: message.type,</span><br><span class="line"> payload: message.data,</span><br><span class="line"> };</span><br><span class="line"> const content = [</span><br><span class="line"> `id: ${message.id}`,</span><br><span class="line"> `data: ${JSON.stringify(data)}`,</span><br><span class="line"> '',</span><br><span class="line"> '',</span><br><span class="line"> ].join('\n'); // 这里,通过空行分割,在消息体的末尾增加 `\n\n` 标记</span><br><span class="line"> return this.encoder.encode(content);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>对消息体的解析,也同样遵循固定的规律,即对二进制中的 \n 和 \n\n 进行解析。</p><p>解析流程:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">1. 通过 \n\n 对消息体进行分割,获得一个消息体的二进制内容并开始解析</span><br><span class="line">2. 通过 \n 对行进行解析,获得 xxx:xxx 这样的一行内容</span><br><span class="line">3. 通过 : 符号,对行进行解析,获得 key:xxx 和 value:xxx 两个内容</span><br><span class="line">4. 整合获得的所有内容,聚合成 {id:xxx,event:xxx,data:xxx,retry:xxx} 这样的消息体</span><br></pre></td></tr></tbody></table></figure><h1 id="消息类型"><a href="#消息类型" class="headerlink" title="消息类型"></a>消息类型</h1><p>SSE 通过 event 字段,可以自定义各种消息体。约定通用的消息体是 message。有如下两种消息体:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">id: xxx\ndata:xxx\nevent:message\n\n</span><br><span class="line">id:xxx\ndata:xxx\nevent:{custom-name}\n\n</span><br></pre></td></tr></tbody></table></figure><p>如果 event 为空,默认当作且应该当作 message 消息来解析和处理。</p><h1 id="重连"><a href="#重连" class="headerlink" title="重连"></a>重连</h1><p>http 通道可能发生中断,每条消息题都可以携带一个 retry 字段,用来告知 client 在断开后多久应该重连。</p><p>而重连的逻辑就是重新发起 http 连接。这里为了保持和之前的通道一致,应该在重连的时候在 header 中携带最后一次收到的消息的 id,可以让 server 侧知道从哪里中断的,以保持连续的服务。</p><h1 id="心跳"><a href="#心跳" class="headerlink" title="心跳"></a>心跳</h1><p>SSE 应该使用应用层心跳,即发送一条空消息体,以保持 http tcp 套接字不被网关、nat 等场景强制关闭。格式如下:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">:\n\n</span><br><span class="line">:heartbeat\n\n</span><br></pre></td></tr></tbody></table></figure><p>这里,心跳可以不遵循消息体的约定。</p><p>之前提到消息体一定需要有 data 字段,因为每一条消息都是需要传递信息,如果没有 data 字段,那么这条消息就无法被解析,也就没有传递的必要。</p><p>但是这套约定,并不是说没有 data 字段,消息的发送、接收、解析 整套流程就会失败,因为对于 TCP/UDP/HTTP 这套协议来说,它并不关心消息体的内容是什么。</p><p>而 Client 端,对消息体的解析应该是包容的,即消息体如果不符合约定,那么就应该抛弃。</p><p>这里的应用层心跳,就会走到这套逻辑里面。IM 是双向通信,为了保障心跳的到达,Client 端需要解析完整的心跳并回执。在 SSE 单向通道里面,只需要保障有一条消息从 Server 发往 Client 即可(没有成功率保障,因为是单向通信)。</p><hr>

设备发现

作者 海驴
2024年10月23日 14:08
<h1 id="Bonjour"><a href="#Bonjour" class="headerlink" title="Bonjour"></a>Bonjour</h1><p>local net 服务发现。核心是两个 api:NSNetService &amp; NSNetServiceBrowser。</p><p>Bonjour 的目的,是希望发现局域网中的设备,包括这些设备的 ip、port 等信息,从而进行下一步业务操作。因为有了 ip、port,就可以精确定位一台设备了,就可以做很多事情了,比如打通 socket 等。</p><p>NSNetService 用来发布服务,表明 m 提供了哪些能力,如打印机、http 服务等等。</p><span id="more"></span><figure class="highlight jsx"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通过指定服务名称、类型和端口号来创建 NSNetService 实例</span></span><br><span class="line">netService = <span class="title class_">NSNetService</span>(<span class="attr">domain</span>: <span class="string">"local."</span>, <span class="attr">type</span>: <span class="string">"_http._tcp."</span>, <span class="attr">name</span>: <span class="string">"MyService"</span>, <span class="attr">port</span>: <span class="number">8080</span>)</span><br></pre></td></tr></tbody></table></figure><p>NSNetServiceBrowser 用来检索服务,查找当前 local net 中存在哪些与查询内容相匹配的服务,进而可以获取到 ip 和 port。</p><figure class="highlight jsx"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">netServiceBrowser = <span class="title class_">NSNetServiceBrowser</span>()</span><br><span class="line"><span class="comment">// 查找局域网中所有 _http._tcp. 类型的服务</span></span><br><span class="line">netServiceBrowser?.<span class="title function_">searchForServices</span>(<span class="attr">ofType</span>: <span class="string">"_http._tcp."</span>, <span class="attr">inDomain</span>: <span class="string">"local."</span>)</span><br></pre></td></tr></tbody></table></figure><h3 id="参数约定-from-ai"><a href="#参数约定-from-ai" class="headerlink" title="参数约定 - from ai"></a>参数约定 - from ai</h3><figure class="highlight jsx"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">`NSNetService`</span> 的参数 <span class="string">`domain`</span> 和 <span class="string">`type`</span> 并不是可以随意设置的,它们有明确的约定和用途,尤其是为了确保服务发现的兼容性和正确性。</span><br><span class="line"></span><br><span class="line">### <span class="number">1.</span> <span class="string">`domain`</span> 参数:</span><br><span class="line">- **约定**:</span><br><span class="line"> - 通常,局域网内使用的域名是 <span class="string">`"local."`</span>,这是 <span class="title class_">Bonjour</span> 标准中定义的默认域,表示当前局域网范围内的服务。</span><br><span class="line"> - <span class="title class_">Bonjour</span> 中一般使用的是 <span class="string">`"local."`</span>,它会自动将服务广播到本地网络。</span><br><span class="line"> - 如果你要在其他特定域中发布服务,也可以指定其他域名,但在局域网服务发现中几乎总是使用 <span class="string">`"local."`</span>。</span><br><span class="line"></span><br><span class="line">- **是否可以随意更改?**:</span><br><span class="line"> - 对于局域网内的服务发现,推荐使用 <span class="string">`"local."`</span>,它是 <span class="title class_">Bonjour</span> 默认的局域网广播域。</span><br><span class="line"> - 如果你有自己的 <span class="variable constant_">DNS</span> 域名或网络环境,你可以指定自定义域名,但这通常涉及更复杂的网络设置,且在局域网内使用可能不兼容。</span><br><span class="line"></span><br><span class="line">### <span class="number">2.</span> <span class="string">`type`</span> 参数:</span><br><span class="line">- **约定**:</span><br><span class="line"> - <span class="string">`type`</span> 定义了服务的协议和传输层信息。它遵循 <span class="variable constant_">IANA</span>(<span class="title class_">Internet</span> <span class="title class_">Assigned</span> <span class="title class_">Numbers</span> <span class="title class_">Authority</span>)发布的服务类型命名标准,并且通常采用如下格式:</span><br><span class="line"> - <span class="string">`_&lt;protocol&gt;._&lt;transport&gt;`</span></span><br><span class="line"> - 例如:</span><br><span class="line"> - <span class="string">`"_http._tcp."`</span>:表示 <span class="variable constant_">HTTP</span> 服务使用 <span class="variable constant_">TCP</span> 传输协议。</span><br><span class="line"> - <span class="string">`"_ftp._tcp."`</span>:表示 <span class="variable constant_">FTP</span> 服务使用 <span class="variable constant_">TCP</span> 传输协议。</span><br><span class="line"> - <span class="string">`"_ipp._tcp."`</span>:表示 <span class="title function_">IPP</span> (<span class="title class_">Internet</span> <span class="title class_">Printing</span> <span class="title class_">Protocol</span>) 使用 <span class="variable constant_">TCP</span> 传输协议。</span><br><span class="line"></span><br><span class="line"> - **标准服务类型**:有许多常见的服务类型,诸如:</span><br><span class="line"> - <span class="string">`"_http._tcp."`</span>:<span class="variable constant_">HTTP</span> 服务</span><br><span class="line"> - <span class="string">`"_ftp._tcp."`</span>:<span class="variable constant_">FTP</span> 服务</span><br><span class="line"> - <span class="string">`"_airplay._tcp."`</span>:<span class="title class_">AirPlay</span> 服务</span><br><span class="line"> - <span class="string">`"_ipp._tcp."`</span>:网络打印协议</span><br><span class="line"> - <span class="string">`"_ssh._tcp."`</span>:<span class="variable constant_">SSH</span> 服务</span><br><span class="line"></span><br><span class="line">- **是否可以随意更改?**:</span><br><span class="line"> - 如果你想发布一个标准协议的服务,必须遵循该协议的命名约定(例如,<span class="string">`"_http._tcp."`</span> 代表 <span class="variable constant_">HTTP</span> 服务)。</span><br><span class="line"> - 如果是自定义服务(非标准服务),你可以创建自定义的类型名称。例如,你可以创建 <span class="string">`"_mycustomservice._tcp."`</span> 这样自定义的服务类型。但是,自定义类型必须以下划线 <span class="string">`_`</span> 开头,以确保与标准服务类型区分。</span><br><span class="line"></span><br><span class="line">### 总结:</span><br><span class="line">- **<span class="string">`domain`</span>**:一般使用 <span class="string">`"local."`</span>,这是 <span class="title class_">Bonjour</span> 的局域网发现默认域。如果你在局域网中进行服务发现,最好不要随意修改这个参数。</span><br><span class="line">- **<span class="string">`type`</span>**:对于标准协议服务,如 <span class="variable constant_">HTTP</span>、<span class="variable constant_">FTP</span> 等,请使用标准的 <span class="variable constant_">IANA</span> 定义的服务类型。如果是自定义服务类型,可以按格式自定义命名,但仍需遵循命名规范(下划线开头)。</span><br><span class="line"></span><br><span class="line">确保遵循这些约定可以提高服务的兼容性,并确保局域网中的其他设备能够正确发现和解析服务。</span><br></pre></td></tr></tbody></table></figure><h1 id="MultipeerConnectivity"><a href="#MultipeerConnectivity" class="headerlink" title="MultipeerConnectivity"></a>MultipeerConnectivity</h1><p>是 Apple 提供的近场通信,基于蓝牙和局域网实现。</p><p>原理也是服务的发布和发现,和 Bonjour 的不同点是,除了局域网之外,还可以通过 蓝牙 来做服务发现。</p><p>和 Bonjour 的差异:</p><ol><li>Bonjour 是为了发布、发现目标设备的 ip、port。</li><li>MultipeerConnectivity 是为了发布、发现目标设备。注意,这是的设备是一个 peer、一个实体,通过这个 peer,可以直接进行 data 的发送等,即这是一个对等的实体对象,可以直接进行通信。发送图片、文字、视频等。</li></ol><h1 id="Network-framework"><a href="#Network-framework" class="headerlink" title="Network.framework"></a>Network.framework</h1><p>这是 Apple 推出的兼容高层级 API 和 低层级 API 的库,它有高级代码封装部分可供开发人员直接使用,也有底层代码部分可供开发人员拼装各种能力。</p><p>在使用上,Network.framewok 完全可以代替 Bonjour 了,因为它集成了 Bonjour 的能力。</p><p>但还不能代替 MultipeerConnectivity,因为 MultipeerConnectivity 的蓝牙通信能力,Network.framework 并没有。</p><p>MultipeerConnectivity 对于 p2p 场景是高度封装的,使用方便、使用场景范围大,但不支持细粒度的调整优化。比如发送数据,就是调用 api 进行数据 send。具体如何 send 并不能控制。</p><p>但是 Network.framework 提供了细粒度的 api 操作,可以选择 tcp、udp 等不同方案以及细节参数。</p><p>Network.framework 更多用于具有网络能力的设备间通信。当然,如果只有蓝牙场景,那只能使用 MultipeerConnectivity 了。</p><h1 id="WI-FI-Direct"><a href="#WI-FI-Direct" class="headerlink" title="WI-FI Direct"></a>WI-FI Direct</h1><p>上面三种局域网近景通信,都不具有普适性。蓝牙是很有普适性的产物,但是蓝牙发展这么多年,有很大的局限性,如速度、距离等。</p><p>目前,WI - FI Direct 已经基本上进入了所有的电子设备中,通过 WI - FI direct,可以非常方便的在两台设备之间打通 p2p 通信通道,进而实现高速数据传输。</p><p>对于苹果全家桶,基本上所有的跨设备互通能力,都是通过 Wi-Fi Direct 能力来实现的,比如 Handoff、通用剪贴板(Universal Clipboard)等。</p><h2 id="Wi-Fi-Direct-技术原理-from-ai"><a href="#Wi-Fi-Direct-技术原理-from-ai" class="headerlink" title="Wi-Fi Direct 技术原理 - from ai"></a>Wi-Fi Direct 技术原理 - from ai</h2><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">1. **基本概念**</span><br><span class="line">- Wi-Fi Direct(也称 Wi-Fi P2P)是一种无线通信标准</span><br><span class="line">- 允许设备间直接通信,无需传统的无线接入点(如路由器)</span><br><span class="line">- 工作在与普通 Wi-Fi 相同的 2.4GHz 或 5GHz 频段</span><br><span class="line">1. **网络架构**</span><br><span class="line">- **组网模式**: 采用对等(P2P)网络架构</span><br><span class="line">- **角色分配**:</span><br><span class="line"> - 组主(Group Owner, GO): 类似微型接入点,负责管理网络</span><br><span class="line"> - 客户端: 连接到组主形成网络</span><br><span class="line">- **组主职责**:</span><br><span class="line"> - 信道选择与管理</span><br><span class="line"> - 网络参数配置(SSID、安全设置等)</span><br><span class="line"> - 资源分配与网络维护</span><br><span class="line">1. **连接建立流程**</span><br><span class="line">- 设备发现阶段:</span><br><span class="line"> 1. 设备相互扫描并发现对方</span><br><span class="line"> 2. 通过设备发现协议交换基本信息</span><br><span class="line">- 组建立阶段:</span><br><span class="line"> 1. 协商确定组主身份</span><br><span class="line"> 2. 组主配置并广播网络</span><br><span class="line">- 连接认证阶段:</span><br><span class="line"> 1. 客户端扫描并识别组主网络</span><br><span class="line"> 2. 通过 WPS 进行安全认证</span><br><span class="line"> 3. 建立加密连接</span><br><span class="line">1. **安全机制**</span><br><span class="line">- 采用 Wi-Fi Protected Setup(WPS)快速配置</span><br><span class="line">- 支持多种连接方式:</span><br><span class="line"> - 按钮配对</span><br><span class="line"> - PIN 码认证</span><br><span class="line"> - NFC 近场通信(如支持)</span><br><span class="line">- 使用 WPA2 等标准 Wi-Fi 安全协议加密通信</span><br><span class="line">1. **服务层功能**</span><br><span class="line">- 服务发现协议:</span><br><span class="line"> - 设备可广播自身提供的服务</span><br><span class="line"> - 其他设备可在连接前发现可用服务</span><br><span class="line">- 支持的服务类型:</span><br><span class="line"> - 文件传输</span><br><span class="line"> - 打印服务</span><br><span class="line"> - 多媒体流传输</span><br><span class="line"> - 屏幕镜像等</span><br><span class="line">1. **数据传输特点**</span><br><span class="line">- 直接设备间传输,无需经过中间节点</span><br><span class="line">- 支持高速数据传输</span><br><span class="line">- 较低的通信延迟</span><br><span class="line">- 不依赖互联网连接</span><br></pre></td></tr></tbody></table></figure><h1 id="AWDL"><a href="#AWDL" class="headerlink" title="AWDL"></a>AWDL</h1><p>AWDL(Apple Wireless Direct Link)与 Wi-Fi Direct 和蓝牙都是无线通信技术,用于设备之间的直接连接。主要用于 Apple 设备间的通信,支持如 AirDrop、AirPlay 等服务。AWDL 能在 Wi-Fi 频带上动态频道跳跃,优化连接质量和减少干扰,主要优化了点对点的数据传输速度和效率。</p><p>Apple 的跨设备数据同步功能,基本上都是使用的 AWDL。</p><p>相比蓝牙来说,Wifi-Direct 和 AWDL 的缺点就是建立连接慢(服务发现慢)。所以蓝牙更多的时候用来当作 AWDL 的前置条件,即通过 蓝牙 来发现设备建立连接,然后通过 AWDL 的进行数据传输。</p><p>Apple 提供了非常多的跨端通信功能如 Handoff、AirDrop、Universal Clipboard、Continuity Camera 等。苹果建议打开蓝牙,是为了更快的做服务发现和建立通道。但是蓝牙不打开,很多功能也都是可以正常使用的。因为还有很多备选的发现方案,如上面的 Bonjour 可以在局域网 wifi 下工作。甚至没有 wifi 的时候,也能使用 AWDL 自己的服务发现(速度慢)。</p><p>但是蓝牙,依旧是服务发现的第一优先级。</p><h2 id="apple-服务列表-from-ai"><a href="#apple-服务列表-from-ai" class="headerlink" title="apple 服务列表 - from ai"></a>apple 服务列表 - from ai</h2><figure class="highlight jsx"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">### <span class="number">1.</span> 基于<span class="title class_">Wi</span>-<span class="title class_">Fi</span>的直接通信技术:</span><br><span class="line"></span><br><span class="line">- **<span class="title class_">AirDrop</span>**:</span><br><span class="line"> - 使用<span class="title class_">Wi</span>-<span class="title class_">Fi</span>和蓝牙结合的方式,进行设备发现和建立安全的点对点<span class="title class_">Wi</span>-<span class="title class_">Fi</span>网络以传输文件。</span><br><span class="line"> </span><br><span class="line">- **<span class="title class_">AirPlay</span>**:</span><br><span class="line"> - 通过<span class="title class_">Wi</span>-<span class="title class_">Fi</span>网络将音频、视频和图片流式传输到支持<span class="title class_">AirPlay</span>的接收设备上。</span><br><span class="line"></span><br><span class="line">### <span class="number">2.</span> 基于云服务的同步技术:</span><br><span class="line"></span><br><span class="line">- **iCloud <span class="title class_">Drive</span>**:</span><br><span class="line"> - 利用云存储来同步和共享文件和文档。</span><br><span class="line"> </span><br><span class="line">- **<span class="title class_">Messages</span> <span class="keyword">in</span> iCloud**:</span><br><span class="line"> - 通过iCloud同步所有设备上的iMessage,保证消息的一致性。</span><br><span class="line"> </span><br><span class="line">- **iCloud <span class="title class_">Photos</span>**:</span><br><span class="line"> - 照片和视频通过iCloud自动同步到所有设备。</span><br><span class="line"></span><br><span class="line">### <span class="number">3.</span> 基于<span class="title class_">Continuity</span>的互操作功能:</span><br><span class="line"></span><br><span class="line">- **<span class="title class_">Handoff</span>**:</span><br><span class="line"> - 利用蓝牙和<span class="title class_">Wi</span>-<span class="title class_">Fi</span>来实现在设备之间无缝切换正在进行的活动(如邮件撰写、网页浏览)。</span><br><span class="line"></span><br><span class="line">- **<span class="title class_">Universal</span> <span class="title class_">Clipboard</span>**:</span><br><span class="line"> - 使用蓝牙和<span class="title class_">Wi</span>-<span class="title class_">Fi</span>通过<span class="title class_">Continuity</span>功能实现剪贴板内容在设备间的共享。</span><br><span class="line"></span><br><span class="line">- **<span class="title class_">Continuity</span> <span class="title class_">Camera</span>**、**<span class="title class_">Continuity</span> <span class="title class_">Markup</span>** 和 **<span class="title class_">Continuity</span> <span class="title class_">Sketch</span>**:</span><br><span class="line"> - 这些服务通过<span class="title class_">Wi</span>-<span class="title class_">Fi</span>和蓝牙连接,允许用户使用一个设备上的功能来直接影响另一个设备上的内容。</span><br><span class="line"></span><br><span class="line">### <span class="number">4.</span> 扩展显示和图形共享技术:</span><br><span class="line"></span><br><span class="line">- **<span class="title class_">Sidecar</span>**:</span><br><span class="line"> - 将iPad作为外部显示器使用,通过<span class="title class_">Wi</span>-<span class="title class_">Fi</span>或有线连接实现与<span class="title class_">Mac</span>的连接。</span><br></pre></td></tr></tbody></table></figure><h2 id="Handoff"><a href="#Handoff" class="headerlink" title="Handoff"></a>Handoff</h2><p>Apple 全家桶之间,可以通过 Handoff 进行操作转移。</p><ol><li>通过蓝牙做设备发现,蓝牙不可用的时候切换到其他设备发现渠道</li><li>通过 AWDL 做数据传输</li><li>app 通过 Activity api 实现 handoff 功能</li></ol><h2 id="Universal-Clipboard"><a href="#Universal-Clipboard" class="headerlink" title="Universal Clipboard"></a>Universal Clipboard</h2><p>和 Handoff 基本一致,通过 <strong>UIPasteboard</strong> api 实现跨设备 copy-paste 功能</p><h1 id="NFC"><a href="#NFC" class="headerlink" title="NFC"></a>NFC</h1><p>NFC(近场通信),和 以上 通信方式都不同。</p><p>NFC 的技术原理基于无线电频率识别(RFID)技术,使用磁场感应来实现在设备间的通信。NFC 设备在 13.56 MHz 频率上操作,通常用于非接触式数据传输,距离范围非常短,通常在几厘米内,传输速率慢,在 400kbps (50kb / s) 左右。<br>技术原理就是:识卡器发出信号(电磁感应),激活了终端(手机自动点亮),然后进行数据交互,并可能需要机主进行身份验证,最后完成信息的交互(支付、上公交车、开门等)。</p><p>NFC 有三种工作模式:点对点通信模式、读卡器模式、卡模拟模式。又分为【主动模式】和【被动模式】,其中一个设备提供射频场,另一个设备利用这个射频场进行通信。<br>使用 NFC,需要两个终端,一个做控制器,用于发射磁场来识别信息。一个做无电源的数据芯片,通过接收到的磁场来感应并传输信息。<br>对于 NFC 设备来说:</p><ol><li>点对点通信:两个 NFC 设备相互交换信息。</li><li>读卡器模式:一个 NFC 作为控制器,读取其他 NFC 芯片中的信息。</li><li>卡模拟模式:一个 NFC 作为数据芯片,其他读卡器可以读取其中的信息。</li></ol><p>现代电子产品中,Android 和 iPhone 都支持 NFC 技术,手机作为 NFC 设备,使用读卡器模式和卡模拟模式,已经可以完成很多事情。</p><ol><li>当作为 NFC 控制器的时候,手机可以主动的读取外部 NFC 芯片中的信息,也可以将必要的信息写入到外部 NFC 芯片。(物流中,可以通过手机对商品挂载的 NFC 芯片进行记录)</li><li>当作为 NFC 芯片的时候,手机可以模拟一个 NFC 芯片,通过软件将信息提前写入手机中,其他读卡器就可以直接读取手机中的信息。(可以实现门禁卡等)</li></ol><p>Android 对 NFC 的 API 开放较多,app 可通过 api 来控制 NFC 进行 读取、写入、模拟 的操作,来实现快捷的智能家居、门禁卡等场景。<br>iPhone 上则比较保守,在【卡模拟】、【卡读取】方面,都有不少限制。</p><h2 id="门禁卡"><a href="#门禁卡" class="headerlink" title="门禁卡"></a>门禁卡</h2><p>普通门禁卡:</p><ol><li>门禁卡中有 【微芯片】(存储卡片的识别信息和其他数据)和 【天线】(用于接收和发送无线信号)。</li><li>门禁卡靠近门禁系统的读卡器 → 读卡器会发出一个射频信号 → 信号通过天线供电给门禁卡上的微芯片 → 微芯片被激活, 通过天线将存储在芯片上的识别信息发送回读卡器 → 读卡器接收到信息后,将数据传输给后端的门禁控制系统。</li></ol><p>NFC 门禁卡:<br>原理基本和普通门禁卡一样,不过,NFC 提供了更高的安全性。它支持双向通信,卡和识卡器之间可以通信。它们之间会进行密钥交换,通过对称、非对称加密来完成数据的安全传输。相比普通门禁卡,NFC 门禁卡会更加的安全。</p><blockquote><p>NFC 是一种普适性的技术方案,手机也可以作为 NFC 终端。这里就可以把 NFC 门禁卡的信息保存在 手机中,使得手机可以充当 NFC 门禁卡的功能。</p></blockquote><p>蓝牙:<br>有些 app 会通过 蓝牙的方式,和门锁连接。这在智能门锁中非常常见。因为距离很远,就可以连接上。而 NFC 需要非常短的距离 (4cm) 才能通信。</p><h2 id="移动支付"><a href="#移动支付" class="headerlink" title="移动支付"></a>移动支付</h2><p>通过 NFC 进行移动支付,主要有三种方案:</p><ol><li><p>卡模拟方案。mobile 录入支付卡信息,被外部读取器识别<br> a. 系统级别的支持,如 apple pay。开发人员没有掌控能力。用户只能通过 apple wallet 录入银行卡信息,然后通过 apple pay 进行支付。<br> b. 应用级别的支持。Android 支持的较好,iPhone 限制很多。</p><ul><li>iPhone 在 iOS 18.1 放开了该限制,app 可以将支付卡信息写入 app 中,支付的时候调用 app 完成支付。</li><li>不对普通开发者开放,需要和 apple 签订商务协议,支付费用,一般都是支付中间商如 Stripe。并且只对个别国家开放。</li></ul></li><li><p>读卡器方案。mobile 作为读取器识别外部实体卡(信用卡等)。Android 支持的叫好,iPhone 限制很多。<br> a. Apple Tap to pay。商家可以在自己的手机中,打开 m app,然后用户把信用卡、iWatch 靠近手机,即可完成支付。</p><ul><li>普通开发人员没有太多的控制能力。也需要签订商务协议,一般都是支付中间商如 Stripe。它们提供 SDK 并和 Apple Api 交互完成支付。</li><li>Apple 和 Stripe 中间商会对地区等有限制。只在少有的地区开放了 Tap To Pay 能力。</li></ul><p> b. alipay 碰一碰。非常聪明的通过 NFC 实现支付的方案。</p><ul><li>支付宝给商家提供 NFC 卡模拟芯片。用户侧的支付宝 app 充当读卡器,读取商家的 NFC 芯片信息。app 获取商家信息后,进行网络处理,完成支付。</li><li>以往的支付,读卡器处于商家侧,如 Tap to pay、POS 机等。只要商家具有稳定的网络,就可以完成支付。</li><li>alipay 碰一碰方案,读卡器处于用户侧 app 中。这就需要用户侧具有稳定的网络,以完成支付。</li></ul></li></ol><h2 id="Apple-iPhone-NFC"><a href="#Apple-iPhone-NFC" class="headerlink" title="Apple iPhone NFC"></a>Apple iPhone NFC</h2><p>简单介绍一下 iPhone 对 NFC 支持的历史:</p><ul><li>WWDC 2017:引入 Core NFC,并具备 NDEF 标签 <strong>读取</strong> 功能。</li><li>WWDC 2018:在较新设备上对 NDEF 消息进行后台标签读取。</li><li>WWDC 2019:重大扩展,允许 NDEF <strong>写入</strong>,支持 ISO 7816、ISO 15693 和 MIFARE 标签,并支持自定义命令。</li><li>WWDC 2020:多标签检测,VAS 协议支持和 ISO 15693 标签的后台读取。</li><li>WWDC 2021-2023:专注于稳定性、性能提升和小幅增强,没有重大 API 更改。</li><li>WWDC 2024:支持 <strong>卡模拟</strong>。</li></ul><hr>

Mac 字体

作者 海驴
2024年10月22日 10:51
<blockquote><p>很久之前,写过一篇关于 【<a href="https://www.yigegongjiang.com/2023/unicode/"><strong>计算机字符编码与内存编码 - Unicode</strong></a>】 的快照,根据 码位、码表 对字符进行了介绍。这里特别说明一下 Mac 系统上的字体库。</p></blockquote><p>系统自带软件:<code>Font book</code><br>字体文件夹: <code>/System/Library/Fonts</code> 、<code>/Library/Fonts</code>、<code>~/Library/Fonts</code><br>苹果提供的字体:<code>Applexxx</code> 、<code>Apple xxx</code> 、<code>SFxxx</code> 、<code>PingFangxxx</code></p><h1 id="如何使用字体"><a href="#如何使用字体" class="headerlink" title="如何使用字体"></a>如何使用字体</h1><ol><li>操作系统根据语言的不同,会使用默认字体。比如中文系统,会使用 <code>PingFang</code> 字体。英文系统,会使用 <code>SF</code> 字体。</li><li>app、dmg 等软件,可以直接使用 <code>defaultfont:xxx</code> 的形式直接使用系统字体,或者使用 <code>fontname:xxx</code> 的形式自行选择字体。自行选择的时候,可以使用系统提供的字体,也可以将 xx 字体打入 app 中来使用。</li><li>app 提供修改字体的功能,用户可以自行选择需要的字体。</li></ol><span id="more"></span><h1 id="图标"><a href="#图标" class="headerlink" title="图标"></a>图标</h1><p>Apple 平台提供了两个图标字体,分别是:<code>Apple Color Emoji</code> 和<code>Apple Symbols</code> 。</p><p>字体库能够包含图标、颜色,在之前阐述 Unicode 的时候已经说明过,因为它们都依靠<code>码位</code> 进行检索。所以在不同的平台上,都会有自己的图标库的系统级别实现,显示效果会不一样。</p><h1 id="字体回退"><a href="#字体回退" class="headerlink" title="字体回退"></a>字体回退</h1><p>每个字体可以适配多种语言,但没有一个字体是全能的。即【一定需要<strong>字体回退</strong>】来对当前字体无法渲染的文字进行兜底。<br>在 mac font app 中,打开一个 font,会列出其 support 的文字集合。</p><ol><li>部分 app 支持设置 1 个字体:回退到系统字体。</li><li>部分 app 支持设置 n 个字体,按照优先级进行回退,最后回退到系统字体。如 browser 等</li><li>css:通过设置 <code>font-family</code> 属性来实现字体回退。例如:<code>font-family: "MyFont", "FallbackFont", sans-serif;</code></li></ol><h1 id="Nerd-Font"><a href="#Nerd-Font" class="headerlink" title="Nerd Font"></a>Nerd Font</h1><p>nerd font 是一个项目集合,它将非常多的字体进行扩展,增加了许多 icon,从而让已经非常优秀的字体扩展了更多的功能。</p><p>可以通过 brew 安装 nerd 字体。</p><figure class="highlight jsx"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">&gt; brew search nerd-font</span><br><span class="line"></span><br><span class="line">font-0xproto-nerd-font font-iosevka-nerd-font</span><br><span class="line">font-<span class="number">3270</span>-nerd-font font-iosevka-term-nerd-font</span><br><span class="line">font-agave-nerd-font font-iosevka-term-slab-nerd-font</span><br><span class="line">font-anonymice-nerd-font font-jetbrains-mono-nerd-font ✔</span><br><span class="line">font-arimo-nerd-font font-lekton-nerd-font</span><br><span class="line">font-aurulent-sans-mono-nerd-font font-liberation-nerd-font</span><br><span class="line">font-bigblue-terminal-nerd-font font-lilex-nerd-font</span><br><span class="line">font-bitstream-vera-sans-mono-nerd-font font-m+-nerd-font</span><br><span class="line">font-blex-mono-nerd-font font-martian-mono-nerd-font</span><br><span class="line">font-caskaydia-cove-nerd-font font-meslo-lg-nerd-font</span><br><span class="line">font-caskaydia-mono-nerd-font font-monaspace-nerd-font</span><br><span class="line">font-code-<span class="keyword">new</span>-roman-nerd-font font-monocraft-nerd-font</span><br><span class="line">font-comic-shanns-mono-nerd-font font-monofur-nerd-font</span><br><span class="line">font-commit-mono-nerd-font font-monoid-nerd-font</span><br><span class="line">font-cousine-nerd-font font-mononoki-nerd-font</span><br><span class="line">font-d2coding-nerd-font font-noto-nerd-font</span><br><span class="line">font-daddy-time-mono-nerd-font font-open-dyslexic-nerd-font</span><br><span class="line">font-dejavu-sans-mono-nerd-font font-overpass-nerd-font</span><br><span class="line">font-droid-sans-mono-nerd-font ✔ font-profont-nerd-font</span><br><span class="line">font-envy-code-r-nerd-font font-proggy-clean-tt-nerd-font</span><br><span class="line">font-fantasque-sans-mono-nerd-font font-recursive-mono-nerd-font</span><br><span class="line">font-fira-code-nerd-font ✔ font-roboto-mono-nerd-font</span><br><span class="line">font-fira-mono-nerd-font font-sauce-code-pro-nerd-font</span><br><span class="line">font-geist-mono-nerd-font font-shure-tech-mono-nerd-font</span><br><span class="line">font-go-mono-nerd-font font-space-mono-nerd-font</span><br><span class="line">font-gohufont-nerd-font font-symbols-only-nerd-font</span><br><span class="line">font-hack-nerd-font ✔ font-terminess-ttf-nerd-font</span><br><span class="line">font-hasklug-nerd-font font-tinos-nerd-font</span><br><span class="line">font-heavy-data-nerd-font font-ubuntu-mono-nerd-font</span><br><span class="line">font-hurmit-nerd-font font-ubuntu-nerd-font</span><br><span class="line">font-im-writing-nerd-font font-ubuntu-sans-nerd-font</span><br><span class="line">font-inconsolata-go-nerd-font font-victor-mono-nerd-font</span><br><span class="line">font-inconsolata-lgc-nerd-font font-zed-mono-nerd-font</span><br><span class="line">font-inconsolata-nerd-font netron ✔</span><br><span class="line">font-intone-mono-nerd-font</span><br></pre></td></tr></tbody></table></figure><p>通过 <code>brew install --cask font-hack-nerd-font</code> 安装的字体,会被安装到 <code>~/Library/Fonts</code> 文件夹中。</p><h2 id="等宽字体"><a href="#等宽字体" class="headerlink" title="等宽字体"></a>等宽字体</h2><p>有 <code>Fira Code</code>、<code>hack</code> 等,在英文场景非常舒服,IDE 场景经常使用。</p><h1 id="zsh"><a href="#zsh" class="headerlink" title="zsh"></a>zsh</h1><p>zsh 有 <code>powerlevel10k</code> 主题。该主题在字体方面比较丰富,主要是采用了很多字体 icon。有些字体如果提供不了 所需 的 icon,显示就会异常。</p><p>但是 <code>powerlevel10k</code> 本身仅仅是配置文件,它不提供字体的安装。所以用户需要自行安装所需要的字体。它和 nerd font 配合比较友好,只要是 nerd font 项目中的字体,都可以被 <code>powerlevel10k</code> 很好的使用。</p><p>操作流程:</p><ol><li>通过 brew 安装 nerd 项目中的字体,如 hack。</li><li>ITerm、Warp 中选择 nerd 字体。</li></ol><h1 id="浏览器切换字体"><a href="#浏览器切换字体" class="headerlink" title="浏览器切换字体"></a>浏览器切换字体</h1><p>安装 hack 等字体后,很多天天见面的 IDE 或者 app,就可以切换喜欢的字体了。<br>其中,浏览器可以设置全局的字体切换,这对爱好某一个字体的同学来说,将非常友好。<br>全局 css 内容推荐如下,后面配置的时候会用到:</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">span</span><span class="selector-pseudo">:not</span>(<span class="selector-class">.material-symbols-outlined</span>, <span class="selector-class">.material-icons</span>, <span class="selector-class">.google-material-icons</span>, <span class="selector-class">.fa</span>, <span class="selector-class">.fas</span>, <span class="selector-class">.far</span>, <span class="selector-class">.fal</span>, <span class="selector-class">.fab</span>, <span class="selector-class">.fad</span>, <span class="selector-attr">[class*=<span class="string">"icon"</span>]</span>, <span class="selector-tag">svg</span>, <span class="selector-attr">[class*=<span class="string">"DP"</span>]</span>, <span class="selector-attr">[class*=<span class="string">"hd"</span>]</span>),</span><br><span class="line"><span class="selector-tag">i</span><span class="selector-pseudo">:not</span>(<span class="selector-class">.material-symbols-outlined</span>, <span class="selector-class">.material-icons</span>, <span class="selector-class">.google-material-icons</span>, <span class="selector-class">.fa</span>, <span class="selector-class">.fas</span>, <span class="selector-class">.far</span>, <span class="selector-class">.fal</span>, <span class="selector-class">.fab</span>, <span class="selector-class">.fad</span>, <span class="selector-attr">[class*=<span class="string">"icon"</span>]</span>, <span class="selector-tag">svg</span>, <span class="selector-attr">[class*=<span class="string">"DP"</span>]</span>, <span class="selector-attr">[class*=<span class="string">"hd"</span>]</span>),</span><br><span class="line"><span class="selector-tag">body</span>, <span class="selector-tag">li</span>, <span class="selector-tag">p</span>, <span class="selector-tag">div</span>, <span class="selector-tag">h1</span>, <span class="selector-tag">h2</span>, <span class="selector-tag">h3</span>, <span class="selector-tag">h4</span>, <span class="selector-tag">h5</span>, <span class="selector-tag">h6</span>, <span class="selector-tag">a</span>, <span class="selector-tag">ul</span>, <span class="selector-tag">ol</span>, <span class="selector-tag">dl</span>, <span class="selector-tag">dt</span>, <span class="selector-tag">dd</span>, <span class="selector-tag">button</span>, <span class="selector-tag">input</span>, <span class="selector-tag">textarea</span>, <span class="selector-tag">select</span>, <span class="selector-tag">option</span>, <span class="selector-tag">optgroup</span>, <span class="selector-tag">label</span>, pre, <span class="selector-tag">code</span>, <span class="selector-tag">kbd</span>, <span class="selector-tag">samp</span>, <span class="selector-tag">var</span>, <span class="selector-tag">table</span>, <span class="selector-tag">th</span>, <span class="selector-tag">td</span>, <span class="selector-tag">tr</span>, <span class="selector-tag">thead</span>, <span class="selector-tag">tbody</span>, <span class="selector-tag">tfoot</span>, <span class="selector-tag">caption</span>, <span class="selector-tag">blockquote</span>, <span class="selector-tag">cite</span>, <span class="selector-tag">q</span>, <span class="selector-tag">strong</span>, <span class="selector-tag">em</span>, <span class="selector-tag">b</span>, small, sub, <span class="selector-tag">sup</span>, <span class="selector-tag">mark</span>, <span class="selector-tag">del</span>, <span class="selector-tag">ins</span>, <span class="selector-tag">abbr</span>, acronym, <span class="selector-tag">address</span>, <span class="selector-tag">time</span>, <span class="selector-tag">form</span>, <span class="selector-tag">fieldset</span>, <span class="selector-tag">legend</span>, <span class="selector-tag">nav</span>, <span class="selector-tag">header</span>, <span class="selector-tag">footer</span>, <span class="selector-tag">section</span>, <span class="selector-tag">article</span>, <span class="selector-tag">aside</span>, <span class="selector-tag">main</span>, <span class="selector-tag">details</span>,</span><br><span class="line"><span class="selector-tag">summary</span> {</span><br><span class="line"> <span class="attribute">font-family</span>: <span class="string">"FiraCode Nerd Font Mono"</span>, sans-serif <span class="meta">!important</span>;</span><br><span class="line"> -webkit-<span class="attribute">font-smoothing</span>: antialiased <span class="meta">!important</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><blockquote><p>Tips:最好不要使用 <code>body, * {}</code> 以及 <code>!important</code>,这样会影响到一些网站的显示效果。因为有些字体是图标字体,会紊乱变形。</p></blockquote><h2 id="safari"><a href="#safari" class="headerlink" title="safari"></a>safari</h2><p>safari 浏览器提供了全局样式表:【setting - advanced - style sheet】,这里可以在选择本地一个 <strong>xx.css</strong> 文件,来设置全局的样式。<br>文件内容就是上面提供的示例 css。</p><h2 id="chrome-家族"><a href="#chrome-家族" class="headerlink" title="chrome 家族"></a>chrome 家族</h2><p>chrome、arc 等,都可以通过插件 <code>Stylus</code> 来设置全局样式表,这个插件非常棒,可以定制很多网站的样式,有很多网友制作的样式,可以直接使用。<br>这里,我们可以通过该插件设定一个全局样式表,内容就是上面提供的示例 css。</p><hr>

trump - 川普

作者 海驴
2024年7月14日 14:17
<p>川普 于 美东时间 2024 年 7 月 13 日 18 时 11 分 在 宾夕法尼亚州 竞选集会 上 遭遇枪击。<br>真没想到,美国的政治已经到了这一步。上一次总统暗杀还是肯尼迪,那已经是 60 年前的事情了。</p><p>从视频上看,川普 被击中和不被击中,概率上没有大的偏差,即 50%。结局是:贯穿了耳朵,没击中大脑。</p><p>上一次川普参加竞选,是和希拉里竞争。那时候希拉里明显有票数优势,阿桑奇 泄漏了 邮件门,让希拉里很多丑闻曝光,使得川普反败为胜。</p><span id="more"></span><p>这一次,在很多原因如疫情、战争的作用下,世界范围都很不稳定,乱世换人本来有优势。<br>但这次的刺客,一弹定江山。没杀死川普,那就得保送了。</p><p>川普上一次在任期间,对华政策很严厉。尤其增加的进口税,拜登在任期间也没有去除。<br>那一次,川普是作为一个商人的影子上台,权利分散、命不下达。甚至于内斗不断,能干事的时间并不多,落地的也少。<br>一转眼 4 年已经过去了,这四年世界范围内日子都不好过,但看起来国内更不好过。而川普的势力,不能同日而语了。</p><p>后面几年中国咋办哦,一届可就是 4 年啊。现在是拔出了刀互看局势,但真要针锋对麦芒的话,还是那句话:在绝对的实力面前,一切都是虚的。</p><p>同一个场景大概率不会有两次好运。川普这次不弄出来大动静,是不可能的。</p><p><img data-src="https://raw.githubusercontent.com/yigegongjiang/image_space/main/blog_img/202407141438096.jpeg"></p><hr>

币圈 - 虚拟货币

作者 海驴
2024年7月10日 00:24
<p>赵长鹏先生在美国认罪后,虚拟货币的江湖地位就算是稳定了。<br>虚拟货币一直游离在法律的边缘,中国早就封杀了。而今最大的交易平台老板因 “大量非法货币流通” 在美国认罪,让很多人觉得未来迷茫。<br>我倒是觉得不用担心了。这个圈子乱到没有点资产都不敢进去,但后面可能会越发趋于平稳。<br>因为至少在美国看来,“币安” 已经合法了,可以被美联储约束了。而不久的将来,中国一定会开放,中国从不会放过割韭菜的好工具。</p><p>两个月前,在国内的网约车上,司机小哥问我懂不懂虚拟货币。我说我不太懂,但理解起来应该比较快,希望他多讲讲。<br>他说他也不懂,就是前两天拉了一个客人。那位客人很客气,在车里长时间打电话,内容是盗了很多币,在和同伙计划跑路的细节。<br>小哥说,那位客人很随和,但是在电话里,他一直在嘲笑被盗的人一早起来发现巨额货币丢失后的绝望。<br>小哥说他不懂,看我戴眼镜,想问问我懂不懂,问我警察会不会去抓捕。<br>我说也不懂。只听说在币圈玩的,都挺野的。</p><span id="more"></span><p>比特币刚出来的时候,我刚参加工作,那时候听到了挖矿。等再听到巨大消息的时候,比特币已经 2w 美元了,现在已经 6w 了。<br>后面只听说不能玩,风险大,也就避而远之。<br>最近实在是有些穷疯了,就想看看币圈到底是什么,哪来的那么多种虚拟币,都还能兑换美金。<br>能兑换美金又不是法定货币,没有法律保护又能交易和购买实物。<br>还有很很多多那些 “币安、以太坊、区块链、去中心、web3” 等等名词,我想去弄明白到底咋回事。</p><p>下面内容就比较散乱了,主要是资料查询过程中的一些备忘。还是先说几个结论吧,tldr。</p><ol><li>整个技术层面,没感觉到特色。</li><li>一切的基石 “区块链” 是单向链表,最简单的数据结构。里面的各种加密,也都是已经成熟的加密方案。</li><li>“去中心化”,就是非常成熟的 p2p。</li><li>在基石之上搭建起来的 “智能合约”,就是 faas 服务。</li><li>从技术层面看,太弱了。所有参与币圈的,都是在豪赌,赌一个新瓶装旧酒能不能讲更多的故事。</li><li>所有的上层建设如币安、NFT 等,都基于最基础的 “区块链”,即相信它的 “不可篡改”。</li><li>然后加了两大护法,分别是基于 “p2p” 的 “去中心化” 和基于 “faas” 的 “智能合约”。这两个护法,是用来增加信心的。</li><li>一切的一切,都在于 <strong>信任</strong> 并 <strong>遵守</strong> “区块链” 链表中保存的数据。这里的信任,是实打实的,就像白纸黑纸一样。但是<strong>遵守</strong>,一直都是空头支票。</li><li>因为 <strong>遵守</strong> 的空缺,整个币圈都是在豪赌,赌一个一落千丈的时间点,或者产业被法律认可。</li><li>转机是赵长鹏先生和币安,向美国认罪。<strong>遵守</strong> 已经有政府背书了(但 “去中心化” 的安全性就名存实亡了)。已经成型的产业大概率不会没落,这里将变成投资机构。但 DAO、web3 是走不下去的。</li><li>建议 USDT,和美元 1:1,可以让资产不贬值。说千道万,就是信不信得过。基于 10,我觉得以后可以简单的信一点。</li><li>整个产业,都在等待法律背书。可现实世界本就有法律、律师、法院了。这是一个矛盾的产业。</li></ol><h1 id="区块链"><a href="#区块链" class="headerlink" title="区块链"></a>区块链</h1><p>区块链是一种数据结构和算法集合。通过这套结构,可以保障以下特性:</p><ol><li>不可篡改</li><li>高度安全</li><li>透明</li></ol><p>区块链就是链表,通过加签增加了修改的复杂度。区块链就是让人相信:你看到的数据一定是真实的。</p><p>因为以上特性,可以基于区块链,做一些事情。只要产品的形态能够使用区块链的数据结构和算法来表达,就可以使用区块链技术。</p><p>比如,药品跟踪:有问题的药品有可能无法溯源。如果基于区块链系统,就可以做到无法篡改、透明。每一支药,从研发、出厂、销售、使用都可以被记录。</p><p>区块链中,对于每一个区块的产生,有一个方案是使用 PoW(工作量证明)。<br>这个方案需要使用巨大的算力才能够输出一个有效的区块,从而可以保障不可篡改。<br>投入到 PoW 矿业的大量计算资源和电力,如果用于科学研究、数据分析、医疗研究等其他领域,可能会带来更直接的人类福祉增益。<br>but,目前需要把这些巨大的资源用来计算一个区块。这个投入和产出的资源配比,的确很不好理解,也只能玩币的人能够下狠心。</p><h1 id="比特币"><a href="#比特币" class="headerlink" title="比特币"></a>比特币</h1><p>比特币没有其他应用如 NFT 玩的花,比特币是完全遵守 区块链 的技术规则来实现的。</p><p>通过 <span class="exturl" data-url="aHR0cHM6Ly9iaXRpbmZvY2hhcnRzLmNvbS9iaXRjb2luLw==">https://bitinfocharts.com/bitcoin/</span> 可以查看当前比特币的一些信息,如区块链总区块数、大小、生成时间等。</p><h2 id="区块"><a href="#区块" class="headerlink" title="区块"></a>区块</h2><ol><li>每个区块,都有一个 nonce(随机数),这个 nonce 可以保障区块被挖出来的时间在 10 分钟左右。通过定期(两周)可能性的调整 nonce 值来保障。</li><li>通过 nonce 计算出一个符合当前区块定义的值,就是说这个区块被挖掘出来了。这就是挖矿。挖矿就是挖每一个区块。</li><li>区块中记录 1000 - 2000 笔交易,其中第一行记录,就是当前挖矿的收益交易。</li><li>每个区块大约 2-4 M 左右,大约每 10 分钟产生一个区块。</li></ol><h2 id="交易"><a href="#交易" class="headerlink" title="交易"></a>交易</h2><ol><li>交易中包含交易 ID、input、output。input 就是来源账户,可能有多个账户。output 就是流向账户,可能有多个账户。</li><li>input 中包含解锁脚本,用来解锁来源账户在上一个记录中的锁定脚本。output 中包含锁定脚本,用来供下一次交易的时候给 input 解锁脚本使用。<ol><li>原理:把解锁脚本和锁定脚本合起来,通过比特币脚本运行器执行,能执行成功,则表示当前来源账号是有效的。即 A 用户可以消费 A 用户自己的钱,不能被 B 用户消费。</li></ol></li><li>示例:A 持有 5 个比特币并支付给 B 2 个比特币:<ol><li>input:A</li><li>output:A (3) + B (2) ⇒ 实际上:A (2.99) + B (1.99),0.02 是手续费。</li></ol></li><li>对于上面的示例,实际上还有手续费,这些手续费是给矿工的。即矿工挖出来一个区块后,不仅得到了比特币奖励,还拿到了每一个交易的交易费用。</li></ol><h2 id="比特币地址"><a href="#比特币地址" class="headerlink" title="比特币地址"></a>比特币地址</h2><ol><li>私钥生成公钥,公钥生成比特币地址。<ol><li>比特币地址是公开的,即别人拿到该地址,就可以往这个地址打币。别人也可以检索区块链中的区块,查到该地址的资金来源和流向。</li><li>1 个用户可以持有 n 个比特币地址用于交易,这样可以保护隐私。也代表需要 n 个公私钥。</li><li>钱包的作用,就是管理公私钥和比特币地址。</li><li>钱包也可以通过 1 个私钥管理 1 个公钥和 n 个比特币地址。这样更方便一些,比较保存 1 个私钥和保存 n 个私钥的复杂度是不一样的。这个技术叫:HD。</li><li>将所有的比特币地址的 UTXO (剩余币) 总和计算出来,就是当前用户持有的币总量。</li></ol></li></ol><h2 id="交易池"><a href="#交易池" class="headerlink" title="交易池"></a>交易池</h2><p>比特币网络中一个临时存储的区域,用来保存尚未被包括在区块中的有效交易。</p><p>A 向 B 支付币,这笔交易,首先进入交易池。其中记录了详细的且验证有效的交易信息以及费用(交易费,交易发起人可以指定奖励矿工多少费用)。</p><p>交易池在哪里:存储在 “全节点” 中。详见下面的【比特币链存储空间】。</p><h2 id="交易流程"><a href="#交易流程" class="headerlink" title="交易流程"></a>交易流程</h2><p>我 (A) 现在通过钱包向 B 支付了 0.5 比特币:</p><figure class="highlight jsx"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">当你通过钱包向另一个用户(B)支付比特币时,整个过程涉及几个关键步骤,从创建交易到最终确认在区块链上的记录。以下是支付<span class="number">0.5</span>比特币的技术流程:</span><br><span class="line"></span><br><span class="line"><span class="number">1.</span> 创建交易</span><br><span class="line">- 输入信息:钱包首先检查你的比特币地址中有足够的余额可用来发送<span class="number">0.5</span>比特币给B。这通常涉及到选择足够的输入(之前的交易输出),来覆盖发送金额和可能的交易费用。</span><br><span class="line">- 输出信息:在交易中创建两个输出:</span><br><span class="line"> - 第一个输出是支付给B的<span class="number">0.5</span>比特币。</span><br><span class="line"> - 如果选定的输入超过了<span class="number">0.5</span>比特币加上交易费,第二个输出将是找零金额,返回到你自己的一个地址上。</span><br><span class="line"></span><br><span class="line"><span class="number">2.</span> 签名交易</span><br><span class="line">- 你的钱包使用你的私钥对交易进行数字签名。这个签名过程是为了证明你有权使用这些输入,并授权这次交易。</span><br><span class="line"></span><br><span class="line"><span class="number">3.</span> 广播交易</span><br><span class="line">- 签名的交易被发送到比特币网络。钱包软件将交易数据广播给网络中的节点(比如全节点)。这些节点接收到交易后,会进行初步验证,比如检查签名是否有效,输入是否未被其他交易双重支付。</span><br><span class="line"></span><br><span class="line"><span class="number">4.</span> 交易验证与确认</span><br><span class="line">- 一旦交易被网络节点接受,它会被放入交易池中,并等待矿工将其打包入区块。</span><br><span class="line">- 矿工选择交易池中的交易,并尝试创建新的区块。矿工在创建新区块的过程中需要解决一个数学难题,这个过程称为工作量证明(<span class="title class_">Proof</span> <span class="keyword">of</span> <span class="title class_">Work</span>)。</span><br><span class="line">- 当一个矿工成功解决难题并创建了一个新区块,包含你的交易在内的区块被添加到区块链上。这个区块随后被广播给所有的节点。</span><br><span class="line"></span><br><span class="line"><span class="number">5.</span> 确认数增加</span><br><span class="line">- 一旦你的交易被包含在一个区块中,它获得了第一次确认。随着更多区块被添加到这个区块之后,你的交易的确认数会逐渐增加。一般来说,六个确认后,交易被认为是非常安全的。</span><br><span class="line"></span><br><span class="line">这个流程确保了比特币交易的安全性和不可逆性。通过网络分布式的节点和矿工的共同工作,比特币网络维护了其透明度和去中心化的特性。</span><br></pre></td></tr></tbody></table></figure><h2 id="比特币链存储空间"><a href="#比特币链存储空间" class="headerlink" title="比特币链存储空间"></a>比特币链存储空间</h2><p>目前 (2024.07.06),比特币链共计 850,932 个区块,每个区块生成时间 11m 10s,每个区块 800.35 KBytes,整个区块链 491.50 GB,第一个区块生成时间是 2009-01-09。</p><p>对于普通用户,不可能存储近 500G 的链在本地。但比特币本身又是去中心化的。原理是:</p><p>区块链存储有两种方式:</p><ol><li>全节点:存储整个区块链数据,并实时新增。</li><li>轻节点:只保存区块头部的信息。来验证交易数据的正确性。比如手机钱包。<ol><li>钱包是公司、个人、开源组织开发的,钱包虽然在手机、电脑上安装,但还是要访问对应的后台服务器。</li><li>后台服务器一般都是全节点存储区块链数据。</li></ol></li></ol><h2 id="全节点"><a href="#全节点" class="headerlink" title="全节点"></a>全节点</h2><p>全节点,是软件,可以由大机构和个人或者开源组织发布。</p><p>当一个用户安装了全节点之后,就会拉取所有区块链数据并成为同步链中的一部分,包括新增区块、交易池等。</p><p>而且,安装了节点软件之后,基于节点发现算法,当前节点会被其他节点感知到。</p><p>这样,A 向 B 的交易转账,就会被同步到当前节点的交易池中,并在后期可能会被矿工拉取过去,成为区块计算的一部分。区块只要计算成功,就代表当前 A 向 B 的交易完成了。</p><h1 id="以太坊"><a href="#以太坊" class="headerlink" title="以太坊"></a>以太坊</h1><p>认识以太坊之前,需要先理解比特币。因为以太坊有很多和比特币相似的概念。但他们是为了不同的场景。</p><h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><ol><li>区块链:和比特币的区块链概念相同。因为它们都是基于区块链自身的那些特性发展起来的,如 “不可篡改”。</li><li>以太币:是以太坊的核心之一。以太币和比特币在概念层级上等价。</li><li>智能合约:是以太坊的另一个核心(总共两个核心基础)。以太坊的区块链上不仅可以记录以太币,还可以记录智能合约。<ol><li>区块链中的每一条记录都是交易,交易中可以仅有以太币的交易,还包含一个字段用来存储合约。</li><li>非智能合约的交易,该字段为空。</li></ol></li><li>虚拟机 (Ethereum Virtual Machine, EVM):用来执行智能合约。<ol><li>有一套计费标准。即合约的执行是需要消耗以太币的。</li></ol></li><li>全局状态:是区块链的抽象层。</li></ol><h2 id="以太坊区块链"><a href="#以太坊区块链" class="headerlink" title="以太坊区块链"></a>以太坊区块链</h2><p>和比特币基本相同,有些许差异,如区块挖矿的标准不同、同一时间同样两个区块被挖出来后的处理结果不同(比特币:使用其中一个,以太坊:两个都使用,最多 7 层)。</p><p>还有就是,存储的数据不同。比特币主要是存储每一笔交易,其中第一个交易项是当前矿工的比特币奖励。以太坊存储的也差不多,不过里面多了一个字段,叫 “智能合约”,即代码。这个代码可以是通过 class 和 func 的形式定义,可以在后期被以太坊专门设计的 EVM 执行。</p><h3 id="全局状态"><a href="#全局状态" class="headerlink" title="全局状态"></a>全局状态</h3><p>比特币中,需要通过全节点,访问每一个区块才能知道某一个比特币地址的消费记录和余额。在以太坊中,查询数据不再需要遍历所有的区块了,因为有一个抽象层,记录了所有以太坊账号的最终结果,就是全局状态。</p><p>全局状态可以理解为是一个非常高效的多叉树,它记录了全量的最终数据,通过它,就可以快速获取某个账号的余额和智能合约。</p><p>在每个区块被挖出来后,就会更新这个全局状态。</p><p>所以,全局状态和区块链一样,是独立存储的。而且,它和区块链一起,被 全节点 持有。它们都是去中心化的。</p><p>实际上,全节点还有一个数据,和比特币一样,就是 “交易池”。所有待录入区块的交易,都会先存储与 “交易池” 中,等待被矿工验证和挖矿成功。</p><h2 id="智能合约"><a href="#智能合约" class="headerlink" title="智能合约"></a>智能合约</h2><p>智能合约,就是代码。一段通过 class 和 func 组成的图灵完备的 脚本,需要在 EVM 上部署。</p><p>它有一个研发生态,可以通过众多后台语言和前端语言进行调用和执行(执行需付费 - 以太币)。</p><p>这些代码存储在区块链中,保障不可更改等特性。</p><p>举个例子,背景是这样:A 将一个项目给 B 做,约定 B 完成 30%、70%、100% 的时候分别收到 3w、5w、2w 的以太币收款。</p><ol><li>A 或者 B 或者 第三方,写智能合约 (写代码),这个合约里面包含一系列的约定函数,如 stageA (…)、stageB (…)、stageC (…),这些函数中需要包含:验收、打款等细节操作。</li><li>创建一条交易,交易中包含 A、B、合约。</li><li>部署合约(需要部署在以太坊平台上)。</li><li>开发前端页面,比如有三个按钮 stage_a、stage_b、stage_c。每个按钮都执行对应的验收逻辑,并最终调用 合约 接口【stageA (xxx)】。</li><li>当 B 完成 30% 后,需要 A 验收并且通过,那么 A 就点击 stage_a 按钮,这个使用 stageA 合约 api 被执行,执行结果是将 A 账户的 3w 以太坊币打到 B 账户。</li></ol><p>说明:</p><p>可以认为这样的合约没有用,比如 B 完成 30% 了,A 也验收通过了,但就是不去点 stage_a 按钮。这样的话,当前合约形同虚设。</p><p>但我们不能说这样的合约不存在,因为在区块链上,这是真实记录的,即 A 和 B 之间一定有这个合约。</p><p>但现在 A 就是不付款,这没有办法。即 智能合约 能够提供约定的准确性,但没有办法保障约定一定被执行。这是君子法则。</p><p>那智能合约和普通的合同相比,就显得操作流程更加复杂但价值也没有提升?</p><p>不。</p><p>普通的合同有可能丢失或者认为破坏了,但智能合约可以保障:它就在这里,当时什么样,现在也一定什么样。</p><p>还是得依靠【君子法则】。</p><h2 id="代币"><a href="#代币" class="headerlink" title="代币"></a>代币</h2><p>代币,是完全基于 “智能合约” 建立起来的。通过 faas 平台,来做各种类型的计算,从而实现业务逻辑,如 “币名称、发行币、交易”,就是调用 faas api。</p><h3 id="ERC-20(通用代币标准)"><a href="#ERC-20(通用代币标准)" class="headerlink" title="ERC-20(通用代币标准)"></a>ERC-20(通用代币标准)</h3><p><strong>USDT(泰达币)</strong> 是一个使用 ERC-20 标准的代币示例。它是一种稳定币,通常与美元的价值挂钩,这意味着每一个 USDT 通常值 1 美元。USDT 在加密货币交易中非常流行,因为它提供了一种相对稳定的资产存储方式,使交易者可以在不直接兑换成法币的情况下减少价格波动的风险。</p><h3 id="ERC-721(非同质化代币标准)"><a href="#ERC-721(非同质化代币标准)" class="headerlink" title="ERC-721(非同质化代币标准)"></a>ERC-721(非同质化代币标准)</h3><p>NFT,常用于数字收藏品和艺术品。</p><h2 id="DAO-组织自治"><a href="#DAO-组织自治" class="headerlink" title="DAO-组织自治"></a>DAO - 组织自治</h2><p>也是基于智能合约。想不通,怎么就能往这方面硬挤(DAO 是不可能的)。</p><p>假设公司决定开发一个新的产品。你可以在 DAO 中启动一个提案,所有员工都可以投票决定是否启动这个新项目。如果多数人同意,智能合约将自动从公司的资金中划拨预算给这个项目。同样地,项目的进展更新和最终的成果也需要报告给 DAO,以便员工可以继续监督和投票决定后续的资金和资源分配。</p><ul><li><strong>提高透明度</strong>:由于所有的决策和交易都记录在区块链上,员工可以清楚地看到公司的每一笔支出和每一个决策。</li><li><strong>增强员工参与感</strong>:员工直接参与到公司决策中,可以提高他们的归属感和积极性。</li><li><strong>减少管理层</strong>:自动执行的智能合约减少了管理层的需要,降低了管理成本。</li></ul><h1 id="和-HTTPS-做对比"><a href="#和-HTTPS-做对比" class="headerlink" title="和 HTTPS 做对比"></a>和 HTTPS 做对比</h1><p>我们都知道 http 是不安全的,但是我们都相信 https 是安全的。</p><p>但 https 其实也不安全,为什么我们还相信它?</p><p>为什么不安全:</p><ol><li>通过中间人 MITM,依旧可以解密本已经加密的数据并明文查看。</li><li>有 MITM 的存在,C 和 S 通过 https 传输的数据,依旧会被部分人看到。有两种场景:<ul><li>C 主动信任了 中间人 证书,主动查看数据。如开发人员。</li><li>C 上当了,信任了黑客的证书。</li><li>基于以上两个场景,S 就没有办法信任 https 了,因为 S 给到 C 的数据可以被明文分析了。<ul><li>方案一:将数据再次加密,使得中间人拿到加密后的数据。这里只能使用对称加密了,对称密钥也可能被从内存中挖取。本质上只是增加了破解的成本。</li><li>方案二:进一步做双向加密认证,使得中间人 MITM 失效。进一步的安全处理上会使用该方案。</li></ul></li></ul></li><li>所以,对于 https 而言,C 和 S 之间的数据依旧有可能被窃听。如何严格按照安全标准,https 也是不安全的。因为安全本身的定义,就是为了处理窃听风险。</li></ol><p>为什么我们还是相信 https 协议:</p><ol><li>我们认为 https 已经能够覆盖大部分安全场景了。</li><li>默认对开发人员和上当信任了黑客证书的人,不在关注了。<ul><li>开发人员:看就看吧。</li><li>上当人员:上当就上当了吧,黑客你就看数据吧,防不了你。</li></ul></li></ol><p>https 的安全,是基于 TLS/SSL 实现的。https 是应用层,使用了 TLS/SSL 这套安全套件。</p><ol><li>https 调用 tls api,tls 完全安全验证后对数据加密,在调用 socket api 将数据给到 tcp 进行传输。</li><li>tls 完成安全校验的前提是 tcp 安全通道的建立。</li></ol><p>区块链 和 https 有很多相似的地方:区块链和 tls 处于同一个层级,而 比特币和以太坊 就如同 https 应用层协议。即 https 使用 tls,以太坊 基于 区块链 技术 。</p><ol><li>区块链用来保障一个很重要的点:不可伪造。它保障了不可伪造,但上层应用基于这个不可伪造的特性,并不能 100% 的完成上层业务。<ul><li>A 通过 区块链 记录了一条向 B 借款 1w 的记录。<ul><li>借款到期后,A 就是不还。A 和 B 以及所有人都知道 A 向 B 借了钱,但 A 就是不还。</li></ul></li><li>通过区块链做了一个 NFT 数字画 J 并卖给了用户 M,用户 M 具有 J 的唯一权。但用户 N 依旧将数字画 J 印在广告上做产品宣传。<ul><li>用户 N 就是盗版、侵权了,但法律的不完善可能无法制约这种行为。</li></ul></li></ul></li><li>为什么还相信区块链:<ul><li>我们默认相信 https 是安全的。</li><li>我们以君子的视角来看待区块链本身。</li></ul></li></ol><p>所有的一切,都是以君子视角来看待。</p><hr><p>币圈一骑红尘,智能合约改变世界,之前得依靠 “君子协议”。显然整个币圈,没有君子。</p><p>往后,还是得依靠法律,成为协议的保护神。</p><p>那有法律背书后,还要这些干什么呢?现在不就是有法律、律师、法院么。</p><p>只能说法律愿意来背书,让这个行业不灭,看看后面能不能有新的价值。</p>

我的 2023

作者 海驴
2024年2月24日 02:29
<p>怀着沉重的心情,还是开始落笔写年终了,相比很多人迟了 2 个月。<br>这一年发生了不少事,感觉清晰可见,又像梦一样不知哪里谈起。离职、旅游、待业、润,每一步都在计划之内逐步落地,但每一步都如履薄冰、心有恐惧。<br>人生中已经有很多重大变动,中考、高考、结婚生子、择业、多次工作变更等,现在回头看每一次都是巨大的转折。但当初处于那些时刻的时候,并没感觉到刻骨铭心。<br>而现在,年过三十、拖家带小的我,依旧在努力的尝试着重大的拐点突破,但底气不是很足。</p><p>记得中考时候很清楚历史和政治两门考的非常糟糕 (加起来都没到 100 分),很清楚弱小的身体没啥体育加分,但考试结束后就在田埂上无忧无虑的瞎转悠。<br>中考能升的高中只有两个,要么 “一中”,要么 “二中”。这两所高中是天壤之别,一个是继续上大学,一个是大专或者打工就业。</p><p>记得高考结束后傻乎乎的二指禅敲键盘,在网吧里通宵玩着飞车和农场。不知道 985 / 211,也不知道专业、就业、指南。填志愿一把梭哈,只因为里面有 “计算机” 三个字。<br>那时候以为终于离开了地狱,谁成想那是最后一次无忧无虑的天堂。<br>当时,只知道家人、老师、同学一起塑造的氛围,是一场 0 或者 1 的战役。但要说黑灯瞎火的努力是为了什么,不知道!<br>都没坐过电梯与公交,也没坐过马桶。我可不知道 “书中自有黄金屋” 里的 黄金屋 意味着什么,有饭能吃饱不饿,天塌下来有爸妈。</p><span id="more"></span><p>上大学时还未成年,有些签字还需要等假期回家后让父母签。经常在自习室弄着 高数、物理,想着考试也能顺带给别人抄。不知道在干什么,没有方向的耗费着时间。<br>后来有了个女朋友,当时还想着能够天长地久,真几把扯蛋。<br>大学,懊悔终生。</p><p>大四开学后,实在学不下去了。主修 C 和 Java 以及一些 JSP Web 页面,面试了 “科大讯飞”,记得面试官有个问题是:Tomcat 怎么改端口。结尾面试官说我冲动气盛,当场没给过。<br>再往后一段时间,有个老板在会议室等人。刚好从门口经过看到了,贸然进去说想去他公司工作,做 iOS App 开发。他说可以,工资 2300 (后面克扣了 600)。<br>我问 “JSP 展示的 Web 页面,数据在服务端已经拼好了。那 App 是怎么拿到 Server 的数据呢?” 老板说有一套数据解析格式。我问是 XML 吗?他说他们刚换成 JSON 了。<br>人生的行业选择,就在这几分钟落地了。进入 iOS 移动开发行业,没有深思熟虑,只因路过会议室门口,老板缺 iOS 实习生,而我想工作了。</p><p>之后在各个创业小公司频繁跳,只因一个接一个的倒闭。<br>在互联网井喷的那几年,有想过作为初始员工是否可以一跃龙门,终究是没赶上这样的机遇。有公司只差临门一脚,但终究是没熬过去。<br>那些年,为了公司的存亡,我这个低微的蝼蚁可是瞎操碎了心。结婚也没去蜜月,孩子出生也没休假,就那样努力的傻傻奋斗着,活成了傻逼。</p><p>之后又迷糊的做了一个错误的规划:想找一个闲工作,多些时间照顾刚出生的孩子。<br>打听到了一家不太忙的混吃喝的大公司,降薪跑了去。入职后的确空闲了一个月,哪知道后面开始改革,又忙碌了几年。<br>结束这份工作的时候,已经 27 岁了。</p><p>回忆了这么多重要时刻的抉择,只想说:人生的前 27 年,都是混沌的。重大的抉择,完全凭运气跨过了。<br>中考侥幸垫底上了好高中,高考误打误撞选择了喜欢的专业,大学迷离糊涂选择了有钱赚的行业,工作后 0 成本找到了非常优秀的老婆,等等。<br>真的,完全凭运气。这些都没有慎重思考过,但都没有走空。</p><p>27 岁那年,做了一个决定,想出去了。<br>这个念想开始膨胀的时候,都没敢和老婆说。联系了一个国外生活的老哥,给了一些可选择的列表,看下来也就只有日本。<br>本就是一个极度平庸的人,只有老婆说我非常棒,那棒不棒自己能不知道么。就连想要出去的可选择国,都没有第二个选择的能力。<br>想着还是得有一些积蓄或者固定资产,于是死磕了几个月的算法和八股文,去阿里工作几年弄了些保底钱。</p><p>我是一个没多少运气在身的人。如果有可选项,在懵逼中选的话,十之八九选错。<br>如上而言,这些年大的人生拐点,已经耗费了那少有的运气家底,以至于没走空。<br>27 岁那年的决定,在 2023 年已经按计划落地。这一次不是靠运气做的抉择,也实难估计收益,可能需要数十年来做验证。</p><p>2023 年,开始自行抉择,摆脱运气约束的元年。这一年,虽已三十而立,又好似成人礼。</p><h1 id="年度数据"><a href="#年度数据" class="headerlink" title="年度数据"></a>年度数据</h1><ul><li>博客:(Google 数据) 日访问量 50-100,日曝光展示 400-800。年新增文章 23(技术文章 12)。&lt;数据惨淡&gt;</li><li>收入:待业期较长,收入锐减 60%。外部欠款降低 50%,外部应收款增加 150%。</li><li>工作:毕业后最开心的一年。工作时期体感 70%,待业时期体感 85%。整体满意度为 “无业一生轻”。</li><li>旅游:国内游 2 次。&lt;威海 - 青岛、千岛湖&gt;</li><li>家庭:陪孩子时间增长 +300%,夫妻和睦度增加 +50%。</li><li>技能:<ul><li><ol><li>普通家常蔬菜可简单制作,口感一般,视觉较差。荤菜还需要进一步尝试,可简单做猪牛肉炖菜。硬菜还不会。</li></ol></li><li><ol start="2"><li>开始迷恋咖啡,2-3 杯 / 日。</li></ol></li><li><ol start="3"><li>软考拿到了证书,日高度人才签可加 5 分。</li></ol></li><li><ol start="4"><li>日语进展缓慢。虽跨过了五十音,但后续无进展。</li></ol></li><li><ol start="5"><li>算法能力增强 200%。目前依旧为菜鸟水平,依据下一年工作内容判断是否持续。</li></ol></li></ul></li><li>社交:<ul><li><ol><li>微信已经不在使用,定期翻看朋友圈及联系人。</li></ol></li><li><ol start="2"><li>开始通过 Twitter 社交,fo 增加 100 人。</li></ol></li></ul></li><li>宗教:虽自诩基督徒,但心知不虔诚。今年对主归属感增加 10%。愿主持续教化,以马内利。</li><li>性情:平和了很多,没有大的起伏。宽容度剧增,存在即合理。已经变的 “自私自利”,愿意与他人给予或索取帮助,但更在乎自己的心情和体验。</li><li>政治:22 年达到顶峰 (顶峰时期关注度也很低)。23 年猛跌 100%,已经不在关心。</li><li>宗旨:差生文具多,又不是不能用拥垒。</li></ul><h1 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h1><p>年初和公司协商离职,于九月份离开了工作三年的公司。<br>阿里虽然下跌的厉害,但依旧是一家非常优秀的公司。那里有非常完善的制度,是一个规则社会。<br>在那里,有非常多的优点可以学习。完善的工作流程、优秀的同事、丰富的经验沉淀等。<br>同时,那里也充满了压力。时时刻刻都在打磨着人的心性。<br>皮实耐操、拥抱变化,是活下去的本钱。<br>离职的时候,很开心。工作压力真的挺大,交还电脑前还在处理着杂七杂八的琐事。解脱了没有理由不开心一把。</p><p>离职后就没打算短期找工作。前老板让过去忙忙,也拒绝了。<br>就是想没日没夜的睡觉。实际上也不怎么睡得着,还是想找些事情做做。于是学习了一些新的语言方面的技能,也把前些年写的不太完善 / 认知错误的技术博客更新了一下。</p><p>年初提出离职的时候,也在规划着是不是直接润,还是再找个工作攒些家底 (还去字节的老朋友那边试了一把)。<br>后面又和将要去日本与妻子团聚的朋友欢聚了一把,聊了一通也下定了决心,离职后就润吧。<br>孩子已经上小学,性格、习性也已经到了固化的年纪。对于我这样拖家带口的组合,时间真的不多了。这一次不赌,后面只会更加的难。</p><p>离职一个月后,敲定了日本工作,是在日本做电子支付业务的,和国内的支付宝有些类似。觉得这是一个有机会的行业,选择本身也是一个赌注。</p><h1 id="性情"><a href="#性情" class="headerlink" title="性情"></a>性情</h1><p>妻子直言,从进入阿里开始,我就变了。变得苛刻、少有欢笑,陪孩子的时间骤减,家庭和睦度急速下降。<br>这几年成长的确很快,如开头所言,这段时间差不多就如成人礼前的极速成长期。在想法、实践、认知方面都有快速的增长。</p><p>这不是好事,人的确变得郁郁寡欢。总是喜欢思考一些小的事情,期望能发掘出大的道理,这实属变态。<br>最直接的改变就是没有多少笑容了。只有和孩子在一起,面对那个空白干净的、未被打磨过的世界,想着以后可能的五彩缤纷,会情不自禁漏出自己的笑容。<br>2023 年,抽了很多烟,喝了很多酒。尤其下半年,烟酒数量和上半年比,有 2-3 倍的增加。</p><p>虽自诩基督徒,但实在不够虔诚。但能感觉到自己对信仰的依赖在持续加深,主对我比较深的影响有两个:</p><ol><li>我的人生有上帝的规划和参与,目前的境遇是最好的,是上帝给予的。少有后悔,只会坚定的走下去。</li><li>我没有偶像也不崇拜任何人,免受他们一言一行的影响。对所有人都只有尊敬而无仰望,会不停的借鉴和学习。</li></ol><h1 id="孩子与家庭"><a href="#孩子与家庭" class="headerlink" title="孩子与家庭"></a>孩子与家庭</h1><p>孩子的一年级已经渡过了半个学期,每日都很恐惧,我们的安抚不能解决丝毫。<br>5 个月里除节假日,没有一天的早上是开心的,前期哭了 3.5 个月,后面 1.5 个月也是闷闷不乐。<br>和班主任说过,不在乎孩子成绩,在班里不用管和打骂我家孩子,但这并没有解决问题。</p><p>孩子期末考试考了第一名,老师发来了祝贺。但我是不开心的。<br>孩子对学习的乐趣已经没了,仅仅在忧心中努力做好一些以防止被批评和责骂。我上学的时候也是这样,后面工作了也有阴影。<br>这颗不善的种子已经发芽了,不想孩子走我已经走过的路。这种时时刻刻在意别人的标签而活不出自己的人生,太累了。</p><p>因为收入锐减的原因,2023 年的生活水平有明显的下降。减少了很多外出吃饭的次数,很多时候都是在家做饭吃。而 21 / 22 年时常去一家自助餐的消费差不多都过万了。<br>有个习惯,对想要的东西,争取做到短期过瘾。对吃来说,就是疯狂的吃,吃到不想吃,吃到没有念想。<br>这些年,打败的瘾有:鸡肉、苹果、梨、格瓦斯、榴莲、三文鱼、西瓜等。<br>我这种山旮旯出来的,打小没见过海鲜。沾上了,就总是想吃。今年把螃蟹、三文鱼吃够了,打字这一刻,无念想。<br>体重也降了 10 斤。</p><p>今年对老婆和这个家庭更加的喜爱和尊重了。<br>老婆一直没有怨言的照顾着家庭,真的很累的。尤其对孩子的照顾和教育,非常细致繁琐,这种焦躁的生活很难容忍。<br>在结婚、生子两个重大的时间点,我都还像未成年的儿童一样,没有什么深刻的生活感知。上帝把我们组成了家庭,非常感谢。<br>感谢老婆对我持续的包容,每想到此,都会心生愧疚。希望做更多的付出,以弥补多年对家人陪伴、感情上的缺失。</p><p>老婆老家现在结个婚,房车彩礼都是标配,得小 200w 才能搞下来。即使当年,单纯彩礼这一项没有 20-30w 也弄不下来。<br>前段时间无意翻看到很多年前和老婆的聊天记录,感慨颇多。<br>那时好穷,结婚时候只有 3w,没固定资产。爸妈只要了 10w 彩礼。向亲戚借,没借到。向朋友借,没借到多少。图省事,贷款了 5.5w,凑齐了彩礼。后面爸妈回礼 7w,挪过来补了窟窿。<br>这些年搬家六次,新房仅住了一年,又要规划去日本从零开始。<br>老婆是有怨言的,我非常清楚,这无可厚非完全情理之中。但老婆一直在支持我,她一直在相信着我。每念及此,都有一种感情想要痛哭流涕。</p><p>感谢主的眷顾,愿老婆和孩子都好,愿我能给她们更多。</p><p>也感谢宠物狗闹闹,这些年都在带给家庭欢乐。你比孩子要长一岁,在我们的生活中,你就是孩子的姐姐,是家庭的一份子。这次日本计划若顺利,也一定会把你带去的。</p><h1 id="日本"><a href="#日本" class="headerlink" title="日本"></a>日本</h1><p>这次日本行,几年前就在计划。虽然也想着给家庭和父母留退路,但直到落地这一刻,也没留下什么。<br>选择日本,因为它得分最高,在距离、经济、生活、工作、育儿等方面综合考虑。<br>国外的月亮没有更圆,<strong>人性的善与恶,放之四海都一样</strong>。<br>生活工作这么多年,清晰的明白<strong>解决问题没有银弹</strong>。如同恋爱,前任总有优缺点难以忘怀,现任也会带来新的感动与悲伤。<br>不过是换一种生活方式,体验两个不同人生。</p><p>制定这个计划,和很多人的政治不满不同。<br>我是一个不关心政治的人,没有恨。毕业后的职业也是顺风,跟着国家的建设讨了不少巧,赚钱养家给了家人不少的支持。这也是恩情。<br>即使恐怖的疫情三年,也没有经历过居家、隔离。<br>我就是一个自私的小人儿。那些人祸、灾难,只多不少,只重不轻,但和我有什么关系呢?我给不了最基础的帮助,也对转机做不出些许改变。<br>即使搅了几天,搅了个天翻地覆,最终也会趋于平静。我连浪花都算不上,能做什么呢?<br>直接来说,铁拳没有一下子落在身上,的确无法感同身受。<br>但,那些悄无声音的、有长有短的束绳索,的确是我这种低微小人儿的顾虑。</p><p>房子从买来至今,已经跌了 30-40%。虽是自住非投资,但银行贷款花的钱还的贷是实打实的。<br>资产大幅缩水,以及周围不少朋友转行、待业的情形,谁能来买单?大家都只是简单的混口饭吃,讨生活的小人儿咋就难上加难。<br>改革是必然,这些年开放取得的成就有目共睹,不能吃老本,也应该有更多的推进。历史的车轮一定是向前的,将时间的跨度拉大,文明、经济、生活一定都是向前进步的。这些年也一直和孩子说,要勇敢面对变化,没有什么是一成不变的,内部外部都在时刻发生着变化。<br>但在时间的长河中,会有多少下坡?需要多久的摸索后才能及时掉转方向?这一届好,下一届、下下届还会好吗?<br>能人志士,下坡也能逆行,可我不是这些聪明人。疫情虽然没被隔离,家里的宠物也安好,但社会和经济的停滞也对我有不少影响。其他如期而至的各种管制也让行业受创,收入和工资都受到影响。<br>我不想下这个赌注,只想每次都能在及格线上增长就行。堵高速里最怕车子不动,只要还在龟行,就还在进步。<br>在三体星球,因三颗星体的引力拉扯,那里无法预测规则。<br>可能白天持续几小时后就迎来了漫长的黑夜。还有那可将石头冻成粉末的寒冬与酷暑,也不知道会持续几个月或者几年。<br>三体人梦寐以求的是像地球这样的四季分明,清晰的黑夜与白天。它们侵略了地球,不是为了资源和土地,是为了稳定和有序的规则。</p><p>姥爷姥姥八十好几了,在农村有车有房,好多儿女,自身存款好几十万了。现在每天辛苦做生意啥的,小麦水稻也都在弄。他们养老不愁,什么都不做,也花不完手里的钱。可还是辛苦劳作,以预防什么。我父母也在走完全同样的路。<br>他们想法都是,哪天真的干不动了就不干了。什么是退休?不知道。<br>而我比他们只是在城市多呆了些时日,从农田耕种搬到了坐办公室。过不了几年,也会走他们差不多的路,只是换一种讨钱形式,但换汤不换药。<br>没有完善的养老、医药制度,安全感就无法建立。<br>医保有没有用?当然有。任何一件事物的存在,都会有哪怕最低微的作用。<br>前两天妈妈手断了,要住院才给报。平常病要用药,医保也需要拿特定的药才行。<br>大伯身体要换支架,国产的 20w 能续命 10 年,进口的贵了好多倍但可以保障正常寿命。筹钱弄了国产,也没得报销。<br>有没有给,给了。能用吗?能用。好用吗?不好用。什么都给了,又设置了层层卡口。<br>到了一定年纪,病不是能躲过去的。它一定会如约而至,没有例外。而这些条条框框的卡口,就是最后一击。<br>当然,对于养老、医药政策,不出几年,肯定会有改革。但这个漫长摸索期和结果本身,也是赌注。</p><p>6 岁前,我和妻子给了孩子很好的家庭教育,主要归功于妻子。6 岁前是孩子性情、习性养成期的关键期,我们很重视。<br>但在 5.5 岁上了小学后,多年的努力废了很多。学校给了孩子不一样的培养方式。<br>如上面 “孩子与家庭” 所描述的,孩子对学习的兴趣已经非常淡薄。几个月前,孩子装病不愿上学,带孩子捡了一天垃圾,孩子翻着脏垃圾桶,也不愿意回学校。<br>我们给孩子尊重、选择和自信,学校给孩子批评、恐吓,迎头一棒的打杀。<br>教育有改革,包括期终成绩等级制 (日常考试还是真实分数),还有 “双减” 等政策。这些改革很无力,弱肉强食的竞争大环境没有改变,对生活和未来的恐惧没有降低,读书依旧是最快捷且近乎唯一的幸福通道。<br>“书中自有颜如玉、知识改变命运” 的钢印,以及 “屎难吃钱难挣” 的现实,孩子只能接受家长和学校同时施加的全负荷填鸭应试教育。好成绩大概率有好未来,一代又一代的轮回。<br>对于下一代成长和学习的二十年里,哪个国度都是辛苦的。想更加优秀,肯定要付出更多的精力、金钱、辛劳。可以不劳,但也无获。但在 “只能通过学习来 xx” 和 “可以通过学习来 xx” 之间,希望给孩子更多的可能性和选择。<br>不担心红色和歪曲的教育,这些可以补救。<br>但希望孩子心智是完整的,成长不是同质化的,童年和学习旅程是健全愉快的,“身心、思维、认知、逻辑、心性” 等受益终生的种子,有足够的时间和空间在心田慢慢发芽成长。</p><p>除此之外呢?其实不多了。日本面试官当时问:为什么要来日本?我的回复很简单,是对未来的一个保障。目前生活的也不错,但不可持续。<br>对于很多生活困难或客观原因的人来说,出去是更好的选择。但与我的家庭而言,至少后面十年内,这里是最安逸的。<br>总能找到比较好的养家工作、爸妈按期寄来新鲜的食物、日常用品也能弄到国外的渠道。<br>可一二十年后呢?孩子面临成年、工作、婚姻,父母面临老去、疾病,而自己的人生也完全定型。那时候上下三代人的生活处境,我有极大的忧虑,因能力跟不上梦想,这是刻骨铭心的痛。<br>出去后日子肯定很艰难,新的生活需要整个家庭来适应,包括收入与支出的巨大波动和差异、语言、教育、稳定性等等。<br>还是希望做些改变,就安慰自己是长期收益吧,搏一个 “未来可期”。</p><h1 id="规划"><a href="#规划" class="headerlink" title="规划"></a>规划</h1><p>人生基本快过半了,时间真的不多了,能挣扎的也不多了。如今把未来压在移民,基本也把少有的拼搏希望给抹灭了。<br>出名要趁早,只因为晚了,希望就不大了。自知很可贵,坦然面对普普通通的现实,别做无谓的挣扎。<br>后面几年,希望达成一些目标:</p><ul><li>24 年要熟悉日本的工作、生活,努力安全降落。如有困难想要退缩,也要为自己当初的抉择买单,撑到 24 年底。</li><li>25 年拿到摩托车驾照、买一辆摩托车。【挑战:24 年拿到驾照】</li><li>24 年日语达到 N4 水平,25 年通过 N2 考试,25 年拿到高度人才签证,28 年之前拿到永驻。</li><li>24 年在日本租独立套房,完成软硬装。</li><li>25 年,将妻儿带到日本生活。(必须完成,否则考虑回国)【挑战:24 年底之前完成】</li><li>26 年家庭总收入达到 1000w 日元。</li><li>28 年购置房产。【挑战:27 年完成】</li><li>24 年降低抽烟量,达到 3 天一包。25 年完成戒烟。</li><li>24 年体重维持 70±1.5KG,25 年维持 68±0.5KG。周锻炼时长 &gt; 4H。</li><li>26 年往后,家庭年外出旅游次数 &gt;= 2。</li></ul><hr>

【Swift】中文词语纠错

作者 海驴
2024年1月6日 04:22
<p>一直有一个写字痛点,就是错别字。尤其在发帖子和写文章这样的正式场合,错别字会引起很大的误解,而每次检查都会很吃力。<br>试用了人气较高的 “写作猫” 和 “火龙果” 两个纠错平台,都有很大的局限性,并不适合我这样的人使用。</p><blockquote><p>写作猫的缺点是 word 走天下,一点也不 nice。<br>火龙果的缺点是没有纠错能力,我用测试文档只检测出来两个不存在的英文错误。<br>它们都不支持 markdown 检测。<br>付费较高,对于非高频使用人员不友好。</p></blockquote><p>于是,尝试自己写一个纠错工具,目前做了开源,支持 CLI 和 GUI,详见:<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3lpZ2Vnb25namlhbmcvSExWU2VudGVuY2U=">HLVSentence</span>、<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3lpZ2Vnb25namlhbmcvSExWWmhDb3JyZWN0">HLVZhCorrect</span>。<br>技术方案是:<strong>检测文本文件</strong> -&gt; <strong>文本分句</strong> -&gt; <strong>词语检测</strong> -&gt; <strong>工具集成</strong>。<br>遇到的核心问题有:</p><ul><li>准确识别文本文件。文本文件无 Magic Number 二进制特征,如何准确识别当前文件为文本文件并读取内容。</li><li>文本分句校准。对于纯中文场景,通过常用的标点符号即可正则分句。但一般会参杂英文、特殊符号,还有 markdown。</li><li>中文纠错检测和校准。找了不少方案,最后选择 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3NoaWJpbmc2MjQvcHljb3JyZWN0b3I=">pycorrector</span> 提供的中文纠错模型。工具需要和 python 环境做对接,并对待检测文本做进一步校准以满足模型。</li><li>非终端环境和 python 脚本互联。需要支持 python 虚拟安装环境。</li><li>SwiftUI 开发 “命令行 CLI 工具” 和 “Mac 菜单栏工具”,将工具对接 brew 平台。</li></ul><p>下面是开发文本纠错工具的一些历程。</p><span id="more"></span><h2 id="Demo-演示"><a href="#Demo-演示" class="headerlink" title="Demo 演示"></a>Demo 演示</h2><p>可查阅 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3lpZ2Vnb25namlhbmcvSExWU2VudGVuY2U=">HLVSentence</span>、<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3lpZ2Vnb25namlhbmcvSExWWmhDb3JyZWN0">HLVZhCorrect</span> 两个项目的 readme 主页,查看 CLI 和 GUI 的效果和使用说明。</p><img data-src="https://cdn.jsdelivr.net/gh/yigegongjiang/image_space@main/blog_img/202401061607920.png" width="40%"><img data-src="https://cdn.jsdelivr.net/gh/yigegongjiang/image_space@main/blog_img/202401061607769.png" width="40%"><h2 id="文本文件识别"><a href="#文本文件识别" class="headerlink" title="文本文件识别"></a>文本文件识别</h2><p>对于具有特定使用场景的二进制文件,它们不需要遵循 Unicode 字符集规范。因为使用场景专一,一般都会通过 <code>Magic Number</code> 对文件类型进行标识,如 ELF、Zip 等。<br>但对于一个文件是否是<code>文本文件</code>,并没有完全行之有效的识别方案。只能通过尝试去理解内容,这是有一定误差的。<br>对于<code>文本文件</code>所具有的识别属性,可以从以下几点考虑:</p><ul><li>文件后缀。【特定场景下稳定,对用户信任】</li><li>magic number 排除。【文本文件均无稳定的 magic number】</li><li>编码识别。【文本文件一定有一致的编码和解码方案】</li></ul><p>通过文本编码可以最为精确的识别当前文件是否是文本文件,从而进一步依靠对应的编码来读取文本内容。但这也会有一些问题:</p><ol><li>性能问题。文本文件一般较小,可以进行全量解码。但待解码的文件可能是图片或者压缩包,这会有极大的性能损耗。</li><li>二进制中存在文本标记。二进制文件中部分位置可能插入文本,用于记录一些信息。如果读取部分内容且正好读取了这一部分,则有一定的误判概率。</li></ol><p>所以,有一个可行的方案:</p><ol><li>先判断有没有 magic number。如果有,则可以识别到当前文件的具体类型。<code>一般只要有 maigc,就不再是文本文件了</code>。</li><li>再对文件内容进行多个位置的截取解码。根据文件大小<code>动态设置采样点</code>,且所有采样点<code>解码全部通过</code>,则判定为文本文件。</li></ol><p>Swift 文本文件识别 源码详见:<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3lpZ2Vnb25namlhbmcvSExWRmlsZUR1bXA=">HLVFileDump</span><br>对于文本编码的详细介绍可查阅 <a href="https://www.yigegongjiang.com/2023/unicode/">计算机字符编码与内存编码</a></p><h2 id="文本分句"><a href="#文本分句" class="headerlink" title="文本分句"></a>文本分句</h2><p>纯中文环境下的分句,比较好实现。只需要控制将<code>?</code>、<code>;</code>等几个特别的断句符号进行识别即可。<br>但很少有纯中文的段落,一般都是中英文混杂,而且还有个数算术符号。如下面几个例子:</p><ol><li><code>今天的温度是 25.3°C,天空晴朗,风速约 10 mph.</code>,这里末尾是英文的<code>.</code> 收尾,且数字中含有<code>.</code>无法做断句。这里只能通过 NLP 语言模型进行分句,Apple 提供了 <code>NaturalLanguage</code> 物料库,可以在多语言混合的场景下,进行语句分割,且能够处理数字符号。</li><li><code>今天天气不错(适合出游),你要参加吗?</code>,这里 NLP 就不能很好识别,会把中文的<code>(</code>进行分句,导致 <code>()</code> 两个字符被分割到两个分句里面。</li><li><code>1. 吃饭 2. 睡觉 3. 打豆豆</code>,这样有数字前缀或者其他特殊字符前后缀的,在分句后理应去除序号或者特殊描述符号,只保留汉字内容。这个 NLP 能初步识别,但也有误差,基本够用。</li><li>特殊标记符号如 <code>\t</code> 等,需要特别处理。它们会影响到分句的质量。</li></ol><p>这里的一个可行方案是:</p><ol><li>先根据中文符号进行一次分句。此时结果基本符合中文语意,但中英文和符号混杂的地方无法分割。</li><li>将上一步的分句结果使用 NLP 二次分句,此时基本符合分句诉求,偏差不大。</li></ol><p>但是在可行方案执行前,还有一个重要的点,是 <strong>NLP 对 markdown 识别不敏感,误差率较高</strong>。还需要先进行 markdown 的去格式化。</p><h2 id="文本纠错"><a href="#文本纠错" class="headerlink" title="文本纠错"></a>文本纠错</h2><p>找了很多文本纠错开源方案,大多不理想。甚至文章开头说到的商业平台,测试下来也没有很优秀。<br>一个很主要的点,是纠错是针对单词而非语境的。针对单词的纠错通过大量的单词匹配即可,但误差率较高。<br>最后使用的是 python3 实现的 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3NoaWJpbmc2MjQvcHljb3JyZWN0b3I=">pycorrector</span> 模型,能够部分识别语境。</p><p>对模型参数进行一些调整处理后,目前能够做到:</p><ol><li>如果有错,非常大的概率是能够被检测到。</li><li>如果没错,有小部分概率认为有错。</li></ol><p>就像布隆过滤器一样,没有漏,也是可以的。又不是不能用。</p><p>当然,模型对数据更严格,稍有不慎,就检测失败了。最大的问题是不支持英文和特殊符号。<br>所有在文本纠错的时候,又对上面的分句内容进行中文提取,然后交由 pycorrector 检测。</p><h2 id="python-环境数据互通"><a href="#python-环境数据互通" class="headerlink" title="python 环境数据互通"></a>python 环境数据互通</h2><p>前面分句的结果在 Swift 可执行程序的内存里,后面需要交用 python 脚本执行并拿到处理结果。<br>一个可行的办法是 app 中嵌入 python 解释器,但这会凭空增加复杂度,且不好维护 pycorrector 模型库。</p><p>最后通过 <a href="https://www.yigegongjiang.com/2023/SwiftSystemShell/">SwiftShell 高效的命令行工具</a> 实现了数据互通。<br>因为 app 环境中缺失终端环境变量,这在使用 Conda python 虚拟环境的时候会找不到 python 可执行三方库。<br>最后的解决方案是委屈用户,需要自行通过 <code>echo $PATH</code> 获取终端 path 后设置到 app 中。</p><p>对于 CLI 环境倒比较友好,因为 CLI 本来就是在终端中运行的,已经拥有了终端上下文 Env,没有遇到太多的问题。</p><h2 id="CLI-和-SwiftUI"><a href="#CLI-和-SwiftUI" class="headerlink" title="CLI 和 SwiftUI"></a>CLI 和 SwiftUI</h2><p>Swift 开发 CLI 命令行,整体还是比较快速简便的,官方有较好的支持。<br>入门可以参考:<a href="https://www.yigegongjiang.com/2023/SwiftCommandEnv/">Swift 脚本开发环境搭建</a>,当前项目也是完全按照入门说明来操作的。尤其文中的 <code>不换行更新上一次的输出结果</code> 和 <code>异步等待</code>,在命令行开发过程中很有用。</p><p>SwiftUI 开发 Mac 还是有不少概念需要理清。<br>AppKit 和 UIKit 有很大的差异,比如 Window/WindowController/NSMenu/NSStatus 等。SwiftUI 又要抹平两者的差异,所以 SwiftUI 整体上和 AppKit/UIKit 都有较大差异。<br>建议一个入门方式,是通过 Xcode 的 <code>Development Documentation</code> 快速查看 SwiftUI 的编写规则,混个脸熟。<br>然后在 Github 上面搜索 Api 找到一些比较新的 SwiftUI 项目阅读源码。<br>最后,频繁的咨询 ChatGPT,有时候它给的答案并不对,但能给一个大致的方向。</p><h2 id="brew"><a href="#brew" class="headerlink" title="brew"></a>brew</h2><p>可参考当前工程的私有配置:<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3lpZ2Vnb25namlhbmcvaG9tZWJyZXctdGFw">homebrew-tap</span>。<br>brew 自定义仓库等相关资料较少,本人也没有进一步学习。<br>目前把 CLI 和 GUI 都部署到了自定义仓库中,有需要的可以简单参考下。</p><hr><p>App Store 真的做到了 “花开两朵,各表一支”。<br>在 Mac 上,Mac App Store (MAS) 犹如鸡肋,复杂的沙盒安全形同虚设。<br>Apple 看着 MAS 生意惨淡,却毫无作为。既不能做为平台为优秀的软件做宣传,也阻止不了垃圾软件的恶意收费。</p>

又十年 - 至三十而立

作者 海驴
2023年4月8日 16:43
<p>我一直都在回味前不久过了三十岁的生日,这个年龄坎悄无声息的就来了。<br>那晚,我和以往一样,回家的比较晚。老婆孩子都在等我,他们做了火锅,庆祝我生日。孩子还用压岁钱,送了我一瓶香水。<br>我知道那天是我生日,只是我自己也没有多少在乎和关注。但破防的夜宵,还是让我语言沉默和大脑峰回路转。</p><p>三十而立了。</p><p>当自己走到了三十的路口,停下来看看左右,再看看回头路,才发现目前正在停驻的路口,心里是多么的不想面对以及情绪复杂。<br>哥哥什么时候三十的,我没有注意。老婆三十的坎,我也没什么顿悟。而父母今年也是六十了,我写文章这一刻才发觉。奶奶也已经九十八了。<br>其实我本该对亲人,嗯,本该多一些关心的。老婆评价过我,说我对亲人没得感情。我不承认,但也不否认。真要做个度量的话,还是 “不否认” 占据多数的。</p><p>张学友近期发布了「又十年」,听着听着,就感慨万千,想表达的能表达的,都在一首歌里。<br>「一眨眼 又是一個十年」<br>「生命給我們什麼都不可能拒絕 這句話我當年不了解」<br>「那些瓜葛糾結 在某一天突然迎刃而解」</p><span id="more"></span><p>十年前这个时候,我还在大学里。应该再过几个月,我就要去无锡工作了。<br>算一下,这个时候我应该刚和前女友分开,我依稀记得是过年那些天发生的事。有过迷茫有过愤怒,也都显得幼稚,谈恋爱的时候竟没想过分开。<br>要不是这次回忆,我感觉已经快忘记前女友名字了,当年可都是生离死别的,真是反差。“那些瓜葛糾結,在某一天突然迎刃而解。”</p><p>过后这十年,颠簸流离。离开合肥后,流转江苏、上海,现在定居杭州,下一步还未可知。<br>十年里,在 it 行业经历过 底层爬坡、team leader、pua、996,下一步是什么角色,也未可知。<br>十年里,从独自生活到结婚生子。举办婚礼的时候还朦朦胧胧,现在看着孩子长成了小大人。<br>十年里,朋友渐少。最常见的朋友社交就是参加婚礼。很多老同学老朋友也见一次少一次,话不投机以及改变太大,都使得没法做朋友交流。<br>十年里,体重从 120 升到了 160,最近又降到了 150。十年里酗过酒酗过烟但没赌嫖。<br>这十年,可真是彻底改变一个人的十年,心境和环境上,不断的打磨着。</p><p>三十岁,是一个很重要的人生驿站。<br>二十岁前是拿着一根吸管小心翼翼的吸取核心养分,有性格心境、知识、社交。这时候对人生了解的不多,都是针对性的喜好。<br>三十岁前吸收养分的工具变成了八爪鱼,每个触角都在不停的输送着不同的感知,有压力、欢喜、刺激、痛苦、矛盾等等。这是矛盾的十年,很多世界观的强烈反差。</p><p>以前我总是想着提出自己的想法。我记得小时候我对老家的一个叔叔说你这样计算数量好累,他问我怎么搞?我说我不知道。<br>那时候,仅仅是看一件事不舒服,也要提出自己的想法。<br>现在我就变了,对于周边一些事物,会想着要不要说,或者从其他角度来阐释,甚至会尝试着融入。<br>有时候孩子有些想法或者动作,我知道是不好的或者不对的,但是并不想直接提出来,我愿意看着这颗种子会不会发芽。<br>我渴望从另一个维度,来观看或者影响当前的变化,而非直接干预。后面可能会变得更好或者更糟,但我期待着事态的发展。</p><p>以前我认为世界就是我想的那样。我的世界观可以 1-1 映射到这个世界。<br>我猜测这个社会,是所有的人以诚相待,多数人和睦相处,少数人常来常往这样子。我知道有些恶,只是笃定那些恶只是尘埃里的那部分。光,还是铺满大地的。<br>后来呢,就发现生活、工作中很多都是黑的,连光本身也是黑的。<br>我得靠自己的分辨能力去识别有用的、无用的、假的、真的、香的、臭的。<br>经过好多年不断的锤炼,我变得举止谨慎、观望大于发言、思考多于行动。<br>我的分辨能力有了很大的提升,对待事物会从多个方面来评测和做选择。对于投入价值比,也会刻意的取舍。<br>多维度的思考、分辨、取舍,是我这些年少有的让我满意的地方。算做送给三十岁的自己的礼物吧。<br>而我理解的三十而立,这里的立就是个人在环境中的独立。能够有独立的人格、思维、想法、判断、立场,不逐流也不自我否定,知晓短缺点和风险。</p><p>站在三十的路口,其实我更多的还是迷茫、担忧。知道和经历的多了,渴望也就越多,满足感的上限也在不断拔高。能力跟不上梦想,是贴心挖心的痛。<br>前些年我还在挥霍着青春,看着网上一闪而过的「三十、三十五、年薪、工作、家庭」焦虑文,莫得感情。<br>现在我开始理解了,因为我有了经历、正在经历,这些让人无法言出又深入其中的焦虑,像钉子一样,看着没危险,踩上了又拔不出来。<br>我感觉自己有时候会变成两个小人,其中一个小人总是对这个社会有说不完的控诉。控诉这个社会的残酷,也表演自己的无能为力给另一个小人看。<br>另一个小人就知趣的说:“时来天地皆同力,你已经很努力了。万般皆下苦,我们就是那个下。”<br>这两个小人就是这样安慰着我,我就看着他俩的表演,瞅一下自己的梦想,再瞅一下自己的能力。</p><p>已至三十,人生路还要继续。重要的十年不算多,上一个已经走完,下一个刚刚启程。<br>人生如逆旅,道阻且艰,都是行人。</p><hr><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">又十年 - 原唱張學友</span><br><span class="line"></span><br><span class="line">聽說你在去年為了健康已戒了煙</span><br><span class="line">我在每個週末還是會小小喝一點</span><br><span class="line">朋友不太容易約到不再像從前</span><br><span class="line">那些日子可說已完結</span><br><span class="line">有些夜裡我會開車到城裡兜圈</span><br><span class="line">我才領會到什麼叫做往事如煙</span><br><span class="line">一眨眼 又是一個十年</span><br><span class="line">那些瓜葛糾結</span><br><span class="line">在某一天突然迎刃而解</span><br><span class="line">一眨眼 又是一個十年</span><br><span class="line">對於人生起跌 一知半解</span><br><span class="line">我卻已經懂得 應變</span><br><span class="line">一生有幾個理想能夠扛得住歲月</span><br><span class="line">還不如好好地過越來越少的明天</span><br><span class="line">生命給我們什麼都不可能拒絕</span><br><span class="line">這句話我當年不了解</span><br><span class="line">時間它殘忍起來毫無道理可言</span><br><span class="line">我也不忍對你說熱情總會冷卻</span><br><span class="line">一眨眼 又是一個十年</span><br><span class="line">那些瓜葛糾結</span><br><span class="line">在某一天突然迎刃而解</span><br><span class="line">一眨眼 又是一個十年</span><br><span class="line">那些轟轟烈烈</span><br><span class="line">讓人懷念已經跟我沒有 關聯</span><br><span class="line">一眨眼 又是一個十年</span><br><span class="line">那些轟轟烈烈</span><br><span class="line">讓人懷念已經跟我沒有 關聯</span><br><span class="line">還能有多少個十年</span><br></pre></td></tr></tbody></table></figure>
❌
❌