<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Java - tag - 沐木</title><link>https://oldletter.cn/tags/java/</link><description>Java - tag - 沐木</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 11 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://oldletter.cn/tags/java/" rel="self" type="application/rss+xml"/><item><title>信创实战：Java 服务管理脚本（生产级）</title><link>https://oldletter.cn/posts/xinchuang-03-java-service-script/</link><pubDate>Thu, 10 Apr 2025 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/xinchuang-03-java-service-script/</guid><description><![CDATA[<blockquote>
<p>系列：信创国产化适配 | 作者：lin | 日期：2026-05-15</p></blockquote>
<hr>
<h2 id="一背景与问题">一、背景与问题</h2>
<h3 id="11-为什么需要专业的-java-服务管理脚本">1.1 为什么需要专业的 Java 服务管理脚本</h3>
<p>在很多项目中，Java 服务的启动方式还停留在：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-bash">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># ❌ 野蛮启动方式</span>
</span></span><span class="line"><span class="cl">nohup java -jar app.jar &gt; app.log 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$!</span> &gt; app.pid</span></span></code></pre></div></div>
<p>这种方式存在诸多问题：</p>
<table>
  <thead>
      <tr>
          <th>问题</th>
          <th>影响</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>进程意外退出后无法自动重启</td>
          <td>服务不可用，需人工介入</td>
      </tr>
      <tr>
          <td>停止服务直接 <code>kill -9</code></td>
          <td>数据丢失、连接池未关闭</td>
      </tr>
      <tr>
          <td>没有 PID 管理</td>
          <td>重复启动、僵尸进程</td>
      </tr>
      <tr>
          <td>日志输出到 nohup.out</td>
          <td>日志无法轮转，磁盘被打满</td>
      </tr>
      <tr>
          <td>无法集成 systemd</td>
          <td>不符合 Linux 服务管理规范</td>
      </tr>
      <tr>
          <td>每次部署手动操作</td>
          <td>容易出错，效率低下</td>
      </tr>
  </tbody>
</table>
<h3 id="12-设计目标">1.2 设计目标</h3>
<p>我们要实现一个生产级的 Java 服务管理脚本，满足以下要求：</p>
<ol>
<li><strong>自动检测 Java</strong>：JAVA_HOME → 常见安装路径 → PATH，三级探测</li>
<li><strong>自动识别 jar</strong>：无需硬编码 jar 文件名</li>
<li><strong>首次部署兼容</strong>：自动创建目录结构、移动配置文件</li>
<li><strong>systemd 集成</strong>：一键安装/卸载系统服务</li>
<li><strong>受控停机</strong>：发送 SIGTERM 并等待，超时后再由策略决定是否发送 SIGKILL</li>
<li><strong>PID 文件管理</strong>：防止重复启动，精确控制进程</li>
</ol>
<hr>
<h2 id="二jvm-堆内存分配公式">二、JVM 堆内存分配公式</h2>
<p>在配置 <code>JAVA_OPTS</code> 之前，理解 JVM 内存分配至关重要：</p>]]></description></item><item><title>Java Stream 实用技巧：List 与 Map 的花式转换</title><link>https://oldletter.cn/posts/java-05-stream-utils/</link><pubDate>Thu, 03 Jun 2021 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-05-stream-utils/</guid><description><![CDATA[<h2 id="前言">前言</h2>
<p>Java 8 引入的 Stream API 彻底改变了我们处理集合数据的方式。相比传统的 for 循环，Stream 提供了声明式、链式、可组合的数据处理风格，代码更简洁，意图更清晰。</p>
<p>在日常开发中，我们经常面对这样的场景：从数据库查出一个 <code>List&lt;Map&gt;</code>，需要转成按某个 key 分组的 <code>Map</code>；两个接口返回的列表需要按 id 合并；或者需要对字段做批量转换。这些需求用 Stream 几行代码就能搞定，而传统写法往往要写一堆临时变量和嵌套循环。</p>
<p>本文整理了 7 个实用的 Stream 技巧，每个都附带完整可运行的示例代码，希望能帮助你在实际项目中写出更优雅的集合操作。</p>
<hr>
<h2 id="一list-转-mapkey-冲突处理">一、List 转 Map（key 冲突处理）</h2>
<p>基础也常见的操作。假设有一个用户列表，需要按 id 建索引：</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.stream.*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">List</span><span class="o">&lt;</span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">Object</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">users</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">HashMap</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">Object</span><span class="o">&gt;</span><span class="p">()</span><span class="w"> </span><span class="p">{{</span><span class="w"> </span><span class="n">put</span><span class="p">(</span><span class="s">&#34;id&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">1L</span><span class="p">);</span><span class="w"> </span><span class="n">put</span><span class="p">(</span><span class="s">&#34;name&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Alice&#34;</span><span class="p">);</span><span class="w"> </span><span class="p">}});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">users</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">HashMap</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">Object</span><span class="o">&gt;</span><span class="p">()</span><span class="w"> </span><span class="p">{{</span><span class="w"> </span><span class="n">put</span><span class="p">(</span><span class="s">&#34;id&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">2L</span><span class="p">);</span><span class="w"> </span><span class="n">put</span><span class="p">(</span><span class="s">&#34;name&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Bob&#34;</span><span class="p">);</span><span class="w"> </span><span class="p">}});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">users</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"> </span><span class="n">HashMap</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">Object</span><span class="o">&gt;</span><span class="p">()</span><span class="w"> </span><span class="p">{{</span><span class="w"> </span><span class="n">put</span><span class="p">(</span><span class="s">&#34;id&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">3L</span><span class="p">);</span><span class="w"> </span><span class="n">put</span><span class="p">(</span><span class="s">&#34;name&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Charlie&#34;</span><span class="p">);</span><span class="w"> </span><span class="p">}});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">// 基本转换</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">Map</span><span class="o">&lt;</span><span class="n">Long</span><span class="p">,</span><span class="w"> </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">Object</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">users</span><span class="p">.</span><span class="na">stream</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="na">collect</span><span class="p">(</span><span class="n">Collectors</span><span class="p">.</span><span class="na">toMap</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">m</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="n">Long</span><span class="p">)</span><span class="w"> </span><span class="n">m</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">&#34;id&#34;</span><span class="p">),</span><span class="w">   </span><span class="c1">// key mapper</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">m</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">m</span><span class="w">                      </span><span class="c1">// value mapper</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">result</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">//</span><span class="w"> </span><span class="p">{</span><span class="n">1</span><span class="o">=</span><span class="p">{</span><span class="n">name</span><span class="o">=</span><span class="n">Alice</span><span class="p">,</span><span class="w"> </span><span class="n">id</span><span class="o">=</span><span class="n">1</span><span class="p">},</span><span class="w"> </span><span class="n">2</span><span class="o">=</span><span class="p">{</span><span class="n">name</span><span class="o">=</span><span class="n">Bob</span><span class="p">,</span><span class="w"> </span><span class="n">id</span><span class="o">=</span><span class="n">2</span><span class="p">},</span><span class="w"> </span><span class="n">3</span><span class="o">=</span><span class="p">{</span><span class="n">name</span><span class="o">=</span><span class="n">Charlie</span><span class="p">,</span><span class="w"> </span><span class="n">id</span><span class="o">=</span><span class="n">3</span><span class="p">}}</span></span></span></code></pre></div></div>
<p><strong>踩坑提示</strong>：如果 key 有重复，<code>toMap</code> 会抛 <code>IllegalStateException</code>。解决方案是提供第三个参数，合并函数：</p>]]></description></item><item><title>Lombok 进阶指南：从注解原理到 Spring Boot 最佳实践</title><link>https://oldletter.cn/posts/java-01-lombok/</link><pubDate>Mon, 20 Mar 2023 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-01-lombok/</guid><description><![CDATA[<blockquote>
<p>📅 2026-05-15 | 🏷️ Java, Lombok, Spring Boot | 📖 阅读约 15 分钟</p></blockquote>
<h2 id="前言">前言</h2>
<p>Lombok 大家都在用，但你真的用对了吗？</p>
<p>很多团队把 Lombok 当成&quot;少写 getter/setter 的工具&quot;，结果在生产环境踩了一堆坑：<code>@Data</code> 导致的 <code>StackOverflowError</code>、序列化失败、MapStruct 不兼容……</p>
<p>这篇文章并非入门教程，重点是<strong>进阶实战指南</strong>，我会从注解的底层原理讲起，结合 Spring Boot 3.x 工程实践，帮你避开那些&quot;写了半天 debug 两小时&quot;的坑。</p>
<p><strong>适合读者</strong>：有 Lombok 基础使用经验，想深入了解原理和成熟实践的 Java 开发者。</p>
<div class="details admonition note open">
    <div class="details-summary admonition-title">
        <i class="icon far fa-pen-to-square" aria-hidden="true"></i>版本说明<i class="details-icon fas fa-angle-right" aria-hidden="true"></i>
    </div>
    <div class="details-content">
        <div class="admonition-content">本文以 Lombok 1.18.x 的常见行为为基准。Spring Boot 3.x + JDK 17/21 用户请参考第五节的兼容性说明；如果使用更高版本 JDK，记得同步升级 Lombok，否则很容易在编译期踩坑。</div>
    </div>
</div>
<hr>
<h2 id="一基础核心注解的隐藏陷阱">一、基础核心注解的&quot;隐藏陷阱&quot;</h2>
<h3 id="11-data方便但危险">1.1 @Data：方便但危险</h3>
<p><code>@Data</code> 是 Lombok 更常用的注解，它等价于 <code>@Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor</code>。听起来很美好，但问题恰恰出在这个&quot;全家桶&quot;上。</p>]]></description></item><item><title>Maven 国内源配置指南（2026 版）</title><link>https://oldletter.cn/posts/java-02-maven/</link><pubDate>Wed, 24 Nov 2021 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-02-maven/</guid><description><![CDATA[<blockquote>
<p>📅 2026-05-15 | 🏷️ Maven, Gradle, 信创, DevOps | 📖 阅读约 12 分钟</p></blockquote>
<h2 id="前言">前言</h2>
<p>&ldquo;Could not resolve dependencies&rdquo;，这大概是每个 Java 开发者在国内网络环境下更不想看到的错误。</p>
<p>Maven Central 在国内访问速度慢、不稳定，尤其在企业内网和信创环境下，配置国内镜像源几乎是项目初始化的第一步。但你真的会配吗？镜像优先级怎么算？私服和镜像怎么共存？Gradle 的配置和 Maven 有什么不同？</p>
<p>这篇文章给你一个<strong>完整的、可直接复制粘贴的配置方案</strong>，覆盖 Maven、Gradle、私服、代理、信创环境，2026 年当前公开版。</p>
<hr>
<h2 id="一为什么需要国内镜像">一、为什么需要国内镜像</h2>
<p>Maven Central（<code>https://repo.maven.apache.org/maven2</code>）物理上在美国，国内访问存在以下问题：</p>
<ol>
<li><strong>延迟高</strong>：HTTPS 握手 + TCP 往返，单次请求 500ms-2s</li>
<li><strong>不稳定</strong>：国际链路抖动，下载大文件容易中断</li>
<li><strong>带宽受限</strong>：高峰期下载速度可能只有几十 KB/s</li>
<li><strong>安全合规</strong>：信创/政企环境不允许直接访问外网</li>
</ol>
<p>国内镜像源同步了 Maven Central 的所有包，物理上在国内，访问速度提升 10-100 倍。</p>
<div class="details admonition note open">
    <div class="details-summary admonition-title">
        <i class="icon far fa-pen-to-square" aria-hidden="true"></i>Maven 依赖解析机制<i class="details-icon fas fa-angle-right" aria-hidden="true"></i>
    </div>
    <div class="details-content">
        <div class="admonition-content"><p>Maven 的依赖解析遵循<strong>下限路径优先</strong>原则。当同一个依赖存在多个版本时，Maven 会在依赖树中选择深度更浅（路径下限）的那个版本。如果深度相同，则按 POM 中声明的顺序选择第一个。</p>
<p>形式化地说，对于依赖树中的节点 D，其解析版本为所有候选版本中深度下限的那个。因此<strong>直接声明的依赖优先级高于传递依赖</strong>。</p>
</div>
    </div>
</div>
<hr>
<h2 id="二maven-配置">二、Maven 配置</h2>
<h3 id="21-settingsxml-完整配置">2.1 settings.xml 完整配置</h3>
<p>将以下内容保存到 <code>~/.m2/settings.xml</code>（全局配置）或 <code>$MAVEN_HOME/conf/settings.xml</code>（安装级别）：</p>]]></description></item><item><title>PDF 模板生成实战：iText 5.x/9.x 方案对比与开源替代</title><link>https://oldletter.cn/posts/java-03-pdf-template/</link><pubDate>Thu, 15 Jun 2023 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-03-pdf-template/</guid><description><![CDATA[<blockquote>
<p>📅 2026-05-15 | 🏷️ Java, PDF, iText, OpenPDF, PDFBox | 📖 阅读约 15 分钟</p></blockquote>
<h2 id="一背景与问题">一、背景与问题</h2>
<p>在企业级应用中，大量业务文档需要模板化生成：保单、合同、告知书、发票、审批单……这些文档有三个共同特征：</p>
<ol>
<li><strong>格式固定</strong> — 排版、字体、Logo 位置都是确定的</li>
<li><strong>内容动态</strong> — 姓名、金额、日期等字段每次不同</li>
<li><strong>批量生产</strong> — 高峰期可能一次生成数千份</li>
</ol>
<p>常见做法是：<strong>先用 Word 制作模板 → 用 Java 填充数据 → 输出 PDF</strong>。</p>
<p>本文对比两种主流方案，给出完整代码和踩坑记录。</p>
<div class="details admonition note open">
    <div class="details-summary admonition-title">
        <i class="icon far fa-pen-to-square" aria-hidden="true"></i>A4 页面尺寸公式<i class="details-icon fas fa-angle-right" aria-hidden="true"></i>
    </div>
    <div class="details-content">
        <div class="admonition-content"><p>PDF 中常用的 A4 页面尺寸为 210mm × 297mm。在 iText 中使用 point（磅）为单位，换算关系：1 inch = 72 pt = 25.4 mm。</p>
<p>因此 A4 的磅值为：宽 595.28 pt，高 841.89 pt。</p>]]></description></item><item><title>常用工具类汇总：SM2/SM4/拼音/文件/HTTP 全覆盖</title><link>https://oldletter.cn/posts/java-04-utils/</link><pubDate>Fri, 10 Oct 2025 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-04-utils/</guid><description><![CDATA[<blockquote>
<p>📅 2026-05-15 | 🏷️ Java, 工具类, 国密, Hutool | 📖 阅读约 18 分钟</p></blockquote>
<h2 id="一背景">一、背景</h2>
<p>多年项目中积累了不少高频使用的工具代码。它们不算复杂，但每次要用的时候都得翻老项目找，效率很低。这篇文章把它们整理出来，统一收录，方便查阅和复用。</p>
<p>每个工具都包含：<strong>使用场景 → 完整代码 → 调用示例</strong>。</p>
<div class="details admonition tip open">
    <div class="details-summary admonition-title">
        <i class="icon far fa-lightbulb" aria-hidden="true"></i>不要重复造轮子<i class="details-icon fas fa-angle-right" aria-hidden="true"></i>
    </div>
    <div class="details-content">
        <div class="admonition-content"><p>在动手写工具类之前，先看看成熟库是否已经提供了：</p>
<ul>
<li><strong>Hutool 5.8.44</strong>（2026-03-11）：国人开发的 Java 工具库，覆盖字符串、日期、加密、HTTP、JSON 等 90% 的工具需求，API 简洁</li>
<li><strong>Apache Commons</strong>：<code>commons-lang3</code>、<code>commons-io</code>、<code>commons-collections</code> 等，稳定可靠</li>
<li><strong>Guava</strong>：Google 的 Java 工具库，集合、缓存、并发工具强大</li>
</ul>
<p>本文在每个工具节的末尾都会给出&quot;成熟库替代方案&quot;，<strong>优先使用成熟库，只在特定需求时手写</strong>。</p>
</div>
    </div>
</div>
<hr>
<h2 id="二字符串工具">二、字符串工具</h2>
<h3 id="21-中文姓名转拼音生成邮箱账号">2.1 中文姓名转拼音生成邮箱账号</h3>
<p><strong>场景：</strong> 新员工入职系统自动生成企业邮箱，规则是&quot;名.姓@company.com&quot;，需要支持复姓（如诸葛孔明 → <a href="mailto:kongming.zhuge@company.com" rel="">kongming.zhuge@company.com</a>）。</p>
<div class="code-block code-line-numbers" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="nn">cn.hutool.extra.pinyin.PinyinUtil</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.Arrays</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.HashSet</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">java.util.Set</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 中文姓名转拼音邮箱账号工具
</span></span></span><span class="line"><span class="cl"><span class="cm"> *
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 支持：
</span></span></span><span class="line"><span class="cl"><span class="cm"> * - 单姓：张三 → san.zhang
</span></span></span><span class="line"><span class="cl"><span class="cm"> * - 复姓：诸葛孔明 → kongming.zhuge
</span></span></span><span class="line"><span class="cl"><span class="cm"> * - 单名：张伟 → wei.zhang
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">PinyinEmailUtil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 中国常见复姓集合（可根据需要扩充）
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="kd">final</span><span class="w"> </span><span class="n">Set</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span><span class="w"> </span><span class="n">COMPOUND_SURNAMES</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">HashSet</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">Arrays</span><span class="p">.</span><span class="na">asList</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;欧阳&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;太史&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;端木&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;上官&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;司马&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;东方&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;独孤&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;南宫&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;万俟&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;闻人&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;夏侯&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;诸葛&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;尉迟&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公羊&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;赫连&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;澹台&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;皇甫&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;宗政&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;濮阳&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公冶&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;太叔&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;申屠&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公孙&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;慕容&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;仲孙&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;钟离&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;长孙&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;宇文&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;司徒&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;鲜于&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;司空&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;闾丘&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;子车&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;亓官&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;司寇&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;巫马&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公西&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;颛孙&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;壤驷&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公良&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;漆雕&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;乐正&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;宰父&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;谷梁&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;拓跋&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;夹谷&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;轩辕&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;令狐&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;段干&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;百里&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;呼延&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;东郭&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;南门&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;羊舌&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;微生&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公户&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;公玉&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公仪&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;梁丘&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公仲&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公上&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公门&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公山&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公坚&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;左丘&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公伯&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;西门&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公祖&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;第五&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公乘&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;贯丘&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;公皙&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;南荣&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;东里&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;东宫&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;仲长&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;子书&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;子桑&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;即墨&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;达奚&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;褚师&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 将中文姓名转换为拼音邮箱账号
</span></span></span><span class="line"><span class="cl"><span class="cm">     *
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param chineseName 中文姓名（如 &#34;诸葛孔明&#34;）
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param domain      邮箱域名（如 &#34;company.com&#34;）
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @return 邮箱账号（如 &#34;kongming.zhuge@company.com&#34;）
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">toEmail</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">chineseName</span><span class="p">,</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="n">domain</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">chineseName</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">null</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">chineseName</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">2</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">&#34;姓名至少需要2个字符&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">surname</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">givenName</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 判断是否为复姓（取前两个字匹配）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">firstTwo</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">chineseName</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">2</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">COMPOUND_SURNAMES</span><span class="p">.</span><span class="na">contains</span><span class="p">(</span><span class="n">firstTwo</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">surname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">firstTwo</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">givenName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">chineseName</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">2</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">surname</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">chineseName</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">givenName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">chineseName</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 转拼音（去掉声调，转小写）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">surnamePinyin</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">PinyinUtil</span><span class="p">.</span><span class="na">getPinyin</span><span class="p">(</span><span class="n">surname</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">givenNamePinyin</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">PinyinUtil</span><span class="p">.</span><span class="na">getPinyin</span><span class="p">(</span><span class="n">givenName</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">givenNamePinyin</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34;.&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">surnamePinyin</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34;@&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">domain</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 仅生成邮箱前缀（不含域名）
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="kd">static</span><span class="w"> </span><span class="n">String</span><span class="w"> </span><span class="nf">toEmailPrefix</span><span class="p">(</span><span class="n">String</span><span class="w"> </span><span class="n">chineseName</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">String</span><span class="w"> </span><span class="n">prefix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">toEmail</span><span class="p">(</span><span class="n">chineseName</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">).</span><span class="na">replace</span><span class="p">(</span><span class="s">&#34;@&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 去除末尾可能多余的点号</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">prefix</span><span class="p">.</span><span class="na">endsWith</span><span class="p">(</span><span class="s">&#34;.&#34;</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">prefix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">prefix</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"> </span><span class="n">prefix</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">prefix</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div></div>
<p><strong>使用示例：</strong></p>]]></description></item><item><title>问卷考试系统设计（二）：题目 DSL 设计——用 JSON 描述一切题型</title><link>https://oldletter.cn/posts/qnr-02-dsl-design/</link><pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/qnr-02-dsl-design/</guid><description><![CDATA[<p><strong>上一篇：<a href="/posts/qnr-01-architecture/" rel="">问卷考试系统设计（一）：架构选型与数据模型</a></strong></p>
<p>上一篇聊了整体架构和数据模型，这篇讲整个系统核心的部分：怎么用一套统一的 JSON 结构，把单选、多选、量表、矩阵、排序、填空……所有题型都描述清楚。然后让引擎把这些 JSON 跑起来，条件跳题、题目分组、随机排序，这些才是让问卷从&quot;能编辑&quot;变成&quot;能填写&quot;的关键。</p>
<hr>
<h2 id="为什么用-json-而不是多张表">为什么用 JSON 而不是多张表</h2>
<h3 id="传统建模的麻烦">传统建模的麻烦</h3>
<p>直观的做法，每种题型一张表：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-sql">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">question_single_choice</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">id</span><span class="w"> </span><span class="nb">BIGINT</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w"> </span><span class="n">stem</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">option_a</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w"> </span><span class="n">option_b</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w"> </span><span class="n">option_c</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w"> </span><span class="n">option_d</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">correct_answer</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">question_likert</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">id</span><span class="w"> </span><span class="nb">BIGINT</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w"> </span><span class="n">stem</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">scale_type</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span><span class="w"> </span><span class="n">is_reverse</span><span class="w"> </span><span class="nb">BOOLEAN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- 10 种题型 10 张表……</span></span></span></code></pre></div></div>
<p>问题很明显：</p>]]></description></item><item><title>API 鉴权实战（一）：AK/SK、API Key、RBAC 与统一认证授权架构设计</title><link>https://oldletter.cn/posts/api-auth-01-aksk-rbac/</link><pubDate>Tue, 10 Sep 2024 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/api-auth-01-aksk-rbac/</guid><description><![CDATA[<blockquote>
<p>作者：林 | 系列：API 鉴权实战 | 适合读者：Java 后端/架构师</p></blockquote>
<blockquote>
<p>本文代码使用 Lombok 简化 POJO，所有 getter/setter/builder 均由 Lombok 自动生成。</p></blockquote>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-xml">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="c">&lt;!-- pom.xml --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.projectlombok<span class="nt">&lt;/groupId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>lombok<span class="nt">&lt;/artifactId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;scope&gt;</span>provided<span class="nt">&lt;/scope&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div>
<hr>
<h2 id="一背景为什么要做-api-鉴权">一、背景：为什么要做 API 鉴权</h2>
<p>对外暴露 API 时，两个核心问题绕不开：</p>
<ol>
<li><strong>调用方是谁？</strong>（认证，Authentication）</li>
<li><strong>他能做什么？</strong>（授权，Authorization）</li>
</ol>
<p>简单的内部系统可能用一个 API Key 就够了。但当系统开始支持多租户、第三方接入、AI 开放平台、文件上传、额度控制时，单一的鉴权方式撑不住。你需要一套完整的体系：从密钥管理到权限判断，从缓存设计到上下文传递。</p>
<p>本文从更基础的 API Key 讲起，逐步构建到 PDP 策略决策点，把认证授权的全链路打通。</p>
<hr>
<h2 id="二五种鉴权方式详解">二、五种鉴权方式详解</h2>
<h3 id="21-api-key">2.1 API Key</h3>
<p><strong>原理：</strong> 给每个调用方分配一个唯一的密钥字符串，请求时放在 Header 里，服务端查库校验。</p>
<p><strong>适用场景：</strong> 内部服务间调用、快速原型、低安全要求的公开 API。</p>
<p><strong>不适用场景：</strong> 对外开放的 API，API Key 是静态的，明文传输，一旦被截获攻击者就能冒充调用方。</p>]]></description></item><item><title>Java 为什么不能直接解析 Lambda？从一个 ORM 查询说起</title><link>https://oldletter.cn/posts/java-lambda-parse-01-why/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-lambda-parse-01-why/</guid><description><![CDATA[<blockquote>
<p>📅 2026-06-10 | 🏷️ Java, Lambda, JVM | 📖 阅读约 15 分钟</p></blockquote>
<hr>
<h2 id="问题起源">问题起源</h2>
<p>做 Java ORM 的时候，更让人羡慕的就是 C# 的 LINQ：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-csharp">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// C# — 直接写 Lambda，框架自动转 SQL</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">users</span> <span class="p">=</span> <span class="k">await</span> <span class="n">context</span><span class="p">.</span><span class="n">Users</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">Where</span><span class="p">(</span><span class="n">u</span> <span class="p">=&gt;</span> <span class="n">u</span><span class="p">.</span><span class="n">Age</span> <span class="p">&gt;</span> <span class="m">18</span> <span class="p">&amp;&amp;</span> <span class="n">u</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;Tom&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">OrderBy</span><span class="p">(</span><span class="n">u</span> <span class="p">=&gt;</span> <span class="n">u</span><span class="p">.</span><span class="n">Id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">ToListAsync</span><span class="p">();</span></span></span></code></pre></div></div>
<p>我们也想在 Java 里这么写：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 理想中的 Java ORM 写法</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="n">query</span><span class="p">.</span><span class="na">where</span><span class="p">(</span><span class="n">u</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">u</span><span class="p">.</span><span class="na">getAge</span><span class="p">()</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">18</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">u</span><span class="p">.</span><span class="na">getName</span><span class="p">().</span><span class="na">equals</span><span class="p">(</span><span class="s">&#34;Tom&#34;</span><span class="p">));</span></span></span></code></pre></div></div>
<p>但试了一下就发现，<strong>Java 运行时核心拿不到 Lambda 里面的结构</strong>。<code>u -&gt; u.getAge() &gt; 18</code> 这个表达式，运行时只能执行它（传一个 User 进去，返回 true/false），但拿不到&quot;age &gt; 18&quot;这个结构信息。</p>]]></description></item><item><title>编译期方案：用 SerializedLambda 和注解处理器提取属性名</title><link>https://oldletter.cn/posts/java-lambda-parse-02-compile-time/</link><pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/java-lambda-parse-02-compile-time/</guid><description><![CDATA[<blockquote>
<p>📅 2026-06-10 | 🏷️ MyBatis Plus, QueryDSL, SerializedLambda, APT | 📖 阅读约 25 分钟</p></blockquote>
<hr>
<h2 id="前言">前言</h2>
<p>上一篇说了 Java 的 Lambda 编译后丢失了 AST 信息，不能类似 C# 一样直接解析。但框架开发者们没有放弃，而是找到了其他办法。</p>
<p>这一篇讲两种<strong>编译期方案</strong>：它们不解析 Lambda 本身，而是利用 Java 的其他机制来提取属性信息。</p>
<ul>
<li><strong>方案 1: SerializedLambda</strong> — MyBatis Plus 用的方式，从方法引用中提取 getter 方法名</li>
<li><strong>方案 2: Annotation Processor</strong> — QueryDSL 用的方式，编译期扫描实体字段，生成元模型类</li>
</ul>
<hr>
<h2 id="方案-1-serializedlambdamybatis-plus-路线">方案 1: SerializedLambda（MyBatis Plus 路线）</h2>
<h3 id="发现过程">发现过程</h3>
<p>这个方案的关键发现是：<strong>当一个 Lambda 表达式实现了 <code>Serializable</code> 接口时，Java 编译器会额外生成一个 <code>writeReplace()</code> 方法</strong>。</p>
<p>这个方法返回一个 <code>SerializedLambda</code> 对象，里面包含了：</p>
<ul>
<li><code>implClass</code> — 实现方法所在的类名</li>
<li><code>implMethodName</code> — 实现方法的方法名</li>
<li><code>implMethodSignature</code> — 方法签名</li>
<li><code>capturedArgCount</code> — 捕获的外部变量数量</li>
</ul>
<p>对于 <code>User::getAge</code> 这种方法引用，<code>implMethodName</code> 就是 <code>&quot;getAge&quot;</code>，这就是我们要的属性信息。</p>]]></description></item></channel></rss>