<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://luis-ai-systems.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://luis-ai-systems.github.io/" rel="alternate" type="text/html" /><updated>2026-03-31T13:33:58+08:00</updated><id>https://luis-ai-systems.github.io/feed.xml</id><title type="html">Luis Li</title><subtitle>技术，生活</subtitle><author><name>Luis Li</name></author><entry><title type="html">构建纯静态前沿交互：基于 Canvas 的绝对避让排版引擎</title><link href="https://luis-ai-systems.github.io/2026/03/31/build-canvas-physics-engine/" rel="alternate" type="text/html" title="构建纯静态前沿交互：基于 Canvas 的绝对避让排版引擎" /><published>2026-03-31T00:00:00+08:00</published><updated>2026-03-31T00:00:00+08:00</updated><id>https://luis-ai-systems.github.io/2026/03/31/build-canvas-physics-engine</id><content type="html" xml:base="https://luis-ai-systems.github.io/2026/03/31/build-canvas-physics-engine/"><![CDATA[<p>为了将博客的极简赛博风贯彻到底，我在这两天实现了一个非常有实验性的页面——<strong>“次元（Absolute Terror Field）”</strong>。</p>

<p>灵感来源于 <a href="https://github.com/chenglou/pretext">Chenglou/Pretext</a> 震撼的排版避让效果，但与之不同的是，我们没有引入庞大的 React 生态或外网后端。我仅仅通过 <strong>原生 HTML5 <code class="language-plaintext highlighter-rouge">&lt;canvas&gt;</code></strong> 和百行的纯 JavaScript 物理弹簧算法，就在静态博客中复现了满屏字母的引力场效果。</p>

<hr />

<h2 id="ⅰ-核心技术痛点解析">Ⅰ. 核心技术痛点解析</h2>

<p>构建这套纯前端交互矩阵涉及到了多个底层问题：</p>
<ol>
  <li><strong>真实物理模拟与碰撞检测</strong>：字符不仅要躲开我们的光标/节点，离开后还要像挂着弹簧一样优雅地“崩”回原位。</li>
  <li><strong>主题同步 (Theme Awareness)</strong>：传统的 <code class="language-plaintext highlighter-rouge">&lt;canvas&gt;</code> 绘图是静态像素，无法跟随博客左上角的“月亮/太阳”白天黑夜模式自动变色。</li>
  <li><strong>MacBook 触控板事件劫持</strong>：苹果 Safari 或 Chrome 中的触控板惯性拖拽容易被识别为滚动而打断物理坐标采集。</li>
  <li><strong>代码压缩引擎的“静默暗杀”</strong>：由于博客应用了 HTML 代码压缩机制，一段无害的 “//” 注释引发了长达半天的黑屏惨案（后文详述）。</li>
</ol>

<hr />

<h2 id="ⅱ-物理引擎运转架构">Ⅱ. 物理引擎运转架构</h2>

<p>为了彻底搞清这个循环结构，我梳理了整个引擎渲染和处理的管线（Pipeline）。</p>

<pre><code class="language-mermaid">graph TD
    A["Window resize / boot"] --&gt; B("初始化 Canvas 尺寸、Nodes 与 Particles")
    B --&gt; C{"requestAnimationFrame"}
    
    %% Input Layer
    subgraph 交互监听层
        I1("mousedown / touchstart") --&gt; |捕获高亮节点| I2("修改节点坐标及 isDragging 属性")
        I3("mousemove / touchmove") --&gt; |刷新绝对坐标| I2
    end
    
    %% Physics Calc
    subgraph 物理运算帧
        P1("遍历 Particles") --&gt; |计算与基底距离| P2("施加恢复拉力 vx = vx + dx * 0.08")
        P2 --&gt; P3("计算与 Nodes 距离矩阵")
        P3 --&gt; |若 Dist &lt; Radius| P4("施加弹斥力 Mathf.atan2 计算夹角推开距")
        P4 --&gt; P5("速度附加快衰减 vx = vx * 0.7 降噪避免乱飞")
        P5 --&gt; P6("更新字符最新坐标 x, y")
    end
    
    %% Render Pipeline
    subgraph 像素绘制管线
        R1["读取根节点主题状态"] --&gt; R2["清理上一帧 clearRect"]
        R2 --&gt; R3["重绘动态底色 fillRect"]
        R3 --&gt; R4["绘制二次贝塞尔曲线连结 (跳线)"]
        R4 --&gt; R5["根据物理运算层坐标重绘所有字体"]
        R5 --&gt; R6["绘制 Nodes 与发光滤镜 shadowBlur"]
    end

    C --&gt; |读取| I2
    C --&gt; P1
    P6 --&gt; R1
    R6 --&gt; C
</code></pre>

<hr />

<h2 id="ⅲ-血泪踩坑指南">Ⅲ. 血泪踩坑指南</h2>

<p>在实现这套机制时，除了算法上的调整，这三个堪称隐形杀手的坑耗费了最多时间：</p>

<h3 id="1-jekyll-compress-html-压缩器与-javascript-的量子纠缠">1. Jekyll <code class="language-plaintext highlighter-rouge">compress</code> HTML 压缩器与 JavaScript 的量子纠缠</h3>
<p>在这个系统中，最离谱的 Bug 就是——<strong>页面在本地开发正常，一旦推送到 GitHub Action 部署后，整个 Canvas 画面只剩下一片纯黑幕布，代码抛出完全隐藏的闪退。</strong></p>

<p>最终发现问题的核心竟是：
博客为了性能使用了 <code class="language-plaintext highlighter-rouge">{% layout compress %}</code> 模块来极度压缩 HTML 代码。它会在构建时<strong>粗暴地删掉 HTML 里的所有换行符（Newline）</strong>。
这导致我在 <code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> 里的 <code class="language-plaintext highlighter-rouge">// 计算反向推力</code> 等单行注释，因为失去了“换行”保护，硬生生地把 <code class="language-plaintext highlighter-rouge">//</code> 后面长达几百行的核心 Javascript 全部当成了注释给吃掉了，直接引发<strong>严重词法 SyntaxError</strong>。</p>

<p><strong>修复方案：</strong> 必须在静态博客的直接内联脚本中强制放弃 <code class="language-plaintext highlighter-rouge">//</code> 单行注释，严格采用 <code class="language-plaintext highlighter-rouge">/* Block 注释 */</code> 以防被压缩器吞断语境。</p>

<h3 id="2-mac-触控板拖拽与视口滚动">2. Mac 触控板拖拽与视口滚动</h3>
<p>原本仅绑定了 <code class="language-plaintext highlighter-rouge">mousemove</code>，发现 Mac 触控板点按拖拽时常常失灵。
这是因为触控板微小的移动经常被浏览器判定位系统级别 <code class="language-plaintext highlighter-rouge">Scroll</code> 而阻止事件。解决方案必须两路齐发：</p>
<ol>
  <li><strong>CSS 钳制</strong>：为 Canvas 对象加上 <code class="language-plaintext highlighter-rouge">touch-action: none !important;</code> 彻底禁用所有的手势。</li>
  <li><strong>主动拦截</strong>：在 JS 监听事件中，加入 <code class="language-plaintext highlighter-rouge">if (e.cancelable) e.preventDefault();</code> 拒绝原始滚轮和上滑操作，抢夺坐标归属权。</li>
  <li><strong>扩大点击域</strong>：将节点抓取判断放宽到了勾股定理距离求交的 <code class="language-plaintext highlighter-rouge">dx*dx + dy*dy &lt; 2500</code>（50px 半径范围），让触控板不再需要肉眼追踪 1 个特定细微像素。</li>
</ol>

<h3 id="3-主题theme-awareness的解耦跟随">3. 主题（Theme Awareness）的解耦跟随</h3>
<p>Canvas 原生是不讲 CSS <code class="language-plaintext highlighter-rouge">color: red</code> 道理的，它只读十六进制。为了能随着用户手头的<strong>“白昼/暗夜”模式</strong>实时切换效果。我们巧妙地使用 JS 将检测逻辑融入了 60fps 的 <code class="language-plaintext highlighter-rouge">animate()</code> 循环中：</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">isLightTheme</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">contains</span><span class="p">(</span><span class="dl">'</span><span class="s1">light-theme</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>每一帧都会嗅探博客是否注入了发白的主题雷达，随后智能重配文字为 “墨水蓝/赛博紫外”。至此，一套<strong>不需要 React，不需要后端计算，甚至不需要外部 NPM 包</strong>的高能静态实验室绝对领域落地了！</p>

<p>快去上方导航栏体验 <strong>[次元]</strong> 的自动排版威力吧！</p>]]></content><author><name>Luis Li</name></author><category term="折腾" /><category term="前端实验" /><summary type="html"><![CDATA[记录如何零后端、全静态实现陈楼 Pretext 风格的万字符弹簧物理避让矩阵，并解决全屏 Canvas 与博客光暗主题以及 Mac 触控板深坑。]]></summary></entry><entry><title type="html">终极解决暗黑模式闪烁 (FOUC)：硬核内联 CSS 方案</title><link href="https://luis-ai-systems.github.io/2026/03/30/fix-fouc-theme/" rel="alternate" type="text/html" title="终极解决暗黑模式闪烁 (FOUC)：硬核内联 CSS 方案" /><published>2026-03-30T20:30:00+08:00</published><updated>2026-03-30T20:30:00+08:00</updated><id>https://luis-ai-systems.github.io/2026/03/30/fix-fouc-theme</id><content type="html" xml:base="https://luis-ai-systems.github.io/2026/03/30/fix-fouc-theme/"><![CDATA[<p>在给博客加上了极其酷炫的赛博朋克深色/浅色主题切换后，我遇到了一个前端老生常谈、但极其恶心的问题：<strong>FOUC (Flash of Unstyled Content)</strong>。</p>

<p>具体表现为：当我把主题切到“浅色模式”，然后点击导航栏去别的页面时，页面<strong>总是会极快地闪过一下深色背景</strong>，然后再变回浅色。这极其破坏那种极致冷酷的极客体验。</p>

<p>本文记录了我是如何一步步排查，并最终用“内联关键 CSS”将其彻底歼灭的过程。</p>

<h2 id="为什么会闪烁">为什么会闪烁？</h2>

<p>刚开始，我的主题检测脚本写在 <code class="language-plaintext highlighter-rouge">main.js</code> 里：</p>

<ol>
  <li>浏览器加载 HTML</li>
  <li>浏览器加载并解析 <code class="language-plaintext highlighter-rouge">premium.css</code>（里面包含了 <code class="language-plaintext highlighter-rouge">body</code> 默认的 <code class="language-plaintext highlighter-rouge">#050508</code> 深色背景）</li>
  <li>浏览器渲染：此时<strong>页面是深色的</strong></li>
  <li><code class="language-plaintext highlighter-rouge">main.js</code> 被执行，读取 <code class="language-plaintext highlighter-rouge">localStorage</code></li>
  <li>发现用户选了浅色，于是给 <code class="language-plaintext highlighter-rouge">body</code> 加上 <code class="language-plaintext highlighter-rouge">.light-theme</code> class</li>
  <li>CSS 重新计算：<strong>页面跳变回浅色</strong></li>
</ol>

<p>这中间哪怕只有 50 毫秒的时间差，人眼也能明显捕捉到刺眼的“深色闪烁”。</p>

<h3 id="第一次尝试前置检测脚本失败">第一次尝试：前置检测脚本（失败）</h3>

<p>网上的标准解法是将 <code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> 移到 <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> 中阻塞执行，在 <code class="language-plaintext highlighter-rouge">&lt;body&gt;</code> 渲染前给 <code class="language-plaintext highlighter-rouge">&lt;html&gt;</code> 加上主题 class。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;head&gt;</span>
    <span class="nt">&lt;script&gt;</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="dl">'</span><span class="s1">theme</span><span class="dl">'</span><span class="p">)</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">light</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">light-theme</span><span class="dl">'</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;/head&gt;</span>
</code></pre></div></div>

<p>结果：<strong>依然闪烁。</strong></p>

<p>这是因为外部 CSS 文件的加载是异步阻塞的。虽然 <code class="language-plaintext highlighter-rouge">&lt;html&gt;</code> 在第一时间拿到了 <code class="language-plaintext highlighter-rouge">.light-theme</code> 标记，但由于 <code class="language-plaintext highlighter-rouge">html.light-theme { background: #fff; }</code> 的规则写在 <code class="language-plaintext highlighter-rouge">premium.css</code> 里，浏览器必须等这个外部文件下载完才能应用。而在下载期间，默认的深色背景再次霸占了屏幕。</p>

<h2 id="终极方案内联关键-css-critical-css">终极方案：内联关键 CSS (Critical CSS)</h2>

<p>要做到绝对的<strong>零延迟</strong>，唯一的办法是：让浏览器在解析到 <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> 的瞬间，<strong>不需要发任何网络请求</strong>，就拥有渲染浅色背景的全部 CSS 素材。</p>

<p>我们在 <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> 中用 <code class="language-plaintext highlighter-rouge">&lt;style&gt;</code> 标签硬编码了导致页面大面积闪烁的<strong>关键像素规则</strong>：</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;head&gt;</span>
    <span class="c">&lt;!-- 1. 阻塞式读取判定 --&gt;</span>
    <span class="nt">&lt;script&gt;</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="dl">'</span><span class="s1">theme</span><span class="dl">'</span><span class="p">)</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">light</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">light-theme</span><span class="dl">'</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="nt">&lt;/script&gt;</span>

    <span class="c">&lt;!-- 2. 核心覆写规则直接内联 --&gt;</span>
    <span class="nt">&lt;style&gt;</span>
      <span class="c">/* 只要 html 有了 light-theme，立刻覆盖根变量 */</span>
      <span class="nt">html</span><span class="nc">.light-theme</span> <span class="p">{</span>
        <span class="nl">background</span><span class="p">:</span> <span class="m">#f8f9fc</span> <span class="cp">!important</span><span class="p">;</span>
        <span class="py">--color-bg-deep</span><span class="p">:</span> <span class="m">#f8f9fc</span><span class="p">;</span>
        <span class="py">--color-text-primary</span><span class="p">:</span> <span class="m">#1a1a2e</span><span class="p">;</span>
        <span class="c">/* ... 其他核心变量 */</span>
      <span class="p">}</span>
      
      <span class="c">/* 强行杀死深色的网格动画层 */</span>
      <span class="nt">html</span><span class="nc">.light-theme</span> <span class="nt">body</span><span class="nd">::before</span> <span class="p">{</span>
        <span class="nl">animation</span><span class="p">:</span> <span class="nb">none</span> <span class="cp">!important</span><span class="p">;</span>
        <span class="nl">background</span><span class="p">:</span> <span class="nb">none</span> <span class="cp">!important</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="nt">&lt;/style&gt;</span>
<span class="nt">&lt;/head&gt;</span>
</code></pre></div></div>

<h2 id="渲染流水线对比">渲染流水线对比</h2>

<p>下面这张 Mermaid 架构图清晰地展示了优化前后的浏览器渲染流水线差异。你可以看到优化后，浅色背景在 First Paint (首次绘制) 就已经就位了。</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant Browser as 浏览器
    participant Head as &lt;head&gt; 解析
    participant CSS as 外部 CSS 网络请求
    participant Body as &lt;body&gt; 渲染
    participant JS as 外部 JS 执行

    rect rgb(40, 40, 50)
        Note right of Browser: 【优化前】发生闪烁
        Browser-&gt;&gt;Head: 解析开始
        Head-&gt;&gt;CSS: 发起 premium.css 请求
        Browser-&gt;&gt;Body: 开始渲染 (First Paint)
        Note over Browser,Body: ⚠️ 使用默认深色变量渲染&lt;br&gt;此时用户看到深黑色！
        CSS--&gt;&gt;Browser: 返回深色+浅色样式表
        Browser-&gt;&gt;JS: 发起 main.js 请求
        JS--&gt;&gt;Browser: 执行脚本读 localStorage
        JS-&gt;&gt;Body: 添加 .light-theme class
        Note over Browser,Body: 🔄 重新计算样式并重绘&lt;br&gt;画面突变为浅色 (闪烁发生) 
    end

    rect rgb(20, 50, 40)
        Note right of Browser: 【优化后】完美顺滑
        Browser-&gt;&gt;Head: 解析开始
        Note over Head: 执行内联 &lt;script&gt;
        Head-&gt;&gt;Head: 发现浅色，给 &lt;html&gt; 加 class
        Note over Head: 解析内联 &lt;style&gt; (Critical CSS)
        Head-&gt;&gt;CSS: 发起 premium.css 请求
        Browser-&gt;&gt;Body: 开始渲染 (First Paint)
        Note over Browser,Body: ✅ 命中内联规则 html.light-theme&lt;br&gt;首次绘制即为完美的浅色背景！
        CSS--&gt;&gt;Browser: 返回剩余样式
        Browser-&gt;&gt;JS: 发起 main.js 继续绑定事件
    end
</code></pre>

<h2 id="总结">总结</h2>

<p>对付主题闪烁（FOUC），不仅要<strong>阻止渲染（Blocking Script）</strong>，还要解决<strong>CSS 同步阻塞（Critical CSS）</strong>。</p>

<p>把第一帧必须显示的背景颜色、主题变量、甚至伪动画的开关，全部剥离出来，硬写在 <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> 的 HTML 源码里。只有丢掉对外部网络资源的依赖，才能换来真正的丝滑切换体验。</p>]]></content><author><name>Luis Li</name></author><category term="前端" /><category term="技术" /><category term="CSS" /><category term="FOUC" /><category term="主题切换" /><category term="性能优化" /><category term="博客重构" /><summary type="html"><![CDATA[在给博客加上了极其酷炫的赛博朋克深色/浅色主题切换后，我遇到了一个前端老生常谈、但极其恶心的问题：FOUC (Flash of Unstyled Content)。]]></summary></entry><entry><title type="html">从零开始的数字虚空探险</title><link href="https://luis-ai-systems.github.io/2026/03/24/websocket/" rel="alternate" type="text/html" title="从零开始的数字虚空探险" /><published>2026-03-24T10:00:00+08:00</published><updated>2026-03-24T10:00:00+08:00</updated><id>https://luis-ai-systems.github.io/2026/03/24/websocket</id><content type="html" xml:base="https://luis-ai-systems.github.io/2026/03/24/websocket/"><![CDATA[<p>欢迎来到我的全新结界。经过了数次的重构与设计，这里终于拥有了我理想中的黑客与二次元交织的极致冷酷感。</p>

<h2 id="序章">序章</h2>

<p>在这个数字化的虚空世界中，每一个比特都承载着我的意志。在这里，你可能会看到关于编程魔法的代码片段，也可能是某个午后对一部冷门番剧的碎碎念。</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">cast_magic</span><span class="p">():</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"Hello, Digital World!"</span><span class="p">)</span>
    <span class="k">return</span> <span class="bp">True</span>
</code></pre></div></div>

<p><em>保持优雅，保持好奇。</em></p>

<hr />

<h1 id="websocket-架构图-websocket-architecture">WebSocket 架构图 (WebSocket Architecture)</h1>

<p>本文档描述了 Workflow Engine 项目中 WebSocket 实时消息系统的整体架构设计，包含前端连接管理、后端处理逻辑、鉴权流程及消息持久化机制。</p>

<h2 id="1-整体架构图">1. 整体架构图</h2>

<pre><code class="language-mermaid">graph TD
    subgraph FE["前端 - Vue3 Client"]
        WSNB["WebSocketNotificationBell.vue"] --&gt; WSM["WebSocketManager.js"]
        WSM --&gt;|"1. 建立连接"| WS_NATIVE["Native WebSocket"]
        WSM --&gt;|"2. 发送消息/心跳"| WS_NATIVE
        WS_NATIVE --&gt;|"3. 接收实时消息"| WSM
        WSM --&gt;|"4. 分发消息"| WSNB
        WSNB --&gt;|"REST API"| API_GATEWAY["Spring Boot REST Controllers"]
    end

    subgraph BE["后端 - Spring Boot Server"]
        WS_NATIVE &lt;--&gt;|"WebSocket ws://"| WS_CONFIG["WebSocketConfig"]
        WS_CONFIG --&gt; HSI["ChatHandshakeInterceptor"]
        HSI --&gt; ST["Sa-Token Auth"]
        ST --&gt;|"UID"| WS_CONFIG

        WS_CONFIG --&gt; CWH["ChatWebSocketHandler"]

        subgraph MSG["消息处理逻辑"]
            CWH --&gt;|"异步执行"| TE["TaskExecutors"]
            TE --&gt;|"私聊/群聊"| PMG["Private/Group Message Logic"]
            TE --&gt;|"离线消息"| OM["Offline Message Logic"]
            TE --&gt;|"ACK确认"| ACK["Message ACK Logic"]
        end

        subgraph STORE["存储与缓存"]
            PMG --&gt; DB_MSG[("MySQL: chat_message")]
            OM --&gt; DB_OFFLINE[("MySQL: chat_offline_message")]
            PMG --&gt; CACHE_GROUP["Group Member Cache"]
        end

        CWH --&gt;|"Session管理"| SM["Session Map: userId to Sessions"]
    end

    API_GATEWAY --&gt;|"加载历史消息"| DB_MSG
    API_GATEWAY --&gt;|"标记已读"| DB_MSG
</code></pre>

<h2 id="2-组件说明">2. 组件说明</h2>

<h3 id="21-前端组件-frontend-components">2.1 前端组件 (Frontend Components)</h3>

<ul>
  <li><strong>WebSocketManager.js</strong>: 核心连接管理器。
    <ul>
      <li><strong>职责</strong>: 维护单例连接、自动重连（指数退避策略）、发送心跳（30s ping/pong）、消息 JSON 解析及发布订阅模式。</li>
      <li><strong>鉴权</strong>: 从 <code class="language-plaintext highlighter-rouge">localStorage</code> 获取 <code class="language-plaintext highlighter-rouge">Admin-Token</code> 并通过 URL 参数传递。</li>
    </ul>
  </li>
  <li><strong>WebSocketNotificationBell.vue</strong>: 顶部导航条的通知组件。
    <ul>
      <li><strong>职责</strong>: 展示实时通知、统计未读数、展示消息弹窗、与后端交互同步已读状态。</li>
      <li><strong>逻辑</strong>: 首次加载时通过 REST API 获取历史消息，随后通过 WebSocket 接收实时增量消息。</li>
    </ul>
  </li>
</ul>

<h3 id="22-后端组件-backend-components">2.2 后端组件 (Backend Components)</h3>

<ul>
  <li><strong>ChatHandshakeInterceptor</strong>: 握手拦截器。
    <ul>
      <li><strong>职责</strong>: 在 WebSocket 握手阶段利用 <code class="language-plaintext highlighter-rouge">Sa-Token</code> 验证 Token 的合法性，并将解析出的 <code class="language-plaintext highlighter-rouge">userId</code> 存入 WebSocket Session 的属性中。</li>
    </ul>
  </li>
  <li><strong>ChatWebSocketHandler</strong>: 消息核心处理器。
    <ul>
      <li><strong>职责</strong>:
        <ul>
          <li><strong>Session 管理</strong>: 在内存中维护 <code class="language-plaintext highlighter-rouge">userId</code> 到 <code class="language-plaintext highlighter-rouge">Set&lt;WebSocketSession&gt;</code> 的映射，支持同一用户多设备/多标签页同时在线。</li>
          <li><strong>异步处理</strong>: 使用自定义线程池处理业务逻辑，防止 IO 阻塞 WebSocket 主线程。</li>
          <li><strong>协议支持</strong>: 处理 <code class="language-plaintext highlighter-rouge">PRIVATE_MESSAGE</code>（私聊）、<code class="language-plaintext highlighter-rouge">GROUP_MESSAGE</code>（群聊）、<code class="language-plaintext highlighter-rouge">PING</code>（心跳）、<code class="language-plaintext highlighter-rouge">MESSAGE_ACK</code>（消息回执）等多种消息格式。</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>TaskExecutors</strong>: 线程池管理。
    <ul>
      <li><strong>职责</strong>: 隔离 WebSocket IO 线程与具体的业务处理逻辑。</li>
    </ul>
  </li>
</ul>

<h3 id="23-存储机制-storage-mechanism">2.3 存储机制 (Storage Mechanism)</h3>

<ul>
  <li><strong>实时性</strong>: 消息优先推送给在线 Session。</li>
  <li><strong>持久化</strong>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">chat_message</code>: 存储全量消息历史。</li>
      <li><code class="language-plaintext highlighter-rouge">chat_offline_message</code>: 存储用户离线期间收到的消息，待用户上线后自动推送。</li>
    </ul>
  </li>
  <li><strong>消息回执 (ACK)</strong>: 前端收到消息后发送 ACK，后端更新消息状态（已送达/已读），确保消息不丢失。</li>
</ul>

<hr />
<p><em>文档由 Antigravity 自动生成于 2026-03-24</em></p>]]></content><author><name>Luis Li</name></author><category term="随笔" /><category term="二次元" /><category term="日记" /><category term="探险" /><category term="极客" /><summary type="html"><![CDATA[欢迎来到我的全新结界。经过了数次的重构与设计，这里终于拥有了我理想中的黑客与二次元交织的极致冷酷感。]]></summary></entry><entry><title type="html">赛博朋克 2026：前端幻象构建指南</title><link href="https://luis-ai-systems.github.io/2026/03/23/cyberpunk-theme/" rel="alternate" type="text/html" title="赛博朋克 2026：前端幻象构建指南" /><published>2026-03-23T15:30:00+08:00</published><updated>2026-03-23T15:30:00+08:00</updated><id>https://luis-ai-systems.github.io/2026/03/23/cyberpunk-theme</id><content type="html" xml:base="https://luis-ai-systems.github.io/2026/03/23/cyberpunk-theme/"><![CDATA[<p>如何在使用极其贫瘠的 HTML 元素时，使用 CSS 魔法将其转换为具有“呼吸感”的赛博组件？</p>

<h2 id="霓虹光效的秘密">霓虹光效的秘密</h2>

<p>实际上，利用 <code class="language-plaintext highlighter-rouge">box-shadow</code> 和多重渐变叠加，我们可以非常容易地突破矩形的桎梏。</p>

<blockquote>
  <p>“在黑暗中发光的，不只有恒星，还有那 1px 的边框。”</p>
</blockquote>

<h3 id="核心参数体验">核心参数体验</h3>

<p>下面是我平时最喜欢的一套按钮呼吸参数：</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.cyber-button</span> <span class="p">{</span>
    <span class="nl">box-shadow</span><span class="p">:</span> <span class="m">0</span> <span class="m">0</span> <span class="m">10px</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">255</span><span class="p">,</span> <span class="m">255</span><span class="p">,</span> <span class="m">0.5</span><span class="p">),</span> 
                <span class="nb">inset</span> <span class="m">0</span> <span class="m">0</span> <span class="m">10px</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">255</span><span class="p">,</span> <span class="m">255</span><span class="p">,</span> <span class="m">0.5</span><span class="p">);</span>
    <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#00ffff</span><span class="p">;</span>
    <span class="nl">transition</span><span class="p">:</span> <span class="n">all</span> <span class="m">0.3s</span> <span class="n">ease</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.cyber-button</span><span class="nd">:hover</span> <span class="p">{</span>
    <span class="nl">box-shadow</span><span class="p">:</span> <span class="m">0</span> <span class="m">0</span> <span class="m">20px</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">255</span><span class="p">,</span> <span class="m">255</span><span class="p">,</span> <span class="m">0.8</span><span class="p">),</span> 
                <span class="nb">inset</span> <span class="m">0</span> <span class="m">0</span> <span class="m">20px</span> <span class="n">rgba</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">255</span><span class="p">,</span> <span class="m">255</span><span class="p">,</span> <span class="m">0.8</span><span class="p">);</span>
    <span class="nl">transform</span><span class="p">:</span> <span class="n">scale</span><span class="p">(</span><span class="m">1.05</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>通过这样的叠加，元素在深色背景下会散发出如同全息投影一般的冷冽光芒。</p>]]></content><author><name>Luis Li</name></author><category term="技术" /><category term="前端" /><category term="CSS" /><category term="动画" /><category term="极客" /><summary type="html"><![CDATA[如何在使用极其贫瘠的 HTML 元素时，使用 CSS 魔法将其转换为具有“呼吸感”的赛博组件？]]></summary></entry><entry><title type="html">记录一次深夜的系统崩溃与重启</title><link href="https://luis-ai-systems.github.io/2026/03/22/server-crash/" rel="alternate" type="text/html" title="记录一次深夜的系统崩溃与重启" /><published>2026-03-22T02:14:00+08:00</published><updated>2026-03-22T02:14:00+08:00</updated><id>https://luis-ai-systems.github.io/2026/03/22/server-crash</id><content type="html" xml:base="https://luis-ai-systems.github.io/2026/03/22/server-crash/"><![CDATA[<p>昨天半夜，我的服务器毫无征兆地宕机了。</p>

<p>一切仿佛就像是《黑客帝国》里的断电瞬间。我赶紧从床上爬起来，打开终端，输入了那行熟悉的 SSH 命令。</p>

<h2 id="拯救日志">拯救日志</h2>

<p>翻看 <code class="language-plaintext highlighter-rouge">dmesg</code> 和 <code class="language-plaintext highlighter-rouge">/var/log/syslog</code>，发现竟然是因为某个不知名的死循环脚本耗尽了所有的内存，触发了 OOM Killer。</p>

<p>这提醒我们：</p>
<ol>
  <li><strong>进程隔离</strong>是多么重要。</li>
  <li>永远不要相信你匆忙写下、没有加 sleep 的 <code class="language-plaintext highlighter-rouge">while True</code>。</li>
</ol>

<p>最终，我在长达两个小时的抢救后，系统终于恢复了它的跳动。看着满屏绿色的 <code class="language-plaintext highlighter-rouge">[  OK  ]</code> 打印出来，那是一种程序员才能体会到的绝对浪漫。</p>]]></content><author><name>Luis Li</name></author><category term="踩坑" /><category term="随笔" /><category term="服务器" /><category term="运维" /><summary type="html"><![CDATA[昨天半夜，我的服务器毫无征兆地宕机了。]]></summary></entry></feed>