<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/rss/atom-styles.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Ethan Yang</title>
  <subtitle>👋 大家好，我是 Ethan Young，一名常驻南京的程序员，也是一位在生活中持续迭代的普通人</subtitle>
  <link href="https://ethyoung.me//atom.xml" rel="self" type="application/atom+xml"/>
  <link href="https://ethyoung.me/" rel="alternate" type="text/html"/>
  <updated>2026-04-10T04:07:15.328Z</updated>
  <language>zh-CN</language>
  <id>https://ethyoung.me//</id>
  <author>
    <name>Ethan</name>
    <uri>https://ethyoung.me/</uri>
  </author>
  <generator uri="https://github.com/yy921010/Litos" version="5.0">Astro Litos Theme</generator>
  <rights>Copyright © 2026 Ethan</rights>
  
  <entry>
    <title>每周速闻 - 2026-03-20</title>
    <link href="https://ethyoung.me//posts/weekly-news-2026-03-20" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/weekly-news-2026-03-20</id>
    <updated>2026-03-20T00:00:00.000Z</updated>
    <published>2026-03-20T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">本周科技与AI新闻速递：稀宇科技发布MiniMax-M2.7模型、Cursor推出Composer 2、Nvidia悄然打造新业务线、Nothing CEO预测AI Agent将取代手机应用</summary>
    <content type="html"><![CDATA[
<h2>📰 每周速闻 - 2026-03-20</h2>
<p>本期摘要：本周科技界动态纷呈，稀宇科技发布MiniMax-M2.7大模型，Cursor推出自研Composer 2模型显著降低代码开发成本。国际方面，Jeff Bezos计划筹集千亿美元用AI改造传统制造业，Nothing CEO大胆预测智能手机应用将被AI Agent取代。AI技术层面，Google实现多模态统一，Gamma新增图像生成工具挑战Adobe，行业正处于深度变革期。</p>
<hr />
<h2>📰 本周新闻来源</h2>
<img src="assets/2026-03-20-sspai-001.png" alt="少数派" />
<img src="assets/2026-03-20-pingwest-001.png" alt="品玩" />
<img src="assets/2026-03-20-techcrunch-001.png" alt="TechCrunch" />
<hr />
<h2>🇨🇳 国内动态</h2>
<h3>少数派</h3>
<h4><a href="https://sspai.com/post/107563" rel="noopener noreferrer" target="_blank">派早报：小米发布多款新品、稀宇科技发布 MiniMax-M2.7 模型</a></h4>
<p><strong>来源</strong>: <a href="https://sspai.com" rel="noopener noreferrer" target="_blank">少数派</a>
<strong>发布时间</strong>: 2026-03-20</p>
<p><strong>简介</strong>:
本周小米召开新品发布会，推出多款硬件产品，涵盖手机、IoT等多个品类。与此同时，稀宇科技（MiniMax）发布了最新的M2.7大语言模型。MiniMax-M2.7在推理效率和多模态处理能力上有显著提升，专注于为企业和开发者提供更强的大模型服务。此次更新进一步缩小了与国际顶尖模型的差距，在中文理解和生成能力上表现优异，适合智能客服、内容创作等应用场景。</p>
<hr />
<h4><a href="https://sspai.com/post/107514" rel="noopener noreferrer" target="_blank">派早报：腾讯 QClaw 正式上线、小鹏 P7 发布 Max 版</a></h4>
<p><strong>来源</strong>: <a href="https://sspai.com" rel="noopener noreferrer" target="_blank">少数派</a>
<strong>发布时间</strong>: 2026-03-19</p>
<p><strong>简介</strong>:
腾讯正式上线QClaw平台，这是腾讯在AI协作领域的重要布局。QClaw旨在提升团队协作效率，集成AI辅助功能，支持文档编辑、项目管理等多种场景。与此同时，小鹏汽车发布P7 Max版，新车型在智能驾驶和座舱体验上进行了全面升级，搭载最新版本的XNGP智能驾驶系统，续航里程和充电速度均有提升，进一步巩固其在智能电动车市场的竞争力。</p>
<hr />
<h4><a href="https://sspai.com/post/106878" rel="noopener noreferrer" target="_blank">Notion 年度更新评测：7 个案例聊聊 Custom Agent</a></h4>
<p><strong>来源</strong>: <a href="https://sspai.com" rel="noopener noreferrer" target="_blank">少数派</a>
<strong>发布时间</strong>: 2026-03-20</p>
<p><strong>简介</strong>:
Notion在其年度更新中推出了Custom Agent功能，允许用户根据特定需求定制AI助手。本文通过7个实际案例，详细评测了Custom Agent在不同场景下的表现，包括项目管理、文档总结、数据分析等。评测显示，Custom Agent在定制化程度和任务执行效率上表现突出，但学习曲线相对较陡峭。对于重度Notion用户而言，这是一个强大的生产力工具，能够显著提升工作流效率。</p>
<hr />
<h3>PingWest 品玩</h3>
<h4><a href="https://www.pingwest.com/a/312096" rel="noopener noreferrer" target="_blank">苹果 CEO 库克对话雷鸟创新，携手共绘空间计算新蓝图</a></h4>
<p><strong>来源</strong>: <a href="https://www.pingwest.com" rel="noopener noreferrer" target="_blank">品玩</a>
<strong>发布时间</strong>: 2026-03-20</p>
<p><strong>简介</strong>:
苹果CEO蒂姆·库克与雷鸟创新团队在北京展开深度对话，双方就空间计算（Spatial Computing）的未来发展交换了意见。雷鸟创新作为国内AR眼镜领域的领先企业，在硬件设计和用户体验上积累了丰富经验。此次对话被视为苹果在XR生态上的重要布局，可能预示着双方在未来的合作机会，共同推动空间计算技术的普及和应用场景的拓展。</p>
<hr />
<h4><a href="https://www.pingwest.com/a/312278" rel="noopener noreferrer" target="_blank">LibTV 实测：人类用户再也不是产品的全部</a></h4>
<p><strong>来源</strong>: <a href="https://www.pingwest.com" rel="noopener noreferrer" target="_blank">品玩</a>
<strong>发布时间</strong>: 2026-03-20</p>
<p><strong>简介</strong>:
LibTV是一款创新的视频内容平台，其特点是内容不仅为人类用户服务，也面向AI Agent。实测发现，LibTV的内容结构经过特殊设计，AI可以高效地理解和索引这些内容，为用户提供个性化推荐。这意味着，未来的内容消费可能不再局限于人类直接浏览，AI Agent将成为重要的”中介”。LibTV的探索为”AI原生”内容生态提供了新的思路。</p>
<hr />
<h4><a href="https://www.pingwest.com/a/312145" rel="noopener noreferrer" target="_blank">AI 手机的「从尝鲜到常用」：OPPO 在赌一件比点咖啡更难的事</a></h4>
<p>OPPO在AI手机领域投入巨大，目标是让AI功能从”尝鲜”变成”常用”。然而，这比点咖啡还难——因为用户习惯需要长期培养，而且AI功能必须真正解决用户的实际问题。OPPO Find N6搭载了最新的AI芯片和深度定制的ColorOS AI系统，提供实时翻译、智能摘要、图像增强等功能。OPPO的赌注在于，如果AI手机能够成功普及，将彻底改变用户的交互方式和手机厂商的商业模式。</p>
<hr />
<h2>🌍 国际动态</h2>
<h3>TechCrunch</h3>
<h4><a href="https://techcrunch.com/2026/03/19/jeff-bezos-reportedly-wants-100-billion-to-buy-and-transform-old-manufacturing-firms-with-ai/" rel="noopener noreferrer" target="_blank">Jeff Bezos reportedly wants $100 billion to buy and transform old manufacturing firms with AI</a></h4>
<p><strong>来源</strong>: <a href="https://techcrunch.com" rel="noopener noreferrer" target="_blank">TechCrunch</a>
<strong>发布时间</strong>: 2026-03-20</p>
<p><strong>简介</strong>:
据报道，亚马逊创始人杰夫·贝佐斯计划筹集1000亿美元，用于收购和改造传统制造业企业。贝佐斯的策略是利用AI技术提升这些企业的运营效率、优化供应链、并开发新产品。这笔投资规模惊人，显示出贝佐斯对AI改造传统行业的强烈信心。如果计划成功，将重塑制造业的格局，可能催生一批”AI驱动”的工业巨头。</p>
<hr />
<h4><a href="https://techcrunch.com/2026/03/19/online-bot-traffic-will-exceed-human-traffic-by-2027-cloudflare-ceo-says/" rel="noopener noreferrer" target="_blank">Online bot traffic will exceed human traffic by 2027, Cloudflare CEO says</a></h4>
<p><strong>来源</strong>: <a href="https://techcrunch.com" rel="noopener noreferrer" target="_blank">TechCrunch</a>
<strong>发布时间</strong>: 2026-03-19</p>
<p><strong>简介</strong>:
Cloudflare CEO马修·普林斯预测，到2027年，在线机器人流量将超过人类流量。这一预测基于当前AI Agent、爬虫和自动化工具的快速普及。普林斯指出，这既是机遇也是挑战——企业需要优化网站以适应机器访问，同时也要加强安全防护，防止恶意机器人的攻击。这一转变将深刻影响互联网生态，从内容分发到广告投放都可能重新定义。</p>
<hr />
<h4><a href="https://techcrunch.com/2026/03/19/meta-rolls-out-new-ai-content-enforcement-systems-while-reducing-reliance-on-third-party-vendors/" rel="noopener noreferrer" target="_blank">Meta rolls out new AI content enforcement systems while reducing reliance on third-party vendors</a></h4>
<p><strong>来源</strong>: <a href="https://techcrunch.com" rel="noopener noreferrer" target="_blank">TechCrunch</a>
<strong>发布时间</strong>: 2026-03-19</p>
<p><strong>简介</strong>:
Meta推出了新的AI内容执行系统，旨在更高效地管理平台内容，同时减少对第三方供应商的依赖。新系统使用自研的AI模型，能够实时识别和标记违规内容，包括仇恨言论、虚假信息、暴力内容等。Meta表示，这将提高内容审核的准确性和响应速度，同时降低成本。然而，外界对AI审核的透明度和潜在偏见仍有担忧。</p>
<hr />
<h4><a href="https://techcrunch.com/2026/03/19/doordash-launches-a-new-tasks-app-that-pays-couriers-to-submit-videos-to-train-ai/" rel="noopener noreferrer" target="_blank">DoorDash launches a new ‘Tasks’ app that pays couriers to submit videos to train AI</a></h4>
<p><strong>来源</strong>: <a href="https://techcrunch.com" rel="noopener noreferrer" target="_blank">TechCrunch</a>
<strong>发布时间</strong>: 2026-03-18</p>
<p><strong>简介</strong>:
外卖平台DoorDash推出了一款名为”Tasks”的新应用，付费邀请外卖员提交视频，用于训练其AI系统。这些视频可能包含外卖员的工作场景、配送路线、客户互动等内容。DoorDash表示，这将帮助优化配送算法和提升服务体验。但这一做法也引发了隐私和伦理方面的讨论——外卖员是否知情？数据如何使用？这些问题仍待明确。</p>
<hr />
<h4><a href="https://techcrunch.com/2026/03/18/nvidia-networking-division-building-a-multibillion-dollar-behemoth-to-rival-its-chips-business/" rel="noopener noreferrer" target="_blank">Nvidia is quietly building a multibillion-dollar behemoth to rival its chips business</a></h4>
<p><strong>来源</strong>: <a href="https://techcrunch.com" rel="noopener noreferrer" target="_blank">TechCrunch</a>
<strong>发布时间</strong>: 2026-03-20</p>
<p><strong>简介</strong>:
英伟达正在悄然打造一个价值数十亿美元的新业务线，旨在与其核心芯片业务形成互补。虽然具体细节尚未公开，但分析师推测，这可能涉及云计算、AI服务或软件平台等领域。如果成功，英伟达将从一家硬件公司转型为”硬+软”的综合AI解决方案提供商，进一步增强其市场护城河。这一动向值得关注，因为它可能改变AI产业的竞争格局。</p>
<hr />
<h2>🤖 AI 技术进展</h2>
<h4>Gamma adds AI image-generation tools in bid to take on Canva and Adobe</h4>
<p><strong>来源</strong>: <a href="https://techcrunch.com" rel="noopener noreferrer" target="_blank">TechCrunch</a>
<strong>发布时间</strong>: 2026-03-19</p>
<p><strong>简介</strong>:
演示文稿工具Gamma新增了AI图像生成功能，直接向Canva和Adobe发起挑战。用户只需输入文字描述，Gamma就能自动生成匹配的插图、图表和设计元素。这些图像不仅美观，而且与演示文稿的主题和风格保持一致。Gamma的这一举措进一步降低了设计门槛，让非专业用户也能快速创建高质量的视觉内容。Canva和Adobe预计将面临更大的竞争压力。</p>
<hr />
<h4>Patreon CEO calls AI companies’ fair use argument ‘bogus,’ says creators should be paid</h4>
<p><strong>来源</strong>: <a href="https://techcrunch.com" rel="noopener noreferrer" target="_blank">TechCrunch</a>
<strong>发布时间</strong>: 2026-03-18</p>
<p><strong>简介</strong>:
Patreon CEO在公开场合批评AI公司的”合理使用”（Fair Use）论点是”虚假的”。他指出，AI模型在训练过程中大量使用了创作者的内容，但这些创作者从未获得报酬。Patreon呼吁建立更公平的内容授权机制，确保创作者能够从AI的使用中受益。这一观点得到了许多创作者和版权组织的支持，AI公司正面临越来越大的道德和法律压力。</p>
<hr />
<h4>Mistral bets on ‘build-your-own AI’ as it takes on OpenAI, Anthropic in enterprise</h4>
<p><strong>来源</strong>: <a href="https://techcrunch.com" rel="noopener noreferrer" target="_blank">TechCrunch</a>
<strong>发布时间</strong>: 2026-03-20</p>
<p><strong>简介</strong>:
Mistral AI在企业AI市场采取了差异化策略，主打”自建AI”（build-your-own AI）。相比于直接提供预训练的模型，Mistral更倾向于帮助企业基于其平台定制和训练专属模型。这一策略降低了企业对OpenAI和Anthropic的依赖，同时也保护了数据隐私。Mistral表示，企业级AI市场需要的是灵活性和可控性，而非”一刀切”的解决方案。</p>
<hr />
<h4>Rebel Audio is a new AI podcasting tool aimed at first-time creators</h4>
<p><strong>来源</strong>: <a href="https://techcrunch.com" rel="noopener noreferrer" target="_blank">TechCrunch</a>
<strong>发布时间</strong>: 2026-03-19</p>
<p><strong>简介</strong>:
Rebel Audio是一款面向首次创作者的AI播客制作工具。它利用AI技术，帮助用户快速完成播客的各个环节：从脚本生成、音频录制、到后期编辑和分发。Rebel Audio特别适合那些有想法但缺乏技术背景的创作者，大大降低了播客制作的门槛。工具还提供AI驱动的声音优化和音乐推荐，让播客听起来更加专业。随着播客市场的持续增长，此类工具有望吸引更多创作者入场。</p>
<hr />
<h2>📊 行业趋势</h2>
<h3>AI Agent 正在崛起</h3>
<p>本周有多条新闻指向同一个趋势：AI Agent（智能代理）正在成为新的交互范式。</p>
<ul>
<li><strong>Nothing CEO 的预测</strong>：智能手机应用将被AI Agent取代</li>
<li><strong>LibTV 的探索</strong>：内容不仅为人类服务，也为AI Agent优化</li>
<li><strong>Google 的多模态统一</strong>：为AI Agent理解复杂世界打下基础</li>
</ul>
<p>这意味着，未来的用户体验可能从”打开应用完成任务”转变为”告诉AI Agent需求，让Agent自主调用服务”。这一转变将对应用生态、商业模式和用户习惯产生深远影响。</p>
<h3>多模态融合成为共识</h3>
<p>Google、Mistral等公司在多模态技术上取得进展：</p>
<ul>
<li><strong>Google</strong>：将文本、图片、视频、音频、PDF统一到同一向量空间</li>
<li><strong>Mistral Small 4</strong>：统一推理与多模态能力</li>
</ul>
<p>多模态融合是AI发展的必经之路，它使AI能够像人类一样，通过多种感官理解世界。这将为图像搜索、内容创作、教育培训等领域带来新的可能。</p>
<h3>企业级 AI 落地加速</h3>
<p>多家企业在AI工具和平台上的投入加大：</p>
<ul>
<li><strong>腾讯 QClaw</strong>：AI协作平台正式上线</li>
<li><strong>Notion Custom Agent</strong>：定制化AI助手提升生产力</li>
<li><strong>Cursor Composer 2</strong>：降低代码开发成本</li>
<li><strong>钉钉升级”悟空”</strong>：AI驱动企业协作</li>
</ul>
<p>这些工具的共同点是聚焦于实际应用场景，解决企业真实痛点，而非单纯展示技术实力。这表明AI已经从”炫技”阶段进入”务实”阶段。</p>
<h3>AI 与传统行业的结合加速</h3>
<ul>
<li><strong>贝佐斯的千亿美元计划</strong>：用AI改造传统制造业</li>
<li><strong>OPPO 的AI手机</strong>：从尝鲜走向常用</li>
<li><strong>小鹏 P7 Max</strong>：智能驾驶能力升级</li>
</ul>
<p>AI不再局限于互联网和科技行业，而是深入到制造、汽车、金融等传统领域。这种融合将催生新的商业模式和就业机会，同时也带来转型期的挑战。</p>
<h3>内容生态面临重构</h3>
<ul>
<li><strong>Patreon 的声音</strong>：批评AI公司的”合理使用”论点，呼吁创作者获得报酬</li>
<li><strong>Meta 的新系统</strong>：AI内容审核减少对第三方的依赖</li>
<li><strong>DoorDash 的Tasks</strong>：付费收集用户视频训练AI</li>
</ul>
<p>这些事件反映了内容生态的矛盾与调整：AI既能高效生产内容，也可能侵犯创作者权益；AI可以审核内容，但透明度存疑；AI需要训练数据，但数据获取方式引发争议。未来，内容生态需要在效率、公平和隐私之间找到平衡。</p>
<hr />
<h2>🎯 重点关注</h2>
<h3>OpenAI 与军方合作引发争议</h3>
<p>本周有报道指出，OpenAI 正在与AWS合作，扩大其政府业务。与此同时，美国国防部表示，Anthropic的某些政策”红线”构成”国家安全风险”。这反映出AI公司在政府合作上的两难境地：一方面，政府是重要的客户和市场；另一方面，与军方的合作可能引发伦理争议。AI公司需要谨慎定义自己的”红线”，避免损害公众信任。</p>
<h3>AI 的性别平等问题</h3>
<p>Rana el Kaliouby（Affectiva创始人）警告，AI行业的”男孩俱乐部”可能扩大女性的财富差距。她指出，AI公司的高层和技术团队中男性占主导地位，这导致AI产品可能忽视女性需求，同时也影响女性的职业发展。解决这一问题需要从招聘、晋升、文化建设等多方面入手，推动AI行业的多样性。</p>
<h3>AI 在电商的应用探索</h3>
<p>DoorDash推出的Tasks应用，展示了AI在电商和O2O服务中的一种应用方向：通过收集用户生成内容（视频）来训练AI，优化服务。这种模式具有潜力，但也需要解决隐私、透明度和用户授权等问题。未来，我们可能会看到更多类似的AI应用场景，从外卖出行到零售金融。</p>
<hr />
<h2>📌 本期总结</h2>
<p>本周科技/AI领域动态密集，核心趋势可以概括为：</p>
<ol>
<li><strong>AI Agent 范式崛起</strong> - 应用形态可能发生根本性改变</li>
<li><strong>多模态技术突破</strong> - Google、Mistral统一多模态处理</li>
<li><strong>企业级 AI 加速落地</strong> - 钉钉、Notion、Cursor等工具聚焦务实场景</li>
<li><strong>AI 改造传统行业</strong> - 贝佐斯计划用AI重塑制造业</li>
<li><strong>内容生态重构进行时</strong> - Patreon、Meta、DoorDash探索新模式</li>
</ol>
<p>下周我们将继续关注这些趋势的发展，特别是AI Agent的落地应用和多模态技术的商业化进展。</p>
<hr />
<p><em>图片来源：少数派、品玩、TechCrunch 首页截图</em>
<em>新闻来源：少数派、品玩、TechCrunch</em></p>]]></content>
    <category term="AI" />
    <category term="科技" />
    <category term="每周速闻" />
    <category term="新闻" />
  </entry>
  <entry>
    <title>tmux 使用技巧与配置</title>
    <link href="https://ethyoung.me//posts/tmux-tips" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/tmux-tips</id>
    <updated>2026-03-05T00:00:00.000Z</updated>
    <published>2026-03-05T00:00:00.000Z</published>
    <author>
      <name>Ethan</name>
    </author>
    <summary type="text">tmux 终端复用器的实用技巧、完整配置方案及智能命令集合</summary>
    <content type="html"><![CDATA[<img src="https://ethyoung.me/_astro/cover.Epj1Dgd6_ZhWB93.webp" alt="tmux 使用技巧与配置" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h3>快捷键使用说明</h3>
<p>一个常见的误区：<code>Ctrl+b d</code> 分离会话时需要<strong>先按 <code>Ctrl+b</code> 并松开</strong>，然后再按 <code>d</code>，而不是同时按下所有键。</p>
<h2>完整配置方案</h2>
<h3>一、安装依赖</h3>
<pre><code># 安装 fzf (模糊查找工具)
brew install fzf
$(brew --prefix)/opt/fzf/install

# 安装 tmux
brew install tmux

# 安装 Oh My Zsh (可选，用于 shell 增强)
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

# 安装 TPM (tmux 插件管理器)
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
</code></pre>
<h3>二、tmux 配置 (~/.tmux.conf)</h3>
<h4>基础设置</h4>
<pre><code># 启用鼠标支持
set -g mouse on

# 历史记录行数
set -g history-limit 10000

# 窗口和面板索引从 1 开始
set -g base-index 1
setw -g pane-base-index 1

# 减少按键延迟
set -s escape-time 0

# 终端颜色支持
set-option -g default-terminal "screen-256color"
</code></pre>
<h4>状态栏美化</h4>
<pre><code># 状态栏样式
set -g status-style bg=black,fg=white
set -g status-left-length 30
set -g status-right-length 150

# 左侧显示：会话名称
set -g status-left "#[fg=green]#S #[fg=yellow]|#[default]"

# 右侧显示：日期时间
set -g status-right "#[fg=cyan]%Y-%m-%d %H:%M:%S#[default]"
</code></pre>
<h4>插件管理</h4>
<pre><code># TPM 插件列表
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tmux-resurrect'    # 会话保存/恢复
set -g @plugin 'tmux-plugins/tmux-continuum'    # 自动保存会话

# 自动保存/恢复配置
set -g @continuum-restore 'on'              # 启动时自动恢复
set -g @continuum-save-interval '5'         # 每 5 分钟自动保存
</code></pre>
<h4>自定义快捷键</h4>
<pre><code># 修改前缀键为 Ctrl+a
unbind C-b
set-option -g prefix C-a
bind-key C-a send-prefix

# 分割窗口快捷键
bind | split-window -h    # 垂直分割
bind - split-window -v    # 水平分割

# 重载配置文件
bind r source-file ~/.tmux.conf \; display-message "Config reloaded!"
</code></pre>
<h4>初始化 TPM</h4>
<pre><code># 必须放在配置文件末尾
run '~/.tmux/plugins/tpm/tpm'
</code></pre>
<blockquote><p><strong>提示</strong>：首次使用时，进入 tmux 后按 <code>Ctrl+a</code> + <code>I</code> (大写) 安装所有插件。</p></blockquote>
<h3>三、智能命令别名 (~/.zshrc)</h3>
<h4>基础命令别名</h4>
<pre><code># 常用命令简化
alias t='tmux'                    # 启动 tmux
alias tn='tmux new -s'            # 创建新会话
alias ta='tmux attach -t'         # 附加到会话
alias tls='tmux ls'               # 列出会话
alias tk='tmux kill-session -t'   # 删除会话
alias tka='tmux kill-server'      # 关闭所有会话
alias tr='tmux rename-session -t' # 重命名会话
alias tw='tmux new-window -n'     # 创建新窗口
alias tl='tmux list-windows'      # 列出窗口
alias td='tmux detach'            # 分离会话
</code></pre>
<h4>智能会话管理 (集成 fzf)</h4>
<pre><code># 智能附加/创建会话
tma() {
    # 检查是否在交互式终端中
    if [ ! -t 1 ]; then
        echo "❌ 当前环境不是交互式终端，无法使用 fzf 选择"
        echo "请在终端中运行 tma 命令"
        return 1
    fi

    # 无参数时使用 fzf 选择会话
    if [ -z "$1" ]; then
        local session
        session=$(tmux ls 2&gt;/dev/null | awk -F: '{print $1}' | fzf --height 40% --reverse --border)
        if [ -n "$session" ]; then
            tmux attach -t "$session"
        else
            echo "未选择任何 tmux 会话"
        fi
    else
        # 有参数时尝试附加，失败则创建新会话
        tmux attach -t "$1" 2&gt;/dev/null || tmux new -s "$1"
    fi
}
</code></pre>
<h4>快速切换窗口</h4>
<pre><code># 使用 fzf 快速切换窗口
tmw() {
    # 检查是否在交互式终端中
    if [ ! -t 1 ]; then
        echo "❌ 当前环境不是交互式终端，无法使用 fzf 选择窗口"
        echo "请在终端中运行 tmw 命令"
        return 1
    fi

    # 确认是否在 tmux 会话中
    if [ -z "$TMUX" ]; then
        echo "⚠️ 当前不在 tmux 会话中，请先进入 tmux 再使用 tmw"
        return 1
    fi

    # 获取窗口列表并使用 fzf 选择
    local target
    target=$(tmux list-windows -F '#I: #W' | fzf --height 40% --reverse --border)

    if [ -n "$target" ]; then
        local win_id
        win_id=$(echo "$target" | cut -d: -f1 | tr -d ' ')
        tmux select-window -t "$win_id"
    else
        echo "未选择任何窗口"
    fi
}
</code></pre>
<h4>会话保存与恢复</h4>
<pre><code># 手动保存/恢复会话
alias tsave='tmux run-shell ~/.tmux/plugins/tmux-resurrect/scripts/save.sh'
alias trestore='tmux run-shell ~/.tmux/plugins/tmux-resurrect/scripts/restore.sh'
</code></pre>
<h4>常用会话快捷方式</h4>
<pre><code># 快速进入预定义会话
alias tdev='tmux attach -t dev || tmux new -s dev'
alias twork='tmux attach -t work || tmux new -s work'
alias tplay='tmux attach -t play || tmux new -s play'
</code></pre>
<h4>FZF 增强配置</h4>
<pre><code># FZF 默认选项
export FZF_DEFAULT_OPTS='--height 40% --reverse --border --preview-window=down:3:hidden:wrap'

# FZF 文件搜索命令 (需要安装 fd)
export FZF_CTRL_T_COMMAND='fd --type f --hidden --follow --exclude .git'
</code></pre>
<h2>使用示例</h2>
<pre><code># 创建或附加到名为 "work" 的会话
tma work

# 不带参数时弹出 fzf 选择器
tma

# 在 tmux 会话中使用 fzf 快速切换窗口
tmw
</code></pre>]]></content>
    <category term="tmux" />
  </entry>
  <entry>
    <title>Tmux 环境下的 Shell 配置隔离问题与解决方案</title>
    <link href="https://ethyoung.me//posts/tmux-shell-isolation" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/tmux-shell-isolation</id>
    <updated>2026-01-27T00:00:00.000Z</updated>
    <published>2026-01-27T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">#tmux #shell #配置管理 #进程隔离</summary>
    <content type="html"><![CDATA[<img src="https://ethyoung.me/_astro/cover.Epj1Dgd6_ZhWB93.webp" alt="Tmux 环境下的 Shell 配置隔离问题与解决方案" style="width: 100%; height: auto; margin-bottom: 1em;" />
<h2>问题场景</h2>
<p>最近在开发一个 tmux 窗口快速切换功能时，遇到了一个有趣的问题：</p>
<pre><code># 定义了一个窗口切换函数
tmw() {
  # 使用 fzf 选择并切换 tmux 窗口
  local target=$(tmux list-windows -F '#{window_index}:#{window_name}' | fzf ...)
  tmux select-window -t "${target%%:*}"
}
</code></pre>
<p>奇怪的是：</p>
<ul>
<li>✅ 在外层 shell 中 <code>source ~/.zshrc</code> 后，函数定义正常</li>
<li>❌ 但在 tmux 会话内运行 <code>tmw</code> 时，执行的仍是旧版本</li>
<li>🤔 即使多次 <code>source ~/.zshrc</code>，tmux 内部的函数也不更新</li>
</ul>
<h2>根本原因：Shell 环境隔离</h2>
<h3>1. 进程隔离机制</h3>
<pre><code>外层 Shell (PID: 1000)
├── tmux server (PID: 1001)
    ├── tmux session "dev"
        ├── window 0: zsh (PID: 1002) ← 独立的 shell 进程
        ├── window 1: zsh (PID: 1003) ← 另一个独立的 shell 进程
        └── window 2: vim (PID: 1004)
</code></pre>
<p><strong>关键理解</strong>：tmux 中的每个窗口都运行着<strong>独立的 shell 进程</strong>，它们：</p>
<ul>
<li>有自己的环境变量空间</li>
<li>有自己的函数定义作用域</li>
<li>不会自动继承外层 shell 的配置更新</li>
</ul>
<h3>2. 配置加载时机</h3>
<pre><code># 时间线分析
10:00 - 启动外层 shell，加载 ~/.zshrc (版本A)
10:05 - 进入 tmux，创建新 shell 进程，继承当时的配置 (版本A)
10:10 - 修改配置文件 (版本B)
10:15 - 外层 shell: source ~/.zshrc  ← 只更新外层 shell 为版本B
10:20 - tmux 内运行函数  ← 仍使用版本A！
</code></pre>
<h2>解决方案对比</h2>
<h3>方案一：tmux 内部重新加载 ⭐️</h3>
<pre><code># 在 tmux 会话内执行
source ~/.zshrc
# 或者直接加载特定配置
source ~/.config/zsh/.tmux_cfg
</code></pre>
<p><strong>优点</strong>：快速、精确
<strong>缺点</strong>：每个窗口都需要单独执行</p>
<h3>方案二：Detach &amp; Reattach</h3>
<pre><code># 退出当前会话
tmux detach

# 重新进入（会创建新的 shell 进程）
tmux attach -t session-name
</code></pre>
<p><strong>优点</strong>：一次性解决所有窗口
<strong>缺点</strong>：中断当前工作流</p>
<h3>方案三：tmux 广播命令 🚀</h3>
<pre><code># 向所有窗口发送重载命令
tmux send-keys -t session-name: 'source ~/.zshrc' C-m

# 或者写成函数
treload() {
  local session="${1:-$(tmux display-message -p '#S')}"
  tmux list-windows -t "$session" -F '#{window_index}' | \
  while read window; do
    tmux send-keys -t "${session}:${window}" 'source ~/.zshrc' C-m
  done
}
</code></pre>
<p><strong>优点</strong>：批量更新，不中断工作流
<strong>缺点</strong>：稍复杂，可能干扰正在运行的命令</p>
<h2>最佳实践建议</h2>
<h3>1. 开发时的配置管理</h3>
<pre><code># ~/.config/zsh/dev-utils.zsh
# 开发期间的快速重载函数
dev_reload() {
  echo "🔄 重载配置..."
  source ~/.zshrc
  echo "✅ 配置已更新"

  # 如果在 tmux 中，提醒其他窗口
  if [[ -n "$TMUX" ]]; then
    echo "💡 提示：其他 tmux 窗口需要手动重载"
  fi
}

alias dr='dev_reload'
</code></pre>
<h3>2. 生产环境的配置策略</h3>
<pre><code># 在 ~/.zshrc 中添加版本检查
export ZSH_CONFIG_VERSION="2024.01.27"

check_config_version() {
  local expected_version="2024.01.27"
  if [[ "$ZSH_CONFIG_VERSION" != "$expected_version" ]]; then
    echo "⚠️  配置版本过期，建议重载: source ~/.zshrc"
  fi
}

# 在 tmux 窗口启动时检查
if [[ -n "$TMUX" ]]; then
  check_config_version
fi
</code></pre>
<h3>3. 自动化解决方案</h3>
<pre><code># ~/.tmux.conf
# 绑定快捷键快速重载所有窗口配置
bind R run-shell '\
  for window in $(tmux list-windows -F "#{window_index}"); do \
    tmux send-keys -t :$window "source ~/.zshrc" C-m; \
  done; \
  tmux display-message "已重载所有窗口配置"'
</code></pre>
<h2>延伸思考</h2>
<p>这个问题揭示了几个重要的系统概念：</p>
<ol>
<li><strong>进程隔离</strong>：每个进程有独立的内存空间和环境</li>
<li><strong>配置继承</strong>：子进程只继承创建时父进程的环境</li>
<li><strong>状态管理</strong>：分布式环境下的配置同步挑战</li>
</ol>
<p>类似的场景还出现在：</p>
<ul>
<li>Docker 容器内的环境变量更新</li>
<li>SSH 会话中的配置同步</li>
<li>IDE 集成终端的环境隔离</li>
</ul>
<h2>总结</h2>
<p>Tmux 的 shell 环境隔离是一个设计特性，不是 bug。理解这个机制有助于：</p>
<ul>
<li>🎯 更好地管理开发环境</li>
<li>🔧 避免配置不生效的困扰</li>
<li>🚀 设计更健壮的配置管理策略</li>
</ul>
<p>记住：<strong>修改配置后，别忘了在 tmux 内部也要重新加载！</strong></p>]]></content>
    <category term="tmux" />
  </entry>
  <entry>
    <title>2025 年 ， end 随笔</title>
    <link href="https://ethyoung.me//posts/2025-year-end" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/2025-year-end</id>
    <updated>2026-01-04T00:00:00.000Z</updated>
    <published>2026-01-04T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">回顾 2025 年的生活、工作和成长，总结经验与感悟。</summary>
    <content type="html"><![CDATA[
<p>拾掇了一下 2025 年的计划，最终还是决定把它们合入到 2026 年的计划里。</p>
<p>回头看这一年，整体状态基本维持原样。年初计划去做的一些事情，如今回想起来，可以说是几乎全军覆没。</p>
<p>时常会感叹：人生里的梦想铺得很大，而现实真正能施展的力量却始终有限。</p>
<h2>生活</h2>
<p>生活上基本没有什么改变，依然是朝九晚五的工作节奏，偶尔加班，日子一天天往前走。</p>
<p>今年最大的变化，来自小朋友。慢慢长大，开始上幼儿园，生活的重心也随之发生了变化。外出游玩的次数比往年多了一些，大概去了三座城市，看了些不同的风景。</p>
<p>总体而言，生活依旧平稳。</p>
<h2>技术</h2>
<p>技术上谈不上有什么突破，依然是熟悉的技术栈：前端为主，搭配一些后端工作。</p>
<p>这一年更多是在对过去的项目做升级和改造，让它们变得更稳定、更可维护，新的技术尝试并不多。</p>
<p>倒是慢慢学会了如何去 <em>vibe coding</em>。程序员这份职业有时候确实够”狠”——优化和重构起来，干掉过去的自己从不手软。</p>
<h2>读书</h2>
<p>基本没怎么看书。</p>
<h2>写作</h2>
<p>写作方面同样没有太多进展，博客更新频率依然偏低。</p>
<p>主要还是零散地写了一些技术文章，记录并分享工作中的经验。整体来看，输出依旧不多，离自己期望的状态还有不小的距离。</p>
<h2>Finally</h2>
<p>总体而言，2025 年过得相对平淡，没有明显的高峰，也没有太深的低谷。</p>
<p>希望 2026 年能在这种平稳之中找到一些新的可能。至于具体计划，这里就先不展开了——顺其自然吧。</p>]]></content>
    <category term="总结" />
    <category term="Life" />
  </entry>
  <entry>
    <title>我也来回答这些问题吧</title>
    <link href="https://ethyoung.me//posts/blog-interview" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/blog-interview</id>
    <updated>2025-08-18T00:00:00.000Z</updated>
    <published>2025-08-18T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">我也来回答这些问题吧</summary>
    <content type="html"><![CDATA[
<p>最近看到 <a href="https://anotherdayuw.com/2024/5962/" rel="noopener noreferrer" target="_blank">博客作者呀，我想采访你这 9 个问题！</a> 和 <a href="https://flowersink.com/blog/blog-detail/54" rel="noopener noreferrer" target="_blank">好耶，我来回答这 9 个问题！——博客问卷</a> 的文章，觉得挺有意思的，所以我也来回答这些问题吧。</p>
<h2>简单介绍下自己或者你的博客</h2>
<p>我叫 Ethan Young，也可叫 charles Young， 反正代号而已， 是一名常驻南京的前端 web 开发，博客地址是 <a href="https://ethanyoung.me" rel="noopener noreferrer" target="_blank">https://ethanyoung.me</a>。博客主要记录我的生活、分享技术、表达思考和见证成长。充当自己的树洞吧。</p>
<h2>什么契机让你开始写博客？</h2>
<p>其实我并不擅长写作，表达能力也有限，加上性格偏内向。有时候觉得写博客就像是把自己的内心世界暴露在公共场合，仿佛在众人面前”裸奔”。但转念一想，观众本就不多，而且大家彼此并不熟悉。或许这种”坦诚相见”、剖析自我的过程，反而能让我更清楚地认识自己。</p>
<p>人生其中一道命题就是：我是谁？</p>
<p>你的经历，你自己的决定，决定了你是谁吧。</p>
<p>答案暂定吧～</p>
<h2>你是如何完成创作的？</h2>
<p>我通常会在工作之余抽出时间来写作。使用 Obsidian 记录新的想法或经历，慢慢整理成一篇文章。</p>
<h2>运营博客的过程中是否有失去过动力？如果有，是为什么恢复的？如果没有，请问您又是如何保持创作的激情？</h2>
<p>失去过；有过多次，但每次都能恢复；第一版博客因为成本问题，需要服务器，前端技术又不够成熟，页面各种 bug，放弃了；</p>
<p>第二版博客因为工作太忙，没时间维护，放弃了；</p>
<p>第三版，一直维持到现在</p>
<h2>如何搭建博客，以及运营博客每年需要投入的资金？</h2>
<ol>
<li>博客使用的是 大名鼎鼎的 nextjs，没有前后端分离,UI 使用的是 tailwindCss，内容使用 markdown 或者 MDX 语法编写。</li>
<li>博客部署在 Vercel 上，免费版就够用了。</li>
<li>域名买的是 namesilo，年费大概 99 元。</li>
<li>DNS 使用的是 Cloudflare，免费版。</li>
</ol>
<h2>推荐 1 篇你博客中的文章，并推荐一个你喜欢读的博客，聊聊原因</h2>
<p>自己推荐博客是<a href="https://ethyoung.me/2025-01-29" rel="noopener noreferrer" target="_blank">2024：转折与成长的一年</a></p>
<p>推荐<a href="https://darmau.co/zh/article/which-country-do-you-love" rel="noopener noreferrer" target="_blank">你爱的是哪个国？</a> 虽然文章所阐述的部分观点我不认同，但文章还是不错的。</p>
<h2>推荐 1 个近期喜欢的事物？</h2>
<p>电视《异形：地球》，对于异形系列的科幻迷来说，这部剧集是一个不错的补充，剧情还行可看。</p>
<p>电影《超人》，将以前的超人由神降为了人，有点弱，剧情一般般；不是很好看</p>
<h2>想做还没有做的事，或想尝试还没有尝试的主题？</h2>
<p>想做的事情很多，画画，摄影，骑行，或者做个小小的开源项目, 只是一直没有开始而已</p>
<h2>写到这里，闭上你的眼睛，深呼吸几分钟，或是出去溜达一圈，然后回来写任何你想写的东西</h2>
<p>人生路漫漫，做好自己</p>]]></content>
    <category term="Life" />
    <category term="Interview" />
  </entry>
  <entry>
    <title>学习 Vim 的过程</title>
    <link href="https://ethyoung.me//posts/learn-vim" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/learn-vim</id>
    <updated>2025-07-29T00:00:00.000Z</updated>
    <published>2025-07-29T00:00:00.000Z</published>
    <author>
      <name>Ethan</name>
    </author>
    <summary type="text">学习 Vim 的过程，从入门到熟悉的过程。</summary>
    <content type="html"><![CDATA[
<blockquote><p>学习 vim 时，其实就是尽量减少手指离开键盘的频率</p></blockquote>
<h2>过程</h2>
<p>其实入门简单，我是直接在控制台上面输入<code>vimtutor</code>，然后跟着教程一步步来。各个操作都可以在教程中找到。</p>
<p>问题点在于，实际使用中，总有些快捷键不记得。需要不断的进行去查找。</p>
<p>后来看到一款编辑器，叫 neovim，基于 vim 的改进版，支持插件系统，社区活跃。</p>
<p>试着去配置时，有种写代码感觉，本身配置语言就是 Lua，配置文件也可以像代码一样进行组织</p>
<p>但还是感觉很麻烦，看了一圈配置，最终还是选择 lazvim 进行一站式配置。</p>
<p>目前使用的 lazyvim 配置， 逐步熟悉 vim 操作，关键在于大量的练习，改变过去基于 vscode 按键肌肉记忆，有点难度。</p>
<h2>问题</h2>
<ul>
<li>习惯了 vscode 的快捷键，切换到 vim 时，按键会有些不适应， 目前 vscode 借助的 neovim 插件，总感觉体验有点割裂</li>
<li>由于项目使用 vue2+pug，这样在编辑器环境中，这些语法高亮和代码提示都不太好，目前只能使用 webstorm 进行编辑，
但 webstorm 的快捷键又和 vim 不对，使用了 ideaVim 插件，配置部分 <a href="https://gist.github.com/mikeslattery/d2f2562e5bbaa7ef036cf9f5a13deff5" rel="noopener noreferrer" target="_blank">mikeslattery/.idea-lazy.vim</a>
感觉还行，95% 的快捷键都能使用。</li>
<li>同时切换到 vim 时，git GUI 使用了 lazygit 和 webstorm 相互配置工作。 webstorm git 解决冲突， lazygit 进行提交和查看日志。</li>
</ul>
<h2>finally</h2>
<ul>
<li>希望自己能坚持吧～</li>
</ul>]]></content>
    <category term="vim" />
    <category term="编辑器" />
    <category term="学习笔记" />
  </entry>
  <entry>
    <title>Surge 自定义配置，踩坑记录</title>
    <link href="https://ethyoung.me//posts/surge-custom-config" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/surge-custom-config</id>
    <updated>2025-06-23T00:00:00.000Z</updated>
    <published>2025-06-23T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">记录在 Surge 中设置自定义策略组时遇到的问题及解决方案，帮助用户正确配置网络流量走向。</summary>
    <content type="html"><![CDATA[
<h2>背景</h2>
<p>在 surge 的自定义策略组中，我设置了 github 策略组，并且设置了一个默认的策略组为 <code>github</code>，用于将所有 github 所有数据走当前的策略组。</p>
<p>而我将此策略组模式选择了 US 策略，以告之当前机器的 github 全部流量走 US 节点。</p>
<h2>问题</h2>
<p>问题在于我设置了 US 节点，但是没有任何作用，通过查看器观察到的流量还是走的默认节点。</p>
<img src="./assets/1750666842698.png" alt="1750666842698.png" />
<h2>分析</h2>
<p>经过分析，发现 surge 的策略组是自上而下穿透的，因此需要将 github 策略组放在全局策略组之前。</p>
<img src="./assets/1750667025869.png" alt="1750667025869.png" />]]></content>
    <category term="surge" />
    <category term="Tech" />
  </entry>
  <entry>
    <title>记录一次引入循环，导致undefined</title>
    <link href="https://ethyoung.me//posts/circle-denpenth" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/circle-denpenth</id>
    <updated>2025-06-04T00:00:00.000Z</updated>
    <published>2025-06-04T00:00:00.000Z</published>
    <author>
      <name>Ethan</name>
    </author>
    <summary type="text">记录在项目中引入循环依赖导致 undefined 错误的过程，分析问题原因并分享解决方案。</summary>
    <content type="html"><![CDATA[
<h1>是什么</h1>
<p>循环依赖(Circular dependency)是指在模块系统中，两个或多个模块形成相互引用的依赖关系。这种情况会导致模块加载和初始化过程变得复杂且难以预测。</p>
<h3>循环依赖示例</h3>
<p>如下图所示，循环依赖可以是直接的相互引用，也可以是间接的依赖环：</p>
<pre><code>flowchart TD
    subgraph "循环依赖结构"
        ModuleA((模块A)) --&gt;|导入| ModuleB((模块B))
        ModuleB --&gt;|导入| ModuleA
    end

    subgraph "间接循环依赖"
        ModC((模块C)) --&gt;|导入| ModD((模块D))
        ModD --&gt;|导入| ModE((模块E))
        ModE --&gt;|导入| ModC
    end
</code></pre>
<h3>循环依赖引发的常见问题</h3>
<pre><code>flowchart TD
    subgraph "引发问题"
        Problems[循环依赖] --&gt; P1[未完成的对象初始化]
        Problems --&gt; P2[undefined值]
        Problems --&gt; P3[执行顺序不可预测]
        Problems --&gt; P4[调试困难]
    end
</code></pre>
<h3>问题详解</h3>
<ol>
<li><strong>未完成的对象初始化</strong>：模块在被完全初始化前就被引用，导致访问到不完整的对象</li>
<li><strong>undefined值</strong>：尝试访问尚未定义的导出，导致运行时错误</li>
<li><strong>执行顺序不可预测</strong>：不同的模块系统处理循环依赖的方式不同，造成代码行为难以预测</li>
<li><strong>调试困难</strong>：错误堆栈跟踪混乱，难以定位问题根源</li>
</ol>
<h1>为什么？</h1>
<pre><code>flowchart TD
    subgraph "模块加载过程"
        Start[开始加载] --&gt; LoadA[开始加载模块A]
        LoadA --&gt; A1[解析模块A的依赖]
        A1 --&gt; A2[发现模块A依赖模块B]
        A2 --&gt; LoadB[开始加载模块B]
        LoadB --&gt; B1[解析模块B的依赖]
        B1 --&gt; B2[发现模块B依赖模块A]

        B2 --&gt; Decision{模块系统如何处理?}

        Decision --&gt;|CommonJS| CJS[返回未完成的A]
        CJS --&gt; B3[完成模块B的初始化]
        B3 --&gt; A3[继续模块A的初始化]
        A3 --&gt; Done[加载完成]

        Decision --&gt;|ES Modules| ESM[完成构造阶段]
        ESM --&gt; ESM2[执行模块A代码]
        ESM2 --&gt; ESM3[执行模块B代码]
        ESM3 --&gt; Done

        Decision --&gt;|未处理| Error[运行时错误]
    end
</code></pre>
<h1>如何解决？</h1>

























<table><thead><tr><th>方法</th><th>示例</th><th>说明</th></tr></thead><tbody><tr><td>✅ 延迟加载</td><td><code>require()</code> 写到函数内部</td><td>避免初始化阶段访问未定义</td></tr><tr><td>✅ 提取公共模块</td><td>建立 shared.js</td><td>A 和 B 不直接依赖彼此</td></tr><tr><td>✅ 重构依赖方向</td><td>抽象高层逻辑模块</td><td>减少相互耦合</td></tr></tbody></table>
<h1>实际案例分析</h1>
<h2>全国易捷工单退款的循环依赖问题</h2>
<h3>现象</h3>
<p>当从工单列表进入工单详情时，导致页面展示空白。代码报错：</p>
<pre><code>hook.js:608 TypeError: _pages_cashRegister_ThirdPlatformService_SinopecCompService__WEBPACK_IMPORTED_MODULE_16__.SinopecCompService is not a constructor
    at ./src/pages/cashRegister/ThirdPlatformService/service.js (service.js:12:68)
    at __webpack_require__ (bootstrap:853:1)
    at fn (bootstrap:150:1)
    at ./src/class/cashRegister/GatheringInfo.js (42.js:725:107)
    at __webpack_require__ (bootstrap:853:1)
    at fn (bootstrap:150:1)
    at ./src/class/cashRegister/index.js (index.js:1:1)
    at __webpack_require__ (bootstrap:853:1)
    at fn (bootstrap:150:1)
    at ./src/pages/cashRegister/ThirdPlatformService/SinopecCompService.js (42.js:3437:78)
overrideMethod @ hook.js:608
s2.f6yc.comnull/:1
</code></pre>
<h3>定位过程</h3>
<p>错误日志中关键信息：</p>
<p><code>_pages_cashRegister_ThirdPlatformService_SinopecCompService__WEBPACK_IMPORTED_MODULE_16__.SinopecCompService is not a constructor</code></p>
<p>报错位置在：</p>
<p><code>at ./src/pages/cashRegister/ThirdPlatformService/service.js (service.js:12:68)</code></p>
<p>相关代码：</p>
<pre><code>import { SinopecCompService } from '@/pages/cashRegister/ThirdPlatformService/SinopecCompService'
import { ChinaSinopecCashPayService } from '@/pages/cashRegister/ThirdPlatformService/ChinaSinopecCashPayService'
import { ChinaSinopecService } from '@/pages/cashRegister/ThirdPlatformService/ChinaSinopecService'

const serviceList = [
  new SinopecService(),
  new MasterTooService(),
  new SinopecCompService(),
  new ChinaSinopecCashPayService(),
  new ChinaSinopecService(),
]
</code></pre>
<p>报错原因是SinopecCompService不是一个构造函数。我们尝试打印SinopecCompService：</p>
<img src="./images/6846f96863ce6.png" alt="CleanShot_2025-06-09_at_10.06.10.png" />
<p>报错信息表明<code>SinopecCompService</code>在被实例化时不是一个构造函数，实际上它在调用时是<code>undefined</code>。</p>
<h3>调试过程</h3>
<p>我们将断点打到SinopecCompService对象上面：</p>
<img src="./images/6846f96b344bf.png" alt="CleanShot_2025-06-09_at_10.27.13.png" />
<p>得到以下的堆栈，分析调用问题：</p>
<pre><code>./src/pages/cashRegister/ThirdPlatformService/service.js (service.js:13)
__webpack_require__ (bootstrap:853)
fn (bootstrap:150)
./src/class/cashRegister/GatheringInfo.js (42.js:725)
__webpack_require__ (bootstrap:853)
fn (bootstrap:150)
./src/class/cashRegister/index.js (index.js:1)
__webpack_require__ (bootstrap:853)
fn (bootstrap:150)
./src/pages/cashRegister/ThirdPlatformService/SinopecCompService.js (42.js:3437)
__webpack_require__ (bootstrap:853)
fn (bootstrap:150)
./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/pages/newMaintain/pages/ViewDetail/index.vue?vue&amp;type=script&amp;lang=js&amp; (NewMaintainDetailView.js:1028)
__webpack_require__ (bootstrap:853)
fn (bootstrap:150)
./src/pages/newMaintain/pages/ViewDetail/index.vue?vue&amp;type=script&amp;lang=js&amp; (index.vue:1)
__webpack_require__ (bootstrap:853)
fn (bootstrap:150)
./src/pages/newMaintain/pages/ViewDetail/index.vue (index.vue:1)
__webpack_require__ (bootstrap:853)....
</code></pre>
<p>通过调用堆栈，分析出循环依赖路径：</p>
<ol>
<li>
<p>从<code>ViewDetail/index.vue</code>开始，导入了<code>SinopecCompService.js</code></p>
</li>
<li>
<p>然后<code>SinopecCompService.js</code>导入了<code>Payment</code>类<code>from '@/class/cashRegister'</code></p>
</li>
<li>
<p><code>class/cashRegister/index.js</code>中导出了<code>GatheringInfo</code>等模块</p>
<pre><code>export { GatheringInfo } from './GatheringInfo'
export { GatheringConfig } from './GatheringConfig'
export { Payment } from './Payment'
export { CzkCard } from './CzkCard'
export { Coupon } from './Coupon'
</code></pre>
</li>
<li>
<p><code>GatheringInfo.js</code>导入了<code>service.js</code>中的<code>hiddenPayment</code></p>
<pre><code>import { hiddenPayment } from '@/pages/cashRegister/ThirdPlatformService/service'
</code></pre>
</li>
<li>
<p>最后<code>service.js</code>又导入了<code>SinopecCompService</code>，从而形成了循环依赖</p>
</li>
</ol>
<p>分析过程如图：</p>
<img src="./images/6846f967d4fdd.png" alt="CleanShot_2025-06-09_at_11.48.28.png" />
<h3>解决方案</h3>
<p>解决方法很简单：将原先从<code>ViewDetail/index.vue</code>开始导入<code>SinopecCompService.js</code>的逻辑移出去，打破循环依赖链。</p>]]></content>
    <category term="javascript" />
    <category term="debug" />
    <category term="循环依赖" />
  </entry>
  <entry>
    <title>群晖SSL证书替换</title>
    <link href="https://ethyoung.me//posts/replace-ssl" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/replace-ssl</id>
    <updated>2025-05-12T00:00:00.000Z</updated>
    <published>2025-05-12T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">群晖SSL证书替换,记录一下</summary>
    <content type="html"><![CDATA[
<h2>问题</h2>
<p>如果通过群晖直接申请 Let’s Encrypt 证书，会提示证书申请失败，原因是国内的 Let’s Encrypt 服务器无法访问。</p>
<h2>如何解决</h2>
<p>基本流程：</p>
<ol>
<li>从域名服务商那里申请一个免费的 SSL 证书，或者购买一个付费的 SSL 证书。然后将证书文件下载到本地。选择 nginx 格式的证书文件。
<img src="./assets/CleanShot-2025-05-10-at-19.00.15.png" alt="CleanShot 2025-05-10 at 19.00.15.png" /></li>
<li>下载解压缩包，解压缩后会得到以下文件：
<ul>
<li>域名.com_bundle.crt</li>
<li>域名.com_bundle.pem</li>
<li>域名.com.csr</li>
<li>域名.com.key</li>
</ul>
</li>
<li>将证书文件上传到群晖的 NAS 上，进入”控制面板” -&gt; “安全性” -&gt; “证书”，点击”导入”按钮，选择”从文件导入”，然后选择刚才下载的证书文件。</li>
<li>在”导入证书”页面中，选择”从文件导入”选项，然后点击”浏览”按钮，选择需要导入的证书文件。</li>
<li>证书选择 域名.com_bundle.pem，私钥选择域名.com.key。</li>
<li>点击”确定”按钮，等待证书导入完成。</li>
</ol>
<h2>特殊</h2>
<blockquote><p>由于阿里云的证书有效时间是 1 月，所有每个月都得申请，太麻烦，因此我采取曲线救国的方式。</p></blockquote>
<ol>
<li>从腾讯云申请一个免费的 SSL 证书，腾讯云的证书有效时间是 3 个月。</li>
<li>由于腾讯云需要验证 DNS 解析，所以需要在域名服务商那里添加一条 TXT 记录，验证通过后就可以申请证书了。
<img src="./assets/CleanShot-2025-05-14-at-09.11.29.png" alt="CleanShot 2025-05-14 at 09.11.29.png" /></li>
<li>进入阿里云的域名解析，添加一条 TXT 记录，内容为腾讯云提供的验证信息。</li>
<li>等待腾讯云的验证通过后，就可以申请证书了。</li>
<li>采取以上的基本流程，就可以完成替换 SSL 证书了。</li>
</ol>]]></content>
    <category term="NAS" />
    <category term="Tech" />
  </entry>
  <entry>
    <title>小小计划</title>
    <link href="https://ethyoung.me//posts/tiny-plan" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/tiny-plan</id>
    <updated>2025-02-27T00:00:00.000Z</updated>
    <published>2025-02-27T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">记录近期的计划和目标，涵盖 IPTV 项目开发、博客优化以及设备选购等方面的内容。</summary>
    <content type="html"><![CDATA[
<h2>近期计划</h2>
<p>今天，打算开始着手 IPTV 项目的开发。服务端目前先实现最基本的功能，主要是资源的检测功能。待有更好的资源后，再逐步开发后续功能。</p>
<p>同时，我也计划对博客进行升级，增加”我看过的书籍”和”电影”这两个新功能板块。今天就正式开始！</p>
<h2>关于设备</h2>
<p>其实我一直想买一台 Mac Mini，但又担心会造成性能浪费，毕竟家里通过 NAS 已经搭建了不少服务。不过 Surge 的网络管理功能确实很吸引我，这方面还是很心动的。</p>
<h2>博客优化计划</h2>
<p>在开发过程中，我注意到博客在页面切换时存在明显的闪烁问题，这影响了用户体验。计划通过以下方式解决：</p>
<ol>
<li>添加页面过渡动画效果</li>
<li>实现内容预加载机制</li>
<li>优化资源加载策略</li>
</ol>
<h2>博客计划清单</h2>
<p>接下来的博客内容规划如下：</p>
<ul>
<li> 通过 NAS 自建的各类服务介绍</li>
<li> 家庭网络架构梳理</li>
<li> IPTV 项目的开发日志</li>
<li> 博客添加 TDL（Today I Learn）板块</li>
<li> 个人作品展示页面</li>
<li> 解决页面切换闪烁问题</li>
</ul>
<h2>changelog</h2>
<ul>
<li>2025-05-29 还是无法解决博客中闪烁问题，不知道是因为 Astro 的问题还是我自己的问题。暂时先放弃了，等有时间再来解决。
<ul>
<li>博客由 Astro 切换到 Next.js，主要部署在 Vercel 上，毕竟是自家产品，而且 Next.js 的生态也更完善。</li>
</ul>
</li>
</ul>]]></content>
    <category term="Life" />
  </entry>
  <entry>
    <title>群晖安装 jenkins 遇到的问题</title>
    <link href="https://ethyoung.me//posts/synology-install-jenkins-tips" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/synology-install-jenkins-tips</id>
    <updated>2025-01-31T00:00:00.000Z</updated>
    <published>2025-01-31T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">群晖docker in docker 安装 jenkins 遇到的问题</summary>
    <content type="html"><![CDATA[
<h2>背景：</h2>
<p>前几天在群晖上安装 jenkins 时，遇到了一些问题，记录一下。由于需要打包 docker 镜像同时通过 jenkins 进行部署。</p>
<h2>问题</h2>
<ol>
<li>首先执行 docker 命令，发现提示没有权限；</li>
</ol>
<p>此时我们需要映射群晖系统系统上的 docker.sock 文件，这样 jenkins 就可以使用 docker 命令。记住需要 root 用户，不然启动失败。</p>
<ol>
<li>其次执行 docker 命令，提示 <code>docker is not command</code>；</li>
</ol>
<p>这个问题是因为 jenkins 容器中没有安装 docker 客户端，我们需要安装 docker 客户端。</p>
<h2>解决方案</h2>
<p>群晖安装 jenkins 时，需要注意的几点：</p>
<ol>
<li>
<p>一般我们在群晖上面安装 jenkins，会使用 docker 安装，这样可以方便的管理 jenkins 的版本，同时也不会影响群晖的其他服务。</p>
</li>
<li>
<p>安装 jenkins 的时候，需要映射 docker.sock 文件，这样 jenkins 可以使用 docker 命令，同时也可以使用 docker 命令启动其他容器。</p>
<pre><code>/var/run/docker.sock:/var/run/docker.sock
</code></pre>
</li>
<li>
<p>TIPS: 安装 jenkins 的时候，需要安装 docker 客户端，这样 jenkins 可以使用 docker 命令，同时也可以使用 docker 命令启动其他容器。</p>
</li>
</ol>
<h2>如何安装 docker 客户端</h2>
<h3>第一种方式</h3>
<ol>
<li>
<p>我们首先进入 docker 容器，然后安装 docker 客户端</p>
<pre><code>docker exec -it jenkins bash
</code></pre>
</li>
<li>
<p>安装 docker 客户端</p>
<pre><code>apt-get update
apt-get install -y docker.io
</code></pre>
</li>
<li>
<p>安装完成后，我们可以使用 docker 命令</p>
<pre><code> docker --version
</code></pre>
<blockquote><p>这种情况有个问题，就是每次重启 jenkins 容器，都需要重新安装 docker 客户端。</p></blockquote>
</li>
</ol>
<h3>第二种方式</h3>
<ol>
<li>
<p>我们可以通过流水线的方式，进行安装，但是需要注意的是，我们需要在 jenkins 的容器中安装 docker 客户端，这样我们可以使用 docker 命令。</p>
<pre><code>pipeline {
    agent any
    stages {
        stage('Install Docker') {
            steps {
                sh 'apt-get update'
                sh 'apt-get install -y docker.io'
            }
        }
    }
}
</code></pre>
<blockquote><p>每次构建都会安装 docker 客户端，这样就可以使用 docker 命令。但是这种方式也有个问题，就是每次构建都会安装 docker 客户端，这样会浪费时间。</p></blockquote>
</li>
</ol>]]></content>
    <category term="NAS" />
    <category term="Tech" />
  </entry>
  <entry>
    <title>2024：转折与成长的一年</title>
    <link href="https://ethyoung.me//posts/2024-review" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/2024-review</id>
    <updated>2025-01-29T00:00:00.000Z</updated>
    <published>2025-01-29T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">记录2024年的工作转变、生活点滴和个人成长</summary>
    <content type="html"><![CDATA[
<h2>工作</h2>
<h3>艰难的开始</h3>
<p>年初时，我调离原先的岗位来到公司待岗，准备转战新项目。从这一刻直到 5 月份，噩梦才刚刚开始。</p>
<p>项目接手后可以说是一穷二白：没有环境、没有代码仓库，更为恐怖的是需求不明确。有些需求连客户自己也不清楚，需要我们反向讲解。就这样，我们踏上了一条破船，准备扬帆起航。</p>
<h3>人员变动</h3>
<p>我们团队最初由一个产品、两个后端、一个前端，外加一个待产的测试组成。项目开展一个月后，第一次考验来临。作为前端，我在完成与后端的接口对接后就开始独立开发。然而在开发完第一波需求页面，准备联调时，意外发生了——对接的后端同事要离职。</p>
<p>当然前期他和说过一次，毕业五年工资没涨，因此今天去提了一下，发现被拒绝了，于是准备走人。我寻思着这哥们儿走了之后，总得有人做他的活吧。就这样等了十五天，在这十五天后端的活硬生生的停滞了。我们的 leader 也在找解决办法，问了我一句：你会不会 java？我说我只看的懂一点，要是开发不太行。他说，这样啊，这个项目比较重要，而且实在找不到人了，这样你先写点，到时候公司给你部分奖金。我无法拒绝答应了，开始了磕磕绊绊的后端代码之路。</p>
<p>其实看了一波下来，发现功能不是很复杂，就是同步数据入库，难点在于对接系统的客户给的 api 文档不明确，导致很多字段对应不上。在此我也明白了那哥们儿不容易。总之一个坑。</p>
<h3>测试着重细节</h3>
<p>还有个事儿，待产的测试回家生娃了，又来一个测试。我原以为已经黑暗了，没想到，至暗时刻才刚刚开始。一开始我们和测试讨论了下，先不用在意细节，我们先把大体流程跑通，这个很关键。可是测试像是生活在另个时空，完全就没有听我们的意见，每天过来就是盯着细节测试。什么这边颜色不对，那边按钮间距有点问题，一些无伤大雅的问题。后来我们也各退一步，不管了先让她测吧，反正到时候流程问题也会测试到。就这样我们测试流程就上来了。</p>
<h3>再将一军</h3>
<p>等到交付时候的，环境出问题了。我们没有足够的机器，导致部署的时候一台环境完全不够用。问题影响呢就是客户完全看不了我们做的 demo，很多的问题。由于当前环境的不够用，外加没有人懂后端部署，由于本人有搭建自己的博客的经验，因此临时又担当部署系统的负责人。我被迫放弃现在手头的活花了三天时间，将测试环境搭建完成。</p>
<h3>彻底失衡</h3>
<p>时间来到交付的阶段，问题累计是显而易见的。无法交付！功能没有打通。我们 4 个人之后开启了地狱般的加班模式。连续一周的通宵，那几天让我想到了什么叫生不如死，萌生离职的想法。在这样我的身体肯定是吃不消的。</p>
<h3>转机</h3>
<p>也许上天的眷顾吧，一天别的项目需要一个资深前端，让我去面试了，那边很满意。让我尽快参与到项目中去。这边的工作我就全部交出。我就在接下来的几天编写交接文档和部署文档。这样在一周后我就投入了下个项目中，算是临时解脱了。</p>
<h3>来到另一天地</h3>
<p>来到新团队后，一切都变得不同。不用加班，完成既定工作就能准时下班，不必承担额外工作。在这里，我学到了很多新知识（不仅仅是前端相关）。同事们都很专业，每个人都致力于将问题处理得尽善尽美。</p>
<p>我的问题处理思路也发生了改变：从原来的简单修复，转变为先理解问题的根本原因再进行修复。在开发需求时，更注重”可扩展性和面向对象”的思想。当时还和同事讨论为什么在前端逻辑中使用面向对象编程——虽然前端通常推崇函数式编程，但由于项目的”历史债务问题”（没有使用 TypeScript 进行类型定义），现在只能通过 class 方式来确保数据的一致性。再加上 Vue2 对 TypeScript 的支持有限，最终我们选择了这种折中的方案。</p>
<h2>博客篇</h2>
<p>我将框架由原先的 Next.js 换成了 Astro，原因在于它更加方便、简洁、迅速。此外域名续费买了三年，算是作为接下来写博客的动力吧。</p>
<h3>新计划</h3>
<p>接下来博客还是持续更新，包括功能，也许我会增加模块：</p>
<ol>
<li><strong>TIL（Today I Learned）</strong>：记录每天的学习收获</li>
<li><strong>项目进展</strong>：记录正在进行的项目。这样的记录既能减轻写作压力，又可以积累经验，方便未来回顾。</li>
</ol>
<h2>生活</h2>
<h3>家庭</h3>
<ul>
<li><strong>儿子</strong>：逐年长大的小家伙，兴趣爱好越来越多。喜欢积木，喜欢奥特曼（和老子一个德行），今年给他买了不少玩具
<img src="assets/679a3d7fa003f.png" alt="在线压缩图片 IMG 6730.png" /></li>
<li><strong>老婆</strong>：和往常一样，日常上班，下班刷剧，没什么特别的兴趣</li>
</ul>
<h3>数码生活</h3>
<p>迈入了典型的中年兴趣领域：</p>
<ul>
<li><strong>NAS</strong>：使用 Synology 920+</li>
<li><strong>充电器</strong>：Anker 240W</li>
<li><strong>路由器</strong>：软路由，刷了 iostoreOS
<img src="assets/679a3df74fe6b.png" alt="1738161652624.png" /></li>
<li><strong>智能家居</strong>：搭建了 HA，连接 Apple HomeApp 和米家应用</li>
<li><strong>HomeLab</strong>：搭建了一个轻量级的环境，支持日常应用使用（虽然在影音解码上还有些问题）</li>
</ul>
<h3>展望未来</h3>
<p>给自己立个 flag：完成一直想做的 IPTV 应用。虽然之前几次都半途而废，但 2025 年一定要推出第一个版本！</p>
<h2>年终感悟</h2>
<p>时光飞逝，白驹过隙。转眼间从校园毕业已有 10 年，从一个热血少年变成了一个略显发福的中年人。有时坐在阳台上，回想起当年在银杏树下的自己，不知是否曾经畅想过今天的模样。</p>
<h3>告别 2024</h3>
<ul>
<li>后疫情时代的一年</li>
<li>略显疲惫的一年</li>
<li>不知不觉溜走的一年</li>
</ul>]]></content>
    <category term="总结" />
    <category term="Life" />
  </entry>
  <entry>
    <title>记录一次在 Namesilo 上的封禁</title>
    <link href="https://ethyoung.me//posts/namesilo-ban" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/namesilo-ban</id>
    <updated>2025-01-23T00:00:00.000Z</updated>
    <published>2025-01-23T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">记录在 Namesilo 上注册域名时遇到的封禁问题及解决过程，分享相关经验和注意事项。</summary>
    <content type="html"><![CDATA[
<p>去年的时间点，我在 Namesilo 上注册了一个账号, 用来注册域名。由于一些原因，我在 Namesilo 上注册的域名被封禋了。这里记录一下这次封禁的经过。</p>
<h2>注册原因</h2>
<p>生活在国内，在使用任何公共服务你都的需要备案; 而且备案的过程也是非常繁琐的， 因此在国外网站注册域名是一个非常好的选择。</p>
<h2>封禁原因</h2>
<p>一天早上邮箱收到如下信息：</p>
<img src="assets/67930b801fe95.png" alt="1737689980932.png" />
<blockquote><p>原因是我的信息不完整，需要我提供一些信息。</p></blockquote>
<p>想起来当时注册信息的时候，我确实没有填写真实的信息，因为我不想让我的信息泄露出去。</p>
<h2>解封</h2>
<p>不知道如何解封，直接在回了一封邮件；寻问如何解封？ 用 chatGPT 写了封英文邮件；</p>
<p><img src="assets/67930c46b5511.png" alt="1737690178829.png" />
原以为会要等几天，没想到几个小时过后，收到回复。</p>
<img src="assets/67930e9de6cd1.png" alt="1737690779910.png" />
<p>按照邮件给出的地址，填写相关信息，等了几个小时，域名就解封了。</p>
<h2>TIPS</h2>
<p>在域名被封禁的情况下，域名管理界面是没有已经购买的域名的；</p>]]></content>
    <category term="Domain" />
    <category term="Namesilo" />
  </entry>
  <entry>
    <title>博客使用 view-transition-api 添加黑暗模式</title>
    <link href="https://ethyoung.me//posts/custom-darkmode" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/custom-darkmode</id>
    <updated>2025-01-08T00:00:00.000Z</updated>
    <published>2025-01-08T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">记录在博客中使用 view-transition-api 实现黑暗模式切换时的实现细节和遇到的问题，分享相关经验和解决方案。</summary>
    <content type="html"><![CDATA[
<p>最近进行博客重构，因此参照了 <a href="https://antfu.me/" rel="noopener noreferrer" target="_blank">antfu</a> 大神的博客的样式。</p>
<p>基本样式参照了其中，可是对于其中黑暗模式切换，被深深的吸引了，如下：</p>
<img src="assets/677e9ae05ca6f.gif" alt="CleanShot 2025-01-08 at 23.32.33.gif" />
<p>当时在想，这个是怎么做到的，为何次动画如此丝滑，我得研究下；
<img src="assets/67828a6c00acf.png" alt="1736608357113.png" /></p>
<p>放弃了～～～</p>
<p>直接吧啦源码，看到如下关键信息：</p>
<pre><code>::view-transition-old(view-transition-name)
::view-transition-new(view-transition-name)
</code></pre>
<p>印象中，这段使用的是 <strong>View Transition API</strong> 中的内容；于是乎去翻阅各种文档；发现大佬这边文章 <a href="https://arc.net/l/quote/lnuwtrci" rel="noopener noreferrer" target="_blank">页面级可视动画 View Transitions API 初体验</a>，大佬深入浅出详细说明了此 API 的作用以及使用；这里不做过多的说明；</p>
<blockquote><p>mark：View Transitions API 简化了复杂动画的实现，无需手动处理位置计算或动画控制，尤其适合页面级的场景切换和动画增强。</p></blockquote>
<img src="assets/6783c733778da.png" alt="1736689455217.png" />
<h3>流程图说明</h3>
<ol>
<li><strong>触发动画</strong>：用户通过调用 <code>document.startViewTransition()</code> 开始动画。</li>
<li><strong>捕获状态快照</strong>：浏览器在 DOM 更新前后分别捕获旧状态和新状态。</li>
<li><strong>生成动画</strong>：浏览器对比新旧快照的差异，生成过渡动画。</li>
<li><strong>执行动画</strong>：根据 CSS 控制动画的伪元素定义的规则执行动画。</li>
<li><strong>动画完成</strong>：动画结束后更新页面状态。</li>
<li><strong>移除伪元素</strong>：动画伪元素被移除，最终状态呈现。</li>
</ol>
<h2>如何扩散</h2>
<p>从上图显示效果而言，扩散是从一个点扩散到浏览器整体视窗；而视窗的最大半径我们可以通过鼠标点击或者 touch 事件来触发获取元素的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect" rel="noopener noreferrer" target="_blank"><strong><code>Element.getBoundingClientRect()</code></strong></a>：</p>
<pre><code>const rect = this.themeLabel?.getBoundingClientRect()
</code></pre>
<p>boundingClientRect 包含两个 x 和 y，代码当前相对于视窗相对位置，借用 MDN 图：</p>
<img src="assets/element-box-diagram.png" alt="Pasted image 20250111235731.png" />
<p>而此时我们需要计算由我们点击位置向外扩散圆的半径；如下图：</p>
<img src="assets/678297d3cd958.png" alt="1736611793245.png" />
<pre><code>const radius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y))
</code></pre>
<ul>
<li><code>Math.hypot</code> 计算直角三角形的斜边长度，确保动画覆盖整个视窗。</li>
<li><code>Math.max</code> 确定从触发点到视窗边缘的最远距离，确保动画从中心点覆盖整个页面。</li>
</ul>
<h3>关于 <code>innerWidth</code></h3>
<p><code>innerWidth</code> 是一个只读属性，返回窗口的文档显示区的宽度（以像素为单位）。它包括滚动条的宽度（如果有）。在计算动画扩散半径时，我们使用 <code>innerWidth</code> 来确定从触发点到视窗边缘的最远距离。</p>
<p>接下来我们定义动画绘画路径：</p>
<pre><code>const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${radius}px at ${x}px ${y}px)`]
</code></pre>
<ul>
<li><code>clipPath</code> 是一个数组，表示动画的起始和结束状态：
<ul>
<li>起始状态：半径为 0 的圆（即无显示）。</li>
<li>结束状态：覆盖整个页面的圆（最大半径）。</li>
</ul>
</li>
</ul>
<h3>创建动画</h3>
<pre><code>await document.documentElement.animate(
  { clipPath: currentTheme === 'dark' ? clipPath.reverse() : clipPath },
  {
    duration: 350,
    easing: 'ease-out',
    pseudoElement: currentTheme === 'dark' ? '::view-transition-old(root)' : '::view-transition-new(root)',
  }
).finished
</code></pre>
<p><strong>动画执行效果</strong></p>
<ol>
<li><strong>切换到深色模式</strong>：
<ul>
<li>动画从大圆过渡到小圆（反转 <code>clipPath</code>）。</li>
<li>动画目标是 <code>::view-transition-new(root)</code>。</li>
</ul>
</li>
<li><strong>切换到浅色模式</strong>：
<ul>
<li>动画从小圆过渡到大圆。</li>
<li>动画目标是 <code>::view-transition-new(root)</code>。</li>
</ul>
</li>
</ol>
<p>结合上面的流程图，在 <code>view-transition</code> 之后，使用 <code>pseudoElement</code> 精细控制新旧主题之间的过渡。</p>
<p>最终代码如下：【本次使用的是 WebComponent 进行组件的抽取】</p>
<pre><code>&lt;script&gt;
import { themeAtom } from "~/store";

class ThemeSwitcher extends HTMLElement {
  private themeLabel: HTMLLabelElement | null = null;
  private themeInput: HTMLInputElement | null = null;

  constructor() {
    super();
    this.initTheme();
  }

  private getSystemTheme(): "light" | "dark" {
    return window.matchMedia("(prefers-color-scheme: dark)").matches
      ? "dark"
      : "light";
  }

  private initTheme(): "light" | "dark" {
    const localTheme = window.localStorage.getItem("theme");
    return localTheme === "auto"
      ? this.getSystemTheme()
      : (localTheme as "light" | "dark") || "light";
  }

  private updateTheme(theme: "light" | "dark"): void {
    document.documentElement.classList.toggle("dark", theme === "dark");
    document.documentElement.style.colorScheme = theme;
    document.documentElement.setAttribute("data-theme", theme);
    this.themeLabel?.classList.toggle("swap-active", theme === "light");
    themeAtom.set(theme);
  }

  private async animateThemeTransition(x: number, y: number): Promise&lt;void&gt; {
    const currentTheme = this.initTheme();
    const radius = Math.hypot(
      Math.max(x, innerWidth - x),
      Math.max(y, innerHeight - y)
    );

    const clipPath = [
      `circle(0px at ${x}px ${y}px)`,
      `circle(${radius}px at ${x}px ${y}px)`,
    ];

    try {
      await document.documentElement.animate(
        { clipPath: currentTheme === "dark" ? clipPath.reverse() : clipPath },
        {
          duration: 350,
          easing: "ease-out",
          pseudoElement:
            currentTheme === "dark"
              ? "::view-transition-old(root)"
              : "::view-transition-new(root)",
        }
      ).finished;
    } catch (error) {
      console.error("Animation failed:", error);
    }
  }

  connectedCallback(): void {
    this.themeLabel = this.querySelector("label");
    this.themeInput = this.querySelector("input");

    if (!this.themeLabel || !this.themeInput) {
      console.error("Required elements not found");
      return;
    }

    const currentTheme = this.initTheme();
    this.updateTheme(currentTheme);

    this.themeInput.addEventListener("click", async (event) =&gt; {
      const currentTheme = this.initTheme();
      const newTheme = currentTheme === "dark" ? "light" : "dark";

      try {
        const transition = document.startViewTransition(async () =&gt; {
          this.updateTheme(newTheme);
          window.localStorage.setItem("theme", newTheme);
        });

        await transition.ready;
        const rect = this.themeLabel?.getBoundingClientRect();
        if (rect) {
          await this.animateThemeTransition(rect.x, rect.y);
        }
      } catch (error) {
        console.error("Theme switch failed:", error);
        // 回退方案
        this.updateTheme(newTheme);
        window.localStorage.setItem("theme", newTheme);
      }
    });
  }

  disconnectedCallback(): void {
    this.themeInput?.removeEventListener("click", () =&gt; {});
  }
}

customElements.define("switch-theme", ThemeSwitcher);
&lt;/script&gt;
</code></pre>]]></content>
    <category term="Web" />
    <category term="CSS" />
  </entry>
  <entry>
    <title>随笔</title>
    <link href="https://ethyoung.me//posts/october-essay" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/october-essay</id>
    <updated>2024-10-23T00:00:00.000Z</updated>
    <published>2024-10-23T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">在岁月的静谧中独坐，任时光流逝，与心灵共老。</summary>
    <content type="html"><![CDATA[
<p>今日偶听《知我》，心中竟泛起涟漪，仿若一池春水被细雨轻扰。那久违的醉酒青春，在旋律间悄然复苏，少时的梦想，竟在刹那间被唤醒。人生在世，几许沧桑，几度无常，但无非是尽力过好自己罢了。</p>
<p>时常迷茫，不知前路何在，亦不晓心之所欲。倒不如停下脚步，将喧嚣隔绝，静心回望，细细品味那曾踏过的泥泞与辉煌。人这一生，是否必须不停向前？也许，并不需要。偶尔坐下来，倚一片青山，看众人行过，听风吹叶落，或许便是另一种圆满。</p>
<p>我不求同行者，不愿奔波争渡。只想独自坐在岁月的岸边，看时间从指间悄然流走，在沉静中，让心灵与天地共老。</p>]]></content>
    <category term="Life" />
    <category term="Essay" />
  </entry>
  <entry>
    <title>Docker 入门</title>
    <link href="https://ethyoung.me//posts/docker-tutorial" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/docker-tutorial</id>
    <updated>2024-05-06T00:00:00.000Z</updated>
    <published>2024-05-06T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">Docker 入门，作为一个前端，需要了解的一些docker 知识</summary>
    <content type="html"><![CDATA[
<h2>是什么？</h2>
<p>想象一下，你要搬家，却不想一件件搬所有的家当，而是把所有东西都装进一个统一的箱子，不管目的地是哪个城市，随时打开箱子就能继续生活。Docker 就是这样的”箱子”！它是一种开源的容器化技术，可以把应用程序和它需要的环境”打包”起来，让它能随时随地跑起来，无需担心”跑不动”。</p>
<p>Docker 的主要特点包括：</p>
<ol>
<li><strong>轻量级</strong>：Docker 容器共享主机的操作系统内核，因此相比于虚拟机，它们占用的资源更少，启动更快速。</li>
<li><strong>可移植性</strong>：Docker 容器可以在任何支持 Docker 的环境中运行，无论是开发、测试还是生产环境，保持一致性。</li>
<li><strong>快速部署</strong>：Docker 镜像包含了应用程序及其所有依赖项，因此可以快速部署和启动容器，无需进行繁琐的配置和安装。</li>
<li><strong>环境一致性</strong>：Docker 容器将应用程序与其依赖项打包在一起，确保在不同环境中运行时具有一致的行为。</li>
<li><strong>资源隔离</strong>：Docker 使用 Linux 内核的容器功能，实现了容器之间的资源隔离，保证容器之间互不影响。</li>
</ol>
<h2>概念</h2>
<ul>
<li>镜像（Image）：像做饭的”菜谱”，告诉你怎么搭建应用。</li>
<li>容器（Container）：根据”菜谱”做出来的实际饭菜，一个运行中的实例。</li>
<li>仓库（Repository）：存储”菜谱”的地方，随时可以拿出来用，比如全球知名的”菜谱库”——Docker Hub。</li>
</ul>
<h2>如何工作</h2>
<p>这里借用图片 <a href="http://blog.bytebytego.com/" rel="noopener noreferrer" target="_blank">bytebytego</a>
<img src="assets/docker-diagram.jpeg" alt="Untitled.jpeg" /></p>
<h3>Docker 的工作原理</h3>
<ol>
<li>编写一个 Dockerfile
它是一张”TODO 清单”，告诉 Docker 需要用什么基础环境、拷贝哪些代码、安装哪些依赖。</li>
<li>构建一个 镜像
Docker 根据 Dockerfile 把你的应用和环境”打包”成一个文件。</li>
<li>运行 容器
用镜像”生成”一个容器，容器里跑的就是你的应用，随时可以启动、停止。</li>
</ol>
<h4>编写 Dockerfile</h4>
<p>Dockerfile 是一个文本文件，包含了一条条的指令（Instruction），每一条指令构建一层，因此每一条指令的内容都会对镜像产生影响。Dockerfile 的基本格式如下：</p>
<pre><code># Base image
FROM node:14    # 指定基础镜像

# Author
MAINTAINER matrixpunk &lt;
# Set working directory
WORKDIR /app    # 设置工作目录

# Copy source code
COPY . /app     # 拷贝文件

# Install dependencies
RUN npm install # 安装依赖

# Expose port
EXPOSE 3000     # 暴露端口

# Start app
CMD ["npm", "start"] # 启动命令

</code></pre>
<h4>构建 Docker 镜像</h4>
<pre><code>docker build -t my-node-app .
</code></pre>
<h4>运行 Docker 容器</h4>
<pre><code>docker run -d -p 3000:3000 my-node-app
</code></pre>
<blockquote><p>运行 docker 容器时，可以使用 <code>-d</code> 参数让容器在后台运行，<code>-p</code> 参数指定端口映射。
剩余参数如下：</p><ul>
<li><code>-i</code>：以交互模式运行容器</li>
<li><code>-t</code>：分配一个伪终端</li>
<li><code>--name</code>：指定容器名称</li>
<li><code>-v</code>：挂载数据卷</li>
<li><code>--rm</code>：容器停止后自动删除</li>
</ul></blockquote>
<h3>其余命令</h3>
<h4>查看所有容器</h4>
<pre><code>docker ps -a
</code></pre>
<h4>停止容器</h4>
<pre><code>docker stop &lt;container_name&gt;
</code></pre>
<h4>删除容器</h4>
<pre><code>docker rm &lt;container_name&gt;
</code></pre>
<h4>列举所有容器</h4>
<pre><code>docker ps
</code></pre>
<h4>列举所有容器 (包括停止的)</h4>
<pre><code>docker ps -a
</code></pre>
<h4>列举所有容器 (包括停止的)</h4>
<pre><code>docker ps -a
</code></pre>
<h4>列举所有镜像</h4>
<pre><code>docker images
</code></pre>
<h4>删除镜像</h4>
<pre><code>docker rmi &lt;image_name&gt;
</code></pre>
<h4>拉取 Docker 镜像</h4>
<pre><code>docker pull &lt;image_name&gt;
</code></pre>
<h4>推送 Docker 镜像</h4>
<pre><code>docker push &lt;image_name&gt;
</code></pre>
<h4>查看 Docker 容器信息</h4>
<pre><code>docker inspect &lt;container_name&gt;
</code></pre>
<h4>查看 Docker 容器日志</h4>
<pre><code>docker logs &lt;container_name&gt;
</code></pre>
<h4>Docker 容器操作</h4>
<pre><code>docker exec &lt;container_name&gt; &lt;command&gt;
</code></pre>
<h4>进入 Docker 容器</h4>
<pre><code>docker exec -it &lt;container_name&gt; /bin/bash
</code></pre>
<blockquote><p>当我们使用 docker 构建多个镜像时，我们可以使用 docker-compose 来管理多个容器</p></blockquote>
<h3>Docker Compose</h3>
<p>如果你的项目涉及多个容器，比如一个跑应用、一个跑数据库，那么你需要 Docker Compose。它像一本多菜谱的菜单，一键可以上齐所有菜。</p>
<ul>
<li><strong>多容器应用程序</strong>：当您的应用程序由多个容器组成时，可以使用 Docker Compose 来定义、管理和运行这些容器。</li>
<li><strong>开发环境</strong>：在开发过程中，使用 Docker Compose 可以轻松地设置开发环境，包括数据库、缓存和其他服务，以便团队成员可以快速启动整个开发环境。</li>
<li><strong>测试环境</strong>：您可以使用 Docker Compose 在测试环境中快速部署和管理多个容器，以便进行集成测试和端到端测试。</li>
<li><strong>简化部署</strong>：通过在生产环境中使用 Docker Compose，您可以轻松地部署整个应用程序栈，而不必手动设置每个容器。</li>
<li><strong>快速原型</strong>：使用 Docker Compose 可以快速创建原型和演示环境，而无需手动安装和配置多个服务。</li>
</ul>
<blockquote><p>通常用于本地开发环境，生产环境建议使用 Docker Swarm 或 Kubernetes。</p></blockquote>
<h4>使用 Docker Compose</h4>
<blockquote><p>确保当前目录存在 docker-compose.yml compose.yml 文件
10.10.10.10 服务器的 docker-compose.yml 文件在/home/ocs/docker 目录下</p></blockquote>
<h4>Yml 文件如下，以及参数说明</h4>
<pre><code>version: '3' # 版本
services:
  sample: # 服务名
    build: # 构建镜像目录
      context: ./
      dockerfile: ./docker/Dockerfile
      args:
        NODE_ENV: production
    restart: always # 是否重启之后进行重启
    image: sampleName #  镜像名
    ports: # 端口
      - 3007:80 # 映射端口方式   -&gt;  主机端口:容器端口
    container_name: sampleNameContainer # 容器名
    environment: # 环境变量
      - NGINX_PORT=80
      - API_ENV=api
      - API_URL=http://10.10.10.10:8081/horizon/
      - NODE_ENV=production
      - WEBAPP=horizon
    volumes: # 映射路径地址 -&gt;  主机路径：容器路径
      - /home/nginx/conf.d:/etc/nginx/conf.d
    command: /bin/sh -c "envsubst '$$API_ENV,$$NGINX_PORT,$$API_URL,$$WEBAPP' &lt; /etc/nginx/conf.d/https.template &gt; /etc/nginx/conf.d/default.conf  &amp;&amp; exec nginx -g 'daemon off;'" # 启动命令
    networks: # 虚拟网络名字
      - app-net
networks:
  app-net:
    external:
      name: app-net
</code></pre>
<h4>启动</h4>
<pre><code>docker-compose start 服务名
</code></pre>
<h4>停止</h4>
<pre><code>docker-compose stop 服务名
</code></pre>
<h4>删除容器</h4>
<pre><code>docker-compose rm 服务名
</code></pre>
<h4>构建镜像</h4>
<pre><code>docker-compose build 服务名
</code></pre>
<h4>构建容器</h4>
<pre><code>docker-compose up -d 服务名
</code></pre>
<h4>查看日志 最后 500 行</h4>
<pre><code>docker-compose logs --tail 500 服务名
</code></pre>
<h3>参考</h3>
<ul>
<li><a href="https://docs.docker.com/" rel="noopener noreferrer" target="_blank">Docker 官方文档</a></li>
<li><a href="https://yeasy.gitbook.io/docker_practice/" rel="noopener noreferrer" target="_blank">Docker — 从入门到实践</a></li>
<li><a href="https://www.runoob.com/docker/docker-tutorial.html" rel="noopener noreferrer" target="_blank">Docker 教程</a></li>
</ul>]]></content>
    <category term="Docker" />
    <category term="DevOps" />
  </entry>
  <entry>
    <title>ncc 打包编译nestjs</title>
    <link href="https://ethyoung.me//posts/ncc-build-node" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/ncc-build-node</id>
    <updated>2022-06-22T00:00:00.000Z</updated>
    <published>2022-06-22T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">记录在使用 ncc 打包编译 nestjs 时遇到的问题及解决方案，特别是与 hbs 模版引擎相关的配置调整。</summary>
    <content type="html"><![CDATA[
<h1>背景：</h1>
<p>最近在使用<a href="https://github.com/vercel/ncc" rel="noopener noreferrer" target="_blank">ncc</a> 打包编译 nestjs，由于 nestjs 使用 hbs 作为模版引擎。</p>
<p>原先官网样例：</p>
<pre><code>npm install --save hbs
</code></pre>
<pre><code>import { NestFactory } from '@nestjs/core'
import { NestExpressApplication } from '@nestjs/platform-express'
import { join } from 'path'
import { AppModule } from './app.module'

async function bootstrap() {
  const app = await NestFactory.create&lt;NestExpressApplication&gt;(AppModule)

  app.useStaticAssets(join(__dirname, '..', 'public'))
  app.setBaseViewsDir(join(__dirname, '..', 'views'))
  //直接使用设置模版
  app.setViewEngine('hbs')

  await app.listen(3000)
}
bootstrap()
</code></pre>
<p>这样通过 ncc 编译，会存在丢失问题：</p>
<p>因此做如下设置即可：</p>
<pre><code>import { NestFactory } from '@nestjs/core'
import { NestExpressApplication } from '@nestjs/platform-express'
import { join } from 'path'
import { AppModule } from './app.module'
import * as HBS from 'hbs'

async function bootstrap() {
  const app = await NestFactory.create&lt;NestExpressApplication&gt;(AppModule)
  app.useStaticAssets(join(__dirname, '..', 'public'))
  app.setBaseViewsDir(join(__dirname, '..', 'views'))
  //手动重写
  app.set('view engine', 'hbs')
  app.engine('hbs', HBS.__express)

  await app.listen(3000)
}
bootstrap()
</code></pre>
<p>通过将其引入，再次编译即可。</p>]]></content>
    <category term="Nodejs" />
    <category term="Tech" />
  </entry>
  <entry>
    <title>&#039;谣言&#039;</title>
    <link href="https://ethyoung.me//posts/rumors" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/rumors</id>
    <updated>2019-12-22T00:00:00.000Z</updated>
    <published>2019-12-22T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">有些所谓的&quot;谣言&quot;，未必只是谣言，或许是提醒我们未雨绸缪的&quot;预警信号&quot;。</summary>
    <content type="html"><![CDATA[
<p>今天听说有传言说 2003 年的”非典”又回来了，还提到一些武汉的消息，说是有人传播病毒，但没有证据，应该只是传言。</p>
<p>2003 年非典的时候，我还在上小学，印象很深。每天进学校，老师都会拿着电子温度计给我们测体温。那时候，非典传播得很快，很多人感染了，新闻里说病毒是因为有人吃野味传播的。大家都很害怕，不敢出门，也不敢去人多的地方。记得老爸那时想从外地回家，奶奶硬是把他劝住了。</p>
<p>现在听到类似的消息，心里也不免有些担心，怕会像 2003 年一样，病毒快速传播，很多人受影响。</p>
<p>微博上看到不少人对李文亮先生的指责，说他散布谣言，但我觉得他只是出于好意，提醒大家注意防护。这些指责好像都没有真正的依据，只是站在一种权威的立场上批评他。</p>
<p>我当时就和老婆说，先去买些口罩吧，毕竟多做准备总没坏处。</p>
<h4>似乎有些’谣言’，成了 ‘遥遥领先的预言’</h4>
<blockquote><p>在面对不确定的信息时，与其急于否定，不如多一分审慎和反思，毕竟防患于未然远比亡羊补牢来得重要。</p></blockquote>]]></content>
    <category term="Life" />
  </entry>
  <entry>
    <title>2018年终总结</title>
    <link href="https://ethyoung.me//posts/2018-year-end" rel="alternate" type="text/html"/>
    <id>https://ethyoung.me//posts/2018-year-end</id>
    <updated>2018-12-04T00:00:00.000Z</updated>
    <published>2018-12-04T00:00:00.000Z</published>
    <author>
      <name>Ethan Yang</name>
    </author>
    <summary type="text">忙碌奔跑的一年，点滴回忆成诗，2018告别，2019再启新程。</summary>
    <content type="html"><![CDATA[
<p>天，越来越冷，听说过几天还要下雪。想想 2018 年就快要过去，满心的不舍。写点东西，就算是年终总结。</p>
<p>最近几天想想，时间过的真快啊，再过两年三十了，都说是三十而立。可是总感觉在某些自己还没有准备好。比如说自己工资还赶不上花销。好像这一年所有的事情都在催着往前走！像是一头被鞭子抽了之后的疯牛，漫无目的，口吐白沫；疯狂地撒丫子向前狂奔。</p>
<p>这一年计划，想想完了 90%，这里心里还有些欣慰！</p>
<p>有时候发现，年龄越大，认识的事情方式也随着改变。看到一些不爽的事物，在以前总会义愤填膺。现在想想，何必呢，有些事情你是无法改变的；还不如顺其自然，人嘛！对得起自己，问心无愧吧。</p>
<p>就在前一天，自己还想着减肥呢。可是懒啊！只能慢慢来了。</p>
<h2>1 月 2 日，领取结婚证 😃</h2>
<p>去年过年前夕，在老丈人家举办了婚礼。原本打算把领取结婚证日子往后拖拖，可是父母一直在那儿催着，没办法啊。</p>
<p>这天，天空灰蒙蒙的，还是很冷。于是早上起了大早，喊上了思思，走领证去。</p>
<p>骑着老妈的小电驴，以 30 迈的速度晃悠悠的向着民政局走着！到了民政局，本以为会像电视剧里面一样：去排队号，等待叫号递交材料，宣誓，最后盖上钢印。工作人员会说一下：恭喜二位哈。</p>
<p>看来是我想多了，在我们还没有照相时，还没有等我们坐好，一张照片就好了，我俩还没有整理呢。太快了！然后收取了一个强制购买盒子。妈蛋啊！50 啊。</p>
<p>不过不去想这么多了，因为迈入了一个里程了！我结婚了。</p>
<h2>5 月 9 日，举办婚礼 😃</h2>
<p>怎么说呢，不怎么喜欢这个日子；刚刚过完五一假，然后再去请婚假，而且好多亲朋好友都得要请个假才能过来，怪麻烦人家的！奈何没有办法，老妈算的日子啊。</p>
<p>其实我一直认为婚礼就是过个形式，有没有都无所谓的；主要大家在一起吃个饭就行。后来我发现我错了。</p>
<p><code>生活中，有时候还是需要一些仪式感</code></p>
<p>白天零零碎碎的忙碌，都是为晚上正餐做准备的。</p>
<p>晚上，婚礼开始了；参加过好多别人的婚礼，没有想过自己的婚礼会是什么样式。晚宴之前，司仪把我俩叫过去，去对流程。</p>
<p>… 说了很多，没怎么记得。因为有点紧张，手心一直在冒汗。交流结束，还好老婆在旁边提醒着，稍微记得一些。晚上接近八点，婚礼开始了。</p>
<p>在她牵着父亲的手，来到我身边时。</p>
<p>也许是旁边音乐的原因</p>
<p>也许是朋友的祝福</p>
<p>或许是现场的气氛</p>
<p>那一刻我明白，我明白了我守护的人，余生的牵绊的人，就是她了。我的妻子！</p>
<p>那一刻，说实话我差点儿就哭了。只不过我忍住了。</p>
<h2>度蜜月 😚</h2>
<p>其实我是想去看山的，而她要去看海；综合对比了下家庭地位，我输了！当然了看海就去<strong>三亚</strong>，<strong>毛里求斯</strong>等地方了，由于办理签证需要时间，加上的咱的假期就剩下 12 天了。最终决定了去<strong>三亚</strong>。</p>
<p>飞机 😄</p>
<p>长这么大，还没有坐过飞机，因此还有些期待！嗯！期待着上天！</p>
<p>原本原为飞机以为飞机应该和电视剧里面一样是那种大飞机！可事实呢。好小啊。</p>
<p>算了！！</p>
<p>飞起~~~~</p>
<p>嗯~~ 天很蓝，没有雾霾，我喜欢！</p>
<img src="assets/82s74kD.jpg" alt="蓝天" />
<p>呼呼了两个小时，我们来到了三亚。下完飞机！走在的沿海的沙滩上面。哇！真的是海天一色。</p>
<img src="assets/MM4AnMD.jpg" alt="海天一色" />
<p><strong>第二天</strong></p>
<p>来到蜈支洲岛</p>
<p>感受到了三亚的太阳，真毒！就昨天下飞机后三个小时，我的脚背已经晒伤了！</p>
<img src="assets/V2xiVWg.jpg" alt="蜈支洲岛" />
<img src="assets/dvLFAOA.jpg" alt="蜈支洲岛" />
<p>下午看了下三亚千古情：</p>
<img src="assets/OilORNg.jpg" alt="三亚千古情" />
<p>晚上吃了一顿海鲜大餐(其实我内心在说，真少，不够吃啊)：</p>
<img src="assets/uqclQ64.jpg" alt="海鲜大餐" />
<p><strong>第三天</strong></p>
<p>我们早上来到了<strong>玫瑰谷</strong>，感觉这里不像是景区，倒是人家的产业园。</p>
<p>各种各样的玫瑰，顺便科普到一个知识：玫瑰和月季，其实是同一个物种！有点惊讶。</p>
<p>白玫瑰</p>
<img src="assets/6HRxxCN.jpg" alt="白玫瑰" />
<p>红玫瑰</p>
<img src="assets/S03O9kn.jpg" alt="红玫瑰" />
<p>上了一座山，具体不知道叫啥，站在山上看海吧！！</p>
<img src="assets/ruyguAH.jpg" alt="山上看海" />
<img src="assets/9OKXDWd.jpg" alt="山上看海" />
<p><strong>拽根</strong>的雕塑</p>
<img src="assets/2RcGb1B.jpg" alt="拽根雕塑" />
<p><strong>亚龙湾</strong>，好多水上运动，很可惜没有玩啊</p>
<img src="assets/8yGC3LX.jpg" alt="亚龙湾" />
<p>晚上，坐着大船看看，经典建筑</p>
<img src="assets/4hqbas2.jpg" alt="经典建筑" />
<img src="assets/mzCah3d.jpg" alt="经典建筑" />
<img src="assets/sAW0HZc.jpg" alt="经典建筑" />
<p><strong>第四天</strong>，早早的起床，去南山（佛教圣地）</p>
<img src="assets/LsB3d4v.jpg" alt="南山" />
<blockquote><p>只让拍这里。。。。。</p></blockquote>
<p>下午，我们来到了<strong>天涯海角</strong>,貌似是海南的最南端了</p>
<img src="assets/V2Nxqyy.png" alt="天涯海角" />
<img src="assets/uuGwVTk.jpg" alt="天涯海角" />
<p>让她很自然的笑</p>
<img src="assets/3soN1ZM.jpg" alt="自然的笑" />
<p>就这样的第四天行程结束！意味着三亚之行也就结束了。</p>
<p>坐飞机回家~~~~~~好累啊</p>
<img src="assets/tehgBaK.jpg" alt="坐飞机回家" />
<p>趁着夜色的降临</p>
<img src="assets/8mg9kGN.jpg" alt="夜色" />
<p>结束了这一次的旅程！！！</p>
<img src="assets/DGCKknW.jpg" alt="2018再见" />
<p>不过还有一点；就是很累啊！😩</p>
<h2>买车子 😄</h2>
<p>其实买车子，原本不再考虑范围的，但是考虑到老丈人家，比较远。大过年的拎着东西回家，很不方便。于是乎着手去买车。</p>
<p>或许因为自己的不太懂车，买车的速度堪比买鞋了！快，😄</p>
<p>买车的全程都是销售帮我看，算账啊。我们俩客客气气的，弄的销售都有点不好意思。不过感觉遇到了一个好人吧。好多注意点，销售都帮我们处理的比较好。没有任何担心！</p>
<p><strong>第一次开车</strong>，</p>
<img src="assets/Cwmeyh5.jpg" alt="第一次开车" />
<p>老婆在旁边全程紧张 😱，能够明显感觉出来，她说话的语气有点抖！好在，本人开车比较稳。慢悠悠的开回家了。</p>
<h2>拿房子 😔</h2>
<p><strong>消息</strong></p>
<p>新房拿到手，大家都会着手装修；即便是精装修都会稍微整修一下。然而从这次装修中，总有了一些不愉快的经历！</p>
<p>9 月 30 日，开发商那边打电话，说是我们可以拿房了。由于前期看过样板房，所以这次拿房新鲜感就很少。</p>
<p>前期没有多少的电话，😔 从各个风声中传来我们是最后一批交房，心里咯噔一下；是不是因为我们前期闹装修问题，闹的太狠，导致这次把我们安排在最后一批？</p>
<img src="assets/ygLkkhu.jpg" alt="新房" />
<p><strong>拿房</strong></p>
<p>9 月 30 日，起了个大早，晃晃悠悠的坐上地铁，匆匆赶到了新家，密密麻麻的楼群。</p>
<img src="assets/GfYEUkb.jpg" alt="楼群" />
<p>拿着一个箱子，里面钥匙啊，合同啊，遥控器什么的，全部都放在里面。感觉还是蛮方便的。</p>
<p>就进去验房了。</p>
<p>反正我不喜欢这样的精装修，于是和家里人商量下，我们稍微整修一下。</p>
<p>目前还在装修中，不过看着房子一天一天的朝着自己构想在变化，还是很满意的！</p>
<p><strong>2018</strong> 年，年初列了一个计划清单，现在上面基本上完成的差不多了。</p>
<p>整一年都是很忙碌，都是在奔跑中。</p>
<p><strong>2019</strong> 年的计划还没有列出来。</p>
<p>总之写到这里，不知道写是不是年终总结了，不纠结了。就当是给自己做个汇报吧！</p>
<p>2018 年 886~~ 😄</p>
<p>2019 年 见</p>]]></content>
    <category term="总结" />
    <category term="Life" />
  </entry>
</feed>