<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="https://www.herodotus.cn/rss.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <atom:link href="https://www.herodotus.cn/rss.xml" rel="self" type="application/rss+xml"/>
    <title>Dante Cloud 企业级微服务解决方案</title>
    <link>https://www.herodotus.cn/</link>
    <description>Dante Cloud 国内首个阻塞式+响应式并行的微服务云原生基座。独创一套代码实现微服务/单体灵活切换，融合DDD设计，符合等保三级，支持国密加密、多租户的高质量企业级微服务平台。</description>
    <language>zh-CN</language>
    <pubDate>Thu, 30 Apr 2026 08:15:06 GMT</pubDate>
    <lastBuildDate>Thu, 30 Apr 2026 08:15:06 GMT</lastBuildDate>
    <generator>@vuepress/plugin-feed</generator>
    <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
    <category>配置属性</category>
    <item>
      <title>ThingsBrain</title>
      <link>https://www.herodotus.cn/thingsbrain/</link>
      <guid>https://www.herodotus.cn/thingsbrain/</guid>
      <source url="https://www.herodotus.cn/rss.xml">ThingsBrain</source>
      <description>项目介绍</description>
      <pubDate>Thu, 30 Apr 2026 04:11:14 GMT</pubDate>
      <content:encoded><![CDATA[<h2>项目介绍</h2>
]]></content:encoded>
    </item>
    <item>
      <title>概述</title>
      <link>https://www.herodotus.cn/thingsbrain/device-authentication/overview.html</link>
      <guid>https://www.herodotus.cn/thingsbrain/device-authentication/overview.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">概述</source>
      <description>设备接入物联网平台之前，需通过身份认证。目前，Thingsbrain 物联网平台支持使用设备密钥进行设备身份认证。 设备密钥认证 创建产品时，认证方式选择为设备密钥，然后在该产品下添加设备，获取物联网平台颁发的 ProductSecret、DeviceSecret 等密钥。设备接入物联网平台时，会使用物联网平台颁发的密钥信息，进行身份认证。 针对不同的...</description>
      <pubDate>Thu, 30 Apr 2026 04:11:14 GMT</pubDate>
      <content:encoded><![CDATA[<p>设备接入物联网平台之前，需通过身份认证。目前，Thingsbrain 物联网平台支持使用设备密钥进行设备身份认证。</p>
<h2>设备密钥认证</h2>
<p>创建产品时，<strong>认证方式</strong>选择为<strong>设备密钥</strong>，然后在该产品下添加设备，获取物联网平台颁发的 <code>ProductSecret</code>、<code>DeviceSecret</code> 等密钥。设备接入物联网平台时，会使用物联网平台颁发的密钥信息，进行身份认证。</p>
<p>针对不同的使用环境，物联网平台提供以下四种设备密钥认证方案。</p>
<ul>
<li><a href="/thingsbrain/device-authentication/unique-certificate-per-device-verification.html" target="_blank">一机一密</a>：每台设备烧录自己的设备证书（<code>ProductKey</code>、<code>DeviceName</code>和<code>DeviceSecret</code>）。</li>
<li><a href="/thingsbrain/device-authentication/unique-certificate-per-product-verification.html" target="_blank">一型一密</a>预注册：同一产品下设备烧录相同产品证书（<code>ProductKey</code>和<code>ProductSecret</code>）。开通产品的动态注册功能，设备通过动态注册获取<code>DeviceSecret</code>。</li>
<li>一型一密免预注册：同一产品下设备烧录相同产品证书（<code>ProductKey</code>和<code>ProductSecret</code>）。开通产品的动态注册功能，通过动态注册，设备不获取<code>DeviceSecret</code>，而是获取<code>ClientID</code>与<code>DeviceToken</code>的组合。</li>
</ul>
<p>四种方案在易用性和安全性上各有优势，您可以根据设备所需的安全等级和实际的产线条件灵活选择。方案对比，如下表所示。</p>
<p>| 对比项                                           | 一机一密                                   | 一型一密预注册                                     | 一型一密免预注册                                   |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>一机一密</title>
      <link>https://www.herodotus.cn/thingsbrain/device-authentication/unique-certificate-per-device-verification.html</link>
      <guid>https://www.herodotus.cn/thingsbrain/device-authentication/unique-certificate-per-device-verification.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">一机一密</source>
      <description>概述 一机一密认证，是预先为每个设备烧录其唯一的设备证书（ProductKey、DeviceName和DeviceSecret）。当设备与物联网平台建立连接时，物联网平台对其携带的设备证书信息进行认证。认证通过后，设备完成激活，然后与物联网平台进行通信。 一机一密认证方式的安全性较高，推荐使用。 一机一密使用流程示意图：一机一密使用流程示意图： 直连设...</description>
      <pubDate>Thu, 30 Apr 2026 04:11:14 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>一机一密认证，是预先为每个设备烧录其唯一的设备证书（<code>ProductKey</code>、<code>DeviceName</code>和<code>DeviceSecret</code>）。当设备与物联网平台建立连接时，物联网平台对其携带的设备证书信息进行认证。认证通过后，设备完成激活，然后与物联网平台进行通信。</p>
<p>一机一密认证方式的安全性较高，推荐使用。</p>
<figure><img src="/assets/image/thingsbrain/device-authentication/unique-certificate-per-device-verification.png" alt="一机一密使用流程示意图：" tabindex="0" loading="lazy"><figcaption>一机一密使用流程示意图：</figcaption></figure>
<h2>直连设备认证流程</h2>
<p>直连设备支持使用 MQTT 和 HTTPS协议，认证流程如下。</p>
<h3>[1]创建产品</h3>
<p>创建产品时，<strong>节点类型</strong> 为 <strong>直连设备</strong>。</p>
<h3>[2]添加设备</h3>
<p>在已创建产品下添加设备，并获取设备证书信息。</p>
<h3>[3]产线烧录</h3>
<h4>1. 选择直连设备接入物联网平台的协议</h4>
<p>选择直连设备接入物联网平台的协议, MQTT 协议和 HTTPS 协议，配置设备证书信息和接入域名，进行注册认证。</p>
<p>注册认证的开发方法如下：</p>
<ol>
<li>MQTT：MQTT-TCP连接通信和MQTT-WebSocket连接通信。</li>
<li>HTTPS：HTTPS 连接通信。</li>
</ol>
<h4>2. 根据实际业务需求，完成设备端SDK开发</h4>
<p>例如设备物模型Topic通信、设备自定义Topic通信、OTA升级、设备影子等功能开发。</p>
<h4>3. 在产线上，将已开发完成的设备SDK烧录至设备中</h4>
<h3>[4]设备联网认证</h3>
<p>设备上电联网后，携带设备证书发起认证请求。</p>
<h3>[5]设备激活上线</h3>
<p>物联网平台校验设备证书通过后，与设备建立连接。设备便可通过设备Topic与物联网平台通信</p>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/thingsbrain/device-authentication/unique-certificate-per-device-verification.png" type="image/png"/>
    </item>
    <item>
      <title>一型一密</title>
      <link>https://www.herodotus.cn/thingsbrain/device-authentication/unique-certificate-per-product-verification.html</link>
      <guid>https://www.herodotus.cn/thingsbrain/device-authentication/unique-certificate-per-product-verification.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">一型一密</source>
      <pubDate>Thu, 30 Apr 2026 04:11:14 GMT</pubDate>
    </item>
    <item>
      <title>权限控制</title>
      <link>https://www.herodotus.cn/user-guide/operation/permission.html</link>
      <guid>https://www.herodotus.cn/user-guide/operation/permission.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">权限控制</source>
      <description>概述 本章节“权限管理”，主要包含前端菜单以及动态权限配置 [一]菜单管理 [1]菜单列表 在菜单管理页面，可以查看系统菜单列表，并进行相应的管理操作。 菜单列表菜单列表 [2]新建菜单 在菜单管理页面中，新建菜单。菜单相关属性如下： 新建菜单新建菜单 对于 Vue 类型的前端，菜单相关属性与 Vue 的路由配置相对应。以下面静态路由配置为例，进行说明...</description>
      <pubDate>Mon, 16 Mar 2026 17:03:08 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>本章节“权限管理”，主要包含前端菜单以及动态权限配置</p>
<h2>[一]菜单管理</h2>
<h3>[1]菜单列表</h3>
<p>在菜单管理页面，可以查看系统菜单列表，并进行相应的管理操作。</p>
<figure><img src="/assets/image/operation/menu-01.png" alt="菜单列表" tabindex="0" loading="lazy"><figcaption>菜单列表</figcaption></figure>
<h3>[2]新建菜单</h3>
<p>在菜单管理页面中，新建菜单。菜单相关属性如下：</p>
<figure><img src="/assets/image/operation/menu-02.png" alt="新建菜单" tabindex="0" loading="lazy"><figcaption>新建菜单</figcaption></figure>
<p>对于 Vue 类型的前端，菜单相关属性与 Vue 的路由配置相对应。以下面静态路由配置为例，进行说明：</p>
<div class="language-javascript line-numbers-mode" data-highlighter="shiki" data-ext="javascript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-javascript"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'/security'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  component</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#0184BC;--shiki-dark:#61AFEF"> import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'@/views/layouts/Index.vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  meta</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: { </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">title</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'用户安全管理'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">sort</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">icon</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'mdi-lock-pattern'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> },</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  redirect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'/security/user'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  children</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      path</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '/security/user'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> ComponentNameEnum</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#986801;--shiki-dark:#E06C75">SYS_USER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      meta</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">title</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '用户管理'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">icon</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'mdi-badge-account-horizontal'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">isHideAllChild</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> },</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">      component</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#0184BC;--shiki-dark:#61AFEF"> import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'@/views/pages/security/user/Index.vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      children</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          path</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '/security/user/content'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'SysUserContent'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          meta</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">title</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '用户详情'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">icon</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'mdi-clipboard-account'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">isDetailContent</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> },</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">          component</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#0184BC;--shiki-dark:#61AFEF"> import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'@/views/pages/security/user/Content.vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        },</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          path</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '/security/user/authorize'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'SysUserAuthorize'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          meta</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">title</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '用户角色'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">icon</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'mdi-badge-account-alert'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">isDetailContent</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> },</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">          component</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#0184BC;--shiki-dark:#61AFEF"> import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'@/views/pages/security/user/Authorize.vue'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        },</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      ],</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    },</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  ]</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>属性说明：</p>
<h4>1.应用类型</h4>
<p><strong>应用类型</strong>：主要用于区分当前菜单是用于哪一种终端</p>
<h4>2.菜单场景</h4>
<p><strong>菜单场景</strong>：主要用于区分当前菜单是用于应用主页面菜单还是个人设置页面菜单。自 4.0.X 版本之后，开始区分菜单场景，个人设置页面采用独立菜单，支持配置。</p>
<h4>3.Vue Router 请求路径</h4>
<p><strong>Vue Router 请求路径</strong>：对应前面 Vue Router 配置中的 <code>path</code> 的值</p>
<h4>4.Vue Component 名称</h4>
<p><strong>Vue Component 名称</strong>：对应前面 Vue Router 配置中 <code>name</code> 的值。如果是最上级的路径，例如：上面的 <code>/security</code> 配置，则留空不配置任何信息</p>
<h4>5.显示标题</h4>
<p><strong>显示标题</strong>：对应前面 Vue Router 配置中 meta 里的 <code>title</code> 的值。该值主要用于菜单以及 Tab 页上的标题显示。</p>
<h4>6.显示ICON</h4>
<p><strong>显示ICON</strong>：对应前面 Vue Router 配置中 meta 里的 <code>icon</code> 的值。该值主要用于菜单以及 Tab 页上的 ICON 显示。当前使用 <code>Material Design Icons</code>，可以输入部分字符进行模糊查询。<a href="https://pictogrammers.github.io/@mdi/font/7.4.47/" target="_blank" rel="noopener noreferrer">Material Design Icons(mdi)</a></p>
<h4>7.Vue Component 页面相对路径</h4>
<p><strong>Vue Component 页面相对路径</strong>：对应前面 Vue Router 配置中 <code>component</code> 方法对应 <code>import</code> 函数中的值，实际对应前端工程中页面代码。</p>
<h4>8.Vue Router 重定向地址</h4>
<p><strong>Vue Router 重定向地址</strong>：对应前面 Vue Router 配置中的 <code>redirect</code> 的值。没有 <code>redirect</code> 则留空。</p>
<h4>9.该页面不需要KeepAlive缓存</h4>
<p><strong>该页面不需要KeepAlive缓存</strong>：即，Vue KeepAlive 机制。如果不设置 KeepAlive 缓存，每次打开页面都会重新绘制。如果设置 KeepAlive 缓存，就会保留页面第一次绘制状态。</p>
<h4>10.该页面不需要权限验证</h4>
<p><strong>该页面不需要权限验证</strong>：页面不需要登录系统也可以访问。</p>
<h4>11.该页面包含子页面</h4>
<p><strong>该页面包含子页面</strong>：对应前面 Vue Router 配置中，只要配置中包含 <code>children</code>，就需要选中该选项。</p>
<h4>12.该页面包含子页面</h4>
<p><strong>在菜单中隐藏该节点下所有子节点</strong>：如果选中 <strong>该页面包含子页面</strong> 会显示该属性。用于控制子页面是否显示在菜单树中。</p>
<h4>13.该页面包含子页面</h4>
<p><strong>该页面是三级路由页面</strong>：该选项主要用于 <code>第三级路由页面</code>，对应前面 Vue Router 配置中的 <code>/security/user/content</code> 和 <code>/security/user/authorize</code> 配置</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>常规 Vue 仅支持两级路由。第三级路由需要自己单独处理。</p>
<p>Dante Cloud UI 实现了 <code>第三级路由页面</code>，该类型页面为动态组件的加载。这类页面通常为数据列表中，数据条目的设置或编辑操作，操作完成就会被关闭，不会像数据表格页面一样支持长时间显示。因此，该类型页面通常也不会显示在菜单树中。</p>
</div>
<h3>[3]为菜单分配角色</h3>
<p>新建菜单色之后，可以为该菜单指定具体的角色。</p>
<figure><img src="/assets/image/operation/menu-03.png" alt="为菜单分配角色" tabindex="0" loading="lazy"><figcaption>为菜单分配角色</figcaption></figure>
<h2>[二]元数据管理</h2>
<h3>[1]元数据列表</h3>
<p>在元数据管理页面，可以查看系统菜单列表。元数据没有新增和删除操作，主要原因手动管理会非常笨拙系统提供了自动管理机制，只需要在该功能上进行修改操作即可。</p>
<figure><img src="/assets/image/operation/attribute-01.png" alt="元数据列表" tabindex="0" loading="lazy"><figcaption>元数据列表</figcaption></figure>
<h3>[2]元数据分配权限</h3>
<p>这是系统的核心管理操作，也是控制 REST API 权限的核心管理功能。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>自 Dante Cloud 4.0.3.0 开始，元数据管理已经支持对 gRPC 方法的管理。</p>
</div>
<figure><img src="/assets/image/operation/attribute-02.png" alt="元数据分配权限" tabindex="0" loading="lazy"><figcaption>元数据分配权限</figcaption></figure>
<p>详细的权限设计，参见<a href="/develop-guide/design/permission.html" target="_blank">【权限体系设计】</a>。</p>
<h3>[3]动态修改接口权限</h3>
<p>Dante Cloud 最大的特色之一，就是不需要在接口上手动写死 <code>@PreAuthorize</code> 注解，通过后台管理功能就可以动态变更接口的权限。</p>
<p>选择一条元数据信息，点击修改即可以动态修改 REST API 或 gRPC 的权限。</p>
<figure><img src="/assets/image/operation/attribute-03.png" alt="动态修改接口权限" tabindex="0" loading="lazy"><figcaption>动态修改接口权限</figcaption></figure>
<p>具体的权限类型说明：</p>
<ul>
<li><strong>permitAll</strong>：允许所有请求通过，无论用户是否认证、是否匿名、有无特定角色。不做任何安全检查</li>
<li><strong>anonymous</strong>：仅允许 <strong>匿名用户</strong> （即未登录的用户）访问。如果当前用户已通过身份认证（包括 remember-me 认证），则拒绝访问（返回 403）。</li>
<li><strong>rememberMe</strong>：仅允许通过 Remember-Me 记住我功能 认证的用户访问。如果用户是通过普通登录（用户名/密码）认证的，或者未认证，则拒绝访问。</li>
<li><strong>denyAll</strong>：拒绝所有请求，无论用户身份如何。即强制返回 403 禁止访问。</li>
<li><strong>authenticated</strong>：要求用户已经通过身份认证（即已登录），包括通过普通登录和 remember-me 登录的用户。匿名用户将被拒绝。</li>
<li><strong>fullyAuthenticated</strong>：要求用户是 <strong>完全认证</strong> 的，即不是通过 remember-me 认证的。用户必须通过正常的登录流程（如提供用户名/密码）完成认证。如果当前用户仅通过 remember-me 认证，则会被拒绝。</li>
</ul>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Spring Security 中对请求的安全控制，包括：认证和鉴权两部分：</p>
<ul>
<li>认证：即用户登录。例如：使用用户名和密码登录，并正常返回了 Token。</li>
<li>鉴权：验证用户是否有调用某个接口的权限。通常 Token 中会包含用户所拥有的权限，鉴权就是验证 Token 中是否包含某个权限。</li>
</ul>
<p>例如：<strong>authenticated</strong> 权限类型，表示用户必需已经通过认证（即：登录），但是不需要对其 Token 中是否包含某个权限进行鉴权。</p>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/operation/menu-01.png" type="image/png"/>
    </item>
    <item>
      <title>权限管理</title>
      <link>https://www.herodotus.cn/user-guide/operation/rbac.html</link>
      <guid>https://www.herodotus.cn/user-guide/operation/rbac.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">权限管理</source>
      <description>概述 Dante Cloud 的主要权限体系采用的 RBAC 权限模型，在此基础之上进行了一定的扩展。相关的数据模型设计如下： 用户权限ER图用户权限ER图 详细的权限设计，参见。权限体系主要涉及的管理功能如下： [一]用户管理 [1]用户列表 在用户管理页面，可以查看系统用户列表，并进行相应的管理操作。 用户列表用户列表 注意 新建用户之后，系统并不...</description>
      <pubDate>Mon, 16 Mar 2026 17:03:08 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>Dante Cloud 的主要权限体系采用的 RBAC 权限模型，在此基础之上进行了一定的扩展。相关的数据模型设计如下：</p>
<figure><img src="/assets/image/design/rbac.png" alt="用户权限ER图" tabindex="0" loading="lazy"><figcaption>用户权限ER图</figcaption></figure>
<p>详细的权限设计，参见<a href="/develop-guide/design/permission.html" target="_blank">【权限体系设计】</a>。权限体系主要涉及的管理功能如下：</p>
<h2>[一]用户管理</h2>
<h3>[1]用户列表</h3>
<p>在用户管理页面，可以查看系统用户列表，并进行相应的管理操作。</p>
<figure><img src="/assets/image/operation/user-01.png" alt="用户列表" tabindex="0" loading="lazy"><figcaption>用户列表</figcaption></figure>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>新建用户之后，系统并不会为该用户分配默认密码，需要手动在用户列表中为该用户设置密码。</p>
</div>
<h3>[2]为用户分配角色</h3>
<p>新建完用户之后，可以为该用户指定具体的角色。</p>
<figure><img src="/assets/image/operation/user-02.png" alt="为用户分配角色" tabindex="0" loading="lazy"><figcaption>为用户分配角色</figcaption></figure>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>Dante Cloud 采用的是 RBAC 权限模型，所以角色是用户权限的关键要素。角色关联着前端的菜单以及后端可用的接口。</p>
</div>
<h2>[二]角色管理</h2>
<h3>[1]角色列表</h3>
<p>在角色管理页面，可以查看系统角色列表，并进行相应的管理操作。</p>
<figure><img src="/assets/image/operation/role-01.png" alt="角色列表" tabindex="0" loading="lazy"><figcaption>角色列表</figcaption></figure>
<h3>[2]为用户分配权限</h3>
<p>新建完角色之后，可以为该用户指定具体的角色。</p>
<figure><img src="/assets/image/operation/user-02.png" alt="为用户分配角色" tabindex="0" loading="lazy"><figcaption>为用户分配角色</figcaption></figure>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>Dante Cloud 采用的是 RBAC 权限模型，所以角色是用户权限的关键要素。角色关联着前端的菜单以及后端可用的接口。</p>
</div>
<h2>[三]权限管理</h2>
<p>在权限管理页面，可以查看系统权限列表，并进行相应的管理操作。</p>
<figure><img src="/assets/image/operation/permission-01.png" alt="权限列表" tabindex="0" loading="lazy"><figcaption>权限列表</figcaption></figure>
<h2>[四]默认角色</h2>
<p>Dante Cloud 支持手机验证码登录、第三方应用社会化登录。</p>
<figure><img src="/assets/image/operation/default-role-01.png" alt="登录方式" tabindex="0" loading="lazy"><figcaption>登录方式</figcaption></figure>
<p>这几种登录方式，均支持用户的自动注册。自动注册后的用户，仅能实现登录，是没有任何权限的。</p>
<p>默认角色功能，就是为涉及自动注册类型的登录，提供默认的角色，以保证用户使用该种方式登录后拥有默认的权限，可以正常使用系统。</p>
<figure><img src="/assets/image/operation/default-role-02.png" alt="设置默认角色" tabindex="0" loading="lazy"><figcaption>设置默认角色</figcaption></figure>
<h2>总结</h2>
<p>RBAC 相关管理功能，总体就是在管理 RBAC 相关的基础数据，以及数据之间的关联关系，这和大多数系统的管理功能相同。</p>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/design/rbac.png" type="image/png"/>
    </item>
    <item>
      <title>应用管理</title>
      <link>https://www.herodotus.cn/user-guide/operation/application.html</link>
      <guid>https://www.herodotus.cn/user-guide/operation/application.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">应用管理</source>
      <description>概述 Dante Cloud 核心认证授权功能是基于支持 OAuth 2.1 协议的 Spring Authorization Server 组件实现。 在 OAuth2 协议中，客户端 是最关键和最核心的概念之一，Dante Cloud 中的 应用管理 就是对 OAuth2 客户端 的管理。 [一]应用管理 [1]应用列表 在应用管理页面，可以查看系...</description>
      <pubDate>Mon, 16 Mar 2026 09:40:19 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>Dante Cloud 核心认证授权功能是基于支持 OAuth 2.1 协议的 <code>Spring Authorization Server</code> 组件实现。</p>
<p>在 OAuth2 协议中，<code>客户端</code> 是最关键和最核心的概念之一，Dante Cloud 中的 <code>应用管理</code> 就是对 OAuth2 <code>客户端</code> 的管理。</p>
<h2>[一]应用管理</h2>
<h3>[1]应用列表</h3>
<p>在应用管理页面，可以查看系统应用列表，并进行相应的管理操作。</p>
<figure><img src="/assets/image/operation/oauth2-application-01.png" alt="应用列表" tabindex="0" loading="lazy"><figcaption>应用列表</figcaption></figure>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>在 Dante Cloud 的设计中，将微服务中各个服务都视作为一个 OAuth2 <code>客户端</code>，这样可以进一步提升系统的安全性。当然，也可以让整个系统更加灵活，例如：如果某个服务需要支持更多不同的授权场景。</p>
<p>因此，您在新建服务之后，需要在 <code>应用管理</code> 模块中为新服务新建一个 <code>客户端</code>，该操作的主要作用是为新服务分配新的 <code>clientId</code> 和 <code>clientSecret</code>。当然，如果您觉得麻烦，也可以服用其它服务的 <code>clientId</code> 和 <code>clientSecret</code>。</p>
</div>
<h3>[2]关键参数</h3>
<p>管理 <code>应用</code> 会涉及到很多参数，这些参数都是 <code>Spring Authorization Server</code> 所需要的参数。</p>
<figure><img src="/assets/image/operation/oauth2-application-02.png" alt="应用参数一" tabindex="0" loading="lazy"><figcaption>应用参数一</figcaption></figure>
<figure><img src="/assets/image/operation/oauth2-application-03.png" alt="应用参数二" tabindex="0" loading="lazy"><figcaption>应用参数二</figcaption></figure>
<p>因为参数比较多，这里仅对常用的、必要的参数进行解释说明。对于非必要的、不影响使用的参数将会掠过。感兴趣的朋友可以深入看看 <code>Spring Authorization Server</code> 的源代码。</p>
<h4>1.应用名称</h4>
<p>起一个名字，用于区分不同的应用</p>
<h4>2.应用类型</h4>
<p>用于区分当前的 <code>应用</code> （或者 OAuth2 <code>客户端</code>）的主要用途。</p>
<h4>3.认证模式</h4>
<p>即当前的 OAuth2 <code>客户端</code> 支持哪些认证模式。目前 Dante Cloud 支持以下认证模式。</p>
<ul>
<li>授权码模式（Authorization Code Grant）</li>
<li>客户端凭证模式（Client Credentials Grant）—— Dante Cloud 扩展认证模式，支持 REST 接口通过 <code>Scope</code> 进行鉴权</li>
<li>设备码模式（Device Authorization Grant）</li>
<li>密码模式（Resource Owner Password Credentials Grant）—— Dante Cloud 自定义认证模式</li>
<li>社会化模式（Social Credentials Grant）—— Dante Cloud 自定义认证模式</li>
<li>通行密钥模式（WebAuthn Credentials Grant）—— Dante Cloud 自定义认证模式</li>
<li><code>Authorization Code + PKCE</code></li>
<li><code>OpenID Connect</code></li>
</ul>
<p>详细的使用方法，参见：<a href="/user-guide/security/grant.html" target="_blank">【登录认证模式】</a></p>
<h4>4.客户端验证模式</h4>
<p>OAuth2 的很多认证模式，在进入认证逻辑之前，要先对 <code>客户端</code> 的 <code>合法性</code> 进行验证，以增强安全性。主要是针对客户端 <code>clientId</code> 和 <code>clientSecret</code> 的验证。</p>
<p>验证 <code>clientId</code> 和 <code>clientSecret</code> 的方法（即：<code>Spring Authorization Server</code> 中的 <code>ClientAuthenticationMethod</code> 属性）也有很多种，主要有：</p>
<ul>
<li><strong>client_secret_basic</strong></li>
<li><strong>client_secret_post</strong></li>
<li><strong>client_secret_jwt</strong></li>
<li><strong>private_key_jwt</strong></li>
<li><strong>tls_client_auth</strong></li>
<li><strong>self_signed_tls_client_auth</strong></li>
<li><strong>none</strong></li>
</ul>
<p>因为，其中部分验证方式要么很少使用，要么当前 Dante Cloud 尚不支持。目前主要使用的验证方法是：<strong>client_secret_basic</strong>、<strong>client_secret_post</strong> 和 <strong>none</strong> 三种：</p>
<ul>
<li><strong>client_secret_basic</strong> 的含义，即：以 Basic Header 的方式传递 <code>clientId</code> 和 <code>clientSecret</code>。</li>
</ul>
<p>使用时，以&lt;clientId&gt;:&lt;clientSecret&gt;的格式对客户端凭据进行 Base64 编码，将编码后的值放入到请求的 Authorization Header 中。</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">'Authorization '</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'Basic '</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> +</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> Base64.encode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(&#x3C;clientId>:&#x3C;clientSecret>)</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ul>
<li><strong>client_secret_post</strong></li>
</ul>
<p><code>client_secret_post</code> 与 <code>client_secret_basic</code> 作用类似，不同点是：</p>
<ul>
<li><code>client_secret_post</code> ：将 <code>clientId</code> 和 <code>clientSecret</code> 值直接放入在 POST 的请求体中</li>
<li><code>client_secret_basic</code>：将 <code>clientId</code> 和 <code>clientSecret</code> 值放入在 POST 请求的 <code>Authorization</code> Header 中。</li>
</ul>
<p>因为作用相似，所以在配置 <code>Spring Authorization Server</code> Client 信息时，通常 <code>client_secret_post</code> 和 <code>client_secret_basic</code> 都是成对出现。</p>
<ul>
<li><strong>none</strong></li>
</ul>
<p>只有当前的 OAuth2 客户端为 Public Client 时，Client Authentication Methods 参数才能设置为 none。</p>
<blockquote>
<p>这意味着该客户端是面向公共开放的或者是用户自己可以保障绝对信任的</p>
</blockquote>
<h4>5.回调地址</h4>
<p>回调地址，即授权码模式中的 <code>redirect_url</code>。</p>
<h4>6.是否需要认证确认</h4>
<p>在标准的授权码认证模式中，会涉及两个 Web 页面：一个为登录页面，用于用户输入登录认证信息进行登录；另一个为确认页面，用于用户指定具体授权的 <code>Scope</code>。</p>
<p>标准数场景下，这两个页面都需要。但是，在有些场景下，可能就不需要确认页面。</p>
<p>所以，<code>是否需要认证确认</code> 参数的作用，就是用来控制是否需要在授权码认证流程中，显示确认页面。</p>
<h4>7.客户端密钥过期时间</h4>
<p>一个安全保障性参数，用于设定 <code>clientSecret</code> 的有效期。一旦超过密钥有效期，该 <code>客户端</code> 将无法继续使用。只有修改了 <code>客户端</code> 密钥过期时间之后，才能继续使用。</p>
<h4>8.令牌有效期</h4>
<p>令牌有效期，是指 <code>Access Token</code> 的有效期，这也是一个重要的参数。令牌有效期到期之后，该令牌将无法再继续使用。对应到前端界面来说，令牌到期之后，用户就只能重新登录系统。</p>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<ol>
<li><code>Access Token</code> 的有效期，一定要设定一为一个合理的时间。如果设定的有效期过短，那么就会出现用户需要频繁 <code>登录系统</code> 或者 <code>重新获取 Token</code>。如果设定的有效期过长，那么用户将会长时间不需要登录系统，会降低账户和数据的安全性。</li>
<li>除了直接从系统获取 <code>Access Token</code> 外，还可以使用 <code>Refresh Token</code> 来获取 <code>Access Token</code>。</li>
</ol>
</div>
<h4>9.刷新令牌有效期</h4>
<p>刷新令牌有效期，是指 <code>Refresh Token</code> 的有效期，这也是一个重要的参数。刷新令牌有效期到期之后，<code>客户端</code> 将无法使用刷新令牌来获取 <code>Access Token</code>。之只能重新获取新的 <code>Access Token</code> 和 <code>Refresh Token</code></p>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>因为 <code>Refresh Token</code> 也是获取 <code>Access Token</code> 的一种有效途径。所以，刷新令牌有效期通常会设定为一个较长的时间，这样可以保证持续复用 <code>Refresh Token</code>。至于，刷新令牌有效期具体应该设定为多长，可以根据业务需要而定。但是有一个基本原则，就是 <strong>刷新令牌有效期</strong> 一定要远大于 <strong>令牌有效期</strong></p>
</div>
<h4>10.授权码有效期</h4>
<p>授权码有效期，是指授权码认证模式中，返回的 <code>code</code> 的有效期。这是 OAuth2 协议中，保障安全性的一个重要措施。</p>
<h4>11.设备激活码有效期</h4>
<p>设备激活码有效期，是指设备码模式中，激活码的有效期。这是 OAuth2 协议中，保障安全性的一个重要措施。</p>
<h4>12.是否允许重用刷新令牌</h4>
<p>刷新令牌 <code>Refresh Token</code> 一旦到期之后，将无法继续使用，只能重新申请新的 <code>Access Token</code> （新的 <code>Access Token</code> 中会包含新的 <code>Refresh Token</code>）。</p>
<p>但是，有些场景下 <code>Refresh Token</code> 到期后，可能还是需要能够继续使用原有 <code>Refresh Token</code>。那么，就可以通过设置 <strong>是否允许重用刷新令牌</strong>，实现 <code>Refresh Token</code> 的复用。</p>
<h2>[二]范围管理</h2>
<p>范围管理，是指对 <code>Scope</code> 的管理。<code>Scope</code> 是 OAuth2 协议中最核心的 <code>权限</code> 元素。OAuth2 协议中，就是利用 <code>Scope</code> 来实现鉴权。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>OAuth2 中的 <code>Scope</code>，其实有点类似于 RBAC 权限模型中的 <code>Role</code>，但是本质和用法又有较大的不同。想要深入理解，可以参见： <a href="/develop-guide/advance/scope.html" target="_blank">《OAuth 2 中的 Scope 与 Role 深度解析》</a></p>
</div>
<h3>[1]范围列表</h3>
<p>在范围管理页面，可以查看系统范围列表，并进行相应的管理操作。</p>
<figure><img src="/assets/image/operation/oauth2-scope-01.png" alt="范围列表" tabindex="0" loading="lazy"><figcaption>范围列表</figcaption></figure>
<h3>[2]为范围配置权限</h3>
<p>新建完范围之后，可以为该范围指定具体的权限。</p>
<figure><img src="/assets/image/operation/oauth2-scope-02.png" alt="为范围配置权限" tabindex="0" loading="lazy"><figcaption>为范围配置权限</figcaption></figure>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>这里的 <code>权限</code> 与系统用户的 <code>权限</code> 是同一套权限。这时为了实现统一管理，同时避免理解混淆的一种设计，而且也是 Dante Cloud 特有的功能之一。</p>
</div>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>虽说 <code>Scope</code> 是 OAuth2 协议中的重要概念之一，但是 OAuth2 并没有为 <code>Scope</code> 提供具体的实现，在 <code>Spring Authorization Server</code> 中，对于 <code>Scope</code> 的鉴权也仅仅是简单的字符串比较。这种设计当然是为了保证更好的扩展性，因此，<code>Scope</code> 具体对应那种类型的权限必须要由 OAuth2 协议的使用者自己实现。</p>
<p>那么，在一个面向 REST API 的应用系统中，必须要自己来实现 REST API 与 <code>Scope</code> 的对应以及具体的鉴权逻辑。</p>
<p>Dante Cloud 基于 <code>Spring Authorization Server</code> 实现了利用 <code>Scope</code> 对 REST API 的鉴权，这也是 Dante Cloud 的特色之一。但是由于在 OAuth2 协议基础之上引入的 RBAC 权限模型，因此在一些授权模式中，通过 <code>Scope</code> 对 REST API 进行鉴权没有意义。目前 Dante Cloud 支持利用 <code>Scope</code> 对 REST API 的鉴权的认证模式，只有 <strong>客户端凭证模式（Client Credentials Grant）</strong> 和 <strong>设备码模式（Device Authorization Grant）</strong> 两种。</p>
<blockquote>
<p>因为 OAuth2 本身的设计是面向“非人”的设施进行设计的，而 RBAC 权限模型是面向“人”的，所以两者在某些方面融合会有问题。详情可以参阅：<a href="/develop-guide/advance/scope.html" target="_blank">《OAuth 2 中的 Scope 与 Role 深度解析》</a></p>
</blockquote>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/operation/oauth2-application-01.png" type="image/png"/>
    </item>
    <item>
      <title>安全管控</title>
      <link>https://www.herodotus.cn/user-guide/operation/audit.html</link>
      <guid>https://www.herodotus.cn/user-guide/operation/audit.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">安全管控</source>
      <description>概述 在前端界面中，Dante Cloud 提供了部分安全相关的管理和展示功能。 [一]凭证管理 凭证管理，即 Token 管理。可以查看目前系统已经颁发的 Token 详情。删除某个 Token 将会强制 用户 或者调用接口的 客户端 重新登录系统。 凭证管理凭证管理 [二]日志审计 日志审计，主要用于显示用户登录、登出等操作的详细日志信息。该功能是...</description>
      <pubDate>Mon, 16 Mar 2026 09:40:19 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>在前端界面中，Dante Cloud 提供了部分安全相关的管理和展示功能。</p>
<h3>[一]凭证管理</h3>
<p>凭证管理，即 Token 管理。可以查看目前系统已经颁发的 Token 详情。删除某个 Token 将会强制 <code>用户</code> 或者调用接口的 <code>客户端</code> 重新登录系统。</p>
<figure><img src="/assets/image/operation/oauth2-token-01.png" alt="凭证管理" tabindex="0" loading="lazy"><figcaption>凭证管理</figcaption></figure>
<h3>[二]日志审计</h3>
<p>日志审计，主要用于显示用户登录、登出等操作的详细日志信息。该功能是等保测评以及企业应用安全合规性测试中，必须要具备的功能。</p>
<figure><img src="/assets/image/operation/audit-user-01.png" alt="日志审计" tabindex="0" loading="lazy"><figcaption>日志审计</figcaption></figure>
<h3>[三]接口审计(企业版)</h3>
<p>接口审计，主要用于记录用户在系统中的各项操作（实际调用了哪些 REST API）。接口审计功能是等保测评以及企业应用安全合规性测试中，必须要具备的功能。</p>
<figure><img src="/assets/image/operation/audit-rest-01.png" alt="接口审计" tabindex="0" loading="lazy"><figcaption>接口审计</figcaption></figure>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<ol>
<li>接口审计功能默认不开启。如需使用需要修改配置手动开启。</li>
<li>因为接口审计会记录大量的信息，随着数据量增大，可能会影响关系数据的存储和查询。所以，Dante Cloud 额外增加了审计数据存储切换功能，仅需要修改配置参数，就可以将接口审计数据存储至 MongoDB，以提供更高效的存储和查询。详见：<a href="/user-guide/persistence/switch-persistence.html#%E4%BA%8C-%E7%B3%BB%E7%BB%9F%E5%AE%A1%E8%AE%A1%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E5%88%87%E6%8D%A2" target="_blank">【系统审计数据存储切换】</a></li>
</ol>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/operation/oauth2-token-01.png" type="image/png"/>
    </item>
    <item>
      <title>证书管理(企业版)</title>
      <link>https://www.herodotus.cn/user-guide/operation/certificate.html</link>
      <guid>https://www.herodotus.cn/user-guide/operation/certificate.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">证书管理(企业版)</source>
      <description>概述 在很多应用场景下，都会涉及到自签名证书的使用。例如：在 Spring Authorization Server 中使用证书来计算 JWT Token 的签名，使用 SSL 来提升 Http Client 访问的安全性等。特别是在微服务架构系统中，为了提升安全性，需要使用到 SSL 或者证书的地方更多。 通常，我们要生成自签名证书，都是使用例如 J...</description>
      <pubDate>Mon, 16 Mar 2026 09:40:19 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>在很多应用场景下，都会涉及到自签名证书的使用。例如：在 Spring Authorization Server 中使用证书来计算 JWT Token 的签名，使用 SSL 来提升 Http Client 访问的安全性等。特别是在微服务架构系统中，为了提升安全性，需要使用到 SSL 或者证书的地方更多。</p>
<p>通常，我们要生成自签名证书，都是使用例如 JDK 自带 Keytool 工具或者安装 OpenSSL，通过在命令行输入指令来生成证书。</p>
<p>虽然 Keytool 或 OpenSSL 使用都很方便，但这都是独立于应用以外的工具，通过程序代码调用特别时 Java 代码，并不是很方便。而且，在使用时还需要了解一大堆的参数，对于不经常使用 Keytool 或 OpenSSL 的用户来说，想要把各种参数了解清楚并无误的生成证书，也是需要花费一定的时间经历的。一段时间不用，可能就忘记了，需要使用的时候还需要再了解一次。最主要的是，外部命令行工具，没有一个统一的、可视化的管理机制。</p>
<p>为了解决这个问题，Dante Cloud 单独开发一套证书管理功能模块，不依赖 Keytool 或 OpenSSL，通过系统界面输入必要参数，就可以签发根证书、CA 证书以及最终实体证书。证书信息通过系统统一管理，支持本地和远端文件存储，还支持不同格式的导出。</p>
<h3>[一]证书管理</h3>
<h3>[1]证书列表</h3>
<p>在证书管理页面，可以查看系统证书列表，并进行相应的管理操作。</p>
<figure><img src="/assets/image/operation/certificate-01.png" alt="证书列表" tabindex="0" loading="lazy"><figcaption>证书列表</figcaption></figure>
<h3>[2]新建证书</h3>
<p>新建证书，根据界面提示输入相关信息，新建证书</p>
<figure><img src="/assets/image/operation/certificate-02.png" alt="新建证书" tabindex="0" loading="lazy"><figcaption>新建证书</figcaption></figure>
<p>说明：</p>
<ol>
<li><strong>根证书</strong>：它是证书颁发机构（CA）的最顶层证书，是整个信任链的起点和锚点。通常存储在离线的硬件设备中（就像锁在保险柜里），不直接参与日常的网络通信，以防被窃取。</li>
<li><strong>中间根证书</strong>：连接根证书和最终用户证书的桥梁。它活跃在服务器上，负责实际的证书签发工作。如果中间证书被黑客入侵，CA可以吊销它，而无需动到最顶层的根证书，从而保护了根证书的绝对安全。</li>
<li><strong>最终实体证书</strong>：也就是叶子证书，是最终用户实际使用的证书。它上面绑定了具体的网站地址（如 <a href="http://google.com" target="_blank" rel="noopener noreferrer">google.com</a>）或用户身份。通常有效期较短（如90天或1年），需要定期续期。</li>
</ol>
<h3>[二]证书下载</h3>
<p>通过证书管理功能新建了证书之后，Dante Cloud 并不会马上生成对应的证书文件。如果需要使用具体的证书文件，点击下载按钮选择具体的之后，Dante Cloud 才会生成对应格式的证书文件，同时将其下载给用户。</p>
<figure><img src="/assets/image/operation/certificate-03.png" alt="证书下载" tabindex="0" loading="lazy"><figcaption>证书下载</figcaption></figure>
<p>证书文件生成之后，可以通过查看文件列表来查看具体的文件。</p>
<figure><img src="/assets/image/operation/certificate-04.png" alt="查看证书" tabindex="0" loading="lazy"><figcaption>查看证书</figcaption></figure>
<h3>[三]后续计划</h3>
<p>当前，开源版和企业版均使用该功能生成的证书，为 Spring Authorization Server 提供 JWT Token 签名支持。</p>
<p>后续，可能会根据开发安全以及实际需要，开发证书体系相关的证书吊销、OCSP 以及 ACME 等完整的 PKI 管理功能。</p>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/operation/certificate-01.png" type="image/png"/>
    </item>
    <item>
      <title>个人主控台</title>
      <link>https://www.herodotus.cn/user-guide/operation/dashboard.html</link>
      <guid>https://www.herodotus.cn/user-guide/operation/dashboard.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">个人主控台</source>
      <description>概述 用户登录系统之后，默认进入的页面就是 个人主控台，也就是常见的 Dashboard。 当前 Dante Cloud 个人主控台中的图表，除了 实时在线用户 外基本为假数据，仅用于效果的展示。之所以这样做的原因如下： 一方面，目前没有找到比较合适的、与平台相关的、适用于主控台展示的、偏重系统管理的数据统计内容。考虑到如果盲目的增加一些，看似为真实的...</description>
      <pubDate>Mon, 16 Mar 2026 09:40:19 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>用户登录系统之后，默认进入的页面就是 <code>个人主控台</code>，也就是常见的 Dashboard。</p>
<p>当前 Dante Cloud 个人主控台中的图表，除了 <code>实时在线用户</code> 外基本为假数据，仅用于效果的展示。之所以这样做的原因如下：</p>
<ol>
<li>一方面，目前没有找到比较合适的、与平台相关的、适用于主控台展示的、偏重系统管理的数据统计内容。考虑到如果盲目的增加一些，看似为真实的内容实则使用价值不高数据统计，反倒会增加使用和改造的成本。</li>
<li>另一方面，真实可用于生产的数据统计内容，通常会涉及定时任务、数据提取计算以及数据视图或存储过程的辅助，并结合实际业务，才能保证数据图标展示的价值性。如果现在增加这部分内容，会影响用户搭建和使用的复杂度。</li>
<li>再一方面，Dante Cloud 还在持续不断的升级和完善中，如果在新增某个功能时发现有特别价值性的统计分析功能，也会同步增加上来。</li>
</ol>
<h2>[一]实时在线用户</h2>
<p>微服务（或者前后端分离系统）由于是基于 Token 的运行设计逻辑，与传统基于 Session 的单体有较大区别。</p>
<p>传统基于 Session 的单体，可以基于 Session 来统计实时在线用户情况（虽然也不是特别的精准）。但是基于 Token ，由于其本身就是无状态的，价值是存储在客户端中（即使后端会存有 Token 的数据），也很难实时统计在线的用户情况。</p>
<p>因为 Dante Cloud 提供了完善的 WebSocket 服务（支持跨实例的消息发送），所以结合这一点来实现 <code>实时在线用户</code> 的统计。基于 WebSocket 来实现 <code>实时在线用户</code> 的统计，有以下优点：</p>
<ol>
<li>实时在线用户统计比较精确。利用 WebSocket 自身的特性（包括：Session 以及通信逻辑），可以很好的规避 Token 无状态性，让用户是否在线的状态性更加精确。</li>
<li>应用场景更贴合实时用户统计。WebSocket 通信通常是需要与前端界面进行配合的，这也就决定了这种场景下统计实时在线用户更有意义。假设还有很多用户是采用调用 API 接口的方式来使用系统，那么这对这一类用户统计实时在线性的就没有意义。</li>
</ol>
<blockquote>
<p>因为只要生成了 Token，什么时间调用完全由用户控制，而且调用响应时间通常不会很长，这种情况下不存在所谓的 <code>在线</code></p>
</blockquote>
<figure><img src="/assets/image/operation/dashboard-01.png" alt="系统首页" tabindex="0" loading="lazy"><figcaption>系统首页</figcaption></figure>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/operation/dashboard-01.png" type="image/png"/>
    </item>
    <item>
      <title>字典管理</title>
      <link>https://www.herodotus.cn/user-guide/operation/dictionary.html</link>
      <guid>https://www.herodotus.cn/user-guide/operation/dictionary.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">字典管理</source>
      <description>概述 在常规的系统开发中，是离不开 字典 的使用的。常规的系统设计会将字典类型的数据设计成 字典表，在通过关联查询给用户提供更直观、更易理解的信息展示。除此以外，特别是在 Java 编程语言中，会使用定义枚举类型来定义 字典。 这两种方式都各有利弊： 使用 字典表：就需要与实际业务的 SQL 进行强关联，不容易做成通用性。而且，频繁的查询会增加数据的压...</description>
      <pubDate>Mon, 16 Mar 2026 09:40:19 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>在常规的系统开发中，是离不开 <code>字典</code> 的使用的。常规的系统设计会将字典类型的数据设计成 <code>字典表</code>，在通过关联查询给用户提供更直观、更易理解的信息展示。除此以外，特别是在 Java 编程语言中，会使用定义枚举类型来定义 <code>字典</code>。</p>
<p>这两种方式都各有利弊：</p>
<ol>
<li>使用 <code>字典表</code>：就需要与实际业务的 SQL 进行强关联，不容易做成通用性。而且，频繁的查询会增加数据的压力，而且不便于实际代码使用。</li>
<li>使用枚举：使用枚举类型字典，可以很好的与代码融合，甚至作为参数进行前后端传输。但这种方式不好与 SQL 进行融合。</li>
</ol>
<p>如果深入思考一下，其实 <code>字典</code> 终极用途就是为了 <strong>更人性化的显示信息同时支持动态的管理</strong>。不管是使用 SQL 关联查询的方式还是 Java 枚举的方式，都是为了给用户更直观的信息展示，而不是单纯展示关联的 id 或者 code。</p>
<p>因此，Dante Cloud 选择了 Java 枚举的方式来做 <code>字典</code>，同时提供了聚合管理方式来支持字典管理。这样做的好处有以下几点：</p>
<ol>
<li>Java 枚举可以很好的和 Java 代码融合，不管是作为分类还是类型判断都很方便。</li>
<li>使用枚举聚合的机制，实现了 Java 枚举的完全动态管理，无需手动录入数据。详见：<a href="/develop-guide/design/enum-aggregation.html" target="_blank">【枚举字典聚合】</a></li>
<li>利用 JPA 实体支持枚举属性以及支持使用枚举作为查询参数的特性，将 Java 枚举与实际 SQL 查询进行关联，而无需单独设计 <code>字典表</code> 进行关联查询。</li>
<li>提供统一的接口，前端使用字典时 <code>随用随取</code>，并采用前端缓存避免反复查询后端。既提高的数据使用效率又避免大量查询或者缓存数据影响性能。</li>
</ol>
<h2>[一]字典管理</h2>
<p>在系统前端提供了 <code>字典</code> 统一管理界面。</p>
<figure><img src="/assets/image/operation/dictionary-01.png" alt="字典管理" tabindex="0" loading="lazy"><figcaption>字典管理</figcaption></figure>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<ol>
<li>Dante Cloud 字典主要用于前端下拉框或者表格等数据的转换显示，所有查询过或者使用到的字典数据均缓存于前端。因此，使用字典管理变更过字典信息，是无法实时显示的，需要用户退出系统重新登录后才会显示最新的信息。</li>
<li>对于系统自动聚合的字典分类下的数据，只能进行修改。如果在该分类下新增条目，与后端枚举不对应，会出现异常。如果仅在前端删除该分类下的条目，后端枚举没有同步修改，系统重新启动后还会将已删除条目补充进来。原理详见：<a href="/develop-guide/design/enum-aggregation.html" target="_blank">【枚举字典聚合】</a></li>
</ol>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/operation/dictionary-01.png" type="image/png"/>
    </item>
    <item>
      <title>人事管理</title>
      <link>https://www.herodotus.cn/user-guide/operation/hr.html</link>
      <guid>https://www.herodotus.cn/user-guide/operation/hr.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">人事管理</source>
      <description>概述 人事管理，即企业内部信息系统中的单位管理、部门管理以及人员管理等常见功能。之所以将人事管理相关功能，作为 Dante Cloud 基础功能之一，主要有以下考虑： Dante Cloud 除了定位于面向互联网以外还希望可以服务于企业内部使用。 除了小程序、第三方系统社交用户等用户类型之外，企业人员类型用户也是一种典型用户类型，Dante Cloud...</description>
      <pubDate>Mon, 16 Mar 2026 09:40:19 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>人事管理，即企业内部信息系统中的单位管理、部门管理以及人员管理等常见功能。之所以将人事管理相关功能，作为 Dante Cloud 基础功能之一，主要有以下考虑：</p>
<ol>
<li>Dante Cloud 除了定位于面向互联网以外还希望可以服务于企业内部使用。</li>
<li>除了小程序、第三方系统社交用户等用户类型之外，企业人员类型用户也是一种典型用户类型，Dante Cloud 期望通过对这种类型用户的兼容，以提升整个系统用户体系的兼容能力</li>
<li>Dante Cloud 提供了工作流体系的支持，人事管理体系是与工作流用户体系非常契合的一种用户体系。</li>
<li>人事管理也是微服务的一个典型应用场景，通过平台统一的用户管理，可以实现对外提供用户信息接口的调用，便于多系统的整合及调用。</li>
</ol>
<p>同时，结合作者以往的工作经验，Dante Cloud 对传统的人事管理功能进行一定个性化设计，主要的特点就是增加了 <code>人员归属</code> 功能。</p>
<p>在企业信息管理系统中，除了单位、部门以及人员这类常规的组织机构形式外，还会存在团青、社会组织等其它多种类型的组织机构形式。在传统单体系统建设时期，就会出现以下问题：</p>
<ol>
<li>大型企业会根据面向团青、社会组织等不同的业务领域，分别建设不同的应用系统。早期各个系统管理各自的组织机构，并不会出现什么问题。随着信息化的发展，需要统一和整合多个系统时，不同的组织机构和用户体系就很难进行统一。</li>
<li>一些企业会直接在现有的系统中，增加团青、社会组织等组织体系的支持。由于与常规组织机构和用户体系的不兼容，常见的解决措施就是会在同一套系统中建设两套用户信息。例如：在人事组织机构体系下的人员 A，对应的系统用户为 a。现在为了适应新的团青组织机构，不得不新建一个用户 b。这样就出现，一个人员在同一个系统对应两个用户的局面。</li>
</ol>
<p><code>人员归属</code> 功能就是为了解决以上问题。主要的设计思想是，系统中始终维护一套人员信息以保证人员信息的唯一性和易维护性。单位和部门支持不同类型的组织机构形式，通过 <code>人员归属</code> 功能维护人员与不同类型组织机构的关联关系。这样，不同组织机构类型互不影响并且互相独立，也可保证系统中始终维护一套标准的人员信息。</p>
<h2>[一]单位管理</h2>
<h3>[1]单位列表</h3>
<p>在单位管理页面，可以查看系统单位列表，并进行相应的管理操作。</p>
<figure><img src="/assets/image/operation/organization-01.png" alt="单位列表" tabindex="0" loading="lazy"><figcaption>单位列表</figcaption></figure>
<h2>[二]部门管理</h2>
<h3>[1]部门列表</h3>
<p>在部门管理页面，可以查看系统部门列表。</p>
<figure><img src="/assets/image/operation/department-01.png" alt="部门列表" tabindex="0" loading="lazy"><figcaption>部门列表</figcaption></figure>
<h3>[2]管理操作</h3>
<p>需要先选中左侧的某个单位之后，才能进行响应的操作。</p>
<figure><img src="/assets/image/operation/department-02.png" alt="管理操作" tabindex="0" loading="lazy"><figcaption>管理操作</figcaption></figure>
<h2>[三]人员管理</h2>
<h3>[1]人员列表</h3>
<p>在人员管理页面，可以查看系统人员列表，并进行相应的管理操作。这里不需要与任何单位和部门进行关联。</p>
<figure><img src="/assets/image/operation/employee-01.png" alt="人员列表" tabindex="0" loading="lazy"><figcaption>人员列表</figcaption></figure>
<h2>[四]人员归属</h2>
<h3>[1]归属列表</h3>
<p>在人员归属管理页面，可以查看某个单位的具体部门下，已配置的人员列表。</p>
<figure><img src="/assets/image/operation/ownership-01.png" alt="归属列表" tabindex="0" loading="lazy"><figcaption>归属列表</figcaption></figure>
<h3>[2]归属配置</h3>
<p>选中某个单位的具体部门后，可以从系统人员列表选择具体的人员，与组织机构进行关联。</p>
<figure><img src="/assets/image/operation/ownership-02.png" alt="归属配置" tabindex="0" loading="lazy"><figcaption>归属配置</figcaption></figure>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/operation/organization-01.png" type="image/png"/>
    </item>
    <item>
      <title>对象存储</title>
      <link>https://www.herodotus.cn/user-guide/operation/oss.html</link>
      <guid>https://www.herodotus.cn/user-guide/operation/oss.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">对象存储</source>
      <description>概述 自 v4.0.X 版本开始，Dante Cloud 开源版全面改用 AWS S3 V2 组件作为 OSS 访问操作的基础组件。原 Dante OSS 组件库已不再进行维护。 改用 AWS S3 V2 组件可以更好的基于 S3 协议操作 OSS，避免了像原 Dante OSS 为了适配不同厂商 OSS 产品，所带来的大量适配工作（不同厂商在 S3 ...</description>
      <pubDate>Mon, 16 Mar 2026 09:40:19 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>自 v4.0.X 版本开始，Dante Cloud 开源版全面改用 AWS S3 V2 组件作为 OSS 访问操作的基础组件。原 Dante OSS 组件库已不再进行维护。</p>
<p>改用 AWS S3 V2 组件可以更好的基于 S3 协议操作 OSS，避免了像原 Dante OSS 为了适配不同厂商 OSS 产品，所带来的大量适配工作（不同厂商在 S3 基础之上做了大量的扩展工作）。但这也导致无法全面支持除了 AWS 以外的任何一家厂商的 OSS 功能。</p>
<p>加之轻量级开源 OSS Minio 已经不再开源。为了方便用户使用，Dante Cloud 基于 AWS S3 V2 组件提供的 API，参考 Minio 界面在前端工程中实现了一套管理界面，以方便用户使用</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>因为 AWS S3 V2 组件 API 无法覆盖所有 Minio 功能，特别是 Minio 自定义实现功能。所以，前端功能仅是在 AWS S3 V2 组件 API 基础之上，尽可能的实现相关功能。</p>
</div>
<h2>[一]存储桶管理</h2>
<h3>[1]存储桶列表</h3>
<p>在存储桶管理页面，可以查看 OSS 存储桶列表，并进行相应的管理操作，包括：开启和关闭存储桶 <code>Public</code> 状态</p>
<figure><img src="/assets/image/operation/oss-bucket-01.png" alt="存储桶列表" tabindex="0" loading="lazy"><figcaption>存储桶列表</figcaption></figure>
<h3>[2]新建存储桶</h3>
<p>在存储桶管理页面，可以新建存储桶。</p>
<figure><img src="/assets/image/operation/oss-bucket-02.png" alt="新建存储桶" tabindex="0" loading="lazy"><figcaption>新建存储桶</figcaption></figure>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>在新建存储桶时，只有开启了 <code>对象锁定</code> 功能后，才会支持 Object <code>Versiong</code>、<code>Retention</code> 和 <code>Legal Hold</code> 等功能</p>
</div>
<h2>[二]对象管理</h2>
<h3>[1]对象列表</h3>
<p>在对象管理页面，可以查看 OSS 对象列表，并进行相应的管理操作。对象列表支持 OSS <code>ContinuationToken</code> 方式的分页。</p>
<figure><img src="/assets/image/operation/oss-object-01.png" alt="存储桶列表" tabindex="0" loading="lazy"><figcaption>存储桶列表</figcaption></figure>
<h3>[2]对象设置</h3>
<p>点击对象列表中某个对象的 <code>设置</code> 按钮，可以进行相应的设置操作</p>
<h4>设置对象留存策略</h4>
<figure><img src="/assets/image/operation/oss-object-02.png" alt="设置对象留存策略" tabindex="0" loading="lazy"><figcaption>设置对象留存策略</figcaption></figure>
<h4>设置对象保留策略</h4>
<figure><img src="/assets/image/operation/oss-object-03.png" alt="设置对象保留策略" tabindex="0" loading="lazy"><figcaption>设置对象保留策略</figcaption></figure>
<h4>查看对象版本信息</h4>
<figure><img src="/assets/image/operation/oss-object-04.png" alt="查看对象版本信息" tabindex="0" loading="lazy"><figcaption>查看对象版本信息</figcaption></figure>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/operation/oss-bucket-01.png" type="image/png"/>
    </item>
    <item>
      <title>系统登录</title>
      <link>https://www.herodotus.cn/user-guide/operation/signin.html</link>
      <guid>https://www.herodotus.cn/user-guide/operation/signin.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">系统登录</source>
      <description>概述 Dante Cloud 提供用户名密码、手机短信验证码、第三方系统社会化、Passkey 通行密钥等多种登录方式。同时，也支持为小程序等应用提供登录认证支持（为后端接口支持前端暂不支持体验） [一]用户名密码登录 Dante Cloud 支持用户名密码登录方式。是通过自定义扩展 Spring Authorization Server，实现基于 O...</description>
      <pubDate>Mon, 16 Mar 2026 09:40:19 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>Dante Cloud 提供用户名密码、手机短信验证码、第三方系统社会化、Passkey 通行密钥等多种登录方式。同时，也支持为小程序等应用提供登录认证支持（为后端接口支持前端暂不支持体验）</p>
<h2>[一]用户名密码登录</h2>
<p>Dante Cloud 支持用户名密码登录方式。是通过自定义扩展 Spring Authorization Server，实现基于 OAuth2 的 <code>密码</code> 认证模式，来实现的用户名密码登录。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<ol>
<li>Spring Authorization Server 是基于 OAuth 2.1 协议的，在 OAuth 2.1 协议中已经不再支持 <code>密码</code> 认证模式。之所以会自定义扩展 <code>密码</code> 认证模式，是为了兼容老客户以及老系统的使用模式。</li>
<li>所以在配置支持密码认证模式的客户端时，需要未该客户端配置 <code>password</code> 认证模式</li>
</ol>
</div>
<figure><img src="/assets/image/operation/signin-01.png" alt="用户名密码登录" tabindex="0" loading="lazy"><figcaption>用户名密码登录</figcaption></figure>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<ol>
<li>为了防止登录敏感信息，例如：密码，在传输过程中被抓包，所以用户名密码登录以及验证码采取的前后端数据加密传输。</li>
<li>Dante Cloud 前后端数据加密，采用数字信封技术，详情见<a href="/user-guide/security/security-extend.html#%E4%BA%8C-%E6%95%B0%E5%AD%97%E4%BF%A1%E5%B0%81" target="_blank">【数字信封】</a>。因此，需要在使用前进行“握手”（交换密钥），在未交换成功之前，所有输入框、按钮都禁止进行操作。</li>
<li>为了提升开放接口（即：/open/** 类型）的安全性，在 <code>数字信封</code> 握手时，会同时验证 OAuth2 <code>客户端</code> 的有效性。如果验证失败或者数据库初始化时缺少必要的数据，那么就会提示<a href="/get-started/install/faq.html#_7-%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%BA%AB%E4%BB%BD%E9%AA%8C%E8%AF%81%E5%A4%B1%E8%B4%A5" target="_blank">【客户端验证失败错误】</a></li>
</ol>
</div>
<h2>[二]手机验证码登录</h2>
<p>Dante Cloud 支持手机验证码方式。</p>
<figure><img src="/assets/image/operation/signin-02.png" alt="手机验证码登录" tabindex="0" loading="lazy"><figcaption>手机验证码登录</figcaption></figure>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<ol>
<li>Dante Cloud 提供了手机验证码登录 <code>沙盒</code> 模式。即，如果暂时尚未开通短信接口，可以使用沙盒模式模拟手机验证码登录的整个过程。该过程除了没有真正向手机发送验证码外，所有过程都与实际过程一致。</li>
<li>手机验证码登录方式，也是采用自定义扩展 Spring Authorization Server 认证模式实现，并且与现有认证模式统一。登录时，统一采用 <code>/oauth2/token</code> 接口获取 Token，</li>
<li>需要为客户端配置 <code>social_credentials</code> 认证模式，才允许该 <code>客户端</code> 使用手机验证码登录</li>
<li>因为涉及到自动注册的过程，所以需要需要为该类型的登录分配 <code>默认角色</code>，以便登录后有可以操作的内容。参见：<a href="/user-guide/operation/rbac.html#%E5%9B%9B-%E9%BB%98%E8%AE%A4%E8%A7%92%E8%89%B2" target="_blank">【设置默认角色】</a></li>
</ol>
</div>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>在 Dante Cloud 真实的手机验证码登录过程中，如果用户使用的手机号码是第一次使用，那么首先会用该手机号码进行注册，然后再进行登录。这一过程与互联网应用逻辑类似。</p>
<p>如果您使用 Dante Cloud 作为企业内部系统使用，特别还涉及到组织机构等内容，请慎重考虑是否需要使用手机验证码登录。当然，可以考虑提前初始化内部人员相关信息，以规避注册过程。</p>
</div>
<h2>[三]第三方系统社会化登录</h2>
<p>第三方系统社会化登录，即利用用户在第三方系统中的账号，来登录 Dante Cloud 系统。这中登录方式，在互联网应用中颇为常见，例如：可以使用支付宝账号登录淘宝或者其它系统。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<ol>
<li>第三方系统社会化登录本质上就是 OAuth2 协议中的 <code>授权码模式</code>，因此需要在第三方系统中先申请注册一个 <code>应用</code>，用分配好的 clientId、clientSecret、redirectUrl 等信息配置在 Dante Cloud 中。然后，就可以使用该系统登录 Dante Cloud。</li>
<li>Dante Cloud 第三方系统社会化登录基于 <code>JustAuth</code> 实现，所以 <code>JustAuth</code> 支持的第三方系统，Dante Cloud 均支持。</li>
</ol>
</div>
<p>在 Dante Cloud 中的示例配置方式如下：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">herodotus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  assistant</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    access</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      justauth</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        enabled</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        configs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          GITEE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            client-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">7c1623d76f3909757912338688cae8a061e241b56070face6d54114eb61</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            client-secret</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">8c343cad9ca732d54242f002d254239e17b68cab11c1d506a57822be</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            redirect-uri</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">http://aw255fkfqy5m.ngrok.xiaomiqiu123.top/social/oauth2/callback/GITEE</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          OSCHINA</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            client-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">7c1623d76f3909757912338688cae8a061e241b56070face6d54114eb61</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            client-secret</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">8c343cad9ca732d54242f002d254239e17b68cab11c1d506a57822be</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            redirect-uri</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">http://192.168.101.10:8847/herodotus-cloud-uaa/oauth/social/gitee</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Dante Cloud 会根据 yaml 中已经配置的第三方系统信息，在前端动态显示可用的第三登录信息，如下图所示。</p>
<figure><img src="/assets/image/operation/signin-03.png" alt="第三方系统社会化登录" tabindex="0" loading="lazy"><figcaption>第三方系统社会化登录</figcaption></figure>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<ol>
<li>第一次使用第三方系统社会化登录 Dante Cloud，会首先进行注册。根据第三方系统提供的信息，动态生成用户信息以便后续登录系统</li>
<li>第三方系统社会化登录，也是采用自定义扩展 Spring Authorization Server 认证模式实现，并且与现有认证模式统一。登录时，统一采用 <code>/oauth2/token</code> 接口获取 Token，</li>
<li>需要为客户端配置 <code>social_credentials</code> 认证模式，才允许该 <code>客户端</code> 使用手机验证码登录</li>
<li>因为涉及到自动注册的过程，所以需要需要为该类型的登录分配 <code>默认角色</code>，以便登录后有可以操作的内容。参见：<a href="/user-guide/operation/rbac.html#%E5%9B%9B-%E9%BB%98%E8%AE%A4%E8%A7%92%E8%89%B2" target="_blank">【设置默认角色】</a></li>
<li>因为不同的系统登录时涉及的参数会有较大差异，Dante Cloud 仅详细调试了部分，如果您用的登陆方式参数有误，可以修改前端工程中的 <code>SocialSignInCallback.vue</code> 添加或调整参数</li>
</ol>
</div>
<h2>[四]内置授权码模式登录</h2>
<p>为了向用户展示更多的 OAuth2 登录方式，在 Dante Cloud 前端界面中，除了常规的 <code>密码</code> 模式外，还增加了 <code>授权</code> 模式。授权码模式是 OAuth 2.1 协议中，主推的登录模式。如下图所示：</p>
<figure><img src="/assets/image/operation/signin-04.png" alt="内置授权码模式登录" tabindex="0" loading="lazy"><figcaption>内置授权码模式登录</figcaption></figure>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>授权码模式其中一个重要的参数就是 <code>redirect_url</code>。在 Dante Cloud 数据初始化脚本中，默认写入的 <code>redirect_url</code> 是作者本地地址。您在部署完成之后，如果需要使用授权码登录模式，就需要修改 <code>redirect_url</code>。</p>
<p>需要以下步骤：</p>
<ol>
<li>修改后端前端对应客户端对应的 <code>redirect_url</code>，可以通过界面，在 <a href="/user-guide/operation/application.html" target="_blank">【应用管理】</a> 中进行修改，或者直接修改数据库。（注意：直接修改数据库，因为缓存的原因不会直接生效，要么清空缓存重新重启应用，要么等待缓存失效）</li>
<li>修改前端配置 <code>VITE_OAUTH2_REDIRECT_URI</code></li>
</ol>
</div>
<h2>[五]通行密钥登录(Passkey)(企业版)</h2>
<p>通行密钥 <code>Passkey</code> 是一种新型的 <strong>无密码登录</strong> 技术，旨在提高在线账户的安全性并简化登录流程。它利用非对称加密技术，通过公钥和私钥对用户身份进行验证，避免了传统密码的使用。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>如果您最近两年使用过 Github，那么就会知道现在 Github 大部分登录或者验证的方式都是使用 Passkey</p>
</div>
<p>通行密钥 <code>Passkey</code> 登录方式与其它登录方式不同，使用通行密钥 <code>Passkey</code>之前，首先要成功登录系统，然后在系统中注册通行密钥 <code>Passkey</code>之后才能使用。</p>
<p>在 Dante Cloud 中，首先要登录系统，然后在个人账号页面中【注册通行密钥】，如下图所示：</p>
<figure><img src="/assets/image/operation/signin-05.png" alt="注册通行密钥" tabindex="0" loading="lazy"><figcaption>注册通行密钥</figcaption></figure>
<p>注册比较简单，只需要输入一个标签名称即可，用于区分具体的注册源。</p>
<figure><img src="/assets/image/operation/signin-06.png" alt="输入标签通行密钥" tabindex="0" loading="lazy"><figcaption>输入标签通行密钥</figcaption></figure>
<p>输入完同行密钥的标签后，就会调用当前系统（设备）支持的登录方式。作者使用的是 Windows 系统，同时配置了 <code>Pin</code> 登录方式，所以就会弹出 <code>Pin</code> 输入框，用于验证是否有本地设备登录权限。</p>
<figure><img src="/assets/image/operation/signin-07.png" alt="输入 Pin 码" tabindex="0" loading="lazy"><figcaption>输入 Pin 码</figcaption></figure>
<p>验证通过后，系统就会记录当前设备的登录信息，之后就可以使用通行密钥 <code>Passkey</code> 登录 Dante Cloud。</p>
<figure><img src="/assets/image/operation/signin-08.png" alt="Passkey配置成功" tabindex="0" loading="lazy"><figcaption>Passkey配置成功</figcaption></figure>
<h3>使用效果</h3>
<figure><img src="/assets/image/operation/passkey.gif" alt="Passkey效果" tabindex="0" loading="lazy"><figcaption>Passkey效果</figcaption></figure>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<ol>
<li>通行密钥 <code>Passkey</code> 使用有其自身的限制，仅能在 <code>https</code> 或者 <code>localhost</code> 使用。如果你将系统配置为公网访问，但是访问协议为 <code>http</code> 也是无法使用的。</li>
<li>Dante Cloud 通行密钥 <code>Passkey</code>，也是采用自定义扩展 Spring Authorization Server 认证模式实现，并且与现有认证模式统一。登录时，统一采用 <code>/oauth2/token</code> 接口获取 Token，</li>
<li>需要为客户端配置 <code>webauthn_credentials</code> 认证模式，才允许该 <code>客户端</code> 使用手机验证码登录</li>
</ol>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/operation/signin-01.png" type="image/png"/>
    </item>
    <item>
      <title>开发手册</title>
      <link>https://www.herodotus.cn/develop-guide/</link>
      <guid>https://www.herodotus.cn/develop-guide/</guid>
      <source url="https://www.herodotus.cn/rss.xml">开发手册</source>
      <description>前言 说明 目前，正在根据最新版 Dante Cloud 中的涉及与实现，逐步补充相关需要了解和掌握的开发内容。 因为，每个人对知识点的需求与理解不同，加之微服务架构本身就纷繁复杂，使用到的组件和基础设施也多，作者本人补充的内容也许未必是你所需要的知道。 那么，你可以在 Gitee 中留言，简述你想要了解的内容。只要是作者掌握的或者知道的，作者会优先安...</description>
      <pubDate>Thu, 12 Mar 2026 15:15:49 GMT</pubDate>
      <content:encoded><![CDATA[<h2>前言</h2>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>目前，正在根据最新版 Dante Cloud 中的涉及与实现，逐步补充相关需要了解和掌握的开发内容。</p>
<p>因为，每个人对知识点的需求与理解不同，加之微服务架构本身就纷繁复杂，使用到的组件和基础设施也多，作者本人补充的内容也许未必是你所需要的知道。</p>
<p>那么，你可以在 Gitee 中留言，简述你想要了解的内容。只要是作者掌握的或者知道的，作者会优先安排补充相关的内容。</p>
<p><a href="https://gitee.com/dromara/dante-cloud/issues/I9PBSA" target="_blank" rel="noopener noreferrer">【留言地址】</a></p>
</div>
<h2>说明</h2>
<p>开发指南旨在帮助 Dante Cloud 用户，更深入的掌握各个系统模块的开发与扩展，设计的思想及技术实现的细节。希望通过对本指南的学习和了解，尽可能全面的掌握 Dante Cloud 的代码的编写方法。</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<ol>
<li>本指南中内容，主要面向 Dante Cloud 4.0.X 及以后的版本。除了新特性的差异外，大部分知识点是各个版本兼容的。</li>
<li>本指南中内容，项目名称仍旧使用开源版本名称 <code>Dante Cloud</code>，但其它相关的信息为了兼容开源版和企业版，相关信息（特别是配置文件名称）均由原来的 <code>dante</code> 变更为了 <code>herodotus</code>。</li>
</ol>
</div>
<h2>交流</h2>
<ol>
<li>Dante Cloud 涉及的内容较多，而且相关内容均力争使用最新、最好的技术，所以对技术基础的要求较高。</li>
<li>既然选择了 Dante Cloud、选择了微服务，那么就请抱着更开放的心态来拥抱新的技术知识体系，切忌不要什么问题都拿旧有的知识生搬硬套。</li>
<li>如果在学习的过程中，对介绍的内容有疑惑或者疑问，可以提ISSUE。真心交流的可以进入交流群深入讨论。官方交流群：<a href="/support/communication.html" target="_blank">【点击了解入群方式】</a></li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>功能特性</title>
      <link>https://www.herodotus.cn/get-started/features.html</link>
      <guid>https://www.herodotus.cn/get-started/features.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">功能特性</source>
      <description>[一]Dante Cloud 4.0.X 版本 [1]功能特性 1.基础依赖全面升级 Dante Cloud 4.X 使用 JDK 版本已经全面升级至 25，充分发挥虚拟线程的能力 Spring Boot 版本升级至 4.0.3 Spring Cloud 版本升级至 2025.1.1 Spring Cloud Alibaba 版本升级至 2025.1....</description>
      <pubDate>Thu, 12 Mar 2026 07:14:13 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]Dante Cloud 4.0.X 版本</h2>
<h3>[1]功能特性</h3>
<h4>1.基础依赖全面升级</h4>
<ol>
<li>Dante Cloud 4.X 使用 JDK 版本已经全面升级至 25，充分发挥虚拟线程的能力</li>
<li>Spring Boot 版本升级至 4.0.3</li>
<li>Spring Cloud 版本升级至 2025.1.1</li>
<li>Spring Cloud Alibaba 版本升级至 2025.1.0.0</li>
<li>全面迁移至 Jackson 3，提升序列化/反序列化性能与安全性</li>
</ol>
<h4>2.全新模块架构</h4>
<ol>
<li>基于 Spring Boot 4 “New Moduler Design” 体系，重构整个核心组件库模块，适配新体系下的模块划分和使用，进一步强化模块的内聚性和独立使用能力</li>
<li>工程 maven dependencies 体系，由原来“继承”模式变更为 <code>import</code> 模式，增强工程的独立性以及模块引入的便捷性，避免在多工程场景下各个工程只能串行使用效率低下问题</li>
</ol>
<h4>3.API 版本控制</h4>
<ol>
<li>新增 REST API 版本控制能力，支持请求头、请求路径和请求参数三种 API 版本设置模式，可通过配置修改。默认已开启基于请求头的版本控制。</li>
<li>新增支持带 API 版本的 REST 接口动态接口鉴权能力，接口权限实时在线变更，不同版本接口可独立设置不同权限</li>
</ol>
<h4>4.改进 gRPC 通信</h4>
<ol>
<li>使用 Spring gRPC 组件替换原有 net.devh grpc-srping-boot-starter，并扩展 gRPC 的动态服务发现能力，让 gRPC 与系统的融合更加成体系化</li>
<li>新增基于 Spring Security 的 gRPC 方法动态鉴权能力，与 REST 接口权限体系保持一致，让 gRPC 通信更加安全</li>
</ol>
<h4>5.安全防护增强</h4>
<ol>
<li>全新实现 PKI 证书管理模块，支持在线生成与管理，无需使用传统命令行方式生成和管理证书</li>
<li>系统基于 Spring Authorization Server 的授权服务器加密逻辑，全部修改为使用系统 PKI 模块生成证书</li>
<li>改造 Spring Boot Admin 监控服务，增加登陆认证保护。使用 Dante Cloud 系统自身提供的 OAuth2 / OIDC 作为 Spring Boot Admin 主要的登录模式</li>
<li>新增静态接口权限 Customizer 聚合模式，服务可根据实际依赖模块<code>按需</code>、<code>动态装配</code>静态接口权限，减少静态权限的重复设置和不必要的解析，进一步提升接口鉴权效率</li>
<li>服务 Docker 镜像基础 JDK 修改为 liberica 加固镜像，以提升镜像整体安全性及性能</li>
</ol>
<h4>6.OSS对象存储</h4>
<ol>
<li>原 Dante OSS 工程已经停止维护，使用 AWS S3 V2 权限实现 OSS 管理模块，并合并至核心组件库</li>
<li>支持在线生成与管理，基于 AWS S3 V2 API 能够提供的能力，全新实现前端对象存储管理功能</li>
<li>重构本地文件存储和远程文件存储多层文件管理逻辑，让代码逻辑更清晰、运行更稳定、配置修改，也更容易理解和维护。</li>
</ol>
<h4>7.前端功能改进</h4>
<ol>
<li>基于 Vuetify Material Design 3 蓝图风格以及 Vue 3 组合式 API，全新编写新版管理界面</li>
<li>采用 monorepo 模块化设计，原版前端(Quasar版)和前端新版(Vuetify版)，共享通用化代码模块，提升代码维护的便捷性</li>
<li>前端菜单权限大幅调整，支持根据多角色动态获取菜单。</li>
<li>支持多类型动态菜单，个人页面菜单从静态化菜单变更为支持动态化配置管理</li>
<li>大幅提升前端代码 Tree shaking 能力和性能</li>
<li>前端组件库模块新增组件 Resolver 支持，方便 IDE 更好的识别组件及其定义</li>
</ol>
<h4>8.其它改进提升</h4>
<ol>
<li>Dante Cloud 主工程也支持中央仓库和 Maven 私库发布，服务的开发和代码组织可以更灵活</li>
<li>使用更优雅及更合理的方式重构自定义 JPA 二级缓存实现，彻底解决需要修改 Hibernate 源代码的问题</li>
<li>优化平台和服务配置，按照 Servlet 和 Reactive 不同环境需求进行拆分，以增强不同运行环境配置的独立性</li>
<li>重构本地文件存储及服务间文件传输体系，简化代码逻辑，清晰化代码定位，便于理解和维护</li>
<li>重构基础 Service 和 Controller 定义，在原有 Spring Data 实体绑定基础上，支持 DTO 类型请求和响应实体。同时支持 Spring Data Page 和 Slice 两种分页场景</li>
</ol>
<h3>[2]注意事项</h3>
<ol>
<li>因为全新架构了核心组件库中的模块，核心模块坐标已统一变更为 org.dromara.dante（企业版保持不变），建议全新检出代码，避免因模块结构调整导致冲突</li>
<li>数据库和 Nacos 配置等，也存在较多的变化，与之前版本无法兼容，要使用新版本建议重新建库和导入配置文件。</li>
<li>若通过 ZIP 方式导出代码，需注释 git-commit-id-maven-plugin，否则编译会因缺少 .git 文件夹报错。建议通过 Git 克隆代码以保留版本记录功能</li>
</ol>
<h2>[二]Dante Cloud 3.3.X 版本</h2>
<h3>[1]新特性</h3>
<ul>
<li><code>Spring Boot</code> 已升级至 3.3.0</li>
<li><code>Spring Authorization Server</code> 已升级至 1.3.0</li>
<li>全面采用 Java 21，默认开启虚拟线程，以改善阻塞操作的处理降低系统资源的消耗</li>
<li>支持传统的 <code>阻塞式</code> 微服务与基于 <code>Reactor</code> 和 <code>WebFlux</code> 的 <code>响应式</code> 微服务同时运行在一套系统之中</li>
<li>不强制使用 <code>响应式</code> 方式开发，可根据自身项目对资源吞吐量、资源消耗、特殊功能性能保障的需求，灵活的选择是采用 <code>响应式</code> 还是 <code>阻塞式</code> 来开发对应的服务。</li>
<li>在保持 Dante Cloud 原有 <code>Spring Authorization Server</code> 深度扩展的各种特性的前提下，实现 <code>响应式</code> 服务的动态鉴权与现有体系的完全融合（无需在代码中使用 <code>@PreAuthorize</code> 写死权限，全部通过后台动态管理）</li>
<li>向“响应式编程”转变，基于 <code>Reactor</code> 重构大量核心代码，进一步提升本系统代码质量和运行效能</li>
<li>重新架构所有核心组件模块，进一步降低各模块的耦合性，减少第三方组件依赖深度，简化各模块使用的复杂度，使用更贴近 Spring Boot 生态官方写法，提升模块组件的可插拔性以及 <code>响应式</code> 和 <code>阻塞式</code> 不同环境下自动配置的适配能力</li>
<li>实现 <code>响应式</code> 和 <code>阻塞式</code> 不同类型服务，Session 共享体系以及自定义 Session 体系的完美融合（谁说微服务就一定用不到 Session ：））。</li>
<li>新增 <code>GRPC</code> 服务间调用和通信方式，系统核心服务间调用支持 <code>OpenFeign</code> 和 <code>GRPC</code> 两种方式，可通过修改配置实现两种方式的切换。</li>
<li>基于 <code>RSocket</code> 全面重写 <code>WebSocket</code> 消息系统，实现 <code>WebSocket</code> 的 <code>响应式</code> 改造以及 <code>RSocket</code> 与 Spring Security 体系的全面集成。支持多实例、跨服务的私信和广播</li>
<li>新增 OAuth2 独立客户端，可用于客户端动态注册以及授权码模式</li>
<li>新增基于 <code>Loki + Grafana</code> 生态的轻量级日志中心和链路追踪解决方案，使用 OSS 作为数据存储，极大地降低资源需求，可作为原有 Skywalking 和 ELK 重量级体系的备选方案，根据实际需要切换。</li>
<li>开放纯手写动态表单功能。可实现BPMN、动态表单、Camunda 流程引擎的串联，实现工作流程运转（目前仅支持简单工作流）</li>
<li>开放包含自定义属性面板的 BPMN 在线设计器功能。</li>
<li>开放物联网设备认证和管理模块，支持基于 Emqx 的物联网设备通信和管理。</li>
<li>开放阿里云内容审核、百度 OCR、环信、Emqx、天眼查、Nacos、PolarisMash等第三方 OpenApi 封装模块</li>
<li>前端工程支持 Docker 运行，相关参数可通过配置环境变量修改。已上传至 Docker Hub，可以直接下载运行。</li>
</ul>
<h3>[2]设计答疑</h3>
<h4>1. 为什么不做“纯血”响应式</h4>
<p>响应式固然有其优势所在，但是使用响应式也不得面对一些现实问题：</p>
<ul>
<li>要做纯血响应式，首先要有生态的保证。目前响应式的接受度还并不是很高，很多组件还都不支持响应式。除非有精力将所有用到的不支持的组件改写一遍，否则很难做到纯血，特别是对于微服务系统来说。</li>
<li>绝大多数应用都是需要使用数据库的，Java 领域现有的 orm 组件，要么不支持响应式（比如 JPA）、要么支持的不是特别好（比如 Hibernate）、要么需要自己编写的内容太多（比如 R2DBC），所以从投入产出比的角度说目前在数据层面做响应式并不“划算”</li>
</ul>
<p>所以，还是要具体看应用系统的类型，在条件不具备的情况下没有必要做到纯血<code>响应式</code>。</p>
<h4>2. 响应式可以带来哪些好处</h4>
<p><code>响应式</code>对<code>阻塞式</code>的好处，网上有大把的文章介绍，具体就不赘述了。对于实际应用中比较明显的优势：</p>
<ul>
<li><code>响应式</code>资源的利用效能更高，对于高资源消耗的功能，<code>响应式</code>的优势更突出</li>
<li>微服务系统往往会需要集成更多内容，特别在数据层面，可能会存在同时使用多种类型数据存储以及数据的流转和迁移，常规事件驱动与响应式的“流”式思维比传统阻塞式更为契合。</li>
<li>响应式可以与事件驱动更好的配合。在 Spring 生态中在多个方面都大量使用了事件驱动，而响应式的核心设计思想也与事件驱动殊途同归。</li>
</ul>
<blockquote>
<p>正因为 Dante Cloud 用了很多 Spring Integration 的内容，传统阻塞式方式越用越别扭，才越来越觉得有必要做响应式支持。如果有时间可以好好看看 Spring Integration，也许会为你打开一个新的世界。</p>
</blockquote>
<h4>3. 学习响应式编程有哪些难点</h4>
<ul>
<li>如果基于 Reactor 学习响应式编程，难点和突破点就在于 <code>Flux</code> 和 <code>Mono</code> 两个类。把这两个类的方法用透、弄明白，基本上就可以消除所有开发阻碍了。</li>
<li>响应式编程最大的难点就是编程思维的转换，因为习惯了阻塞式编程，一时会很难适应<code>响应式</code>的<code>流</code>式开发思维</li>
</ul>
<blockquote>
<p>世上无难事只怕有心人</p>
</blockquote>
<h2>[三]Dante Cloud 3.0.X 特性</h2>
<h3>[1]核心基础依赖便捷切换</h3>
<ul>
<li>新增 <code>Spring Cloud Tencent</code> 和 <code>Spring Cloud</code> 原生微服务全家桶等两种基础设施支持。</li>
<li>新增 <code>Spring Cloud Alibaba</code>、<code>Spring Cloud Tencent</code> 和 <code>Spring Cloud</code> 原生微服务全家桶三种基础设值切换能力，可以以相对便捷的方式切换使用 Alibaba、Tencent、Spring 等基础设施环境。可根据自身实际需求选择，不再局限于只能在某一种基础设施环境中运行。</li>
</ul>
<h3>[2]<code>Spring Authorization Server</code> 全特性支持及扩展</h3>
<ul>
<li>基于 <code>Spring Authorization Server</code> 和 <code>Spring Data JPA</code> 实现多租户系统架构， 支持 Database 和 Schema 两种模式。</li>
<li>基于 <code>Spring Data JPA</code>，重新构建 <code>Spring Authorization Server</code> 基础数据存储代码，替代原有 JDBC 数据访问方式，破除 <code>Spring Authorization Server</code> 原有数据存储局限，扩展为更符合实际应用的方式和设计。</li>
<li>基于 <code>Spring Authorization Server</code>，在 OAuth 2.1 规范基础之上，增加自定义 <code>Resource Ownership Password</code> (密码) 认证模式，以兼容现有基于 OAuth 2 规范的、前后端分离的应用，支持 <code>Refresh Token</code> 的使用。</li>
<li>基于 <code>Spring Authorization Server</code>，在 OAuth 2.1 规范基础之上，增加自定义 <code>Social Credentials</code> (社会化登录) 认证模式，支持手机短信验证码、微信小程序、基于 <code>JustAuth</code> 的第三方应用登录， 支持 <code>Refresh Token</code> 的使用。</li>
<li>扩展 <code>Spring Authorization Server</code> 默认的 <code>Client Credentials</code> 模式，实现真正的使用 Scope 权限对接口进行验证。 增加客户端 Scope 的权限配置功能，并与已有的用户权限体系解耦</li>
<li>支持 <code>Spring Authorization Server</code> <code>Authorization Code PKCE</code> 认证模式</li>
<li>在 <code>Spring Authorization Server</code> 的标准的 <code>JWT Token</code> 加密校验方式外，支持基于自定义证书的 <code>JWT Token</code> 加密校验方式，可通过配置动态修改。</li>
<li>支持 <code>Opaque Token</code> (不透明令牌) 格式及校验方式，降低 <code>JWT Token</code> 被捕获解析的风险。可通过修改配置参数，设置默认 Token 格式是采用 <code>Opaque Token</code> 格式还是 <code>JWT Token</code> 格式。</li>
<li>全面支持 <code>OpenID Connect</code> (OIDC) 协议，系统使用时可根据使用需求，通过前端开关配置，快速切换 OIDC 模式和传统 OAuth2 模式</li>
<li>深度扩展 <code>Authorization Code</code>、<code>Resource Ownership Password</code>、<code>Social Credentials</code> 几种模式，全面融合 <code>IdToken</code>、<code>Opaque Token</code>、<code>JWT Token</code> 与现有权限体系，同时提供 <code>IdToken</code> 和 自定义 Token 扩展两种无须二次请求的用户信息传递方式，减少用户信息的频繁请求。</li>
<li>自定义 <code>Spring Authorization Server</code> 授权码模式登录认证页面和授权确认页面，授权码模式登录采用数据加密传输。支持多种验证码类型，暂不支持行为验证码。</li>
<li>新增基于 <code>Spring Authorization Server</code> 的、支持智能电视、IoT等物联网设备认证模式</li>
<li>无须在代码中配置 <code>Spring Security</code> 权限注解以及权限方法，即可实现接口鉴权以及权限的动态修改。采用分布式鉴权方案，规避 Gateway 统一鉴权的压力以及重复鉴权问题</li>
<li>OAuth2 UserDetails 核心数据支持直连数据库获取和 Feign 远程调用两种模式。OAuth2 直连数据库模式性能更优，Feign 访问远程调用可扩展性更强。可通过配置动态修改采用策略方式。</li>
</ul>
<h3>[3]全体系化应用和开发特性集成</h3>
<ul>
<li>微服务架构全体系 Session 共享，实现 Spring Authorization Server、多实例服务、WebSocket、自定义 Session 以及大前端 Session 的统一。<code>微服务架构下的 Session 可以选择不用，但是不能没有</code>。</li>
<li>混合国密 <code>SM2</code> (非对称) 和 <code>SM4</code> (对称加密) 算法，实现基于数字信封技术的秘钥动态生成加密传输。利用“一人一码机制”，实现前后端数据进行动态加密传输与。Spring Authorization Server OAuth 2.1 授权模式深度融合，构建统一体系的数据传输加密。</li>
<li>全面整合 <code>@PreAuthorize</code> 注解权限与 <code>URL</code> 权限，通过后端动态配置，无须在代码中配置 <code>Spring Security</code> 权限注解以及权限方法，可实现接口鉴权以及权限的统一管理和动态修改</li>
<li>融合 Spring Cloud Stream 和 WebSocket，以优雅的方式实现 WebSocket 服务多实例环境下，点对点、广播消息跨实例推送，在线用户实时统计，完美支持 WebSocket 集群化应用。</li>
<li>借鉴 JPA 标准化设计思想，提取和抽象 OSS 标准化操作，形成统一的 Java OSS API 规范。封装可操作任意厂商的、统一的 REST API，构建定义统一、动态实现的应用模式（类似于 Hibernate 是 JPA 的一种实现，Hibernate 以 Dialect 方式支持不同的数据库一样），在不修改代码的情况下通过修改配置实现 OSS 的无缝切换和迁移</li>
<li>自研基于 <code>JetCache</code> 分布式两级缓存，完美实现 JPA Hibernate 二级缓存，支持各类查询数据缓存以及 JPA <code>@ManyToMany</code>， <code>@ManyToOne</code>等关联查询。完美解决 Spring Cache 仅使用本地缓存、创建 Key 繁琐和分页数据无法更新的问题。支持多实例服务本地缓存和远程缓存数据同步，同时支持 Mybatis Plus 二级缓存</li>
<li>平台统一错误处理，支持自定义错误码体系，有效集成 <code>OAuth2</code>、<code>Spring Validation</code> 等多方错误体系并有机整合 HTTP 状态码。采用 Customizer 模式，采用错误码自动计算和创建模式，支持代码模块级错误码灵活定义扩展。响应结果更加多样灵活，反馈结果也更加人性化，便于理解和定位问题。</li>
<li>全体系 OkHttp 、HttpClient 统一化集成，实现 OkHttp 、HttpClient 与 RestTemplate 、Openfeign 一体化融合。统一使用 Feign 配置参数，对 OkHttp 、HttpClient 进行参数设定，可策略化选择设置使用 OkHttp 或 HttpClient 作为 RestTemplate 、Openfeign 统一的基础 HttpClient</li>
</ul>
<h3>[4]采用 <code>pnpm monorepo</code> 重构前端</h3>
<ul>
<li>未使用任何流行开源模版，使用全新技术栈，完全纯&quot;手写&quot;全新前端工程。</li>
<li>借鉴参考流行开源版本的使用和设计，新版前端界面风格和操作习惯尽量与当前流行方式统一。</li>
<li>充份使用 Typescript 语言特性，解决大量类型校验问题，尽可能规避 &quot;any&quot; 式的 Typescript 编程语言使用方式。</li>
<li>充份使用 Composition Api 和 Hooks 等 Vue3 框架新版特性进行代码编写。</li>
<li>充份利用 Component、Hooks 以及 Typescript 面向对象等特性，抽取通用组件和代码，尽可能降低工程重复代码。</li>
<li>对较多 Quasar 基础组件和应用功能组件进行封装，以方便代码的统一修改维护和开发使用。</li>
<li>对生产模式下，对基于 Vite3 的工程打包进行深度性能优化。</li>
<li>提供以 docker-compose 方式，对工程生产代码进行容器化打包和部署。</li>
<li>该版本基于 pnpm，采用 monorepo 模式对前端工程进行重构。构建 monorepo 版本前端，是为扩展更多功能、增加应用级功能做铺垫</li>
<li>抽取 utils、components、apis、bpmn-designer 等相关代码，形成共享模块。</li>
<li>共享模块已进行优化配置，可编译成独立的组件，单独以组件形式进行发布。</li>
<li>代码以共享模块的方式进行单独维护开发，降低现有工程代码复杂度，便于后续功能的扩展和代码的复用。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>提问智慧</title>
      <link>https://www.herodotus.cn/support/smart.html</link>
      <guid>https://www.herodotus.cn/support/smart.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">提问智慧</source>
      <description>提示 提问题也是个技术活，想要快速的得到他人的帮助，提问时也要掌握方法和技巧。否则，大概率不会有人愿意帮助你。提问之前好好读读这篇文章，对你对他人都有好处。 说明 本文转载自: 原文地址。 How To Ask Questions The Smart Way Copyright © 2001,2006,2014 Eric S. Raymond, Ric...</description>
      <pubDate>Wed, 29 Oct 2025 01:53:13 GMT</pubDate>
      <content:encoded><![CDATA[<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>提问题也是个技术活，想要快速的得到他人的帮助，提问时也要掌握方法和技巧。否则，大概率不会有人愿意帮助你。提问之前好好读读这篇文章，对你对他人都有好处。</p>
</div>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>本文转载自: <a href="https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md" target="_blank" rel="noopener noreferrer">原文地址</a>。</p>
<p><strong>How To Ask Questions The Smart Way</strong></p>
<p>Copyright © 2001,2006,2014 Eric S. Raymond, Rick Moen</p>
<p>本指南英文版版权为 Eric S. Raymond, Rick Moen 所有。</p>
<p>原文网址：<a href="http://www.catb.org/~esr/faqs/smart-questions.html" target="_blank" rel="noopener noreferrer">http://www.catb.org/~esr/faqs/smart-questions.html</a></p>
<p>Copyleft 2001 by D.H.Grand(nOBODY/Ginux), 2010 by Gasolin, 2015 by Ryan Wu</p>
<p>本中文指南是基于原文 3.10 版以及 2010 年由 <a href="https://github.com/gasolin" target="_blank" rel="noopener noreferrer">Gasolin</a> 所翻译版本的最新翻译；</p>
<p>协助指出翻译问题，<strong>请<a href="https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/issues/new" target="_blank" rel="noopener noreferrer">发 issue</a>，或直接<a href="https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/compare" target="_blank" rel="noopener noreferrer">发 pull request</a> 给我。</strong></p>
</div>
<h2>目录</h2>
<ul>
<li><a href="#%E7%9B%AE%E5%BD%95">目录</a></li>
<li><a href="#%E5%A3%B0%E6%98%8E">声明</a></li>
<li><a href="#%E7%AE%80%E4%BB%8B">简介</a></li>
<li><a href="#%E5%9C%A8%E6%8F%90%E9%97%AE%E4%B9%8B%E5%89%8D">在提问之前</a></li>
<li><a href="#%E5%BD%93%E4%BD%A0%E6%8F%90%E9%97%AE%E6%97%B6">当你提问时</a>
<ul>
<li><a href="#%E6%85%8E%E9%80%89%E6%8F%90%E9%97%AE%E7%9A%84%E8%AE%BA%E5%9D%9B">慎选提问的论坛</a></li>
<li><a href="#stack-overflow">Stack Overflow</a></li>
<li><a href="#%E7%BD%91%E7%AB%99%E5%92%8C-irc-%E8%AE%BA%E5%9D%9B">网站和 IRC 论坛</a></li>
<li><a href="#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E4%BD%BF%E7%94%A8%E9%A1%B9%E7%9B%AE%E9%82%AE%E4%BB%B6%E5%88%97%E8%A1%A8">第二步，使用项目邮件列表</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E6%9C%89%E6%84%8F%E4%B9%89%E4%B8%94%E6%8F%8F%E8%BF%B0%E6%98%8E%E7%A1%AE%E7%9A%84%E6%A0%87%E9%A2%98">使用有意义且描述明确的标题</a></li>
<li><a href="#%E4%BD%BF%E9%97%AE%E9%A2%98%E5%AE%B9%E6%98%93%E5%9B%9E%E5%A4%8D">使问题容易回复</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E6%B8%85%E6%99%B0%E6%AD%A3%E7%A1%AE%E7%B2%BE%E5%87%86%E4%B8%94%E5%90%88%E4%B9%8E%E8%AF%AD%E6%B3%95%E7%9A%84%E8%AF%AD%E5%8F%A5">使用清晰、正确、精准且合乎语法的语句</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E6%98%93%E4%BA%8E%E8%AF%BB%E5%8F%96%E4%B8%94%E6%A0%87%E5%87%86%E7%9A%84%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F%E5%8F%91%E9%80%81%E9%97%AE%E9%A2%98">使用易于读取且标准的文件格式发送问题</a></li>
<li><a href="#%E7%B2%BE%E7%A1%AE%E5%9C%B0%E6%8F%8F%E8%BF%B0%E9%97%AE%E9%A2%98%E5%B9%B6%E8%A8%80%E4%B9%8B%E6%9C%89%E7%89%A9">精确地描述问题并言之有物</a></li>
<li><a href="#%E8%AF%9D%E4%B8%8D%E5%9C%A8%E5%A4%9A%E8%80%8C%E5%9C%A8%E7%B2%BE">话不在多而在精</a></li>
<li><a href="#%E5%88%AB%E5%8A%A8%E8%BE%84%E5%A3%B0%E7%A7%B0%E6%89%BE%E5%88%B0-bug">别动辄声称找到 Bug</a></li>
<li><a href="#%E4%BD%8E%E5%A3%B0%E4%B8%8B%E6%B0%94%E4%B8%8D%E8%83%BD%E4%BB%A3%E6%9B%BF%E4%BD%A0%E7%9A%84%E5%8A%9F%E8%AF%BE">低声下气不能代替你的功课</a></li>
<li><a href="#%E6%8F%8F%E8%BF%B0%E9%97%AE%E9%A2%98%E7%97%87%E7%8A%B6%E8%80%8C%E9%9D%9E%E4%BD%A0%E7%9A%84%E7%8C%9C%E6%B5%8B">描述问题症状而非你的猜测</a></li>
<li><a href="#%E6%8C%89%E5%8F%91%E7%94%9F%E6%97%B6%E9%97%B4%E5%85%88%E5%90%8E%E5%88%97%E5%87%BA%E9%97%AE%E9%A2%98%E7%97%87%E7%8A%B6">按发生时间先后列出问题症状</a></li>
<li><a href="#%E6%8F%8F%E8%BF%B0%E7%9B%AE%E6%A0%87%E8%80%8C%E4%B8%8D%E6%98%AF%E8%BF%87%E7%A8%8B">描述目标而不是过程</a></li>
<li><a href="#%E5%88%AB%E8%A6%81%E6%B1%82%E4%BD%BF%E7%94%A8%E7%A7%81%E4%BA%BA%E7%94%B5%E9%82%AE%E5%9B%9E%E5%A4%8D">别要求使用私人电邮回复</a></li>
<li><a href="#%E6%B8%85%E6%A5%9A%E6%98%8E%E7%A1%AE%E5%9C%B0%E8%A1%A8%E8%BE%BE%E4%BD%A0%E7%9A%84%E9%97%AE%E9%A2%98%E4%BB%A5%E5%8F%8A%E9%9C%80%E6%B1%82">清楚明确地表达你的问题以及需求</a></li>
<li><a href="#%E8%AF%A2%E9%97%AE%E6%9C%89%E5%85%B3%E4%BB%A3%E7%A0%81%E7%9A%84%E9%97%AE%E9%A2%98%E6%97%B6">询问有关代码的问题时</a></li>
<li><a href="#%E5%88%AB%E6%8A%8A%E8%87%AA%E5%B7%B1%E5%AE%B6%E5%BA%AD%E4%BD%9C%E4%B8%9A%E7%9A%84%E9%97%AE%E9%A2%98%E8%B4%B4%E4%B8%8A%E6%9D%A5">别把自己家庭作业的问题贴上来</a></li>
<li><a href="#%E5%8E%BB%E6%8E%89%E6%97%A0%E6%84%8F%E4%B9%89%E7%9A%84%E6%8F%90%E9%97%AE%E5%8F%A5">去掉无意义的提问句</a></li>
<li><a href="#%E5%8D%B3%E4%BD%BF%E4%BD%A0%E5%BE%88%E6%80%A5%E4%B9%9F%E4%B8%8D%E8%A6%81%E5%9C%A8%E6%A0%87%E9%A2%98%E5%86%99%E7%B4%A7%E6%80%A5">即使你很急也不要在标题写<code>紧急</code></a></li>
<li><a href="#%E7%A4%BC%E5%A4%9A%E4%BA%BA%E4%B8%8D%E6%80%AA%E8%80%8C%E4%B8%94%E6%9C%89%E6%97%B6%E8%BF%98%E5%BE%88%E6%9C%89%E5%B8%AE%E5%8A%A9">礼多人不怪，而且有时还很有帮助</a></li>
<li><a href="#%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E5%90%8E%E5%8A%A0%E4%B8%AA%E7%AE%80%E7%9F%AD%E7%9A%84%E8%A1%A5%E5%85%85%E8%AF%B4%E6%98%8E">问题解决后，加个简短的补充说明</a></li>
</ul>
</li>
<li><a href="#%E5%A6%82%E4%BD%95%E8%A7%A3%E8%AF%BB%E7%AD%94%E6%A1%88">如何解读答案</a>
<ul>
<li><a href="#rtfm-%E5%92%8C-stfw%E5%A6%82%E4%BD%95%E7%9F%A5%E9%81%93%E4%BD%A0%E5%B7%B2%E5%AE%8C%E5%85%A8%E6%90%9E%E7%A0%B8%E4%BA%86">RTFM 和 STFW：如何知道你已完全搞砸了</a></li>
<li><a href="#%E5%A6%82%E6%9E%9C%E8%BF%98%E6%98%AF%E6%90%9E%E4%B8%8D%E6%87%82">如果还是搞不懂</a></li>
<li><a href="#%E5%A4%84%E7%90%86%E6%97%A0%E7%A4%BC%E7%9A%84%E5%9B%9E%E5%BA%94">处理无礼的回应</a></li>
</ul>
</li>
<li><a href="#%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E6%89%AE%E6%BC%94%E5%A4%B1%E8%B4%A5%E8%80%85">如何避免扮演失败者</a></li>
<li><a href="#%E4%B8%8D%E8%AF%A5%E9%97%AE%E7%9A%84%E9%97%AE%E9%A2%98">不该问的问题</a></li>
<li><a href="#%E5%A5%BD%E9%97%AE%E9%A2%98%E4%B8%8E%E8%A0%A2%E9%97%AE%E9%A2%98">好问题与蠢问题</a></li>
<li><a href="#%E5%A6%82%E6%9E%9C%E5%BE%97%E4%B8%8D%E5%88%B0%E5%9B%9E%E7%AD%94">如果得不到回答</a></li>
<li><a href="#%E5%A6%82%E4%BD%95%E6%9B%B4%E5%A5%BD%E5%9C%B0%E5%9B%9E%E7%AD%94%E9%97%AE%E9%A2%98">如何更好地回答问题</a></li>
<li><a href="#%E7%9B%B8%E5%85%B3%E8%B5%84%E6%BA%90">相关资源</a></li>
<li><a href="#%E9%B8%A3%E8%B0%A2">鸣谢</a></li>
</ul>
<h2>声明</h2>
<p>许多项目在他们网站的帮助文档中链接了本指南。这很好，这正是我们想要的用途。但如果你是该项目管理员并试图创建指向本指南的超链接，请在超链接附近的显著位置注明：</p>
<p><strong>本指南不提供此项目的实际支持服务！</strong></p>
<p>我们已经深刻领教到缺少上述声明所带来的痛苦：我们将不停地被那些认为发布这本指南就意味着有责任解决世上所有技术问题的傻瓜苦苦纠缠。</p>
<p>如果你因寻求某些帮助而阅读本指南，并在离开时还觉得可以从本文作者这里得到直接帮助，那你就是我们之前说的那些傻瓜之一。别问我们问题，我们只会忽略你。我们在这本指南中想教你如何从那些真正懂得你所遇到的软件或硬件问题的人处取得协助，而 99% 的情况下那不会是我们。除非你确定本指南的作者之一刚好是你所遇到的问题领域的专家，否则请不要打扰我们，这样大家都会开心一点。</p>
<h2>简介</h2>
<p>在<a href="http://www.catb.org/~esr/faqs/hacker-howto.html" target="_blank" rel="noopener noreferrer">黑客</a>的世界里，你所提技术问题的解答的好坏, 很大程度上取决于你提问的方式与此问题的难度。本指南将教你如何正确地提问以获得你满意的答案。</p>
<p>现在开源（Open Source）软件已经相当盛行，您通常可以从其他更有经验的用户那里获得与黑客一样好的答案，这是件<strong>好事</strong>；和黑客相比，用户们往往对那些新手常遇到的问题更宽容一些。尽管如此，以我们在此推荐的方式对待这些有经验的用户通常也是从他们那里获得有用答案的最有效方式。</p>
<p>首先你应该明白，黑客们喜爱有挑战性的问题，或者能激发他们思维的好问题。如果我们并非如此，那我们也不会成为你想询问的对象。如果你给了我们一个值得反复咀嚼玩味的好问题，我们自会对你感激不尽。好问题是激励，是厚礼。好问题可以提高我们的理解力，而且通常会暴露我们以前从没意识到或者思考过的问题。对黑客而言，“好问题！”是诚挚的大力称赞。</p>
<p>尽管如此，黑客们有着蔑视或傲慢面对简单问题的坏名声，这有时让我们看起来对新手、无知者似乎较有敌意，但其实不是那样的。</p>
<p>我们不讳言我们对那些不愿思考、或者在发问前不做他们该做的事的人的蔑视。那些人是时间杀手 —— 他们只想索取，从不付出，消耗我们可用在更有趣的问题或更值得回答的人身上的时间。我们称这样的人为 <code>失败者（loser）</code> （由于历史原因，我们有时把它拼作 <code>lusers</code>）。</p>
<p>我们意识到许多人只是想使用我们写的软件，他们对学习技术细节没有兴趣。对大多数人而言，电脑只是种工具，是种达到目的的手段而已。他们有自己的生活并且有更要紧的事要做。我们认可这点，也从不指望每个人都对这些让我们着迷的技术问题感兴趣。尽管如此，我们只为那些真正有兴趣并愿意积极参与问题解决的人调整回答问题的风格。这点不会变，也不该变：否则，我们就是在最擅长的事情上降低效率。</p>
<p>我们（在很大程度上）是自愿的，从繁忙的生活中抽出时间来解答疑惑，而且时常被提问淹没。所以我们无情地滤掉一些话题，特别是拋弃那些看起来像失败者的家伙，以便更高效地利用时间来回答<code>赢家（winner）</code>的问题。</p>
<p>如果你厌恶我们的态度，高高在上，或过于傲慢，不妨也设身处地想想。我们并没有要求你向我们屈服 —— 事实上，我们大多数人非常乐意与你平等地交流，只要你付出小小努力来满足基本要求，我们就会欢迎你加入我们的文化。但让我们帮助那些不愿意帮助自己的人是没有效率的。无知没有关系，但装白痴就是不行。</p>
<p>所以，你不必在技术上很在行才能吸引我们的注意，但你必须表现出能引导你变得在行的特质 —— 机敏、有想法、善于观察、乐于主动参与解决问题。如果你做不到这些使你与众不同的事情，我们建议你花点钱找家商业公司签个技术支持服务合同，而不是要求黑客个人无偿地帮助你。</p>
<p>如果你决定向我们求助，当然你也不希望被视为失败者，更不愿成为失败者中的一员。能立刻得到快速并有效答案的最好方法，就是像赢家那样提问 —— 聪明、自信、有解决问题的思路，只是偶尔在特定的问题上需要获得一点帮助。</p>
<p>（欢迎对本指南提出改进意见。你可以把你的建议发送至 <a href="esr@thyrsus.com">esr@thyrsus.com</a> 或 <a href="respond-auto@linuxmafia.com">respond-auto@linuxmafia.com</a>。然而请注意，本文并非<a href="http://www.ietf.org/rfc/rfc1855.txt" target="_blank" rel="noopener noreferrer">网络礼节</a>的通用指南，而我们通常会拒绝无助于在技术论坛得到有用答案的建议）。</p>
<h2>在提问之前</h2>
<p>在你准备要通过电子邮件、新闻群组或者聊天室提出技术问题前，请先做到以下事情：</p>
<ol>
<li>尝试在你准备提问的论坛的旧文章中搜索答案。</li>
<li>尝试上网搜索以找到答案。</li>
<li>尝试阅读手册以找到答案。</li>
<li>尝试阅读常见问题文件（FAQ）以找到答案。</li>
<li>尝试自己检查或试验以找到答案。</li>
<li>向你身边的强者朋友打听以找到答案。</li>
<li>如果你是程序开发者，请尝试阅读源代码以找到答案。</li>
</ol>
<p>当你提出问题的时候，请先表明你已经做了上述的努力；这将有助于树立你并不是一个不劳而获且浪费别人的时间的提问者。如果你能一并表达在做了上述努力的过程中所<strong>学到</strong>的东西会更好，因为我们更乐于回答那些表现出能从答案中学习的人的问题。</p>
<p>运用某些策略，比如先用 Google 搜索你所遇到的各种错误信息（搜索 <a href="http://groups.google.com/" target="_blank" rel="noopener noreferrer">Google 论坛</a>和网页），这样很可能直接就找到了能解决问题的文件或邮件列表线索。即使没有结果，在邮件列表或新闻组寻求帮助时加上一句 <code>我在 Google 中搜过下列句子但没有找到什么有用的东西</code> 也是件好事，即使它只是表明了搜索引擎不能提供哪些帮助。这么做（加上搜索过的字串）也让遇到相似问题的其他人能被搜索引擎引导到你的提问来。</p>
<p>别着急，不要指望几秒钟的 Google 搜索就能解决一个复杂的问题。在向专家求助之前，再阅读一下常见问题文件（FAQ）、放轻松、坐得舒服一些，再花点时间思考一下这个问题。相信我们，他们能从你的提问看出你做了多少阅读与思考，如果你是有备而来，将更有可能得到解答。不要将所有问题一股脑拋出，只因你的第一次搜索没有找到答案（或者找到太多答案）。</p>
<p>准备好你的问题，再将问题仔细地思考过一遍，因为草率的发问只能得到草率的回答，或者根本得不到任何答案。越是能表现出在寻求帮助前你为解决问题所付出的努力，你越有可能得到实质性的帮助。</p>
<p>小心别问错了问题。如果你的问题基于错误的假设，某个普通黑客（J. Random Hacker）多半会一边在心里想着<code>蠢问题…</code>，一边用无意义的字面解释来答复你，希望着你会从问题的回答（而非你想得到的答案）中汲取教训。</p>
<p>绝不要自以为<strong>够格</strong>得到答案，你没有；你并没有。毕竟你没有为这种服务支付任何报酬。你将会是自己去<strong>挣到</strong>一个答案，靠提出有内涵的、有趣的、有思维激励作用的问题 —— 一个有潜力能贡献社区经验的问题，而不仅仅是被动地从他人处索取知识。</p>
<p>另一方面，表明你愿意在找答案的过程中做点什么是一个非常好的开端。<code>谁能给点提示？</code>、<code>我的这个例子里缺了什么？</code>以及<code>我应该检查什么地方</code>比<code>请把我需要的确切的过程贴出来</code>更容易得到答复。因为你表现出只要有人能指个正确方向，你就有完成它的能力和决心。</p>
<h2>当你提问时</h2>
<h3>慎选提问的论坛</h3>
<p>小心选择你要提问的场合。如果你做了下述的事情，你很可能被忽略掉或者被看作失败者：</p>
<ul>
<li>在与主题不合的论坛上贴出你的问题。</li>
<li>在探讨进阶技术问题的论坛张贴非常初级的问题；反之亦然。</li>
<li>在太多的不同新闻群组上重复转贴同样的问题（cross-post）。</li>
<li>向既非熟人也没有义务解决你问题的人发送私人电邮。</li>
</ul>
<p>黑客会剔除掉那些搞错场合的问题，以保护他们沟通的渠道不被无关的东西淹没。你不会想让这种事发生在自己身上的。</p>
<p>因此，第一步是找到对的论坛。再说一次，Google 和其它搜索引擎还是你的朋友，用它们来找到与你遭遇到困难的软硬件问题最相关的网站。通常那儿都有常见问题（FAQ）、邮件列表及相关说明文件的链接。如果你的努力（包括<strong>阅读</strong> FAQ）都没有结果，网站上也许还有报告 Bug（Bug-reporting）的流程或链接，如果是这样，链过去看看。</p>
<p>向陌生的人或论坛发送邮件最可能是风险最大的事情。举例来说，别假设一个提供丰富内容的网页的作者会想充当你的免费顾问。不要对你的问题是否会受到欢迎做太乐观的估计 —— 如果你不确定，那就向别处发送，或者压根别发。</p>
<p>在选择论坛、新闻群组或邮件列表时，别太相信它的名字，先看看 FAQ 或者许可书以弄清楚你的问题是否切题。发文前先翻翻已有的话题，这样可以让你感受一下那里的文化。事实上，事先在新闻组或邮件列表的历史记录中搜索与你问题相关的关键词是个极好的主意，也许这样就找到答案了。即使没有，也能帮助你归纳出更好的问题。</p>
<p>别像机关枪似的一次“扫射”所有的帮助渠道，这就像大喊大叫一样会使人不快。要一个一个地来。</p>
<p>搞清楚你的主题！最典型的错误之一是在某种致力于跨平台可移植的语言、套件或工具的论坛中提关于 Unix 或 Windows 操作系统程序界面的问题。如果你不明白为什么这是大错，最好在搞清楚这之间差异之前什么也别问。</p>
<p>一般来说，在仔细挑选的公共论坛中提问，会比在私有论坛中提同样的问题更容易得到有用的回答。有几个理由可以支持这点，一是看潜在的回复者有多少，二是看观众有多少。黑客较愿意回答那些能帮助到许多人的问题。</p>
<p>可以理解的是，老练的黑客和一些热门软件的作者正在接受过多的错发信息。就像那根最后压垮骆驼背的稻草一样，你的加入也有可能使情况走向极端 —— 已经好几次了，一些热门软件的作者由于涌入其私人邮箱的大量不堪忍受的无用邮件而不再提供支持。</p>
<h3>Stack Overflow</h3>
<p>搜索，<em>然后</em>在 Stack Exchange 问。</p>
<p>近年来，Stack Exchange 社区已经成为回答技术及其他问题的主要渠道，尤其是那些开放源码的项目。</p>
<p>因为 Google 索引是即时的，在看 Stack Exchange 之前先在 Google 搜索。有很高的几率某人已经问了一个类似的问题，而且 Stack Exchange 网站们往往会是搜索结果中最前面几个。如果你在 Google 上没有找到任何答案，你再到特定相关主题的网站去找。用标签（Tag）搜索能让你更缩小你的搜索结果。</p>
<p>如果你还是找不到任何对你的问题有用的内容，请把你的问题发在与它最相关的网站上。提问的时候请善用格式化工具，尤其注意为代码添加格式，并且添加相关的标签（特别是编程语言、操作系统或库/包的名称）。当有人要求你提供更多相关信息时，请编辑你的贴子来补充它们[译注：而不是发一个回帖或回答！]。如果你觉得一个答案对你有帮助，点击向上的箭头来为它投票；如果一个答案提供了问题的正确解决方案，点击投票按钮下方的对勾来将它标记为正解。</p>
<p>Stack Exchange 已经成长到<a href="https://stackexchange.com/sites" target="_blank" rel="noopener noreferrer">超过一百个网站</a>，以下是最常用的几个站：</p>
<ul>
<li>Super User 是问一些通用的电脑问题，如果你的问题跟代码或是写程序无关，只是一些网络连线之类的，请到这里。</li>
<li>Stack Overflow 是问写程序有关的问题。</li>
<li>Server Fault 是问服务器和网管相关的问题。</li>
</ul>
<h3>网站和 IRC 论坛</h3>
<p>本地的用户群组（user group），或者你所用的 Linux 发行版本也许正在宣传他们的网页论坛或 IRC 频道，并提供新手帮助（在一些非英语国家，新手论坛很可能还是邮件列表），这些都是开始提问的好地方，特别是当你觉得遇到的也许只是相对简单或者很普通的问题时。有广告赞助的 IRC 频道是公开欢迎提问的地方，通常可以即时得到回应。</p>
<p>事实上，如果程序出的问题只发生在特定 Linux 发行版提供的版本（这很常见），最好先去该发行版的论坛或邮件列表中提问，再到程序本身的论坛或邮件列表提问。（否则）该项目的黑客可能仅仅回复“使用<strong>我们的</strong>版本”。</p>
<p>在任何论坛发文以前，先确认一下有没有搜索功能。如果有，就试着搜索一下问题的几个关键词，也许这会有帮助。如果在此之前你已做过通用的网页搜索（你也该这样做），还是再搜索一下论坛，搜索引擎有可能没来得及索引此论坛的全部内容。</p>
<p>通过论坛或 IRC 频道来提供用户支持服务有增长的趋势，电子邮件则大多为项目开发者间的交流而保留。所以最好先在论坛或 IRC 中寻求与该项目相关的协助。</p>
<p>在使用 IRC 的时候，首先最好不要发布很长的问题描述，有些人称之为频道洪水。最好通过一句话的问题描述来开始聊天。</p>
<h3>第二步，使用项目邮件列表</h3>
<p>当某个项目提供开发者邮件列表时，要向列表而不是其中的个别成员提问，即使你确信他能最好地回答你的问题。查一查项目的文件和首页，找到项目的邮件列表并使用它。有几个很好的理由支持我们采用这种办法：</p>
<ul>
<li>任何好到需要向个别开发者提出的问题，也将对整个项目群组有益。反之，如果你认为自己的问题对整个项目群组来说太愚蠢，那这也不能成为骚扰个别开发者的理由。</li>
<li>向列表提问可以分散开发者的负担，个别开发者（尤其是项目领导人）也许太忙以至于没法回答你的问题。</li>
<li>大多数邮件列表都会被存档，那些被存档的内容将被搜索引擎索引。如果你向列表提问并得到解答，将来其他人可以通过网页搜索找到你的问题和答案，也就不用再次发问了。</li>
<li>如果某些问题经常被问到，开发者可以利用此信息来改进说明文件或软件本身，以使其更清楚。如果只是私下提问，就没有人能看到最常见问题的完整场景。</li>
</ul>
<p>如果一个项目既有“用户”也有“开发者”（或“黑客”）邮件列表或论坛，而你又不会动到那些源代码，那么就向“用户”列表或论坛提问。不要假设自己会在开发者列表中受到欢迎，那些人多半会将你的提问视为干扰他们开发的噪音。</p>
<p>然而，如果你<strong>确信</strong>你的问题很特别，而且在“用户”列表或论坛中几天都没有回复，可以试试前往“开发者”列表或论坛发问。建议你在张贴前最好先暗地里观察几天以了解那里的行事方式（事实上这是参与任何私有或半私有列表的好主意）</p>
<p>如果你找不到一个项目的邮件列表，而只能查到项目维护者的电子邮件地址，尽管向他发信。即使是在这种情况下，也别假设（项目）邮件列表不存在。在你的电子邮件中，请陈述你已经试过但没有找到合适的邮件列表，也提及你不反对将自己的邮件转发给他人（许多人认为，即使没什么秘密，私人电子邮件也不应该被公开。通过允许将你的电子邮件转发他人，你给了相应人员处置你邮件的选择）。</p>
<h3>使用有意义且描述明确的标题</h3>
<p>在邮件列表、新闻群组或论坛中，大约 50 字以内的标题是抓住资深专家注意力的好机会。别用喋喋不休的<code>帮帮忙</code>、<code>跪求</code>、<code>急</code>（更别说<code>救命啊！！！！</code>这样让人反感的话，用这种标题会被条件反射式地忽略）来浪费这个机会。不要妄想用你的痛苦程度来打动我们，而应该是在这点空间中使用极简单扼要的描述方式来提出问题。</p>
<p>一个好标题范例是<code>目标 —— 差异</code>式的描述，许多技术支持组织就是这样做的。在<code>目标</code>部分指出是哪一个或哪一组东西有问题，在<code>差异</code>部分则描述与期望的行为不一致的地方。</p>
<blockquote>
<p>蠢问题：救命啊！我的笔记本电脑不能正常显示了！</p>
</blockquote>
<blockquote>
<p>聪明问题：<a href="http://X.org" target="_blank" rel="noopener noreferrer">X.org</a> 6.8.1 的鼠标指针会变形，某牌显卡 MV1005 芯片组。</p>
</blockquote>
<blockquote>
<p>更聪明问题：<a href="http://X.org" target="_blank" rel="noopener noreferrer">X.org</a> 6.8.1 的鼠标指针，在某牌显卡 MV1005 芯片组环境下 - 会变形。</p>
</blockquote>
<p>编写<code>目标 —— 差异</code> 式描述的过程有助于你组织对问题的细致思考。是什么被影响了？ 仅仅是鼠标指针或者还有其它图形？只在 <a href="http://X.org" target="_blank" rel="noopener noreferrer">X.org</a> 的 X 版中出现？或只是出现在 6.8.1 版中？ 是针对某牌显卡芯片组？或者只是其中的 MV1005 型号？ 一个黑客只需瞄一眼就能够立即明白你的环境<strong>和</strong>你遇到的问题。</p>
<p>总而言之，请想像一下你正在一个只显示标题的存档讨论串（Thread）索引中查寻。让你的标题更好地反映问题，可使下一个搜索类似问题的人能够关注这个讨论串，而不用再次提问相同的问题。</p>
<p>如果你想在回复中提出问题，记得要修改内容标题，以表明你是在问一个问题， 一个看起来像 <code>Re: 测试</code> 或者 <code>Re: 新 bug</code> 的标题很难引起足够重视。另外，在不影响连贯性之下，适当引用并删减前文的内容，能给新来的读者留下线索。</p>
<p>对于讨论串，不要直接点击回复来开始一个全新的讨论串，这将限制你的观众。因为有些邮件阅读程序，比如 mutt ，允许用户按讨论串排序并通过折叠讨论串来隐藏消息，这样做的人永远看不到你发的消息。</p>
<p>仅仅改变标题还不够。mutt 和其它一些邮件阅读程序还会检查邮件标题以外的其它信息，以便为其指定讨论串。所以宁可发一个全新的邮件。</p>
<p>在网页论坛上，好的提问方式稍有不同，因为讨论串与特定的信息紧密结合，并且通常在讨论串外就看不到里面的内容，故通过回复提问，而非改变标题是可接受的。不是所有论坛都允许在回复中出现分离的标题，而且这样做了基本上没有人会去看。不过，通过回复提问，这本身就是暧昧的做法，因为它们只会被正在查看该标题的人读到。所以，除非你<strong>只想</strong>在该讨论串当前活跃的人群中提问，不然还是另起炉灶比较好。</p>
<h3>使问题容易回复</h3>
<p>以<code>请将你的回复发送到……</code>来结束你的问题多半会使你得不到回答。如果你觉得花几秒钟在邮件客户端设置一下回复地址都麻烦，我们也觉得花几秒钟思考你的问题更麻烦。如果你的邮件程序不支持这样做，<a href="http://linuxmafia.com/faq/Mail/muas.html" target="_blank" rel="noopener noreferrer">换个好点的</a>；如果是操作系统不支持这种邮件程序，也换个好点的。</p>
<p>在论坛，要求通过电子邮件回复是非常无礼的，除非你认为回复的信息可能比较敏感（有人会为了某些未知的原因，只让你而不是整个论坛知道答案）。如果你只是想在有人回复讨论串时得到电子邮件提醒，可以要求网页论坛发送给你。几乎所有论坛都支持诸如<code>追踪此讨论串</code>、<code>有回复时发送邮件提醒</code>等功能。</p>
<h3>使用清晰、正确、精准且合乎语法的语句</h3>
<p>我们从经验中发现，粗心的提问者通常也会粗心地写程序与思考（我敢打包票）。回答粗心大意者的问题很不值得，我们宁愿把时间耗在别处。</p>
<p>正确的拼写、标点符号和大小写是很重要的。一般来说，如果你觉得这样做很麻烦，不想在乎这些，那我们也觉得麻烦，不想在乎你的提问。花点额外的精力斟酌一下字句，用不着太僵硬与正式 —— 事实上，黑客文化很看重能准确地使用非正式、俚语和幽默的语句。但它<strong>必须很</strong>准确，而且有迹象表明你是在思考和关注问题。</p>
<p>正确地拼写、使用标点和大小写，不要将<code>its</code>混淆为<code>it's</code>，<code>loose</code>搞成<code>lose</code>或者将<code>discrete</code>弄成<code>discreet</code>。不要<strong>全部用大写</strong>，这会被视为无礼的大声嚷嚷（全部小写也好不到哪去，因为不易阅读。<a href="http://en.wikipedia.org/wiki/Alan_Cox" target="_blank" rel="noopener noreferrer">Alan Cox</a> 也许可以这样做，但你不行）。</p>
<p>更白话的说，如果你写得像是个半文盲[译注：<a href="http://zh.wikipedia.org/wiki/%E5%B0%8F%E7%99%BD" target="_blank" rel="noopener noreferrer">小白</a>]，那多半得不到理睬。也不要使用即时通信中的简写或<a href="http://zh.wikipedia.org/wiki/%E7%81%AB%E6%98%9F%E6%96%87" target="_blank" rel="noopener noreferrer">火星文</a>，如将<code>的</code>简化为<code>d</code>会使你看起来像一个为了少打几个键而省字的小白。更糟的是，如果像个小孩似地鬼画符那绝对是在找死，可以肯定没人会理你（或者最多是给你一大堆指责与挖苦）。</p>
<p>如果在使用非母语的论坛提问，你可以犯点拼写和语法上的小错，但决不能在思考上马虎（没错，我们通常能弄清两者的分别）。同时，除非你知道回复者使用的语言，否则请使用英语书写。繁忙的黑客一般会直接删除用他们看不懂的语言写的消息。在网络上英语是通用语言，用英语书写可以将你的问题在尚未被阅读就被直接删除的可能性降到最低。</p>
<p>如果英文是你的外语（Second language），提示潜在回复者你有潜在的语言困难是很好的：<br>
[译注：以下附上原文以供使用]</p>
<blockquote>
<p>English is not my native language; please excuse typing errors.</p>
</blockquote>
<ul>
<li>英文不是我的母语，请原谅我的错字或语法。</li>
</ul>
<blockquote>
<p>If you speak $LANGUAGE, please email/PM me;<br>
I may need assistance translating my question.</p>
</blockquote>
<ul>
<li>如果你说<strong>某语言</strong>，请向我发电邮/私信；</li>
<li>我需要有人协助我翻译我的问题。</li>
</ul>
<blockquote>
<p>I am familiar with the technical terms,<br>
but some slang expressions and idioms are difficult for me.</p>
</blockquote>
<ul>
<li>我对技术名词很熟悉，但对于俗语或是特别用法不甚了解。</li>
</ul>
<blockquote>
<p>I've posted my question in $LANGUAGE and English.<br>
I'll be glad to translate responses, if you only use one or the other.</p>
</blockquote>
<ul>
<li>我把我的问题用<strong>某语言</strong>和英文写出来。</li>
<li>如果你只用其中的一种语言回答，我会乐意将回复翻译成为你使用的语言。</li>
</ul>
<h3>使用易于读取且标准的文件格式发送问题</h3>
<p>如果你人为地将问题搞得难以阅读，它多半会被忽略，人们更愿读易懂的问题，所以：</p>
<ul>
<li>使用纯文字而不是 HTML (<a href="http://archive.birdhouse.org/etc/evilmail.html" target="_blank" rel="noopener noreferrer">关闭 HTML</a> 并不难）。</li>
<li>使用 MIME 附件通常是可以的，前提是真正有内容（譬如附带的源代码或 patch），而不仅仅是邮件程序生成的模板（譬如只是信件内容的拷贝）。</li>
<li>不要发送一段文字只是一行句子但自动换行后会变成多行的邮件（这使得回复部分内容非常困难）。设想你的读者是在 80 个字符宽的终端机上阅读邮件，最好设置你的换行分割点小于 80 字。</li>
<li>但是，对一些特殊的文件<strong>不要</strong>设置固定宽度（譬如日志文件拷贝或会话记录）。数据应该原样包含，让回复者有信心他们看到的是和你看到的一样的东西。</li>
<li>在英语论坛中，不要使用<code>Quoted-Printable</code> MIME 编码发送消息。这种编码对于张贴非 ASCII 语言可能是必须的，但很多邮件程序并不支持这种编码。当它们处理换行时，那些文本中四处散布的<code>=20</code>符号既难看也分散注意力，甚至有可能破坏内容的语意。</li>
<li>绝对，<strong>永远</strong>不要指望黑客们阅读使用封闭格式编写的文档，像微软公司的 Word 或 Excel 文件等。大多数黑客对此的反应就像有人将还在冒热气的猪粪倒在你家门口时你的反应一样。即便他们能够处理，他们也很厌恶这么做。</li>
<li>如果你从使用 Windows 的电脑发送电子邮件，关闭微软愚蠢的<code>智能引号</code>功能 （从[选项] &gt; [校订] &gt; [自动校正选项]，勾选掉<code>智能引号</code>单选框），以免在你的邮件中到处散布垃圾字符。</li>
<li>在论坛，勿滥用<code>表情符号</code>和<code>HTML</code>功能（当它们提供时）。一两个表情符号通常没有问题，但花哨的彩色文本倾向于使人认为你是个无能之辈。过滥地使用表情符号、色彩和字体会使你看来像个傻笑的小姑娘。这通常不是个好主意，除非你只是对性而不是对答案感兴趣。</li>
</ul>
<p>如果你使用图形用户界面的邮件程序（如微软公司的 Outlook 或者其它类似的），注意它们的默认设置不一定满足这些要求。大多数这类程序有基于选单的<code>查看源代码</code>命令，用它来检查发送文件夹中的邮件，以确保发送的是纯文本文件同时没有一些奇怪的字符。</p>
<h3>精确地描述问题并言之有物</h3>
<ul>
<li>仔细、清楚地描述你的问题或 Bug 的症状。</li>
<li>描述问题发生的环境（机器配置、操作系统、应用程序、以及相关的信息），提供经销商的发行版和版本号（如：<code>Fedora Core 4</code>、<code>Slackware 9.1</code>等）。</li>
<li>描述在提问前你是怎样去研究和理解这个问题的。</li>
<li>描述在提问前为确定问题而采取的诊断步骤。</li>
<li>描述最近做过什么可能相关的硬件或软件变更。</li>
<li>尽可能地提供一个可以<code>重现这个问题的可控环境</code>的方法。</li>
</ul>
<p>尽量去揣测一个黑客会怎样反问你，在你提问之前预先将黑客们可能提出的问题回答一遍。</p>
<p>以上几点中，当你报告的是你认为可能在代码中的问题时，给黑客一个可以重现你的问题的环境尤其重要。当你这么做时，你得到有效的回答的机会和速度都会大大的提升。</p>
<p><a href="http://www.chiark.greenend.org.uk/~sgtatham/" target="_blank" rel="noopener noreferrer">Simon Tatham</a> 写过一篇名为《<a href="http://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html" target="_blank" rel="noopener noreferrer">如何有效地报告Bug</a>》的出色文章。强力推荐你也读一读。</p>
<h3>话不在多而在精</h3>
<p>你需要提供精确有内容的信息。这并不是要求你简单的把成堆的出错代码或者资料完全转录到你的提问中。如果你有庞大而复杂的测试样例能重现程序挂掉的情境，尽量将它剪裁得越小越好。</p>
<p>这样做的用处至少有三点。<br>
第一，表现出你为简化问题付出了努力，这可以使你得到回答的机会增加；<br>
第二，简化问题使你更有可能得到<strong>有用</strong>的答案；<br>
第三，在精炼你的 bug 报告的过程中，你很可能就自己找到了解决方法或权宜之计。</p>
<h3>别动辄声称找到 Bug</h3>
<p>当你在使用软件中遇到问题，除非你非常、<strong>非常</strong>的有根据，不要动辄声称找到了 Bug。提示：除非你能提供解决问题的源代码补丁，或者提供回归测试来表明前一版本中行为不正确，否则你都多半不够完全确信。这同样适用在网页和文件，如果你（声称）发现了文件的<code>Bug</code>，你应该能提供相应位置的修正或替代文件。</p>
<p>请记得，还有其他许多用户没遇到你发现的问题，否则你在阅读文件或搜索网页时就应该发现了（你在抱怨前<a href="#%E5%9C%A8%E6%8F%90%E9%97%AE%E4%B9%8B%E5%89%8D">已经做了这些，是吧</a>？）。这也意味着很有可能是你弄错了而不是软件本身有问题。</p>
<p>编写软件的人总是非常辛苦地使它尽可能完美。如果你声称找到了 Bug，也就是在质疑他们的能力，即使你是对的，也有可能会冒犯到其中某部分人。当你在标题中嚷嚷着有<code>Bug</code>时，这尤其严重。</p>
<p>提问时，即使你私下非常确信已经发现一个真正的 Bug，最好写得像是<strong>你</strong>做错了什么。如果真的有 Bug，你会在回复中看到这点。这样做的话，如果真有 Bug，维护者就会向你道歉，这总比你惹恼别人然后欠别人一个道歉要好一点。</p>
<h3>低声下气不能代替你的功课</h3>
<p>有些人明白他们不该粗鲁或傲慢的提问并要求得到答复，但他们选择另一个极端 —— 低声下气：<code>我知道我只是个可悲的新手，一个失败者，但...</code>。这既使人困扰，也没有用，尤其是伴随着与实际问题含糊不清的描述时更令人反感。</p>
<p>别用原始灵长类动物的把戏来浪费你我的时间。取而代之的是，尽可能清楚地描述背景条件和你的问题情况。这比低声下气更好地定位了你的位置。</p>
<p>有时网页论坛会设有专为新手提问的版面，如果你真的认为遇到了初学者的问题，到那去就是了，但一样别那么低声下气。</p>
<h3>描述问题症状而非你的猜测</h3>
<p>告诉黑客们你认为问题是怎样造成的并没什么帮助。（如果你的推断如此有效，还用向别人求助吗？），因此要确信你原原本本告诉了他们问题的症状，而不是你的解释和理论；让黑客们来推测和诊断。如果你认为陈述自己的猜测很重要，清楚地说明这只是你的猜测，并描述为什么它们不起作用。</p>
<p><strong>蠢问题</strong></p>
<blockquote>
<p>我在编译内核时接连遇到 SIG11 错误，<br>
我怀疑某条飞线搭在主板的走线上了，这种情况应该怎样检查最好？</p>
</blockquote>
<p><strong>聪明问题</strong></p>
<blockquote>
<p>我的组装电脑是 FIC-PA2007 主机板搭载 AMD K6/233 CPU（威盛 Apollo VP2 芯片组），<br>
256MB Corsair PC133 SDRAM 内存，在编译内核时，从开机 20 分钟以后就频频产生 SIG11 错误，<br>
但是在头 20 分钟内从没发生过相同的问题。重新启动也没有用，但是关机一晚上就又能工作 20 分钟。<br>
所有内存都换过了，没有效果。相关部分的标准编译记录如下…</p>
</blockquote>
<p>由于以上这点似乎让许多人觉得难以配合，这里有句话可以提醒你：<code>所有的诊断专家都来自密苏里州。</code> 美国国务院的官方座右铭则是：<code>让我看看</code>（出自国会议员 Willard D. Vandiver 在 1899 年时的讲话：<code>我来自一个出产玉米，棉花，牛蒡和民主党人的国家，滔滔雄辩既不能说服我，也不会让我满意。我来自密苏里州，你必须让我看看。</code>） 针对诊断者而言，这并不是一种怀疑，而只是一种真实而有用的需求，以便让他们看到的是与你看到的原始证据尽可能一致的东西，而不是你的猜测与归纳的结论。所以，大方地展示给我们看吧！</p>
<h3>按发生时间先后列出问题症状</h3>
<p>问题发生前的一系列操作，往往就是对找出问题最有帮助的线索。因此，你的说明里应该包含你的操作步骤，以及机器和软件的反应，直到问题发生。在命令行处理的情况下，提供一段操作记录（例如运行脚本工具所生成的），并引用相关的若干行（如 20 行）记录会非常有帮助。</p>
<p>如果挂掉的程序有诊断选项（如 -v 的详述开关），试着选择这些能在记录中增加调试信息的选项。记住，<code>多</code>不等于<code>好</code>。试着选取适当的调试级别以便提供有用的信息而不是让读者淹没在垃圾中。</p>
<p>如果你的说明很长（如超过四个段落），在开头简述问题，接下来再按时间顺序详述会有所帮助。这样黑客们在读你的记录时就知道该注意哪些内容了。</p>
<h3>描述目标而不是过程</h3>
<p>如果你想弄清楚如何做某事（而不是报告一个 Bug），在开头就描述你的目标，然后才陈述重现你所卡住的特定步骤。</p>
<p>经常寻求技术帮助的人在心中有个更高层次的目标，而他们在自以为能达到目标的特定道路上被卡住了，然后跑来问该怎么走，但没有意识到这条路本身就有问题。结果要费很大的劲才能搞定。</p>
<p><strong>蠢问题</strong></p>
<blockquote>
<p>我怎样才能从某绘图程序的颜色选择器中取得十六进制的 RGB 值？</p>
</blockquote>
<p><strong>聪明问题</strong></p>
<blockquote>
<p>我正试着用替换一幅图片的色码（color table）成自己选定的色码，我现在知道的唯一方法是编辑每个色码区块（table slot），<br>
但却无法从某绘图程序的颜色选择器取得十六进制的 RGB 值。</p>
</blockquote>
<p>第二种提问法比较聪明，你可能得到像是<code>建议采用另一个更合适的工具</code>的回复。</p>
<h3>别要求使用私人电邮回复</h3>
<p>黑客们认为问题的解决过程应该公开、透明，此过程中如果更有经验的人注意到不完整或者不当之处，最初的回复才能够、也应该被纠正。同时，作为提供帮助者可以得到一些奖励，奖励就是他的能力和学识被其他同行看到。</p>
<p>当你要求私下回复时，这个过程和奖励都被中止。别这样做，让<strong>回复者</strong>来决定是否私下回答 —— 如果他真这么做了，通常是因为他认为问题编写太差或者太肤浅，以至于不可能使其他人产生兴趣。</p>
<p>这条规则存在一条有限的例外，如果你确信提问可能会引来大量雷同的回复时，那么这个神奇的提问句会是<code>向我发电邮，我将为论坛归纳这些回复</code>。试着将邮件列表或新闻群组从洪水般的雷同回复中解救出来是非常有礼貌的 —— 但你必须信守诺言。</p>
<h3>清楚明确地表达你的问题以及需求</h3>
<p>漫无边际的提问是近乎无休无止的时间黑洞。最有可能给你有用答案的人通常也正是最忙的人（他们忙是因为要亲自完成大部分工作）。这样的人对无节制的时间黑洞相当厌恶，所以他们也倾向于厌恶那些漫无边际的提问。</p>
<p>如果你明确表述需要回答者做什么（如提供指点、发送一段代码、检查你的补丁、或是其他等等），就最有可能得到有用的答案。因为这会定出一个时间和精力的上限，便于回答者能集中精力来帮你。这么做很棒。</p>
<p>要理解专家们所处的世界，请把专业技能想像为充裕的资源，而回复的时间则是稀缺的资源。你要求他们奉献的时间越少，你越有可能从真正专业而且很忙的专家那里得到解答。</p>
<p>所以，界定一下你的问题，使专家花在辨识你的问题和回答所需要付出的时间减到最少，这技巧对你获得有用的答案相当有帮助 —— 但这技巧通常和简化问题有所区别。因此，问<code>我想更好地理解 X，可否指点一下哪有好一点说明？</code>通常比问<code>你能解释一下 X 吗？</code>更好。如果你的代码不能运作，通常请别人看看哪里有问题，比要求别人替你改正要明智得多。</p>
<h3>询问有关代码的问题时</h3>
<p>如果没有提示别人应该从何入手，别要求他人帮你调试有问题的代码。张贴几百行的代码，然后说一声：<code>它不能工作</code>会让你完全被忽略。只贴几十行代码，然后说一句：<code>在第七行以后，我期待它显示 &lt;x&gt;，但实际出现的是 &lt;y&gt;</code>比较有可能让你得到回应。</p>
<p>最有效描述程序问题的方法是提供最精简的 Bug 展示测试用例（bug-demonstrating test case）。什么是最精简的测试用例？那是问题的缩影；一小个程序片段能<strong>刚好</strong>展示出程序的异常行为，而不包含其他令人分散注意力的内容。怎么制作最精简的测试用例？如果你知道哪一行或哪一段代码会造成异常的行为，复制下来并加入足够重现这个状况的代码（例如，足以让这段代码能被编译/直译/被应用程序处理）。如果你无法将问题缩减到一个特定区块，就复制一份代码并移除不影响产生问题行为的部分。总之，测试用例越小越好（查看<a href="#%E8%AF%9D%E4%B8%8D%E5%9C%A8%E5%A4%9A%E8%80%8C%E5%9C%A8%E7%B2%BE">话不在多而在精</a>一节）。</p>
<p>一般而言，要得到一段相当精简的测试用例并不太容易，但永远先尝试这样做是一个好习惯。这种方式可以帮助你了解如何自行解决这个问题 —— 而且即使你的尝试不成功，黑客们也会看到你在尝试取得答案的过程中付出了努力，这可以让他们更愿意与你合作。</p>
<p>如果你只是想让别人帮忙审查（Review）一下代码，在信的开头就要说出来，并且一定要提到你认为哪一部分特别需要关注以及为什么。</p>
<h3>别把自己家庭作业的问题贴上来</h3>
<p>黑客们很擅长分辨哪些问题是家庭作业式的问题；因为我们中的大多数都曾自己解决这类问题。同样，这些问题得由<strong>你</strong>来搞定，你会从中学到东西。你可以要求给点提示，但别要求得到完整的解决方案。</p>
<p>如果你怀疑自己碰到了一个家庭作业式的问题，但仍然无法解决，试试在用户群组，论坛或（最后一招）在项目的<strong>用户</strong>邮件列表或论坛中提问。尽管黑客们<strong>会</strong>看出来，但一些有经验的用户也许仍会给你一些提示。</p>
<h3>去掉无意义的提问句</h3>
<p>避免用无意义的话结束提问，例如<code>有人能帮我吗？</code>或者<code>这有答案吗？</code>。</p>
<p>首先：如果你对问题的描述不是很好，这样问更是画蛇添足。</p>
<p>其次：由于这样问是画蛇添足，黑客们会很厌烦你 —— 而且通常会用逻辑上正确，但毫无意义的回答来表示他们的蔑视， 例如：<code>没错，有人能帮你</code>或者<code>不，没答案</code>。</p>
<p>一般来说，避免用 <code>是或否</code>、<code>对或错</code>、<code>有或没有</code>类型的问句，除非你想得到<a href="https://strcat.de/questions-with-yes-or-no-answers.html" target="_blank" rel="noopener noreferrer">是或否类型的回答</a>。</p>
<h3>即使你很急也不要在标题写<code>紧急</code></h3>
<p>这是你的问题，不是我们的。宣称<code>紧急</code>极有可能事与愿违：大多数黑客会直接删除无礼和自私地企图即时引起关注的问题。更严重的是，<code>紧急</code>这个字（或是其他企图引起关注的标题）通常会被垃圾信过滤器过滤掉 —— 你希望能看到你问题的人可能永远也看不到。</p>
<p>有半个例外的情况是，如果你是在一些很高调，会使黑客们兴奋的地方，也许值得这样去做。在这种情况下，如果你有时间压力，也很有礼貌地提到这点，人们也许会有兴趣回答快一点。</p>
<p>当然，这风险很大，因为黑客们兴奋的点多半与你的不同。譬如从 NASA 国际空间站（International Space Station）发这样的标题没有问题，但用自我感觉良好的慈善行为或政治原因发肯定不行。事实上，张贴诸如<code>紧急：帮我救救这个毛茸茸的小海豹！</code>肯定让你被黑客忽略或惹恼他们，即使他们认为毛茸茸的小海豹很重要。</p>
<p>如果你觉得这点很不可思议，最好再把这份指南剩下的内容多读几遍，直到你弄懂了再发文。</p>
<h3>礼多人不怪，而且有时还很有帮助</h3>
<p>彬彬有礼，多用<code>请</code>和<code>谢谢您的关注</code>，或<code>谢谢你的关照</code>。让大家都知道你对他们花时间免费提供帮助心存感激。</p>
<p>坦白说，这一点并没有比使用清晰、正确、精准且合乎语法和避免使用专用格式重要（也不能取而代之）。黑客们一般宁可读有点唐突但技术上鲜明的 Bug 报告，而不是那种有礼但含糊的报告。（如果这点让你不解，记住我们是按问题能教给我们什么来评价问题的价值的）</p>
<p>然而，如果你有一串的问题待解决，客气一点肯定会增加你得到有用回应的机会。</p>
<p>（我们注意到，自从本指南发布后，从资深黑客那里得到的唯一严重缺陷反馈，就是对预先道谢这一条。一些黑客觉得<code>先谢了</code>意味着事后就不用再感谢任何人的暗示。我们的建议是要么先说<code>先谢了</code>，<strong>然后</strong>事后再对回复者表示感谢，或者换种方式表达感激，譬如用<code>谢谢你的关注</code>或<code>谢谢你的关照</code>。）</p>
<h3>问题解决后，加个简短的补充说明</h3>
<p>问题解决后，向所有帮助过你的人发个说明，让他们知道问题是怎样解决的，并再一次向他们表示感谢。如果问题在新闻组或者邮件列表中引起了广泛关注，应该在那里贴一个说明比较恰当。</p>
<p>最理想的方式是向最初提问的话题回复此消息，并在标题中包含<code>已修正</code>，<code>已解决</code>或其它同等含义的明显标记。在人来人往的邮件列表里，一个看见讨论串<code>问题 X</code>和<code>问题 X - 已解决</code>的潜在回复者就明白不用再浪费时间了（除非他个人觉得<code>问题 X</code>有趣），因此可以利用此时间去解决其它问题。</p>
<p>补充说明不必很长或是很深入；简单的一句<code>你好，原来是网线出了问题！谢谢大家 – Bill</code>比什么也不说要来的好。事实上，除非结论真的很有技术含量，否则简短可爱的小结比长篇大论更好。说明问题是怎样解决的，但大可不必将解决问题的过程复述一遍。</p>
<p>对于有深度的问题，张贴调试记录的摘要是有帮助的。描述问题的最终状态，说明是什么解决了问题，在此<strong>之后</strong>才指明可以避免的盲点。避免盲点的部分应放在正确的解决方案和其它总结材料之后，而不要将此信息搞成侦探推理小说。列出那些帮助过你的名字，会让你交到更多朋友。</p>
<p>除了有礼貌和有内涵以外，这种类型的补充也有助于他人在邮件列表/新闻群组/论坛中搜索到真正解决你问题的方案，让他们也从中受益。</p>
<p>至少，这种补充有助于让每位参与协助的人因问题的解决而从中得到满足感。如果你自己不是技术专家或者黑客，那就相信我们，这种感觉对于那些你向他们求助的大师或者专家而言，是非常重要的。问题悬而未决会让人灰心；黑客们渴望看到问题被解决。好人有好报，满足他们的渴望，你会在下次提问时尝到甜头。</p>
<p>思考一下怎样才能避免他人将来也遇到类似的问题，自问写一份文件或加个常见问题（FAQ）会不会有帮助。如果是的话就将它们发给维护者。</p>
<p>在黑客中，这种良好的后继行动实际上比传统的礼节更为重要，也是你如何透过善待他人而赢得声誉的方式，这是非常有价值的资产。</p>
<h2>如何解读答案</h2>
<p><a id="rtfm"></a></p>
<h3>RTFM 和 STFW：如何知道你已完全搞砸了</h3>
<p>有一个古老而神圣的传统：如果你收到<code>RTFM（Read The Fucking Manual）</code>的回应，回答者认为你<strong>应该去读他妈的手册</strong>。当然，基本上他是对的，你应该去读一读。</p>
<p>RTFM 有一个年轻的亲戚。如果你收到<code>STFW（Search The Fucking Web）</code>的回应，回答者认为你<strong>应该到他妈的网上搜索</strong>。那人多半也是对的，去搜索一下吧。（更温和一点的说法是 <strong><a href="http://lmgtfy.com/" target="_blank" rel="noopener noreferrer">Google 是你的朋友</a></strong>！）</p>
<p>在论坛，你也可能被要求去爬爬论坛的旧文。事实上，有人甚至可能热心地为你提供以前解决此问题的讨论串。但不要依赖这种关照，提问前应该先搜索一下旧文。</p>
<p>通常，用这两句之一回答你的人会给你一份包含你需要内容的手册或者一个网址，而且他们打这些字的时候也正在读着。这些答复意味着回答者认为：</p>
<ul>
<li><strong>你需要的信息非常容易获得</strong>；</li>
<li><strong>你自己去搜索这些信息比灌给你，能让你学到更多</strong>。</li>
</ul>
<p>你不应该因此不爽；<strong>依照黑客的标准，他已经表示了对你一定程度的关注，而没有对你的要求视而不见</strong>。你应该对他祖母般的慈祥表示感谢。</p>
<h3>如果还是搞不懂</h3>
<p>如果你看不懂回应，别立刻要求对方解释。像你以前试着自己解决问题时那样（利用手册，FAQ，网络，身边的高手），先试着去搞懂他的回应。如果你真的需要对方解释，记得表现出你已经从中学到了点什么。</p>
<p>比方说，如果我回答你：<code>看来似乎是 zentry 卡住了；你应该先清除它。</code>，然后，这是一个<strong>很糟的</strong>后续问题回应：<code>zentry 是什么？</code> <strong>好</strong>的问法应该是这样：<code>哦~~~我看过说明了但是只有 -z 和 -p 两个参数中提到了 zentries，而且还都没有清楚的解释如何清除它。你是指这两个中的哪一个吗？还是我看漏了什么？</code></p>
<h3>处理无礼的回应</h3>
<p>很多黑客圈子中看似无礼的行为并不是存心冒犯。相反，它是直截了当，一针见血式的交流风格，这种风格更注重解决问题，而不是使人感觉舒服而却模模糊糊。</p>
<p>如果你觉得被冒犯了，试着平静地反应。如果有人真的做了出格的事，邮件列表、新闻群组或论坛中的前辈多半会招呼他。如果这<strong>没有</strong>发生而你却发火了，那么你发火对象的言语可能在黑客社区中看起来是正常的，而<strong>你</strong>将被视为有错的一方，这将伤害到你获取信息或帮助的机会。</p>
<p>另一方面，你偶尔真的会碰到无礼和无聊的言行。与上述相反，对真正的冒犯者狠狠地打击，用犀利的语言将其驳得体无完肤都是可以接受的。然而，在行事之前一定要非常非常的有根据。纠正无礼的言论与开始一场毫无意义的口水战仅一线之隔，黑客们自己莽撞地越线的情况并不鲜见。如果你是新手或外人，避开这种莽撞的机会并不高。如果你想得到的是信息而不是消磨时光，这时最好不要把手放在键盘上以免冒险。</p>
<p>（有些人断言很多黑客都有轻度的自闭症或亚斯伯格综合症，缺少用于润滑人类社会<strong>正常</strong>交往所需的神经。这既可能是真也可能是假的。如果你自己不是黑客，兴许你认为我们脑袋有问题还能帮助你应付我们的古怪行为。只管这么干好了，我们不在乎。我们<strong>喜欢</strong>我们现在这个样子，并且通常对病患标记都有站得住脚的怀疑。）</p>
<p>Jeff Bigler 的观察总结和这个相关也值得一读 (<strong><a href="http://www.mit.edu/~jcb/tact.html" target="_blank" rel="noopener noreferrer">tact filters</a></strong>)。</p>
<p>在下一节，我们会谈到另一个问题，当<strong>你</strong>行为不当时所会受到的<code>冒犯</code>。</p>
<h2>如何避免扮演失败者</h2>
<p>在黑客社区的论坛中，你以本指南所描述的或类似的方式，可能会有那么几次搞砸了。而你会在公开场合中被告知你是如何搞砸的，也许攻击的言语中还会带点夹七夹八的颜色。</p>
<p>这种事发生以后，你能做的最糟糕的事莫过于哀嚎你的遭遇、宣称被言语攻击、要求道歉、高声尖叫、憋闷气、威胁诉诸法律、向其雇主报怨、不去关马桶盖等等。相反地，你该这么做：</p>
<p>熬过去，这很正常。事实上，它是有益健康且合理的。</p>
<p>社区的标准不会自行维持，它们是通过参与者积极而<strong>公开地</strong>执行来维持的。不要哭嚎所有的批评都应该通过私下的邮件传送，它不是这样运作的。当有人评论你的一个说法有误或者提出不同看法时，坚持声称受到个人攻击也毫无益处，这些都是失败者的态度。</p>
<p>也有其它的黑客论坛，受过高礼节要求的误导，禁止参与者张贴任何对别人帖子挑毛病的消息，并声称<code>如果你不想帮助用户就闭嘴。</code> 结果造成有想法的参与者纷纷离开，这么做只会使它们沦为毫无意义的唠叨与无用的技术论坛。</p>
<p>夸张的讲法是：你要的是“友善”（以上述方式）还是有用？两个里面挑一个。</p>
<p>记着：当黑客说你搞砸了，并且（无论多么刺耳）告诉你别再这样做时，他正在为关心<strong>你</strong>和<strong>他的社区</strong>而行动。对他而言，不理你并将你从他的生活中滤掉更简单。如果你无法做到感谢，至少要表现得有点尊严，别大声哀嚎，也别因为自己是个有戏剧性超级敏感的灵魂和自以为有资格的新来者，就指望别人像对待脆弱的洋娃娃那样对你。</p>
<p>有时候，即使你没有搞砸（或者只是在他的想像中你搞砸了），有些人也会无缘无故地攻击你本人。在这种情况下，抱怨倒是<strong>真的</strong>会把问题搞砸。</p>
<p>这些来找麻烦的人要么是毫无办法但自以为是专家的不中用家伙，要么就是测试你是否真会搞砸的心理专家。其它读者要么不理睬，要么用自己的方式对付他们。这些来找麻烦的人在给他们自己找麻烦，这点你不用操心。</p>
<p>也别让自己卷入口水战，最好不要理睬大多数的口水战 —— 当然，这是在你检验它们只是口水战，并且未指出你有搞砸的地方，同时也没有巧妙地将问题真正的答案藏于其后（这也是有可能的）。</p>
<h2>不该问的问题</h2>
<p>以下是几个经典蠢问题，以及黑客没回答时心中所想的：</p>
<p>问题：<a href="#q1">我能在哪找到 X 程序或 X 资源？</a></p>
<p>问题：<a href="#q2">我怎样用 X 做 Y？</a></p>
<p>问题：<a href="#q3">如何设定我的 shell 提示？</a></p>
<p>问题：<a href="#q4">我可以用 Bass-o-matic 文件转换工具将 AcmeCorp 文件转换为 TeX 格式吗？</a></p>
<p>问题：<a href="#q5">我的程序/设定/SQL 语句没有用</a></p>
<p>问题：<a href="#q6">我的 Windows 电脑有问题，你能帮我吗？</a></p>
<p>问题：<a href="#q7">我的程序不会动了，我认为系统工具 X 有问题</a></p>
<p>问题：<a href="#q8">我在安装 Linux（或者 X ）时有问题，你能帮我吗？</a></p>
<p>问题：<a href="#q9">我怎么才能破解 root 帐号/窃取 OP 特权/读别人的邮件呢？</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>技术交流</title>
      <link>https://www.herodotus.cn/support/communication.html</link>
      <guid>https://www.herodotus.cn/support/communication.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">技术交流</source>
      <description>背景 为了方便 Dante Cloud 开源版及企业版用户交流，深入了解、掌握 Dante Cloud 使用相关技术栈，快速解决实际应用问题。自 2025年8月18日起，Dante Cloud 重新开放 技术交流群，欢迎所有 真心 交流技术朋友加入。 说明 重要 Dante Cloud 项目之前有 QQ 和 微信 技术交流群，但经过一段时间运营发现所谓...</description>
      <pubDate>Tue, 19 Aug 2025 09:16:47 GMT</pubDate>
      <content:encoded><![CDATA[<h2>背景</h2>
<p>为了方便 Dante Cloud 开源版及企业版用户交流，深入了解、掌握 Dante Cloud 使用相关技术栈，快速解决实际应用问题。自 2025年8月18日起，Dante Cloud 重新开放 <strong>技术交流群</strong>，欢迎所有 <code>真心</code> 交流技术朋友加入。</p>
<h2>说明</h2>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>Dante Cloud 项目之前有 QQ 和 微信 技术交流群，但经过一段时间运营发现所谓的技术交流群已经完全走样，不仅有价值的技术交流，而且完全变成偷懒耍滑、急功近利的温床，妥妥的是保姆群、客服群。因此关闭了较长时间。</p>
<p>为了不再出现以上情况，本技术交流群不欢迎以下用户群体:</p>
<ol>
<li>本群不欢迎，任何问题都只希望张张嘴，让别人来帮忙解决的人！</li>
<li>本群不欢迎，不看文档、不愿思考、不爱动脑，不想自己动手实践的人！</li>
<li>本群不欢迎，在技术群里打广告、扯闲篇、吹牛逼、刷存在感的人！</li>
</ol>
</div>
<ul>
<li><strong>如果您觉得以上要求过份或者对您有所冒犯</strong>，大可以划走就当没有看到过，更没有任何交流讨论的必要。</li>
<li><strong>如果您赞同以上观点并且觉得并无任何不妥</strong>，欢迎您进群，任何技术相关内容均可以坦诚的交流。</li>
</ul>
<h2>加群</h2>
<p>可以使用 QQ 扫描下面二维码进群。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>目前不考虑微信群。</p>
<p><strong>如果本项目对你有所帮助，欢迎 Star 一波来支持我们！您的支持是我们前进的动力！</strong></p>
<h2>1. Dante Cloud 主工程</h2>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://gitee.com/dromara/dante-cloud</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://github.com/dromara/dante-cloud</a></li>
<li><strong>AtomGit</strong>：<a href="https://atomgit.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://atomgit.com/dromara/dante-cloud</a></li>
</ul>
<h2>2. Dante Engine 核心组件库</h2>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dante-compass/dante-engine" target="_blank" rel="noopener noreferrer">https://gitee.com/dante-compass/dante-engine</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dante-compass/dante-engine" target="_blank" rel="noopener noreferrer">https://github.com/dante-compass/dante-engine</a></li>
<li><strong>AtomGit</strong>：<a href="https://atomgit.com/dante-compass/dante-engine" target="_blank" rel="noopener noreferrer">https://atomgit.com/dante-compass/dante-engine</a></li>
</ul>
<h2>3. UI 前端工程（旧版）</h2>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dante-compass/dante-cloud-ui" target="_blank" rel="noopener noreferrer">https://gitee.com/dante-compass/dante-cloud-ui</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dante-compass/dante-cloud-ui" target="_blank" rel="noopener noreferrer">https://github.com/dante-compass/dante-cloud-ui</a></li>
<li><strong>AtomGit</strong>：<a href="https://atomgit.com/dante-compass/dante-cloud-ui" target="_blank" rel="noopener noreferrer">https://atomgit.com/dante-compass/dante-cloud-ui</a></li>
</ul>
<h2>4. UI 前端工程（新版）</h2>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dante-compass/herodotus-cloud-ui-vuetify" target="_blank" rel="noopener noreferrer">https://gitee.com/dante-compass/herodotus-cloud-ui-vuetify</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dante-compass/herodotus-cloud-ui-vuetify" target="_blank" rel="noopener noreferrer">https://github.com/dante-compass/herodotus-cloud-ui-vuetify</a></li>
</ul>
<h2>5. ThingsBrain 基于 Dante Cloud 的物联网平台（加速开发中...）</h2>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dante-compass/thingsbrain" target="_blank" rel="noopener noreferrer">https://gitee.com/dante-compass/thingsbrain</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dante-compass/thingsbrain" target="_blank" rel="noopener noreferrer">https://github.com/dante-compass/thingsbrain</a></li>
</ul>
</div>
<figure><img src="/assets/image/main/qq.jpg" alt="QQ群" tabindex="0" loading="lazy"><figcaption>QQ群</figcaption></figure>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/main/qq.jpg" type="image/jpeg"/>
    </item>
    <item>
      <title>友情赞助</title>
      <link>https://www.herodotus.cn/support/donate.html</link>
      <guid>https://www.herodotus.cn/support/donate.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">友情赞助</source>
      <description>赞助人列表</description>
      <pubDate>Thu, 24 Jul 2025 16:11:51 GMT</pubDate>
      <content:encoded><![CDATA[<h2>赞助人列表</h2>
<p>| 序号 |                    赞助人                    |  赞助时间  | 序号 |                     赞助人                     |  赞助时间  | 序号 |                   赞助人                    |  赞助时间  |<br>
| :--: | :</p>
]]></content:encoded>
    </item>
    <item>
      <title>Nacos</title>
      <link>https://www.herodotus.cn/breaking/nacos.html</link>
      <guid>https://www.herodotus.cn/breaking/nacos.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">Nacos</source>
      <description>v3.2.1 全新的 Nacos 3.2.1 版本，表结构发生了较大变化，该版本与之前版本不兼容。需要重新建库重新导入配置。 提示 推荐使用 Nacos 3.2.1 版本，改版本修复了 3.2.0 存在的很多 BUG，新版本界面也让人耳目一新。 v3.2.0 全新的 Nacos 3.2.0 版本，表结构发生了较大变化，该版本与之前版本不兼容。需要重新建...</description>
      <pubDate>Mon, 26 May 2025 16:23:15 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v3.2.1</h2>
<p>全新的 Nacos 3.2.1 版本，表结构发生了较大变化，该版本与之前版本不兼容。需要重新建库重新导入配置。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>推荐使用 Nacos 3.2.1 版本，改版本修复了 3.2.0 存在的很多 BUG，新版本界面也让人耳目一新。</p>
</div>
<h2>v3.2.0</h2>
<p>全新的 Nacos 3.2.0 版本，表结构发生了较大变化，该版本与之前版本不兼容。需要重新建库重新导入配置。</p>
<h2>v3.0.1</h2>
<p>Nacos 自 3.X 版本起, 对 Nacos 控制台和 Nacos 本身进行了拆分和优化。</p>
<p>Nacos 自3.0开始新增Nacos控制台的网络访问端口 <code>8080</code>，不再与 Nacos 的主端口（server.port，默认8848）进行耦合，以提升Nacos的安全性。Nacos 控制台地址变更为 <a href="http://localhost:8080/index.html%E3%80%82" target="_blank" rel="noopener noreferrer">http://localhost:8080/index.html。</a></p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>因 <code>8080</code> 端口，是诸多软件的默认端口，为了减少不必要的冲突，在 Dante Cloud Nacos Docker 中，默认将 Nacos <code>8080</code> 端口映射为 <code>8849</code> 端口。</p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>单体离线演示</title>
      <link>https://www.herodotus.cn/get-started/preview/monomer.html</link>
      <guid>https://www.herodotus.cn/get-started/preview/monomer.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">单体离线演示</source>
      <description>说明 重要 如果您想进一步体验 Dante Cloud 的系统能力，可以考虑下载离线版本进行体验。可以通过修改 Docker 环境变量或者系统 application.yml 配置，来体验更多内容。 离线版镜像与在线演示系统部署的是同一个镜像，只不过是配置不同而已。 基于 Dante Cloud 4.0.X 版本的单体离线演示 Docker 已经发布。...</description>
      <pubDate>Fri, 14 Mar 2025 08:06:25 GMT</pubDate>
      <content:encoded><![CDATA[<h2>说明</h2>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>如果您想进一步体验 Dante Cloud 的系统能力，可以考虑下载离线版本进行体验。可以通过修改 Docker 环境变量或者系统 application.yml 配置，来体验更多内容。</p>
<p>离线版镜像与在线演示系统部署的是同一个镜像，只不过是配置不同而已。</p>
</div>
<p>基于 Dante Cloud 4.0.X 版本的单体离线演示 Docker 已经发布。该版本使用最新的 <code>herodotus-cloud-ui-vuetify</code> 作为前端，具备除了微服务基础能力外的所有功能。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>Dante Cloud 前端 UI 已经全面采用 Vite 8 编译和运行。因前端生态正在逐步适配 Vite 8，所以会有一定的问题。<code>herodotus-cloud-ui-vuetify</code> 前端可以正常应用于开发和使用，由于 Vuetify 尚在适配 Vite 8 的过程中，还有小部分问题。当前，Dante Cloud 4.0.X 版本的单体离线演示 Docker 受 Vuetify 与 Vite 8 适配的影响，存在部署环境部分样式不生效问题，一些界面样式现在看着有些怪，但是不影响功能。后续，问题修复会及时更新。</p>
</div>
<h2>[一]环境准备</h2>
<p>离线体验版本，主要以 Docker 形式进行部署，这会减少很多部署的繁琐性。</p>
<p>如果您想在 Windows 环境下安装运行 Docker 环境，具体安装和配置方式，参见：<a href="/get-started/prepare/docker.html" target="_blank">【本地 Docker 安装配置】</a></p>
<p>如果是要在 Linux 或者其它操作系统中安装 Docker，请自行百度。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>近期国内对 Docker 相关内容进行了屏蔽，为了方便使用，Dante Cloud 已经将相关内容同步至 <a href="http://Quay.IO" target="_blank" rel="noopener noreferrer">Quay.IO</a> 系统，目前均可以正常拉取镜像使用。</p>
<p>涉及的镜像均只是把相关软件官方镜像进行了搬迁，并未做任何更改或包装。Dante Cloud 自己封装的镜像均已经做了多平台支持。所以可以放心使用。</p>
</div>
<h2>[二]镜像地址</h2>
<p>离线镜像提供 Docker Hub 和 <a href="http://Quay.IO" target="_blank" rel="noopener noreferrer">Quay.IO</a> 两个仓库地址。</p>
<ul>
<li>Docker Hub: <code>herodotus/herodotus-cloud-preview</code></li>
<li><a href="http://Quay.IO" target="_blank" rel="noopener noreferrer">Quay.IO</a>: <code>quay.io/herodotus-cloud/herodotus-cloud-preview</code></li>
</ul>
<h2>[三]系统启动</h2>
<h3>[1]Docker</h3>
<p>镜像下载完成之后，你可以直接使用 Docker 命令进行启动。</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">docker</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> run</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --name</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> preview</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> quay.io/herodotus-cloud/herodotus-cloud-preview:latest</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -p</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 8000:8000</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -p</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 8847:8847</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h3>[2]Docker Compose</h3>
<p>除了 Docker 命令外，也可以使用 Docker Compose 脚本启动。</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">services</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  preview</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    image</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">quay.io/herodotus-cloud/herodotus-cloud-preview:latest</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    container_name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">preview</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    hostname</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">preview</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    environment</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      HERODOTUS_USE_DISABLE_DEVTOOL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">false</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      HERODOTUS_GITEE_SOCIAL_REDIRECT_URI</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">http://localhost:8000/preview/social/oauth2/callback/GITEE</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    ports</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">8000:8000</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">8847:8847</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    volumes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">D:\\local-cached\\docker-volumes\\herodotus-preview\\logs:/usr/local/herodotus/logs</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">D:\\local-cached\\docker-volumes\\herodotus-preview\\files:/usr/local/herodotus/files</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[四]环境变量</h2>
<p>离线演示 Docker 镜像，提供了部分环境变量，便于对系统内的一些信息进行修改。</p>
<p>| 变量名                              | 是否可选 | 说明                                                                |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>在线系统演示</title>
      <link>https://www.herodotus.cn/get-started/preview/online.html</link>
      <guid>https://www.herodotus.cn/get-started/preview/online.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">在线系统演示</source>
      <description>说明 重要 因硬件资源问题，目前无法提供在线演示环境。待硬件资源具备条件后，会重新开放。 现阶段如果不想搭建环境，可以尝试下载 Docker 版演示环境： 和 还是维持个人的观点： 传统单体项目通过看界面对系统功能也许就可以了解个大概，毕竟就是 CRUD 和一些业务逻辑的堆叠。 对于微服务系统，核心价值体现是在后端。这种价值是需要通过了解代码和实际运用...</description>
      <pubDate>Fri, 14 Mar 2025 08:06:25 GMT</pubDate>
      <content:encoded><![CDATA[<h2>说明</h2>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>因硬件资源问题，目前无法提供在线演示环境。待硬件资源具备条件后，会重新开放。</p>
<p>现阶段如果不想搭建环境，可以尝试下载 Docker 版演示环境：<a href="/get-started/preview/monomer.html" target="_blank">【单体版 Docker】</a> 和 <a href="/get-started/preview/microservices.html" target="_blank">【微服务版 Docker】</a></p>
</div>
<p>还是维持个人的观点：</p>
<ul>
<li>传统单体项目通过看界面对系统功能也许就可以了解个大概，毕竟就是 CRUD 和一些业务逻辑的堆叠。</li>
<li>对于微服务系统，核心价值体现是在后端。这种价值是需要通过了解代码和实际运用才能了解和体会到的。</li>
<li>期望通过前端界面就可以对一套复杂架构的系统了解个大概完全就是个悖论。假设这种情况可以成立，那么完全没有必要来看什么开源项目，只要打开您手机中任意的APP——淘宝、支付宝、微信、抖音的，看看其界面就可以直接了解国内顶级大厂的系统架构了。</li>
</ul>
<p>原本是一直主张让用户自己动手搭建，自己亲身体验的。之所以所以在开源了4年才搭建了个演示系统，也是情非所愿无奈之举，毕竟真正喜欢自己动手研究技术的人越来越少，省事、便捷的“快餐”才受人喜欢。</p>
]]></content:encoded>
    </item>
    <item>
      <title>体验评价</title>
      <link>https://www.herodotus.cn/support/praise.html</link>
      <guid>https://www.herodotus.cn/support/praise.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">体验评价</source>
      <description>重要 以下内容均截取自使用者对 Danta Cloud 使用感受的反馈 评价一 用户自述用户自述 评价二 用户自述用户自述 注：该位朋友本职工作为运维工作，逐步转向开发工作 评价三 优秀开源项目解读(七)优秀开源项目解读(七) 优秀开源项目解读(七)优秀开源项目解读(七) 评价四 用户自述用户自述 评价五 用户自述用户自述 评价六 注：该位朋友本对知名...</description>
      <pubDate>Tue, 11 Feb 2025 08:37:54 GMT</pubDate>
      <content:encoded><![CDATA[<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>以下内容均截取自使用者对 Danta Cloud 使用感受的反馈</p>
</div>
<h2>评价一</h2>
<figure><img src="/assets/image/support/praise/0012.png" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
<h2>评价二</h2>
<figure><img src="/assets/image/support/praise/0001.jpg" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
<blockquote>
<p>注：该位朋友本职工作为运维工作，逐步转向开发工作</p>
</blockquote>
<h2>评价三</h2>
<figure><img src="/assets/image/support/praise/0002.png" alt="优秀开源项目解读(七)" tabindex="0" loading="lazy"><figcaption>优秀开源项目解读(七)</figcaption></figure>
<figure><img src="/assets/image/support/praise/0003.png" alt="优秀开源项目解读(七)" tabindex="0" loading="lazy"><figcaption>优秀开源项目解读(七)</figcaption></figure>
<h2>评价四</h2>
<figure><img src="/assets/image/support/praise/0012.png" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
<h2>评价五</h2>
<figure><img src="/assets/image/support/praise/0005.jpg" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
<h2>评价六</h2>
<blockquote>
<p>注：该位朋友本对知名的同类产品做了深度对比</p>
</blockquote>
<figure><img src="/assets/image/support/praise/0006.jpg" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
<figure><img src="/assets/image/support/praise/0007.jpg" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
<figure><img src="/assets/image/support/praise/0008.jpg" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
<h2>评价七</h2>
<figure><img src="/assets/image/support/praise/0009.jpg" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
<h2>评价八</h2>
<figure><img src="/assets/image/support/praise/0010.jpg" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
<h2>评价九</h2>
<figure><img src="/assets/image/support/praise/0011.jpg" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
<h2>评价十</h2>
<figure><img src="/assets/image/support/praise/0004.jpg" alt="用户自述" tabindex="0" loading="lazy"><figcaption>用户自述</figcaption></figure>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/support/praise/0012.png" type="image/png"/>
    </item>
    <item>
      <title>添加异常转换</title>
      <link>https://www.herodotus.cn/develop-guide/coding/exception.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/exception.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">添加异常转换</source>
      <description>概述 Java 中 Exception 是很重要的一个组成部分。Exception 也并不是洪水猛兽，在可预期的代码逻辑中合理的使用 Exception 可以增强代码的健壮性，包含丰富信息的以及合理的 Exception 可以帮助开发人员快速定位问题。 日常开发中，我们常见的异常类型主要有两种：编译时异常（Exception）和运行时异常（Runtim...</description>
      <pubDate>Mon, 13 Jan 2025 08:56:33 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>Java 中 Exception 是很重要的一个组成部分。Exception 也并不是<code>洪水猛兽</code>，在可预期的代码逻辑中合理的使用 Exception 可以增强代码的健壮性，包含丰富信息的以及合理的 Exception 可以帮助开发人员快速定位问题。</p>
<p>日常开发中，我们常见的异常类型主要有两种：编译时异常（Exception）和运行时异常（RuntimeException）。</p>
<ul>
<li>编译时异常：Exception及其子类(除了RuntimeException)，在编译时期抛出的异常，在编译期间检查程序是否可能会出现问题。如果可能会有，则预先防范。这一类异常如果不加处理，通常会导致组件或者应用无法启动</li>
<li>运行时异常（Runtime exception） 是用 RuntimeException 类表示的，它描述的是程序设计错误。RuntimeException 的任何子类都无需在 throws 子句中进行声明，指的就是这些问题不需要提前被预防（本质上也可以的，只不过没必要），因为只有在真正运行的时候才能发现是否发生问题，一旦在运行期间发生了问题，则一般不会修正。这一类异常更多的用于“提示”和“提醒”</li>
</ul>
<p>本文中所述的自定义错误代码，主要是面向于 RuntimeException，用于在程序运行期间将操作产生的异常，用更人性化的表述方式展现给使用方，以便更容易、更快捷地定位问题。就像下图所示，显示更人性化的信息比抛出一大堆让人不知所云的错误信息更好。</p>
<figure><img src="/assets/image/install/access-denied.png" alt="异常实例界面" tabindex="0" loading="lazy"><figcaption>异常实例界面</figcaption></figure>
<p>目前，经常提到的 Exception 大概分为以下三类：</p>
<ol>
<li>系统业务代码自定义的 Exception，可能是 RuntimeException 也可能是 Exception</li>
<li>第三方模块代码抛出的 Exception，可能是 RuntimeException 也可能是 Exception</li>
<li>第三方模块代码抛出的继承类型 Exception。即，抛出的是第三方模块自定义 Exception 的基类，实际 Exception 信息被包含在基类 Exception</li>
</ol>
<p>正因为 Java Exception 体系的灵活性以及第三方组件的丰富，使得想要统一处理异常变得越发复杂，这也是自定义错误码系统是 Dante Cloud 重要功能的原因。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>Dante Cloud 的自定义错误码体系，主要面向的错误是运行时异常（Runtime exception）。这样在调用系统 REST 接口出现错误时，可以将更人性化的错误信息，在接口的 REST Response 中显示。让定位问题更加容易便捷，同时减少不同端开发人员的沟通成本。</p>
</div>
<p>下面就来介绍如何在 Dante Cloud 统一的错误码体系中添加和处理各种组件、各种类型的错误。</p>
<h2>[一]常规类型异常</h2>
<p>这里所说的“常规类型异常”，可以是 Java 自带的、大多数组件都会使用的通用类型异常，例如：NullPointerException。也可以是第三方组件自定义的异常。</p>
<p>这一类异常通常是 Exception 及其子类(除了RuntimeException)，比较好处理。利用 Spring Boot 自身 <code>@RestControllerAdvice</code> 的机制，就可以进行拦截。</p>
<p>具体的添加方法如下：</p>
<h3>[1]定义错误信息</h3>
<p>在 <code>core-definition</code> 模块中，找到 <code>ErrorCodes</code> 常量定义接口。在其中定义具体的 Exception 错误描述。例如：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 200</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    OkFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> OK </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> OkFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"成功"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 204</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    NoContentFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> NO_CONTENT </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> NoContentFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"无内容"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 401.** 未经授权 Unauthorized 请求要求用户的身份认证</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> UNAUTHORIZED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"未经授权"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCESS_DENIED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"您没有权限，拒绝访问"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCOUNT_DISABLED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"该账户已经被禁用"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCOUNT_ENDPOINT_LIMITED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"您已经使用其它终端登录,请先退出其它终端"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCOUNT_EXPIRED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"该账户已经过期"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCOUNT_LOCKED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"该账户已经被锁定"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> BAD_CREDENTIALS </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"用户名或密码错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> CREDENTIALS_EXPIRED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"该账户密码凭证已过期"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> INVALID_CLIENT </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"客户端身份验证失败或数据库未初始化"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> INVALID_TOKEN </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"提供的访问令牌已过期、吊销、格式错误或无效"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> INVALID_GRANT </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"提供的授权授予或刷新令牌无效、已过期或已撤销"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> UNAUTHORIZED_CLIENT </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"客户端无权使用此方法请求授权码或访问令牌"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> USERNAME_NOT_FOUND </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"用户名或密码错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> SESSION_EXPIRED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Session 已过期，请刷新页面后再使用"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> NOT_AUTHENTICATED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"请求的地址未通过身份认证"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    ......</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Dante Cloud 中，错误码体系与 HTTP 请求状态码绑定。可根据错误码的描述，对实际的错误进行分类，并使用具体的 <code>XXXFeedback</code> 类进行描述。例如：认证授权方面的错误，就可以使用 <code>UnauthorizedFeedback</code> 进行描述，使用该描述类的错误，会自动生成 <code>401</code> 开头的错误码。</p>
</div>
<h3>[2]添加错误字典</h3>
<p>定义完错误码后，在 <code>core-foundation</code> 模块中，找到 <code>GlobalExceptionHandler</code> 类。在其中将之前定义的错误码，添加至 <code>EXCEPTION_DICTIONARY</code> Map 中。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GlobalExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Logger</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> log </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> LoggerFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getLogger</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">GlobalExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Map</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Feedback</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> EXCEPTION_DICTIONARY </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HashMap</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"AccessDeniedException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_DENIED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"BadSqlGrammarException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">BAD_SQL_GRAMMAR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"BindException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">METHOD_ARGUMENT_NOT_VALID</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"CookieTheftException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">COOKIE_THEFT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"DataIntegrityViolationException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">DATA_INTEGRITY_VIOLATION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"HttpMediaTypeNotAcceptableException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HTTP_MEDIA_TYPE_NOT_ACCEPTABLE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"HttpMessageNotReadableException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HTTP_MESSAGE_NOT_READABLE_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"HttpRequestMethodNotSupportedException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HTTP_REQUEST_METHOD_NOT_SUPPORTED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"IllegalArgumentException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ILLEGAL_ARGUMENT_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"InsufficientAuthenticationException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_DENIED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"InvalidCookieException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_COOKIE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"IOException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">IO_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"MethodArgumentNotValidException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">METHOD_ARGUMENT_NOT_VALID</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"MissingServletRequestParameterException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"NoResourceFoundException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">NO_RESOURCE_FOUND_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"NullPointerException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">NULL_POINTER_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ProviderNotFoundException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">PROVIDER_NOT_FOUND</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"RedisPipelineException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">PIPELINE_INVALID_COMMANDS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"TypeMismatchException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">TYPE_MISMATCH_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"TransactionRollbackException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">TRANSACTION_ROLLBACK</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    ......</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container info">
<p class="hint-container-title">说明</p>
<ul>
<li><code>EXCEPTION_DICTIONARY</code> Map 的 Key 是以具体 Exception 类的名称作为 Key。</li>
<li><code>EXCEPTION_DICTIONARY</code> Map 的 Value 就是上一步定义的错误码</li>
</ul>
<blockquote>
<p>这里使用 Map 是为了提升错误查询的效率，而且方便维护</p>
</blockquote>
</div>
<h3>[3]添加错误配置器</h3>
<p>完成以上步骤后，需要将新定义的错误描述，添加至统一的错误配置器中。这一步主要的目的是让具体的错误描述生效，并且在实现错误码的自动计算。</p>
<h4>步骤一：新建转换器</h4>
<p>在代码中新建一个 <code>XXXErrorCodeMapperBuilderCustomizer</code> 类，该类需要实现 <code>ErrorCodeMapperBuilderCustomizer</code> 接口。然后将新建的错误描述信息，添加到其中，如下所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AccessErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Ordered</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ErrorCodeMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">preconditionFailed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_CONFIG_ERROR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_HANDLER_NOT_FOUND</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SMS_IDENTITY_VERIFICATION_FAILED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">WXAPP_IDENTITY_VERIFICATION_FAILED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">WXMPP_IDENTITY_VERIFICATION_FAILED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">JUATAUTH_IDENTITY_VERIFICATION_FAILED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_PRE_PROCESS_FAILED_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ILLEGAL_ACCESS_ARGUMENT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ILLEGAL_ACCESS_SOURCE</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        );</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getOrder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> ErrorCodeMapperBuilderOrdered</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>上例中，还实现了一个 <code>Ordered</code> 接口。</p>
<p>因为实际开发中可能会有很多个模块，可能每个模块都需要定义一个 <code>XXXErrorCodeMapperBuilderCustomizer</code> 类，在统一进行错误码计算时，是利用 List 遍历进行计算的，这样可能出现计算顺序不一致的情况，就会导致错误码每次计算都不一致。</p>
<p>实现了 <code>Ordered</code> 接口，是为了保证某个模块中的 <code>XXXErrorCodeMapperBuilderCustomizer</code> 类，在计算时始终按照指定的顺序进行计算，这样保证错误码不会计算出错。</p>
</div>
<h4>步骤二：定义配置器Bean</h4>
<p><code>XXXErrorCodeMapperBuilderCustomizer</code> 类定义完成之后，将其定义为 Bean 以保证其在启动时，可以正确注入。示例代码如下：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Configuration</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">proxyBeanMethods</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AssistantAccessConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Logger</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> log </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> LoggerFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getLogger</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">AssistantAccessConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">PostConstruct</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> init</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">debug</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Module [Assistant Access] Configure."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    .....</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> accessErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        AccessErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> customizer</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AccessErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">debug</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Strategy [Access ErrorCodeMapper Builder Customizer] Auto Configure."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> customizer;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    ......</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>上例中，</p>
<ul>
<li><code>XXXErrorCodeMapperBuilderCustomizer</code> 类 Bean 的类型用的是基础类型 <code>ErrorCodeMapperBuilderCustomizer</code> 而不是具体的类，即 <code>ErrorCodeMapperBuilderCustomizer</code> 子类型。</li>
<li>具体的方法名使用的是 <code>accessErrorCodeMapperBuilderCustomizer()</code> 。不同模块中，这个方法名不能相同，否则会出现 Bean 冲突导致无法启动。</li>
</ul>
</div>
<h3>[4]注意事项</h3>
<p>随着使用的第三方组件越来越多，常规的 Exception 定义也会越来越多。一个开发人员也不可能在开发过程中遇到所有的 Exception 情况的，是无法穷举所有 Exception 类，并且生成一个全面的 <code>EXCEPTION_DICTIONARY</code> Map。</p>
<p>目前采取的方式是：随用随加。</p>
<p>如果在使用 Dante Cloud 开发或使用过程中，出现了 <code>EXCEPTION_DICTIONARY</code> Map 中没有的 Exception。那么，可以自己手动将其补充进入到错误码体系中。同时，在 Dante Cloud 中为了方便发现这些未被记录的错误，会在系统日志中打印相关的 Exception 信息，方便发现和记录。如下所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Result</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> result </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> Result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">failure</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> exceptionName </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> ex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getClass</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getSimpleName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">if</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> (</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">StringUtils</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">isNotEmpty</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(exceptionName)</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> &#x26;&#x26;</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">containsKey</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(exceptionName)</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">) {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    Feedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> feedback </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">get</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(exceptionName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    result </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> Result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">failure</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(feedback, exceptionName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">} </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">else</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">warn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Can not find the exception name [{}] in dictionary, please do optimize "</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, exceptionName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>另外，第三方组件中定义的 Exception 虽然很多，但开发中因为会进行大量的保护性代码的编写，实际会遇到的或者说需要提醒的错误并不是特别多。把经常会出现的 Exception 记录下来，就足以满足大量使用场景的需要了。</p>
<h2>[二]特殊类型异常</h2>
<p>前面提到，为了解决常规类型异常的统一，Dante Cloud 定义了统一的 <code>GlobalExceptionHandler</code> 类来处理。这足以满足绝大多数使用需求。</p>
<p>除了 <code>GlobalExceptionHandler</code> 类以外，在 Dante Cloud 中还有定义了一个 <code>SecurityGlobalExceptionHandler</code> 类。<code>SecurityGlobalExceptionHandler</code> 类的使用方法以及用途与 <code>GlobalExceptionHandler</code> 完全相同。</p>
<h3>[1]额外处理的原因</h3>
<p>之所以额外定义了一个 <code>SecurityGlobalExceptionHandler</code> 类，是因为 <code>SecurityGlobalExceptionHandler</code> 类主要是用于 Spring Security 以及 Spring OAuth2 等特殊组件的 Exception 处理。处于以下几点考虑，才会采取这样的处理方式：</p>
<h4>方便模块的解耦，减少不必要的依赖</h4>
<p>Spring Security 是一个完整的生态，整体也比较重,<code>SecurityGlobalExceptionHandler</code> 就依赖于 Spring Security 的部分组件中的类。如果将 <code>SecurityGlobalExceptionHandler</code> 和 <code>GlobalExceptionHandler</code> 合并在一起，就意味着在不需要使用 Spring Security 的场景下，也需要依赖部分 Spring Security 的包。不仅没有什么意义，还增加了依赖间的耦合。</p>
<h4>Spring Security 中 Exception 类型不简单</h4>
<p>Spring Security 生态中，很多自定义 Exception 都是继承类型的。很多场景下，只会抛出统一的自定义父类型 Exception，具体的 Exception（自定义父类型 Exception 的子类）会被包裹在父 Exception 中，需要额外的解析才能拿到具体的错误。</p>
<p>另外，Spring Security 生态中，很多错误都有自成体系的错误码，例如：<code>access_denied</code>。这个与前面所说的常规类型异常不同。所以就需要不同的方式来定位和转换错误描述。</p>
<h3>[2]添加方式</h3>
<p>添加此类错误信息的方式和步骤，与前面添加常规类型异常的方式和步骤完全相同。</p>
<p>唯一不同的是，<code>EXCEPTION_DICTIONARY</code> Map 中 Key 的值不同，如下所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SecurityGlobalExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Logger</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> log </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> LoggerFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getLogger</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SecurityGlobalExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Map</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Feedback</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> EXCEPTION_DICTIONARY </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HashMap</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_DENIED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_DENIED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INSUFFICIENT_SCOPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INSUFFICIENT_SCOPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_CLIENT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_CLIENT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_GRANT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_GRANT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_REDIRECT_URI</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_REDIRECT_URI</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_REQUEST</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_REQUEST</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_SCOPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_SCOPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_TOKEN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_TOKEN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SERVER_ERROR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SERVER_ERROR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">TEMPORARILY_UNAVAILABLE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">TEMPORARILY_UNAVAILABLE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNAUTHORIZED_CLIENT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNAUTHORIZED_CLIENT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNSUPPORTED_GRANT_TYPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNSUPPORTED_GRANT_TYPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNSUPPORTED_RESPONSE_TYPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNSUPPORTED_RESPONSE_TYPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNSUPPORTED_TOKEN_TYPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNSUPPORTED_TOKEN_TYPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCOUNT_EXPIRED_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCOUNT_EXPIRED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">BAD_CREDENTIALS_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">BAD_CREDENTIALS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CREDENTIALS_EXPIRED_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CREDENTIALS_EXPIRED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">DISABLED_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCOUNT_DISABLED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">LOCKED_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCOUNT_LOCKED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCOUNT_ENDPOINT_LIMITED_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCOUNT_ENDPOINT_LIMITED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">USERNAME_NOT_FOUND_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">USERNAME_NOT_FOUND</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SESSION_EXPIRED_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SESSION_EXPIRED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OAuth2ErrorKeys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">NOT_AUTHENTICATED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">NOT_AUTHENTICATED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    ......</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[三]自定义类型异常</h2>
<p>自定义类型异常，是开发中最常见的异常类型。就是实际开发中，根据业务需要自定义的异常类型。</p>
<h3>[1]定义错误信息</h3>
<p>第一步还是定义错误信息，与前面“常规类型异常”中，定义错误信息的方式相同。</p>
<p>不同的是，自定义类型异常的错误信息，不需要像“常规类型异常”一样在 <code>core-definition</code> 模块中进行定义。在任意模块中，新建一个常量接口进行定义即可。例如：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    PreconditionFailedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCESS_CONFIG_ERROR </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> PreconditionFailedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Access 模块配置错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    PreconditionFailedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCESS_HANDLER_NOT_FOUND </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> PreconditionFailedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Access 模块接入处理器未找到错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    PreconditionFailedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCESS_PRE_PROCESS_FAILED_EXCEPTION </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> PreconditionFailedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"接入预操作失败错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    PreconditionFailedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> SMS_IDENTITY_VERIFICATION_FAILED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> PreconditionFailedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"手机短信验证码登录身份认证错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    PreconditionFailedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> WXAPP_IDENTITY_VERIFICATION_FAILED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> PreconditionFailedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"微信小程序登录身份认证错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    PreconditionFailedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> WXMPP_IDENTITY_VERIFICATION_FAILED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> PreconditionFailedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"微信公众号登录身份认证错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    PreconditionFailedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> JUATAUTH_IDENTITY_VERIFICATION_FAILED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> PreconditionFailedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"JustAuth 第三方登录身份认证错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    PreconditionFailedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ILLEGAL_ACCESS_ARGUMENT </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> PreconditionFailedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"社交登录参数错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    PreconditionFailedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ILLEGAL_ACCESS_SOURCE </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> PreconditionFailedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"社交登录Source参数错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>早期 Dante Cloud 版本的错误码，不管实际 Exception 类在哪个模块中，都需要在统一的一个类中进行错误描述信息的定义。这样做不仅导致错误描述信息类越来越臃肿，而且每次添加错误信息都需要修改核心组件库，然后重新编译才能生效。</p>
<p>现在这种方式，允许在任意模块中随意定义错误描述信息，不用每次都修改核心组件库，在具体使用时仍旧会汇总在一起使用，这就给开发打来了极大地灵活性。</p>
</div>
<h3>[2]编写自定义异常</h3>
<p>添加自定义异常类型，第一步就是编写一个 Exception。这个 Exception 需要继承 Dante Cloud 中统一的 <code>PlatformRuntimeException</code>，如下所示:</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AccessConfigErrorException</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> PlatformRuntimeException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AccessConfigErrorException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AccessConfigErrorException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(message);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AccessConfigErrorException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Throwable</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> cause</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(message, cause);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AccessConfigErrorException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Throwable</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> cause</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(cause);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    protected</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AccessConfigErrorException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Throwable</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> cause</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">boolean</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> enableSuppression</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">boolean</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> writableStackTrace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(message, cause, enableSuppression, writableStackTrace);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Feedback</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> AccessErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_CONFIG_ERROR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>在自定义异常中，将 Exception 与错误描述信息进行关联，就不需要像前面 <code>常规类型异常</code> 和 <code>特殊类型异常</code> 一样，要手动定义一个 <code>EXCEPTION_DICTIONARY</code> Map</p>
</div>
<h3>[3]定义配置器Bean</h3>
<p>最后一步，就像前面<code>常规类型异常</code> 和 <code>特殊类型异常</code> 一样，将自定义的错误描述信息，添加至指定的错误配置器就行。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>不用每一个模块都定义 <code>XXXErrorCodeMapperBuilderCustomizer</code> 类。也可以几个模块共用一个 <code>XXXErrorCodeMapperBuilderCustomizer</code> 类。这完全看你自己代码的设计以及模块划分以及解耦的需要。</p>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/install/access-denied.png" type="image/png"/>
    </item>
    <item>
      <title>资源服务</title>
      <link>https://www.herodotus.cn/properties/herodotus/authorization.html</link>
      <guid>https://www.herodotus.cn/properties/herodotus/authorization.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">资源服务</source>
      <description>[一]OAuth2 资源服务器配置 前缀：herodotus.oauth2.authorization token-format Type: Enum Values: OPAQUE 和 JWT Default: OPAQUE Token 校验是采用远程方式还是本地方式。即 Token 类型是 Opaque Token 还是 JWT Token。 str...</description>
      <category>配置属性</category>
      <pubDate>Fri, 27 Dec 2024 05:14:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]OAuth2 资源服务器配置</h2>
<blockquote>
<p>前缀：<code>herodotus.oauth2.authorization</code></p>
</blockquote>
<h3>token-format</h3>
<ul>
<li>Type: <code>Enum</code></li>
<li>Values: <code>OPAQUE</code> 和 <code>JWT</code></li>
<li>Default: <code>OPAQUE</code></li>
</ul>
<p>Token 校验是采用远程方式还是本地方式。即 Token 类型是 Opaque Token 还是 JWT Token。</p>
<h3>strict</h3>
<ul>
<li>Type: <code>Boolean</code></li>
<li>Default: <code>true</code></li>
</ul>
<p>否使用严格模式。严格模式一定要求接口一定要有对应的权限，没有权限则会被拦截。非严格模式不要求接口一定要有权限，只要查询到当前请求上下文中包含已经通过的认证信息（即当前用户已经登录），那么就不会被拦截也可以访问。</p>
<h2>[二]OAuth2 Jwk 配置</h2>
<blockquote>
<p>前缀：<code>herodotus.oauth2.authorization.jwk</code></p>
</blockquote>
<h3>certificate</h3>
<ul>
<li>Type: <code>Enum</code></li>
<li>Values: <code>STANDARD</code> 和 <code>CUSTOM</code></li>
<li>Default: <code>CUSTOM</code></li>
</ul>
<p>证书策略：Standard OAuth2 标准证书模式；custom 自定义证书模式。</p>
<ul>
<li><code>STANDARD</code> 模式，并不使用真正的证书，由系统内代码动态生成 Jwk 信息。</li>
<li><code>CUSTOM</code> 模式，需要在 UAA 中配置放置自定义的 .jks 文件。</li>
</ul>
<h3>jks-key-store</h3>
<ul>
<li>Type: <code>String</code></li>
<li>Default: <code>classpath\*:certificate/herodotus-cloud.jks</code></li>
</ul>
<p>jks证书文件路径</p>
<h3>jks-key-password</h3>
<ul>
<li>Type: <code>String</code></li>
<li>Default: <code>Herodotus-Cloud</code></li>
</ul>
<p>jks证书密码</p>
<h3>jks-store-password</h3>
<ul>
<li>Type: <code>String</code></li>
<li>Default: <code>Herodotus-Cloud</code></li>
</ul>
<p>jks证书密钥库密码</p>
<h3>jks-key-alias</h3>
<ul>
<li>Type: <code>String</code></li>
<li>Default: <code>Herodotus-Cloud</code></li>
</ul>
<p>jks证书别名</p>
<h2>[三]静态权限配置</h2>
<blockquote>
<p>前缀：<code>herodotus.oauth2.authorization.matcher</code></p>
</blockquote>
<h3>static-resources</h3>
<ul>
<li>Type: <code>List</code></li>
<li>Default: <code>空</code></li>
</ul>
<p>静态资源过滤。配置不需要鉴权的资源，例如：css、image、js 资源等。</p>
<h3>permit-all</h3>
<ul>
<li>Type: <code>List</code></li>
<li>Default: <code>空</code></li>
</ul>
<p>配置不需要鉴权的接口。</p>
<h3>has-authenticated</h3>
<ul>
<li>Type: <code>List</code></li>
<li>Default: <code>空</code></li>
</ul>
<p>只校验是否请求中包含Token，不校验Token中是否包含该权限的资源</p>
]]></content:encoded>
    </item>
    <item>
      <title>学习方法和路径建议</title>
      <link>https://www.herodotus.cn/develop-guide/advance/suggestion.html</link>
      <guid>https://www.herodotus.cn/develop-guide/advance/suggestion.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">学习方法和路径建议</source>
      <description>摘要 为什么说前面的内容是一种“耐心”的测试？ 因为如果你能坚持看到这里，说明你是有足够耐心的，至少是想真正想学习东西的人，不会放过任何有可能学习到东西机会，而不是一时兴起或者说来凑个热闹刷个热点。这样不管是作为作者编写技术文章，还是作为读者学习技术内容，才会变得有意义。 可能有些朋友会觉得作者有点矫情，那么不妨先来看几个示例： 示例一 想必熟悉 Da...</description>
      <pubDate>Thu, 26 Dec 2024 15:59:19 GMT</pubDate>
      <content:encoded><![CDATA[<h2>摘要</h2>
<p>为什么说前面的内容是一种“耐心”的测试？</p>
<p><strong>因为如果你能坚持看到这里，说明你是有足够耐心的，至少是想真正想学习东西的人，不会放过任何有可能学习到东西机会，而不是一时兴起或者说来凑个热闹刷个热点。这样不管是作为作者编写技术文章，还是作为读者学习技术内容，才会变得有意义。</strong></p>
<p>可能有些朋友会觉得作者有点矫情，那么不妨先来看几个示例：</p>
<h3>示例一</h3>
<p>想必熟悉 <strong>Dante Cloud</strong> 的朋友都会知道，在2023年4月，一网友在 <strong>OSCHINA</strong> 上不停抹黑 <strong>Dante Cloud</strong>，只要是Dante Cloud 发布的文章，就会在评论中说 Dante Cloud 没有全部开源<strong>。</strong>找他沟通几次，问他为什么这么说，其理由是找不到 <code>cn.herodotus.engine</code>相关的代码，所以坚持认为 <strong>Dante Cloud</strong> 没有完全开源只是一个壳。</p>
<p>那么我们现在假设假设，暂且排除其故意抹黑的因素，那么这里就有一个问题，是真的没有任何线索、途径可以找到 <code>cn.herodotus.engine</code> 相关的代码？各位不妨回想一下，自己是如何知道 <strong>Dante Cloud</strong>？又是如何知道 <strong>Dante Engine</strong>的？</p>
<h3>示例二</h3>
<p>经常关注 <strong>Dante Cloud</strong> 技术交流群信息的朋友，可能就会发现，现在交流群中的问题，很重复、很基础甚至很无聊。基本上都是：“系统登录的用户名密码是什么？”，“为什么提示客户端验证错误？”，“哪位大神给一份 mysql 的数据脚本”，之类的在文档中均已写明了的内容。</p>
<h3>现实问题</h3>
<p>举这两个比较典型的示例，目的不是为了指责谁、污蔑谁，而是希望从中发现、总结问题。而这个问题的本质在我看来就是 —— “<strong>没耐心</strong>”</p>
<p>这不是为了抬高我自己，而是这就是我们这个行业的本质：“<strong>发现问题、解决问题</strong>”，引用 <strong>Dromara</strong> 社区 <strong>Cloud Eon</strong> 作者的话“<strong>最好的程序一开始只是作者对自己每天遭遇问题的个人解决方案，程序流传开来则是因为作者遇到问题成了一大类用户的典型问题</strong>”。而这些事情全部是以“耐心”为基础的。</p>
<p>当然，这个问题其实是可以完全理解的，现在社会一切事物节奏非常快，一切都以“快”为准绳。快餐、快递、被拆分成碎片化的信息等等。微博只可以写 140 个字，为了刷更多内容，可能随便扫两眼也许连 140 个字都没有看全，抓住几个关键字就对某个事件下定论；短视频，一分钟可以刷多少个，也不会去关心前因后果、深挖原因；影视剧多倍速观看，由于短视频的出现更方便，5分钟看一部电影。这就导致很多人希望个人的成长，特别是技术方面的成长也要快，最好是买几个视频、买几套文档看看，然后就可以达到 PX 的级别，独当一面，赚大钱享有无限荣光。</p>
<h3>真的可以这样吗？</h3>
<ul>
<li><strong>没有耐心，很多东西即使摆在眼前，可能都看不到</strong> （上面的示例一就非常典型）。这还只是技术相关知识点而已，如果换成是可以改变人生的机会呢。</li>
<li><strong>没有耐心，即使有人认认真真、详详细细的把你要了解学习的东西告诉你了，可能也只会明白其中极小的一部分</strong> ，甚至听没听进去都得另说。</li>
<li><strong>没有耐心，别说看书了，可能连完整的看完一篇文档都做不到，那还谈什么提升？</strong> 前面示例二，就是这一类问题的典型。自以为自己一目十行的看过就明白，殊不知关键的信息和操作都给漏掉了。</li>
<li><strong>耐心对于学习技术来是基本素质，甚至这都不是有钱就能解决的问题，而是需要自己真真切切的投入的</strong> 。（别在这儿较真啊，说老子有钱随便找人干。这里说的问题是提升自身的技术水平。况且你真那么有钱，TM 来这看什么文档啊？）</li>
<li><strong>凭什么别人要深挖细作、苦苦钻研几年甚至十几年才有现在的水平和威望，你看几个视频、买几个文档就可以做到和别人一样？</strong> 就好比，别人都要寒窗苦读十年才考上的大学，你找个老师讲讲，培训个两三个月就能考上大学？当然不说这种人没有，有这样的天才少年，但这是极其少数的。换个角度，如果真的是天才，也没有必要来看这些文档了。是不是？</li>
</ul>
<p>单独开辟 <strong>关于“耐心”</strong> 这一章节，<strong>核心目的并不是为了说教</strong>。而是希望以这种方式，与广大读者做一个交流。因为个人觉得多少能够认可上面观点的朋友，才是真正想要学习技术提升自己的。只有存在理念相同的朋友，作者也才有坚持写下去的必要。毕竟，“<strong>人对了，任何事情至少成功一半</strong>”，“<strong>志同才能道合，道不同不相为谋</strong>”</p>
<blockquote>
<p>另外，逆向思维一下，如果周围大多数人都没有耐心，学习技术都不踏实。而你则是耐耐心心、踏踏实实地成长，这会不会是另外一种“捷径”？</p>
</blockquote>
<p>······</p>
<h2>阅读</h2>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p><a href="https://my.oschina.net/pointerv/blog/18630283" target="_blank" rel="noopener noreferrer">【在线阅读】</a></p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>权限体系设计</title>
      <link>https://www.herodotus.cn/develop-guide/design/permission.html</link>
      <guid>https://www.herodotus.cn/develop-guide/design/permission.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">权限体系设计</source>
      <description>概述 OAuth2 协议设计的初衷，并不是为了解决“用户”系统授权问题的，主要是为了解决“应用”（系统和系统之间，组件和系统之间）的授权问题。具体的可以阅读高级文章：。 为了让 OAuth2 协议的适用性更强，可以支持更多应用场景，特别是目前流行的前后端分离的、微服务系统，将 OAuth2 和其它权限模型进行整合，例如：RBAC。所以，从某些角度讲基于...</description>
      <pubDate>Thu, 26 Dec 2024 13:31:27 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>OAuth2 协议设计的初衷，并不是为了解决“用户”系统授权问题的，主要是为了解决“应用”（系统和系统之间，组件和系统之间）的授权问题。具体的可以阅读高级文章：<a href="/develop-guide/advance/scope.html" target="_blank">【OAuth 2 中的 Scope 与 Role 深度解析】</a>。</p>
<p>为了让 OAuth2 协议的适用性更强，可以支持更多应用场景，特别是目前流行的前后端分离的、微服务系统，将 OAuth2 和其它权限模型进行整合，例如：RBAC。所以，从某些角度讲基于“用户”的授权是开发者们“强加”在 OAuth2 中的。当然，OAuth2 协议也认识到了这个不足，所以才会有 OIDC 协议的产生。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>OIDC (OpenID Connect) 协议，就是在 OAuth2 协议之上的扩展协议，OAuth2 协议之上增加了一层“用户”信息的支持。</p>
</div>
<p>即使有了 OIDC 的支持，甚至将 OAuth2 协议与其他权限模型进行了融合，由于 OAuth2 协议自身的局限，能否真正的理解以及灵活运用也是一项需要长时间投入的事情。</p>
<h2>[一]融合 OAuth2 和 RBAC 模型的权限体系</h2>
<p>Dante Cloud 在权限模型的设计中也选择了大家较为熟知和易用的 RBAC 模型。同时为了解决前面提到的问题，在融合 OAuth2 和 RBAC 方面也做了加大的努力。下图是 Dante Cloud 权限体系的 ER 图：</p>
<figure><img src="/assets/image/design/full.png" alt="完整的权限ER图" tabindex="0" loading="lazy"><figcaption>完整的权限ER图</figcaption></figure>
<h3>[1]权限体系设计</h3>
<p>用户权限采用 RBAC 模型，所以主体设计与标准的 RBAC 基本一致。该部分的 ER 图如下所示：</p>
<figure><img src="/assets/image/design/rbac.png" alt="用户权限ER图" tabindex="0" loading="lazy"><figcaption>用户权限ER图</figcaption></figure>
<p>标准的 RBAC 模型，正常情况包含：<code>用户</code>、<code>角色</code>和 <code>权限</code> 三个组成要素。因为微服务系统主要是面向 REST 接口，所以在此基础之上使用 RBAC 模型，那么对应的三要素就为<code>用户</code>、<code>角色</code>和 <code>接口</code>。这里的 <code>接口</code> 就是 RBAC 中的 <code>权限</code>。</p>
<p>之所以说 Dante Cloud 与标准的 RBAC 基本一致，意味着还是有一定的改造。在 <code>用户</code>、<code>角色</code> 和 <code>权限</code>三要素的基础之上又增加了一层 <code>属性</code>，其中：</p>
<ul>
<li><code>用户</code>：对应数据表 <code>sys_user</code></li>
<li><code>角色</code>：对应数据表 <code>sys_role</code></li>
<li><code>权限</code>：对应数据表 <code>sys_permission</code></li>
<li><code>属性</code>：对应数据表 <code>sys_attribute</code></li>
</ul>
<p>其中，<code>权限</code> 即真正使用的 RBAC 权限。这个权限就是最终放入到 Token 中，用于接口鉴权的权限。</p>
<h3>[2]为什么要这么设计？</h3>
<p>在早期的 Dante Cloud 中就是使用 REST 接口作为 RBAC 中的权限，即：为<code>用户</code>、<code>角色</code>和 <code>接口</code>。但是通过长时间的实践，发现这种方式并不适合，主要原因是：</p>
<ul>
<li><strong>JWT Token 过长</strong>：直接使用 REST 接口作为权限，权限数据是需要放入到 Token 中，因为字符过多会导致生成 JWT Token 过长。权限数据越多，Token 字符串越长。</li>
<li><strong>权限配置繁琐</strong>：随着业务系统的功能越多，接口也会越多。接口多了非常不利于权限的管理和控制。</li>
<li><strong>功能开发混乱</strong>：微服务是以<code>REST 接口</code>为基础，但是前端却是以<code>功能</code>为基础。前端一个按钮操作可能就涉及多个接口。</li>
</ul>
<p>所以，增加了一层权限设计，这样做可以带来以下好处：</p>
<ul>
<li>配置权限更加简洁：只要配置指定权限即可，无需配置过多的接口。</li>
<li>实现接口权限的聚合：多个接口对应一个权限。一个权限可以对应一个功能或者其它细粒度的权限设计</li>
<li>方便后续扩展：REST 接口只是权限的一种，可能在你的系统中还会涉及其它权限元素。那么这些内容可以统一扩展至权限数据上。</li>
</ul>
<h3>[3]特殊处理</h3>
<p>除了 <code>sys_user</code>、<code>sys_role</code>、<code>sys_permission</code> 和 <code>sys_attribute</code> 四个 RBAC 核心表之外，在数据表中还有一个特殊的数据表 <code>sys_interface</code>。<code>sys_interface</code> 表中的数据与 <code>sys_attribute</code> 基本一致，只是多了特殊的配置而已。</p>
<p>主要的逻辑为：</p>
<ul>
<li>各个服务中的 REST 接口，在服务启动时会汇总至 <code>sys_interface</code> 表中</li>
<li>全部服务启动完成之后，会将有变化的数据同步至 <code>sys_attribute</code> 表中</li>
</ul>
<p>之所以这样设计，是为了平衡 JPA 自身的机制局限与性能损耗。</p>
<blockquote>
<p>JPA 的保存机制是：如果保存实体没有 ID 则进行 <code>insert</code> 做操作，这是会自动分配 ID；如果保存实体有 ID 则进行 <code>update</code> 操作，这里的更新是会更新全部字段，你可以把这个过程的结果效果“想象”成是先 <code>delete</code> 后 <code>insert</code>。注意：并不是真的做了这样的操作。当然，JPA 提供了指定更新字段的机制，但是这样会进一步损耗性能。</p>
</blockquote>
<p>了解了这个机制我们看现在的设计。</p>
<p>现在的设计是以服务、接口映射等信息组成ID，在服务启动时对当前服务所有的接口进行扫描和拼装，然后汇总到 <code>sys_interface</code>。初次部署时这些数据是以 <code>insert</code> 方式保存至数据库。服务再次启动时，就会进行 <code>update</code> 操作。按照前面的逻辑，在 <code>update</code> 时所有的数据都会被更新。在这种情况下，如果在 <code>sys_interface</code> 表中增加了额外的字段，不管设置了什么值，<code>update</code> 时就会被更改为初始值。</p>
<p>为了解决这个问题，增加了 <code>sys_attribute</code> 表的设计。<code>sys_attribute</code> 表中的字段与 <code>sys_interface</code> 表中的字段大体一致，业务需求的额外数据均在 <code>sys_attribute</code> 表中涉及。每次服务启动完成，会对比 <code>sys_interface</code> 表中比 <code>sys_attribute</code> 表中多出的新增数据，然后将新增数据同步至 <code>sys_attribute</code> 表中，这样就仅做 <code>insert</code> 操作，不会影响 <code>sys_attribute</code> 表中额外的业务字段。</p>
<h3>[4]界面操作</h3>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>增删改查常规操作就不一一展示了，只展示相关部分内容</p>
</div>
<figure><img src="/assets/image/design/0001.png" alt="权限元数据列表" tabindex="0" loading="lazy"><figcaption>权限元数据列表</figcaption></figure>
<figure><img src="/assets/image/design/0002.png" alt="配置权限" tabindex="0" loading="lazy"><figcaption>配置权限</figcaption></figure>
<h2>[二]前端界面权限</h2>
<p>前端界面控制的权限，并没有像传统单体系统一样，将其直接与 RBAC 融合。而是利用 RBAC 中的角色，单独额外提取一套类似于 RBAC 的结构进行处理，该部分的 ER 图如下所示：</p>
<figure><img src="/assets/image/design/menu-permission.png" alt="界面权限" tabindex="0" loading="lazy"><figcaption>界面权限</figcaption></figure>
<p>这样设计就意味着系统存在两套<code>权限</code>。一套负责后端接口权限，一套负责前端页面权限。设计思路，参见下面：<a href="#%E4%B8%83-%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%B7%AF%E6%80%BB%E7%BB%93">【[七]设计思路总结】</a></p>
<figure><img src="/assets/image/design/0003.png" alt="菜单列表" tabindex="0" loading="lazy"><figcaption>菜单列表</figcaption></figure>
<figure><img src="/assets/image/design/0004.png" alt="配置菜单" tabindex="0" loading="lazy"><figcaption>配置菜单</figcaption></figure>
<h2>[三]独有设计的动态接口鉴权</h2>
<p>基于 <code>Spring Security</code> 的微服务架构，具体接口的鉴权是在接口所在的服务自身完成。</p>
<p>大多数使用 <code>Spring Security</code> 组件的系统，都会采用注解 <code>@PreAuthorize</code> 的方式，将权限数据写死。如果要修改权限或者存在权限变更，就需要通过修改代码来完成。Dante Cloud 设计了独有的动态权限体系，通过界面操作即可动态修改权限。无需修改代码或者重启服务。具体操作演示如下所示：</p>
<figure><img src="/assets/image/advance/oauth2expression.gif" alt="OAuth2动态接口鉴权" tabindex="0" loading="lazy"><figcaption>OAuth2动态接口鉴权</figcaption></figure>
<div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>即使实现了接口的动态鉴权，权限变更之后仍旧需要退出系统重新登录。因为，权限数据均存储在 Token 之中。设计思路，参见下面：<a href="#%E4%B8%83-%E8%AE%BE%E8%AE%A1%E6%80%9D%E8%B7%AF%E6%80%BB%E7%BB%93">【[七]设计思路总结】</a></p>
</div>
<h2>[四]静态配置接口鉴权</h2>
<p>有些情况下对于一些不会经常变化的接口或者需要临时进行验证的一些接口，启动服务配置动态鉴权可能略显繁琐。系统也提供了另外的一种鉴权管理方式：静态配置接口鉴权。方便开发调试或者统一管控需求。</p>
<p>Dante Cloud 在几个方面提供静态配置接口鉴权支持：1、网关统一管控；2、服务静态鉴权；3、统一开放配置</p>
<h3>[1]网关统一管控</h3>
<p>网关，是微服务系统的安全门户，是系统安全的重要保障。对于网关服务后端的所有服务，提供“宏观性”的管理保障。对于整个系统管控以外的接口，通过网关服务就会拦截，不会进入到后续服务中，以保障整体的安全性。</p>
<p>在 Dante Cloud 中，网关服务仅做简单的鉴权处理：</p>
<ol>
<li>包含 Token 的接口访问，允许进入到后端服务进行二次鉴权。</li>
<li>网关服务配置的静态权限，即使没有 Token 也允许进入到后端。（例如：系统中会存在一些特殊的开放性接口，这些接口一般不需要用户登录即可使用，这种情况下网关就需要放行）</li>
<li>对于既不包含 Token 也不在静态鉴权配置中的接口，网关服务即直接拦截返回。这样也避免了后端无畏的性能损耗。</li>
</ol>
<p>在 Gateway 的配置文件中即可以直接修改网关的静态权限配置：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">herodotus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  gateway</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    white-list</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"/oauth2/token"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"/oauth2/authorize"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"/v3/api-docs/**"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"/openapi*"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"/open/**"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>也许会有朋友想到：能否将网关的鉴权机制改成动态方式，可以通过界面统一管理？</p>
<p>在 Dante Cloud 的设计理念中，网关一定要保持简洁和高安全性，在网关服务增加的 Gateway 自身支持以外的功能越多，安全性就越低，整体架构也越不符合规范。在有些系统的设计中，主打所谓的“网关统一动态鉴权”，个人认为是一种“四不像”的设计，具体解释可以阅读高级文章：<a href="/develop-guide/advance/authentication.html" target="_blank">【《OAuth 2 中的鉴权和动态接口鉴权》】</a></p>
</div>
<h3>[2]服务静态鉴权</h3>
<p>服务静态鉴权是一种便捷的配置权限的方式，大多情况下是为了开发和调试方便。例如：正在开发一个接口，又不想进行繁琐的权限配置，那么就可以将其配置成静态权限。当然，这里所说的方便开发和调试，仅仅是一种建议，你完全可以结合自己的使用应用，将静态权限应用于更多场景。</p>
<p>服务静态权限也是通过服务的配置文件进行配置，示例配置如下：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">herodotus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  oauth2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    authorization</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      matcher</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        permit-all</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/security/social/binding/list</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>更多的配置，可以参见：<a href="/properties/herodotus/authorization.html" target="_blank">【资源服务属性配置】</a></p>
<h3>[3]Ant 风格权限支持</h3>
<p>在 Dante Cloud 中，所有静态权限均支持 Ant 风格路径模式。</p>
<p><code>Ant</code> 风格就是一种路径匹配表达式。主要用来对 <code>uri</code> 的匹配。其实跟正则表达式作用是一样的，只不过正则表达式适用面更加宽泛，<code>Ant</code> 仅仅用于路径匹配。</p>
<h4>Ant 风格的路径模式的基本通配符</h4>
<ul>
<li><code>*</code>：匹配零个或多个字符。</li>
<li><code>**</code>：匹配路径中的零个或多个目录，可以跨多级目录。</li>
</ul>
<h4>通配符的用法及示例</h4>
<ul>
<li><code>/user/*</code>：匹配 <code>/user/name</code>、<code>/user/game</code>、<code>/user/123</code> 等路径。</li>
<li><code>/user/**</code>：匹配 <code>/user/name</code>、<code>/user/game</code>、<code>/user/123</code>、<code>/user/files/filename</code> 等路径。</li>
</ul>
<h4>Ant 风格路径模式权限配置</h4>
<p>Dante Cloud 中使用 <code>Ant</code> 风格路径模式配置权限示例如下：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">herodotus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  oauth2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    authorization</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      matcher</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        permit-all</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/security/**</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[五]动态权限和静态权限去重混合鉴权</h2>
<p>在 Dante Cloud 中，动态权限和静态权限并不是各自独立的，而是互相融合的。</p>
<h3>[1]权限去重</h3>
<p>如果服务中配置了静态权限，并且该静态权限是 <code>Ant</code> 路径风格的权限。或者动态权限中，REST 接口也是 <code>Ant</code> 路径风格。那么服务在做接口到权限的转换过程中会进行权限重叠分析，将可覆盖的权限进行剔除，而选用覆盖范围最高的权限作为鉴权元数据。</p>
<p>具体逻辑可以参考以下示例：</p>
<p>在服务A中，存在两个接口：一个接口 <code>a</code> 为 <code>GET</code> - <code>/security/permission/{id}</code>, 另一个接口 <code>b</code> 为 <code>GET</code> - <code>/security/permission/list</code>。</p>
<p>因为在 <code>Ant</code> 风格下，<code>a</code> 可以涵盖 <code>b</code>，所以经过系统分析之后，会以 <code>a</code> 作为鉴权元数据同时忽略 <code>b</code>。</p>
<h3>[2]权限优先</h3>
<p>Dante Cloud 鉴权列表分析的主要逻辑为：</p>
<ul>
<li>首先将动态权限和静态权限中，<code>Ant</code> 路径风格权限进行汇总</li>
<li>然后分析所有的非 <code>Ant</code> 路径风格权限。如果某个权限可以与 <code>Ant</code> 路径风格权限匹配，那么会将该权限从权限列表中剔除，不再参加鉴权校验。</li>
</ul>
<p>所以，根据该逻辑设计 <code>Ant路径风格权限的优先级 &gt; 非Ant路径风格权限的优先级</code>。</p>
<h2>[六]OAuth2 Scope 接口鉴权</h2>
<p>在使用 OAuth2 时，经常让人比较疑惑的知识点就是 <code>Scope</code>。OAuth2 默认的权限就是 <code>Scope</code>，而不是我们熟悉的接口（REST API）。</p>
<p>在使用 OAuth2 时，我们可以通过设计实现，修改 OAuth2 所使用的权限体系，比如和 RBAC 权限体系结合，让其不再局限于使用 <code>Scope</code>。但是由于 RBAC 权限体系是面对“用户”的，所以与 OAuth2 结合以后，只能对授权码模式（<code>Authorization Code Grant</code>）、密码模式（<code>Resource Owner Password Credentials Grant</code>）以及其它自定义的、涉及需要“用户”参与的授权模式起效。</p>
<blockquote>
<p>想要深入了解，可以阅读<a href="/develop-guide/advance/scope.html" target="_blank">【OAuth2中Scope与Role】</a>。</p>
</blockquote>
<p>对于像客户端凭证模式（<code>Client Credentials Grant</code>）这种，完全没有“用户”参与的授权模式，权限体系还必须使用 <code>Scope</code>。这就导致：</p>
<ol>
<li>如果我们开发涉及 REST 接口的、使用客户端凭证模式的应用，使用 <code>Scope</code> 是无法保护接口安全的。</li>
<li>在 <code>Spring Authorization Server</code> 的标准实现中，<code>Scope</code> 权限的验证也仅仅是字符串的比较，达不到安全需求。</li>
</ol>
<p>因此，Dante Cloud 专门对 <code>Spring Authorization Server</code> 进行了扩展，支持将 <code>Scope</code> 权限与接口进行绑定。配置好 <code>Scope</code> 权限之后，就可以直接对请求的接口进行权限校验。相关 ER 图如下图所示：</p>
<figure><img src="/assets/image/design/oauth2-scope.png" alt="Scope权限" tabindex="0" loading="lazy"><figcaption>Scope权限</figcaption></figure>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>这是 Dante Cloud 中比较具有特色的功能，目前还少有开源微服务系统支持。</p>
</div>
<p>Dante Cloud 对客户端凭证模式进行了扩展，实现了通过分配 <code>Scope</code> 权限就可以对 REST 接口进行访问控制的能力。验证的方式如下：</p>
<ol>
<li>以接口 <code>/security/user</code> 为例，先请求一下这个接口，可以看到系统返回没有访问权限的提示，如下图所示</li>
</ol>
<figure><img src="/assets/image/infrastructure/client-credentials-02.png" alt="客户端凭证模式使用" tabindex="0" loading="lazy"><figcaption>客户端凭证模式使用</figcaption></figure>
<ol start="2">
<li>我们为这个接口配置一个客户端凭证授权模式，同时指定其授权范围 <code>Scope</code> 为 <code>read-user-by-page</code></li>
</ol>
<figure><img src="/assets/image/infrastructure/client-credentials-03.png" alt="客户端凭证模式使用" tabindex="0" loading="lazy"><figcaption>客户端凭证模式使用</figcaption></figure>
<ol start="3">
<li>再次请求接口 <code>/security/user</code>，系统返回了接口响应数据</li>
</ol>
<figure><img src="/assets/image/infrastructure/client-credentials-04.png" alt="客户端凭证模式使用" tabindex="0" loading="lazy"><figcaption>客户端凭证模式使用</figcaption></figure>
<h2>[七]设计思路总结</h2>
<p>熟悉传统单体系统设计和开发的朋友，看到前面对本系统权限体系设计，可能会感觉很疑惑，甚至觉得与以往的认知格格不入。</p>
<blockquote>
<p>还是本人常说的，单体架构和微服务架构不是简简单单的将一个系统拆分为多个服务，看似两者差不多实则在开发方法和设计思路方面有较大差别。使用微服务之前还是建议学习相关的知识，先改变以往开发的认知和习惯。否则只是简单的将单体应用中内容往微服务中生搬硬套，只会适得其反。</p>
</blockquote>
<p>列举一下单体架构和微服务架构的主要差别，方便大家更好的理解以上的内容。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>本文所说的单体架构特指的是传统的单体应用，即传统的使用 JQuery、Bootstrap作为前端核心组件，前端页面和后端打包在一起的应用。</p>
<p>本文所说的微服务架构，除了标注意义上的微服务系统以外，还包含前后端分离的单体应用。即：前端使用例如 Vue 之类的组件，前端是单独工程需要单独部署的架构。这两中架构复杂度不同，但是设计和开放逻辑相同。</p>
</div>
<h3>[1]核心元素和存放位置不同</h3>
<h4>单体架构的核心元素</h4>
<p>单体架构的核心元素是 <code>Session</code>。<code>Session</code> 由后端服务生成，并且存放于服务端内存中，可以直接通过代码读取和设置。</p>
<p><code>Session</code> 本质是为了解决 Web 应用的无状态性，所以传统单体项目用了 <code>Session</code> 可以说就是“有状态”的。所以传统单体系统所有的权限或者用户标识等信息均存放于后端 Session，变更权限、读取权限也相对方便，也可以“实时”改变 Session 中数据的状态。</p>
<h4>微服务架构架构的核心元素</h4>
<p>微服务架构（前后端分离）的核心元素是 <code>Token</code>。<code>Token</code> 由后端签发之后，是存放于各种类型的“前端”中。<code>Token</code> 是无状态的，所以用户相关的权限是要存储于 <code>Token</code> 中。</p>
<p>虽然，最终的鉴权还是在服务端，但是无法像 <code>Session</code> 一样“实时”修改 <code>Token</code> 中的内容。即使修改了后端存储的 Token，已经签发并且存放于前端的 Token 是无法修改的，只能通过重新生成 Token 的方式，比如说重新登录才能更新 Token 中的内容。</p>
<h3>[2]权限面向的元素不同</h3>
<h4>单体架构的权限元素</h4>
<p>传统单体架构核心的权限元素，通常是页面地址（如果使用的是 Spring 组件，那么这里所说的页面就是 <code>@Controller</code> 对应的代码）。比如说功能菜单对应的都是各个页面，而权限体系也是根据这些内容进行构建。当然，也可能会以“按钮”之类作为权限元素，但是终归是页面上的元素。</p>
<p>不管是页面上的元素作为权限还是实际的页面作为权限，都可以存储在后端的 <code>Session</code> 中随取随用。而且修改了权限数据后，页面也可以同步变化显示内容。前端是不容一获取到 Session 中存储的信息的。</p>
<h4>微服务架构的权限元素</h4>
<p>微服务架构核心的权限元素，对于后端来说就是 REST 接口（如果使用的是 Spring 组件，那么这里所说的页面就是 <code>@RestController</code> 对应的代码）。对于前端来说又是另外的权限元素。</p>
<p>虽然，可以将前端的权限元素存储在也存储在 Token 中，就像单体架构存储在 Session 一样。但始终会存在问题：</p>
<ol>
<li>前后端分离的应用，前端一个功能或者一个操作，可能对应后端多个接口。如果将两者融合在一起，权限逻辑会非常混乱</li>
<li>如果将前端权限数据也存储在 Token 中，那么会导致 Token 越来越长，增大传输压力。而且要是自包含的 JWT Token，权限数据会和容易暴露。</li>
</ol>
<h3>[3]面对的前端种类不同</h3>
<h4>单体架构的前端种类</h4>
<p>传统单体架构的前端，不管使用的是 JQuery 等组件还是原始的 JSP 等技术，最终面对的还是在同一个浏览器中运行的 HTML。</p>
<blockquote>
<p>如果你曾经做过让单体应用支持移动端，不仅需要再重新创建一套 Token 体系，而且会发现用法与原来的权限体系格格不入。</p>
</blockquote>
<h4>微服务架构的前端种类</h4>
<p>微服务架构所面对的前端，已经不仅仅只包含传统的 Web，还会包括原生移动端、小程序甚至桌面程序，前端的元素会是多种多样的。如果这么多前端的权限控制，都与后端接口融合在一起，那么将会是多么混乱的意见事情。</p>
<h3>[4]应对变更的态度不同</h3>
<h4>单体架构应对变更</h4>
<p>单体架构如果需求变化或者代码修改后，需要整体代码进行发布。一些企业发布管理流程和周期可能会非常长，也因此导致单体架构的设计偏重灵活性设计，就是在尽量不停机不发布的情况下，通过后端配置就可以产生更多变化以适应更多客户的需求。比如说：常说的数据权限就是由此而来。</p>
<h4>微服务架构应对变更</h4>
<p>微服务应对需求的变更的逻辑并不是“以不变应万变”，而是配合容器、DevOps、云技术可以实现快速的、大规模的发布和扩展。所以遇到变更，会通过修改代码，然后通过自动化手段，快速的发布部署。所以才会产生蓝绿部署、滚动部署、灰度发布、金丝雀发布等各种不同的发布策略。</p>
<blockquote>
<p>一个很多朋友都会问到的问题：微服务架构是否支持数据权限。我的回答是：一方面不需要，如果可以实现快速发布，个人认为给接口增加个参数要远远优于配置一大堆所谓的数据权限；另一方面，实际的微服务会有很多个服务，可能一个服务会有多个实例，这种情况下做个数据权限去应对变化，岂不是自讨苦吃。</p>
</blockquote>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>这些内容完全是本人多年的积累和理解，不奢求各位能够完全理解和认同，也不一定就是正确的。如果您有不同的见解或建议，欢迎大家新建 ISSUE 讨论。</p>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/design/full.png" type="image/png"/>
    </item>
    <item>
      <title>v2.5.X</title>
      <link>https://www.herodotus.cn/logs/2.5.html</link>
      <guid>https://www.herodotus.cn/logs/2.5.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">v2.5.X</source>
      <description>v2.5.7.20 修正 OpenFeign 调用过程中，UserAgent 头信息被修改，新增的(KHTML,like Gecko) 信息中，产生换行符，导致 illegalArgumentException 问题。 修正平台 data-access-strategy: remote （OpenFeign 数据访问模式） 模式下，核心接口未授权问题。...</description>
      <pubDate>Thu, 26 Dec 2024 07:36:25 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v2.5.7.20</h2>
<ol>
<li>修正 OpenFeign 调用过程中，UserAgent 头信息被修改，新增的(KHTML,like Gecko) 信息中，产生换行符，导致 illegalArgumentException 问题。</li>
<li>修正平台 data-access-strategy: remote （OpenFeign 数据访问模式） 模式下，核心接口未授权问题。</li>
<li>优化平台全局错误信息描述和后端响应的错误信息，让错误提示信息更加准确以及更加友好</li>
<li>修正 OAuth2 Throwable Cause 信息转换成 自定义错误信息类型错误问题。</li>
</ol>
<ul>
<li>依赖包版本更新
<ul>
<li>aliyun-java-sdk-core 升级至 4.5.30</li>
<li>dysmsapi20170525 升级至 2.0.7</li>
<li>aliyun-sdk-oss 升级至 3.13.2</li>
<li>jpush-client 升级至 3.5.5</li>
<li>jiguang-common 升级至 1.1.12</li>
<li>bce-java-sdk 升级至 0.10.185</li>
</ul>
</li>
</ul>
<h2>v2.5.7.10</h2>
<ol>
<li>Skywalking 版本升级至 8.8.0</li>
<li>修正 docker-compose 配置错误问题</li>
<li>okhttps 升级至 3.3.1</li>
</ol>
<h2>v2.5.7.0</h2>
<ul>
<li>
<p>重大更新</p>
<ol>
<li>Spring Boot 升级至 2.5.7</li>
</ol>
</li>
<li>
<p>其它更新</p>
<ol>
<li>优化 Spring Cloud Gateway 响应体分段传输代码。修复在响应体特别大的情况下，Spring Cloud Gateway 读取响应体出错问题。</li>
<li>优化 Linux 和 Windows 两个版本的 docker-compose 脚本，修正脚本配置错误以及镜像引用错误。</li>
<li>优化部分后端响应的错误信息，让错误提示信息更加准确以及更加友好</li>
<li>优化前端登录页面部分错误信息提示展示方式。</li>
<li>修复前端登录页面密码输入错误后，再次发送请求不执行的问题。</li>
</ol>
</li>
</ul>
<h2>v2.5.6.60</h2>
<ul>
<li>
<p>重大更新</p>
<ol>
<li>Spring Cloud Alibaba Sentinel 升级至 1.8.2</li>
<li>扩展改造 Spring Cloud Alibaba Sentinel，实现应用上报的 Metrics 持久化存储到 Influxdb 时序数据库中，支持反向将 Sentinel 流控配置信息以配置文件的形式持久化存储至 Nacos 中。时序数据存储基于 Influxdb 1.X 版本实现，默认使用原有内存方式存储数据，可通过配置参数动态开启或关闭 Influxdb 和 Nacos 存储机制。</li>
<li>全面使用扩展的 Sentinel Dashboard 进行限流、熔断、降级等管理。扩展的 Sentinel Dashboard 已封装为 Docker 镜像，并上传至 Docker Hub，可通过命令 docker pull herodotus/sentinel-dashboard:1.8.2(latest) 使用。Sentinel、Influxdb、Nacos 等相关内容均支持动态参数配置。</li>
<li>新增 <code>TICK</code> 开源监控产品套件 Telegraf、InfluxDB、Chronograf、Kapacitor 支持，新增 Influxdb 集成模块，补充平台时序数据存储、管理能力以及拓展更多维度的运行监控和数据展现能力。提供默认配置文件及 docker-compose 脚本。</li>
<li>新增 Linux 相关操作系统下使用的 docker-compose 脚本，同时提供 Linux 和 Windows 两种系统 docker-compose 脚本供选择使用。</li>
</ol>
</li>
<li>
<p>其它更新</p>
<ol>
<li>修正 Docker 打包参数错误，解决 Docker 打包过程中无法定位服务 jar 文件问题。</li>
<li>前端 Vuetify 版本升级至 2.6.0</li>
<li>前端工程升级大量依赖包版本，重新编译生成组件库</li>
</ol>
</li>
</ul>
<h2>v2.5.6.50</h2>
<ul>
<li>重大更新
<ol>
<li>重构平台 Engine 代码，提取并新建 Cache、 Web、 Message 等代码模块。代码逻辑更内聚、模块职责更清晰。规避由于代码包以及模块间过度依赖，而导致模块使用过程中必须通过排除依赖或排除注入才能正常使用模块的问题。从根本上根除，由于过度依赖导致 spring-integration、spring-actuator 等组件在不受控的状态下启动或检查额外依赖组件问题。</li>
<li>增加 Sentinel 配置持久化机制。默认使用 Nacos 进行配置持久化存储与更新。</li>
<li>原 Management 服务名称变更为 Monitor，独立出 Management 服务作为平台各项配置统一管理服务。</li>
<li>Spring Boot Admin 升级至 2.5.4 版本</li>
</ol>
</li>
</ul>
<h2>v2.5.6.40</h2>
<ol>
<li>前端工程支持 docker-compose 打包，进行容器化部署。集成 Nginx，支持 gzip 压缩，提升页面首次加载效率。</li>
<li>解决 Vue 工程打包部署至 Nginx，favicon.ico 图标报 404 错误</li>
<li>优化 Management 服务 Docker 打包逻辑，修复 Spring Boot Admin 混入 Skywalking 监控逻辑问题。</li>
<li>修复 Spring Boot Admin 在正式环境下，依赖的 spring-boot-starter-actuator 主动连接 Redis 问题。</li>
<li>优化 docker-compose 脚本，补充 BPMN 服务运行脚本。</li>
<li>优化 Logstash 日志收集内容格式，修复 Logback 会创建多个 Logback 上下文导致冲突的问题</li>
<li>补充 Nacos 2.0.3 数据库初始化脚本，删除低版本无用脚本。</li>
<li>docker-maven-plugin 升级至 0.38.0</li>
<li>Vuetify 升级至 2.5.14。</li>
</ol>
<h2>v2.5.6.30</h2>
<ol>
<li>修复单体版代码依赖错误问题。单体版补充 Camunda 工作流功能。</li>
<li>优化代码包结构，外部依赖应用提取为单独包结构，方便代码管理及维护</li>
<li>增加对象存储(OSS)模块，开放 Minio 对象存储功能，支持注解@EnableXXX 条件开启和关闭。</li>
<li>优化日志全链路跟踪信息，在 ELK 日志中心存储的结构。让日志聚合分析更加清晰准确。</li>
<li>优化数据库初始化数据脚本。</li>
<li>Hutool 升级至 5.7.16</li>
<li>前端工程升级依赖包版本，Vuetify 升级至 2.5.13，重新编译组件库。</li>
</ol>
<h2>v2.5.6.20</h2>
<ul>
<li>
<p>后端更新</p>
<ol>
<li>修复 <code>Gateway</code> 网关服务，在生产环境下，关闭 <code>SpringDoc</code> 和 <code>Swagger</code> ，Bean 注入不正确问题。</li>
<li>优化 <code>Gateway</code> 网关服务发现服务动态接入服务 <code>Swagger</code> 文档监听器配置，通过配置动态关闭，在生产环境下不再开启。</li>
<li>单体版应用默认端口修改为 8847，与微服务版 <code>Gateway</code> 一致，方便前端接入</li>
<li>升级依赖包版本：
<ul>
<li>Redisson 升级至 3.16.4</li>
<li>Okhttps 升级至 3.3.0</li>
<li>weixin-java-miniapp 升级至 4.2.0</li>
</ul>
</li>
</ol>
</li>
<li>
<p>前端更新</p>
<ol>
<li>优化组件库中，各个组件的配置以及导出逻辑，解决在生产模式下，组件库中组件显示不正确问题。</li>
<li>对生产模式下，Vue 工程打包进行深度性能优化。</li>
<li>对打包生成的各类资源进一步压缩，降低资源文件大小。</li>
<li>依赖包拆分变更为使用动态配置，编译时将所有依赖包都动态拆分为独立 js 文件，无须再根据分析结果手动进行拆分配置，极大地降低 Vue 默认打包文件的大小，解决首屏加载时间过长问题。</li>
<li>修复前端工程打包之后，部署至 Nginx 或 Express 页面空白或显示不正确问题。</li>
<li>解决 Vue 工程打包，index.html 文件丢失问题。</li>
<li>升级大量依赖包版本，重新编译库</li>
</ol>
</li>
</ul>
<h2>v2.5.6.10</h2>
<ul>
<li>
<p>重大更新</p>
<ol>
<li>Spring Boot Admin 升级至 2.5.4</li>
<li>Camunda Open Api 升级至 7.16.0</li>
</ol>
</li>
<li>
<p>其它更新</p>
<ol>
<li>修复 Camunda Open API 在 Swagger 中，无法正常显示问题。</li>
<li>删除本地静态文档，后续请使用在线文档进行查阅。</li>
<li>优化在线文档显示，增强在线文档可读性与易读性。</li>
<li>增加 Jenkins 持续集成组件部署配置</li>
<li>前端工程升级大量依赖包，重新编译生成组件库</li>
</ol>
</li>
</ul>
<h2>v2.5.6.0</h2>
<ol>
<li>Spring Boot 版本升级至 2.5.7</li>
<li>优化自定义多级缓存，对不同的 JPA 实体（数据表）设置不同的缓存时间，支持使用 Spring Boot Yml 配置，。支持 Duration 时间表达式，配置更灵活方便，提升自定义多级缓存的可控性。</li>
<li>使用基于 Caffeine 和 Redis 的多级缓存，实现 Mybatis 自定义二级缓存，让 Mybatis 的数据缓存也可以支持分布式，同时降低数据缓存频繁访问 Redis 问题。</li>
</ol>
<h2>v2.5.5.70</h2>
<ul>
<li>
<p>重大更新</p>
<ol>
<li>在现有架构基础之上，集成 Redisson 客户端。与 Spring Data Redis 同时使用，支持 Redisson 与 Lettuce 或 Jedis 共存。</li>
<li>重构核心包 eurynome-cloud-data 内主要 Configuration 代码。让各个 Configuration 职责更清晰,代码更内聚，更加便于理解,使用及扩展。</li>
<li>增加 WebSocket 核心代码模块，全面使用 STOMP 上层协议，支持 WebSocket 集群 Session 共享,信息广播及点对点发送,在线统计，可方便拓展断开重连,心跳机制。</li>
</ol>
</li>
<li>
<p>其它更新</p>
<ol>
<li>前端工程升级大量依赖包版本，重新编译生成组件库</li>
<li>SpringDoc 升级至 1.5.12</li>
<li>Hutool 升级至 5.7.15</li>
<li>JustAuth 升级至 1.16.5</li>
</ol>
</li>
</ul>
<h2>v2.5.5.60</h2>
<ol>
<li>整合职责相近代码包，删除 eurynome-cloud-common 代码包，让各个代码包职责更明晰,逻辑更清楚。</li>
<li>增加开发环境配置基础知识文档，帮助萌新少走弯路，可以更快地投入到系统的使用和代码研究学习的过程中来。</li>
<li>依赖组件升级
<ul>
<li>Hutool 升级至 5.7.14</li>
<li>JustAuth 升级至 1.6.5</li>
<li>Camunda 升级至 7.16.0</li>
<li>okhttps 升级至 3.2.0</li>
<li>bce-java-sdk 升级至 0.10.179</li>
<li>jpush-client 升级至 3.5.3</li>
</ul>
</li>
<li>Camunda 数据库脚本升级至 7.16.0</li>
</ol>
<h2>v2.5.5.50</h2>
<ol>
<li>新增 Social Authentication 自定义授权模式方式，替代传统 Filter 过滤器方式，优化手机验证码,微信小程序,第三方认证的集成与 Token 的分派。代码更加简洁易于维护，解决了第三方认证游离于 OAuth 2 管控体系之外的问题。</li>
<li>本着“高内聚,低耦合”的原则，调整代码，优化代码分包。提升代码分包的合理性，降低各代码包之间的耦合性。</li>
<li>清除大量无用代码。</li>
<li>前端代码升级大量依赖包版本，重新编译库。</li>
<li>新增 Social Authentication 授权模式配置功能。</li>
<li>回滚 compression-webpack-plugin 组件版本，解决高版本运行错误问题。</li>
<li>补充在线文档 IDEA 使用常见问题章节。</li>
<li>解决 MySQL 数据更新脚本数据类型不兼容问题</li>
</ol>
<h2>v2.5.5.40</h2>
<ol>
<li>简化 OAuth2 资源服务器 ResourceServerConfigure 配置，代码更简洁规范。</li>
<li>进一步融合 OAuth2 错误体系，解决 OAuth2 部分错误提示与系统自定义错误体系不一致,不融合的问题。</li>
<li>解决包含路径参数的接口，可以跳过鉴权机制直接访问问题</li>
<li>解决人员与用户 @OneToOne 映射，由 Jackson 反序列化实体导致 JPA 保存或修改失败问题。</li>
<li>优化接口统一信息反馈类别，新增空数据信息结果反馈，让信息反馈内容更加友好</li>
<li>补充常用正则表达式库</li>
<li>优化人员管理,角色管理关键信息异步校验功能，解决人员管理，使用枚举作为数据类型类型导致的修改数据错误问题。</li>
<li>新增为组织机构人员分配默认用户功能。</li>
<li>新增系统默认角色配置功能。支持机构人员,手机验证码,微信小程序,QQ,微博,百度,微信开放平台,微信公众号,企业微信二维码,企业微信网页,钉钉,钉钉账号,阿里云,淘宝,支付宝,Teambition,华为,飞书,京东,抖音,今日头条,小米,人人,美团,饿了么,酷家乐,喜马拉雅,码云,开源中国,Github,Gitlab,Stackoverflow,Coding,谷歌,微软,脸书,领英,推特,亚马逊,Slack,Line,Okta,Pinterest 等多种途径或第三方登录默认角色的配置。</li>
</ol>
<h2>v2.5.5.30</h2>
<ol>
<li>Debezimu 升级至 1.7.0.Final</li>
<li>优化统一结果返回实体封装，使用更加便捷。</li>
<li>使用统一结果返回实体新接口，替换已有代码。</li>
<li>优化接口数据前后端加密传输机制，全面支持使用 Spring @RequestParam 注解接口数据加解密。</li>
<li>统一使用 @RequestParam 注解接口加解密方式，改进 OAuth 2 密码模式，用户名,密码参数加密，不再使用传统 Filter 方式，代码更加简洁规范清晰。</li>
<li>重新调整 WebMvc 配置核心代码，依赖关系更合理，代码逻辑更清晰。</li>
<li>优化完善前端用户管理相关功能。</li>
<li>增加基于 VeeValidate 组件的服务端异步校验机制</li>
</ol>
<h2>v2.5.5.20</h2>
<ul>
<li>
<p>重大更新</p>
<ol>
<li>Spring Boot Admin 升级至 2.5.2</li>
<li>基于最新版 Axios 定义，所有 Delete 接口，修改为路径参数形式</li>
<li>混合 RSA(非对称) 和 AES(对称加密) 算法，基于自定义注解，设计接口数据前后端加密传输机制。</li>
<li>设计自定义数据传输 Session，规避 Vue Session 变化问题。基于自定义 Session，实现 AES KEY 动态生成,加密传输,一人一钥的安全机制，提高系统安全性。</li>
<li>实现 OAuth 2 密码模式，用户名,密码参数加密传输。</li>
</ol>
</li>
<li>
<p>其它更新</p>
<ol>
<li>修复 Spring Validation 错误信息不会抛出，循环引用问题。</li>
<li>前端 Utils 工具包，新增加密算法模块</li>
<li>基于最新版 Axios 定义，优化 Axios 请求通用代码</li>
<li>实现前端 node-rsa 包 RSA 算法，与后端 Hutool SecureUtil RSA 算法互相加,解密。</li>
</ol>
</li>
<li>
<p>依赖包版本升级</p>
<ol>
<li>Guava 升级至 31.0.1-jre</li>
<li>SpringDoc 升级至 1.5.11</li>
<li>Mybatis Plus Generator 升级至 3.5.1</li>
<li>前端工程升级大量依赖包</li>
</ol>
</li>
</ul>
<h2>v2.5.5.10</h2>
<ol>
<li>解决 OAuth2 自带业务表通过 Spring Data JPA 自动创建，字段名变化为小写问题。</li>
<li>完善前端 Camnuda 工作流编辑器组件功能。</li>
<li>完善前端部分功能，解决已知问题。</li>
<li>更新在线文档，补充前端工程详细介绍。</li>
</ol>
<h2>v2.5.5.0</h2>
<ul>
<li>
<p>重大更新</p>
<ol>
<li>Spring Boot 升级至 2.5.5</li>
<li>Spring Cloud 升级至 2020.0.4</li>
<li>大幅改进系统数据库表和数据初始化方式，实现 OAuth2 业务表自动创建，取消使用脚本的创建方式，提升便捷性</li>
<li>新增人力资源管理相关功能，功能和模型设计实现与 Camunda 用户体系统一。便于用户体系数据的同步和管理。</li>
<li>基于 rollup,lerna 和 yarn workspaces，以 monorepo 方式重新构建前端工程。新版前端工程是以 Vue2,Typescript 开发的，组件库式的前端功能。代码更清晰,组件化和重用化程度更高。为升级至 Vue3 做前序铺垫。</li>
</ol>
</li>
<li>
<p>其它更新</p>
<ol>
<li>解决 Skywalking UI 连接 Skywalking OAP Server 出错问题。</li>
<li>修改防刷机制的默认配置</li>
<li>重新梳理错误体系，优化错误信息，错误提示更加友好。</li>
<li>解决单体版对 Basic 模式认证跨域拦截的问题</li>
<li>实现 OAuth2 Password 模式部分参数加密传输方式，提升系统安全性。</li>
<li>修复老版本前端工程已知问题。</li>
<li>在线文档同步更新。</li>
</ol>
</li>
<li>
<p>依赖包版本升级</p>
<ol>
<li>Hutool 升级至 5.7.13</li>
<li>okhttps 升级至 3.1.5</li>
<li>weixin-java-miniapp 升级至 4.1.9.B</li>
<li>JustAuth 升级至 1.16.4</li>
<li>jasypt-spring-boot-starter 升级至 3.0.4</li>
<li>mybatis-plus-boot-starter 升级至 3.4.3.4</li>
<li>dysmsapi20170525 升级至 2.0.5</li>
</ol>
</li>
</ul>
<h2>v2.5.4.140</h2>
<ol>
<li>优化 Antisamy 通用代码，提升 Xss 分析西能，去除严格拦截导致的 JSON 解析错误。</li>
<li>解决本地权限缓存并发写入冲突，抛出 com.esotericsoftware.kryo.KryoException: java.util.ConcurrentModificationException 问题。</li>
<li>解决 OAuth Starter 引入 Upms Logic 重复导入权限数据问题。</li>
<li>修改默认验证码字体配置</li>
<li>去除无用依赖包</li>
<li>新增 MySQL57 数据库切换配置。更新 Nacos 配置 SQL 脚本，增加最新导入包。</li>
<li>新增 Camunda 官方脚本</li>
<li>优化数据导入脚本</li>
</ol>
<h2>v2.5.4.130</h2>
<ol>
<li>修复单体版 Knife4j 依赖错误问题</li>
<li>修复单体版配置错误问题</li>
<li>增加 Gitee 流程模版</li>
<li>修复 OAuth2 自定义 confirm_access.html,error.html,login.html 页面，数据类型编译错误。</li>
<li>优化自定义页面显示内容，增加 Exception StackTrace 输出</li>
<li>修复 XssUtils 校验出错问题。</li>
<li>优化 ResourceServer 安全配置</li>
<li>修复 OAuth2 四种模式中授权码模式（Authorization Code）,隐式授权模式（Implicit Grant）模式出错问题。</li>
<li>解决前端控制台出错问题</li>
<li>更新在线文档，增加 OAuth2 四种模式验证说明</li>
</ol>
<h2>v2.5.4.120</h2>
<ol>
<li>使用 Springdoc 全面替换 Springfox，配置更灵活,配置更多样</li>
<li>Swagger 文档注解全面升级为支持 OpenAPI</li>
<li>使用 Springdoc 重构文档聚合功能，支持聚合查阅和服务独立查阅。Knife4j 同步升级至 3.0.3 版本。</li>
<li>Swagger 文档默认支持 OAuth2 Password,Authorization Code 两种认证流程</li>
<li>整合 Camunda Engine Rest 与 Swagger，实现 Camunda API 接口文档查阅，支持聚合查阅。</li>
<li>修复 Bpmn 服务默认启动 Tomcat 问题。</li>
<li>升级前端依赖包版本</li>
<li>更新 Nacos 配置文件及导入包</li>
<li>更新在线文档</li>
</ol>
<h2>v2.5.4.110</h2>
<ol>
<li>重新构建项目文档体系，使用纯静态页面，替代已有 Gitee Wiki 文档。优化文档结构，提升文档阅读体验。提供全文搜索，便于文档查阅。支持文档独立部署，方便使用者搭建独立的文档服务查阅。</li>
<li>更换 Nacos 导入包，解决上一版本导入包导入重复问题</li>
<li>Update Readme</li>
</ol>
<h2>v2.5.4.100</h2>
<ol>
<li>重新梳理所有 Nacos 配置，提取共性配置至统一配置文件，优化配置属性结构和归类，便于参数修改，降低维护复杂度</li>
<li>重构数据库相关 Nacos 配置，优化 Maven,Nacos 多环境配置与数据库切换的联动性，让数据库切换所需修改的参数更少，切换更顺滑</li>
<li>解决 eurynome-cloud-monitor 编译出现的 Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources) on 4. project XXXX: Input length = 1 -&gt; [Help 1]错误</li>
<li>解决 Redis 设置密码后，无法连接出现 NOAUTH Authentication required 错误</li>
<li>增加 MySQL 数据库默认数据初始化脚本</li>
<li>增加最新版 Nacos 配置导入包</li>
<li>修复前端 UI 申请 APPKEY 页面错误</li>
<li>修复前端 UI 切换至单体版后，连接错误问题。</li>
<li>升级前端 UI 依赖包版本。</li>
<li>同步更新相关文档，补充新建子模块,常见问题等部分文档。</li>
</ol>
<h2>v2.5.4.90</h2>
<ol>
<li>使用 Mybatis Plus 全面替换已有 Mybatis，与 Spring Boot Data JPA 共存且支持同时使用。使用任何技术都可以无障碍的进行业务代码编写。</li>
<li>整合 Mybatis Plus 和 Spring Boot Data JPA 更换数据库配置属性，一处修改即可以同时修改 Mybatis Plus 和 Spring Boot Data JPA 使用数据库类型。</li>
<li>新增接口 XSS 脚本攻击过滤机制，同时支持请求参数和 JSON 请求体过滤。采用 Ebay XSS 过滤模型，进一步提升防控能力。</li>
<li>新增 SQL 注入攻击防控机制。</li>
<li>解决 eurynome-cloud-gateway 和 eurynome-cloud-monitor 服务启动调用 Kafka 问题。</li>
<li>解决 CacheConfigException 错误问题，在错误体系中增加配置参数不合理提醒，让信息反馈更加友好。</li>
<li>解决 Spring Boot Admin 不支持 Java 8 时间类型问题。</li>
<li>解决 Spring Boot Admin 不显示 Git Properties 信息问题。</li>
<li>解决修改 Redis 密码配置生效问题</li>
<li>梳理 dependencies 依赖包，对已有依赖进行进行更合理的分类，更加便于依赖包的找寻和维护。</li>
<li>升级依赖包版本
<ul>
<li>spring-boot-admin 升级至 2.5.1</li>
<li>git-commit-id-plugin 升级至 4.9.10</li>
<li>docker-maven-plugin 升级至 0.37.0</li>
<li>hutool 升级至 5.7.10</li>
<li>okhttps 升级至 3.1.4</li>
<li>JustAuth 升级至 1.16.3</li>
<li>aliyun-java-sdk-core 升级至 4.5.25</li>
<li>baiducloud-java-sdk 升级至 0.10.175</li>
<li>aliyun-java-sdk-oss 升级至 3.13.1</li>
<li>cn.jpush.api 升级至 3.5.2</li>
</ul>
</li>
<li>规范项目文档，增加系统部署,数据库切换等多部分内容</li>
<li>增加 Nacos 配置导入包，在没有自动部署功能支持的情况下，也可以更加方便的导入配置。</li>
<li>替换 UI SweetAlert 过期方法，解决弹出框不会关闭问题</li>
<li>解决授权码模式（authorization code）验证码被拦截问题</li>
</ol>
<h2>v2.5.4.80</h2>
<ul>
<li>合并 eurynome-cloud-curd 包和 eurynome-cloud-rest 包，减少包数量，提升代码维护便捷度。</li>
<li>增加接口幂等处理机制，防止重复提交。增加接口防刷限制机制，防止接口恶意频繁刷新。</li>
<li>接口幂等和防刷机制，均支持全局配置控制，同时提供@Idempotent 和@AccessLimited 注解进行灵活的,个性化的配置。</li>
<li>接口幂等和防刷机制，缓存标记采用分布式多级缓存进行存储，将低单一访问 Redis 带来的访问压力，同时支持多实例数据多级缓存本地数据同步。</li>
<li>接口幂等和防刷机制，所涉及标记缓存时间配置全部统一支持 Duration 时间格式，简化配置参数，提升配置便捷度。同时，优化平台错误响应体系，返回更加友好的错误信息提示。</li>
<li>定义 Stamp 签章体系，采用统一体系，对 SMS 短信验证码,JustAuth State,环信 Token 以及接口幂等和防刷等需临时存储标记相关应用进行统一实现。同时，采用分布式多级缓存进行数据存储，降低单一访问 Redis 压力。</li>
</ul>
<h2>v2.5.4.65</h2>
<ul>
<li>优化 Skywalking 打包内容，直接使用 Skywalking 官方容器</li>
<li>补充 RequestMappingScanner 对应事件代码，解决单体版扫描 Rest API 接口后不会存储问题</li>
<li>优化数据库脚本以及数据库表结构和默认数据自动初始化机制。解决在第一次运行时，Spring Data JPA JDBC 初始化机制与 Hibernate 初始化机制冲突问题。</li>
<li>在 Gitee Wiki 中，增加数据库初始化说明文档</li>
<li>删除部分预留代码，减少暂时不必要的代码对使用者带来的误导。</li>
<li>前端 UI 中，增加部分配置，使用者可以通过注释部分配置，让前端 UI 快速支持单体版。</li>
</ul>
<h2>v2.5.4.60</h2>
<ul>
<li>Spring Boot 版本升级至 2.5.4</li>
<li>Skywalking 版本升级至 8.7.0</li>
<li>微服务日志输出至日志中心格式。</li>
<li>增加 Swagger 注入条件注解，优化日志中心相关配置采用统一常量控制</li>
<li>增加自定义 Property&quot;助手&quot;数据统一自动编译生成配置，无须额外设置，在 IDE 中编辑配置属性时可自动弹出提示</li>
<li>修复 Kafka 配置不生效问题。</li>
<li>解决单体版自动启动 Kafka 问题。</li>
<li>暂时关闭单体版 Swagger，规避 Swagger 会自动启动 Kafka 问题。</li>
</ul>
<h2>v2.5.4.55</h2>
<ul>
<li>整合代码，将 constant 包代码整合至 common 包中。删除 constant 包。</li>
<li>完善 Rest 接口校验机制，以及相关自定义错误码</li>
<li>统一 spring boot validation 错误信息，将其整合至平台统一响应实体 Result 中。无须在各个 Rest 接口中，添加 BindingResult 参数。</li>
</ul>
<h2>v2.5.4.50</h2>
<ul>
<li>优化服务本地权限存储逻辑，解决权限属性数据重复存储，不会替换问题。</li>
<li>重新梳理 Spring Security OAuth2 方法级表达式动态权限鉴权逻辑，摒弃无用的权限验证 Voter 逻辑，使用统一逻辑实现@PreAuthorize 注解权限的全面动态可配置化。统一平台接口- 白名单，IP 地址白名单，以及 Scope 绑定 URL 的管理。</li>
<li>重构 UserDetails 用户信息组织逻辑，使用 Spring Security 标准代码，替换自定义逻辑代码，降低代码冗余，与自研方法级动态权限完美融合。</li>
<li>优化平台权限从 Controller 扫描,汇总存储至服务器以及动态修改后最终回传同步至服务的整理逻辑以及事件流。完美支持单体式架构,UPMS 自身应用需求,分布式架构以及分布式各服- 务多实例等各种应用场景。</li>
<li>修复部分已知 BUG，将部分代码中日志由@Slf4j 改回传统日志编写方式，一方面提高编译效率，另一方面解决源代码包查看时 Idea 提醒代码不一致问题。</li>
<li>清理系统无用代码。</li>
<li>增加方法级动态权限演示动图，更新 Readme</li>
</ul>
<h2>v2.5.4.40</h2>
<ul>
<li>优化自定义多级缓存，实现可以统一设置 allowNullValues 值，并解决存储空值时卡死问题。</li>
<li>重新梳理,优化系统权限从 Controller 扫描,汇总存储至服务器以及动态修改后最终回传同步至服务的整理逻辑以及事件流。完美支持单体式架构,UPMS 自身应用需求,分布式架构以及- - 分布式各服务多实例等各种应用场景。</li>
<li>增加 Kafka 条件注入配置，将条件转换为@ConditionalOnXXX，方便管理和维护。避免单体版应用相关依赖过度依赖 Kafka 而导致的无法启动</li>
<li>修复部分已知 BUG，将部分代码中日志由@Slf4j 改回传统日志编写方式，一方面提高编译效率，另一方面解决源代码包查看时 Idea 提醒代码不一致问题。</li>
<li>去除传统 Kafka Producer 通用类，改为统一使用 Spring Cloud Bus</li>
</ul>
<h2>v2.5.4.30</h2>
<ul>
<li>重新调整 Property 配置，规范 Property 定义。修改相关配置</li>
<li>调整包，以及包相关依赖关系。让包之间的依赖更加合理</li>
<li>增加 Kafka 配置，增加@KafkaListener 动态控制，以避免在不需要的情况下 Kafka 的自动开启。</li>
<li>调整部分常理代码位置，常量代码基本调整完成</li>
<li>将原有自定义条件，转换为@ConditionalOnXXX 注解，让使用更加便捷</li>
<li>日志中心是否开启状态，改为@ConditionalOnLogCenterEnabled 注解</li>
</ul>
<h2>v2.5.4.20</h2>
<ul>
<li>本地权限缓存更换为 JetCache，为服务多实例的权限扫描和存储提供更好的支持</li>
<li>将数据访问策略从 Conditional 类，升级为 Conditional 注解，使用更加便捷</li>
<li>调整包依赖关系，新建 assistant,constant 包，删除 message 包。</li>
<li>逐步将平台中各类非独有常量移入 constant 包方便管理和修改</li>
<li>采用 Spring Boot Event 和 Spring Cloud Bus Event 机制重构接口收集逻辑。支持单体架构,UPMS,分布式多实例等不同场景接口扫描的特殊需求</li>
<li>优化 Docker Compose 配置，使用 Debezium Kafka 替换已有 kafka，以支持 Debezium 应用</li>
<li>删除无用代码</li>
</ul>
<h2>v2.5.4.10</h2>
<ul>
<li>全网首个实现 Spring Security 动态 URL 权限与注解表达式权限有机整合，并且可以动态配置的微服务框架。</li>
<li>全面支持方法级权限控制，Security OAuth2 permitAll 等方法权限以及@PreAuthorize 注解权限，均支持动态配置。目前支持以下权限的动态配置：<br>
· hasRole<br>
· hasAnyRole<br>
· hasAuthority<br>
· hasAnyAuthority<br>
· hasIpAddress<br>
· #oauth2.clientHasRole<br>
· #oauth2.clientHasAnyRole<br>
· #oauth2.hasScope<br>
· #oauth2.hasAnyScope<br>
· #oauth2.hasScopeMatching<br>
· #oauth2.hasAnyScopeMatching<br>
· #oauth2.denyOAuthClient<br>
· #oauth2.isOAuth<br>
· #oauth2.isUser<br>
· #oauth2.isClient</li>
<li>彻底解决使用 withObjectPostProcessor 方式，会覆盖外部匹配规则问题。</li>
<li>真正实现 Scope 权限与 URL 权限的关联与管控，拓展 OAuth2 默认只进行 Scope 简单对比的实现逻辑。</li>
<li>实现动态权限配置的多服务同步。</li>
<li>暂时去除 JetCache，全面使用自研支持 Hibernate 二级缓存的多级缓存。</li>
<li>修改配置文件配置</li>
<li>删除无用代码</li>
</ul>
<h2>v2.5.3.60</h2>
<ul>
<li>Nacos 版本升级至 2.0.3</li>
<li>Hutool 版本升级至 5.7.6</li>
<li>修改部分代码名称</li>
<li>修改配置文件配置参数</li>
</ul>
<h2>v2.5.3.50</h2>
<ul>
<li>将所有服务包括 UAA 的权限存储，改为本地和认证中心，多级分布式存储和验证。</li>
<li>增加策略模式，支持认证中心权限数据直连和远程消息两种存储方式动态切换。</li>
<li>改进自研多级缓存，解决 Hibernate 二级缓存进行数据缓存时产生的事务锁问题。</li>
<li>删除无用代码</li>
<li>为动态 Scope 权限做铺垫。</li>
<li>全面支持方法级权限控制。</li>
</ul>
<h2>v2.5.3.40</h2>
<ul>
<li>Spring Boot 版本升级至 2.5.4</li>
<li>采用新的 Hash 算法，缩短权限标识；简化用户权限信息，以缩短平台生成 JWT Token 的长度</li>
<li>git commit 插件由 pl.project13.maven » git-commit-id-plugin 改为 io.github.git-commit-id » git-commit-id-maven-plugin。</li>
<li>git commit 插件升级至 5.0.0 版本</li>
</ul>
<h2>v2.5.3.30</h2>
<ul>
<li>重构用户中心用户人员体系，将平台用户,第三方社交登录用户,人事管理用户以及 Camunda 工作流用户体系无缝融合。</li>
<li>人事管理体系人员唯一化管理，同时支持企业,党组,团青等多种类型的机构类型，便于企业人事管理。</li>
<li>基于 Debezium 实现数据库变更数据捕获，实现人事管理信息与 Camunda 工作流用户实时同步。</li>
<li>树形结构通用代码实现逻辑与 Hutool Tree 一致，因此采用 Hutool Tree 全面替换已有的 Tree 代码。</li>
<li>删除不再使用的通用类代码。</li>
<li>修改说明文档</li>
</ul>
<h2>v2.5.3.10</h2>
<ul>
<li>自研基于 Caffeine 和 Redis 分布式两级缓存</li>
<li>完美支持 JPA Hibernate 二级缓存</li>
<li>完美支持各类查询数据缓存以及 JPA @ManyToMany, @ManyToOne 等关联查询。</li>
<li>实现基于 Caffeine 的 Hibernate 二级缓存，可与自研两级缓存快速切换，仅使用本地缓存。</li>
<li>极大的简化了原有自研的基于 JetCache 的缓存使用方式。</li>
<li>保留 JetCache，可根据实际使用需要使用。</li>
<li>已有服务接口相关代码，均已更新自研分布式两级缓存模式。</li>
</ul>
<h2>v2.5.2.40</h2>
<ul>
<li>Redis Value 默认序列化工具修改为 Jackson2JsonRedisSerializer</li>
<li>jetcache valueEncoder 和 valueDecoder 修改为 kryo</li>
<li>部分代码的日志改为传统方式，不再使用@Slf4j 注解</li>
<li>抽象实体共性属性，拓展基础实体，以支持 JPA 视图类的 ORM 映射。</li>
<li>重构基础 Service，将基础 Service 的读操作与其它操作分离，以支持“视图”相关 Service 的编写。</li>
<li>重构基础 Controller，将基础 Controller 的读操作与其它操作分离，以支持“视图”相关 Controller 的编写。</li>
<li>将 Hibernate Validator 替换为 spring-boot-starter-validation</li>
<li>删除 JPA 过期方法封装</li>
</ul>
<h2>v2.5.2.30</h2>
<ul>
<li>Spring Boot 版本升级至 2.5.2</li>
<li>Spring Boot Admin 版本升级至 2.4.2</li>
<li>升级其它相关依赖版本</li>
</ul>
<h2>v2.5.2.25</h2>
<ul>
<li>Skywalking 升级至 8.6.0</li>
</ul>
<h2>v2.5.2.20</h2>
<ul>
<li>Spring Boot 升级至 2.5.1</li>
<li>Camunda 升级至 7.15.0</li>
<li>其它依赖包版本升级</li>
<li>Swagger 回滚至 2.9.2，解决 Swagger 接口测试相关问题以及 Knife4 Authorize 不显示问题</li>
<li>结构性调整平台相关配置属性</li>
</ul>
<h2>v2.5.1.0</h2>
<ul>
<li>Spring Boot 大版本升级至 2.5.0</li>
</ul>
<h2>v2.4.5.60</h2>
<ul>
<li>修改负载均衡不生效问题</li>
<li>调整包依赖关系</li>
<li>升级版本</li>
</ul>
<h2>v2.4.5.48</h2>
<ul>
<li>正式发布开源版本</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>v2.6.X</title>
      <link>https://www.herodotus.cn/logs/2.6.html</link>
      <guid>https://www.herodotus.cn/logs/2.6.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">v2.6.X</source>
      <description>v2.6.7.30 主要更新 Spring Boot Admin 版本升级至 2.6.7 其它更新 Maven Invoker 版本升级至 3.2.0 Minio 版本升级至 8.4.1 Hutool 版本升级至 5.8.0 Bce-java-sdk 版本升级至 0.10.209 Alipay-sdk-java 版本升级至 4.23.11.ALL Al...</description>
      <pubDate>Thu, 26 Dec 2024 07:36:25 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v2.6.7.30</h2>
<ul>
<li>主要更新
<ul>
<li>Spring Boot Admin 版本升级至 2.6.7</li>
</ul>
</li>
<li>其它更新
<ul>
<li>Maven Invoker 版本升级至 3.2.0</li>
<li>Minio 版本升级至 8.4.1</li>
<li>Hutool 版本升级至 5.8.0</li>
<li>Bce-java-sdk 版本升级至 0.10.209</li>
<li>Alipay-sdk-java 版本升级至 4.23.11.ALL</li>
<li>Aliyun-sdk-oss 版本升级至 3.15.0</li>
</ul>
</li>
</ul>
<h2>v2.6.6.20</h2>
<ul>
<li>主要更新
<ul>
<li>JetCache 版本升级至 2.6.3，感谢原作者修复 Spring Boot 2.6 下依赖循环问题。删除本项目原有临时解决 JetCache 依赖循环代码。</li>
<li>Spring Boot Admin 版本升级至 2.6.6</li>
<li>Skywalking Agent 版本升级至 8.10.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>Antisamy 版本升级至 1.6.7</li>
<li>Logstash Logback Encoder 版本升级至 7.1.1</li>
<li>Minio 版本升级 8.3.8</li>
<li>Okhttps 版本升级至 3.5.0</li>
<li>WxJava 版本升级至 4.3.0</li>
<li>Bce-java-sdk 版本升级至 0.10.204</li>
<li>Qiniu-java-sdk 版本升级至 7.10.0</li>
<li>Alipay-sdk-java 版本升级至 4.22.86.ALL</li>
</ul>
</li>
</ul>
<h2>v2.6.6.10</h2>
<ul>
<li>主要更新
<ul>
<li>Spring Boot Admin 版本升级至 2.6.5</li>
<li>Camunda 版本升级至 7.17.0，同时升级 Camunda Open API 文档，补充 Camunda 7.17.0 数据库脚本</li>
</ul>
</li>
<li>其它更新
<ul>
<li>Antisamy 版本升级至 1.6.6.1</li>
<li>Springdoc 版本升级至 1.6.7</li>
<li>Okhttps 版本升级至 3.4.6</li>
<li>Qiniu-java-sdk 版本升级至 7.9.5</li>
<li>Alipay-sdk-java 版本升级至 4.22.81.ALL</li>
</ul>
</li>
</ul>
<h2>v2.6.6.0</h2>
<ul>
<li>重要更新
<ul>
<li>Spring Boot 版本升级至 2.6.6。以规避（CVE-2022-22965）问题。当前环境不具备漏洞出现条件，只是进一步预防和规避。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>Okhttps 版本升级至 3.4.5</li>
<li>WxJava 版本升级至 4.2.9.B</li>
<li>Bce-java-sdk 版本升级至 0.10.202</li>
</ul>
</li>
</ul>
<h2>v2.6.5.0</h2>
<ul>
<li>重要更新
<ul>
<li>Spring Boot 版本升级至 2.6.5</li>
<li>Spring Boot Admin 版本升级至 2.6.3</li>
</ul>
</li>
<li>一般更新
<ul>
<li>强制降低 Spring Integration 版本至 5.5.9，临时解决 Spring Integration 5.5.10 不兼容 Java 8 问题。已提交 ISSUE 至 Spring Integration 项目 <a href="https://github.com/spring-projects/spring-integration/issues/3761" target="_blank" rel="noopener noreferrer">#3761</a>。确定在 Spring Integration 5.5.11 修复该问题</li>
<li>修复 @Async 与 @Transactional 注解冲突，导致 @Transactional 失效问题。</li>
<li>修复因@Transactional 失效，导致的权限数据存储死锁问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>Redisson 版本升级至 3.17.0</li>
<li>Fastjson 版本升级至 1.2.80</li>
<li>Okhttps 版本升级至 3.4.4</li>
<li>Bce-java-sdk 版本升级至 0.10.201</li>
<li>Alipay-sdk-java 版本升级至 4.22.67.ALL</li>
<li>Logback 版本升级至 1.2.11</li>
</ul>
</li>
</ul>
<h2>v2.6.4.20</h2>
<ol>
<li>Spring Cloud Gateway 安全问题修复 （CVE-2022-22947）</li>
<li>依赖包版本升级
<ul>
<li>maven-embedder 版本升级至 3.8.5</li>
<li>maven-compat 版本升级至 3.8.5</li>
<li>WxJava 版本升级至 4.2.8.B</li>
<li>bce-java-sdk 版本升级至 0.10.200</li>
<li>qiniu-java-sdk 版本升级至 7.9.4</li>
<li>alipay-sdk-java 版本升级至 4.22.57.ALL</li>
<li>com.baidu.aip 版本升级至 4.16.7</li>
</ul>
</li>
</ol>
<h2>v2.6.4.10</h2>
<ul>
<li>
<p>主要更新</p>
<ol>
<li>增加 <code>SpringDoc</code> 配置，采用最新配置方式，解决 Swagger UI 认证界面无法输入 <code>Client Secret</code> 以及反复输入<code>Client Id</code> 和 <code>Client Secret</code> 问题。该问题需要更新 Nacos 配置才能生效。（注意：正式使用，建议单独为 Swagger 分配 <code>Client Id</code> 和 <code>Client Secret</code>，以确保安全性）</li>
<li>为提升 Swagger UI 显示内容的时效性，默认关闭 Swagger UI 页面缓存。</li>
<li>修改 Swagger UI 支持认证模式，默认支持 OAuth2 <code>Password</code> 模式和 <code>Client Credentials</code> 模式，关闭 <code>Authorization Code</code> 模式以及支持 PKCE 的 <code>Authorization Code</code> 模式</li>
</ol>
</li>
<li>
<p>其它更新</p>
<ul>
<li>Guava 版本升级至 31.1-jre</li>
<li>Hutool 版本升级至 5.17.22</li>
<li>WxJava 版本升级至 4.2.7.B</li>
<li>Mybatis-plus-generator 版本升级至 3.5.2</li>
<li>Bce-java-sdk 版本升级至 0.10.199</li>
<li>Alipay-sdk-java 版本升级至 4.22.49.ALL</li>
</ul>
</li>
</ul>
<h2>v2.6.4.0</h2>
<ul>
<li>主要更新
<ol>
<li>Spring Boot 版本升级至 2.6.4</li>
<li>Spring Cloud Alibaba 版本升级至 2021.0.1.0</li>
<li>删除为升级 Spring Boot 2.6.X，而编写的临时解决 Sentinel 循环注入代码，全新使用 Spring Cloud Alibaba 标准代码。</li>
</ol>
</li>
<li>其它更新
<ul>
<li>docker-maven-plugin 版本升级至 0.39.1</li>
<li>minio 版本升级至 8.3.7</li>
<li>log4j-api 版本升级至 2.17.2</li>
</ul>
</li>
</ul>
<h2>v2.6.3.30</h2>
<ul>
<li>
<p>主要更新</p>
<ul>
<li>Spring Cloud 版本升级至 2021.0.1</li>
</ul>
</li>
<li>
<p>其它更新</p>
<ul>
<li>Hutool 版本升级至 5.17.21</li>
<li>WxJava 版本升级至 4.2.6.B</li>
<li>alipay-sdk-java 版本升级至 4.22.37.ALL</li>
</ul>
</li>
<li>
<p>额外说明</p>
</li>
</ul>
<p>近期发布版本的频次降低，更新的内容也比较少。主要原因一方面是恰逢新春佳节，另一方面也是最主要的原因也是为后期的更新迭代做积极的准备和开发工作。</p>
<p>为什么现在积极准备后期的更新迭代，主要是因为后期的更新迭代对现有系统影响比较大：</p>
<p>Spring 官宣 spring-security-oauth 即将不再维护。虽然 spring-security-oauth2-autoconfigure 等组件包依旧可用，现目前 Spring 依赖的 spring-security-oauth2-autoconfigure 版本比较低，高版本所有的代码均已标记为过期。</p>
<blockquote>
<p>所以近期一直在进行使用 Spring Authorization Server 替换现有 spring-security-oauth 的开发工作。包括 Herodotus Engine 工程的提取，以及 Dante Cloud v2.7.0.Beta2 的开发，均是为了降低已有系统代码间耦合，为 Spring Authorization Server 使用和升级铺路。</p>
</blockquote>
<p>Spring Boot 3 下一代框架将基于 Java 17; Kafka 3.0 也已经宣布弃用 Java 8 的支持;Hibernate 宣布他们目前积极维护的分支都支持 Java17。Java 生态正在潜移默化进入一个新的时代，所以开始着手准备支持 Java 17 势在必行。</p>
<p>虽然说 Java 一直都是向下兼容的，但是微服务架构依赖的第三方开源包非常多，势必会存在一些依赖组件包在 Java 17 下使用不畅的情况，所以需要及时更新或替换。不断的升级更新，跟随业界的发展趋势，用新的技术来改善开发和使用。同时，规避处于长期不更新导致后期即使很小的更新都会牵一发而动全身的尴尬境地。</p>
<h2>v2.6.3.20</h2>
<ul>
<li>
<p>重要更新</p>
<ul>
<li>Skywalking 版本升级至 8.9.0</li>
<li>升级 Antisamy xml 配置，强化 XSS 防控能力</li>
</ul>
</li>
<li>
<p>其它更新</p>
<ul>
<li>docker-maven-plugin 版本升级至 0.39.0</li>
<li>antisamy 版本升级至 1.6.5</li>
<li>springdoc 版本升级至 1.6.6</li>
<li>skywalking 版本升级至 8.9.0</li>
<li>minio 版本升级 8.3.6</li>
<li>bce-java-sdk 版本升级至 0.10.193</li>
<li>qiniu-java-sdk 版本升级至 7.9.3</li>
<li>alipay-sdk-java 版本升级至 4.22.32.ALL</li>
<li>jpush-client 版本升级至 3.6.1</li>
</ul>
</li>
</ul>
<h2>v2.6.3.10</h2>
<ol>
<li>Spring Boot Admin 版本升级至 2.6.2</li>
<li>修改 Dockerfile 配置，将 alpine 容器源修改为阿里源，提升打包 Docker 字体等资源下载速度</li>
<li>修复服务打包成 Docker 镜像后，openjdk:8-jdk-alpine 无法找到字体，导致渲染图形验证码抛空问题。</li>
<li>将 alpine 容器默认 UTC 时区，修改为 &quot;GTM + 8&quot; 时区</li>
<li>前端工程 Vuetify 版本升级至 2.6.3。同步升级大量其它依赖包版本。</li>
<li>后端工程依赖升级：
<ul>
<li>Redisson 版本升级至 3.16.8</li>
<li>Hutool 版本升级至 5.7.20</li>
<li>WxJava 版本升级至 4.2.5.B</li>
<li>mybatis-plus-boot-starter 版本升级至 3.5.1</li>
<li>dysmsapi20170525 版本升级至 2.0.9</li>
<li>bce-java-sdk 版本升级至 0.10.190</li>
<li>alipay-sdk-java 版本升级至 4.22.30.ALL</li>
<li>baidu-java-sdk 版本升级至 4.16.5</li>
<li>aliyun-sdk-oss 版本升级至 3.14.0</li>
</ul>
</li>
</ol>
<h2>v2.6.3.0</h2>
<ul>
<li>
<p>重要更新</p>
<ol>
<li>Spring Boot 版本升级至 2.6.3</li>
<li>Nacos 版本升级至 2.0.4</li>
</ol>
</li>
<li>
<p>其它更新</p>
<ul>
<li>okhttps 版本升级至 3.4.2</li>
<li>minio 升级 值 8.3.5</li>
</ul>
</li>
</ul>
<h2>v2.6.2.90</h2>
<ol>
<li>Spring Boot Admin 升级至 2.6.1</li>
<li>修复前端工程，因阿里库依赖包签名不正确，导致执行 yarn 编译命令以及 yarn 升级依赖命令，会抛出 Integrity check failed for &quot;graceful-fs&quot; (computed integrity doesn't match our records 错误问题。</li>
</ol>
<h2>v2.6.2.80</h2>
<ul>
<li>重要更新</li>
</ul>
<ol>
<li>Spring Boot Admin 升级至 2.6.0。
<ul>
<li>Spring Boot Admin 监控元数据包含 null 值会抛出 NullPointerException 的问题已解决。感谢 Dante Cloud 技术交流群群友【liviing{}{}】发现此问题，并提供解决方案。才得以提交 ISSUE 至 Spring Boot Admin 得到快速解决， ISSUE ID：#1925。</li>
</ul>
</li>
<li>Sentinel 全面升级至 1.8.3
<ul>
<li>解决使用 Spring Cloud Alibaba 2021.1 导致 Sentinel 无法升级问题。</li>
<li>封装的 Sentinel Dashboard 同步升级至 1.8.3。最新打包 herodotus/sentinel-dashboard 镜像已上传至 Docker Hub</li>
<li>Docker Compose 脚本修改为使用最新 herodotus/sentinel-dashboard:1.8.3</li>
</ul>
</li>
<li>新增支付核心模块
<ul>
<li>支付模块对阿里支付、微信支付核心支付 API 进行了封装，支持普通商户及 ISV 模式，使用更加便捷。</li>
<li>商户信息可配置，支持多商户管理。默认使用配置文件进行商户信息配置，支持使用关系型数据库进行商户信息存储，也可灵活自定义其它类型的存储媒介。</li>
<li>采用事件机制实现支付异步通知以及异步回调处理，规避多次异步通知问题。</li>
<li>最大程度上降低支付模块与实际业务的耦合性，便于开发更加独立的、更易扩展的订单、支付等微服务。</li>
</ul>
</li>
</ol>
<ul>
<li>其它更新
<ol>
<li>解决阿里支付 SDK 自身依赖包与 Antisamy 依赖冲突，导致系统接口无法正常调用问题。</li>
<li>解决行为验证码偶尔出现缓存数据无法序列化问题。</li>
<li>前端工程升级大量依赖包，重新编译组件库。</li>
<li>后端依赖包版本升级
<ul>
<li>Springdoc 版本升级至 1.6.4</li>
<li>Hutool 版本升级至 5.7.19</li>
<li>WxJava 版本升级至 4.2.4.B</li>
<li>mybatis-plus-boot-starter 版本升级至 3.5.0</li>
<li>aliyun-java-sdk-core 版本升级至 4.6.0</li>
<li>bce-java-sdk 版本升级至 0.10.188</li>
<li>qiniu-java-sdk 版本升级至 7.9.2</li>
<li>alipay-sdk-java 版本升级至 4.22.17.ALL</li>
<li>jpush-client 版本升级至 3.6.0</li>
<li>jiguang-common 版本升级至 1.2.0</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2>v2.6.2.70</h2>
<ol>
<li>升级 bcprov-jdk15on 版本，修复安全漏洞 CVE-2020-28052，解决 bcprov-jdk15on 在工程中存在多个版本重复的依赖包问题。</li>
<li>优化平台新服务创建 Maven Archetype，解决 .gitignore 被过滤不会拷贝的问题。</li>
<li>增加 Maven 管理代码包，可通过代码或界面操作实现新服务的创建</li>
</ol>
<h2>v2.6.2.60</h2>
<ol>
<li>新增平台服务 Maven Archetype，方便新服务的构建和开发</li>
<li>优化平台核心 Maven Dependences，去除无用依赖内容，让 pom.xml 内容更清晰</li>
<li>升级 Debezimu 版本至 1.8，同步修改 docker-compose 脚本。</li>
<li>删除原有验证码接口无用的权限设置代码</li>
<li>删除 dante-cloud-influxdb 和 dante-cloud-oss 模块代码，减少额外代码对学习本系统带来的复杂度</li>
<li>升级系统依赖包版本：
<ul>
<li>Springdoc 版本升级至 1.6.3</li>
<li>Hutool 版本升级至 5.7.18</li>
<li>Mybatis 版本升级至 3.5.9</li>
<li>Log4j 版本升级至 2.17.1</li>
</ul>
</li>
</ol>
<h2>v2.6.2.50</h2>
<ol>
<li>Jetcache 版本升级至 2.6.2 （提了 Jetcache 2.6.1 版本 redis 缓存 key 中包含 null 的 ISSUE，作者快速响应并修复，感谢 Jetcache 作者）</li>
<li>强制升级 Logback 版本至 1.2.10，以规避 Logback 安全漏洞 CVE-2021-42550</li>
<li>回滚 Spring Boot Admin 版本至 2.5.4，以临时解决 Spring Boot Admin 2.5.5 版本在元数据包含 null 值的情况下会抛出 NullPointerException 的问题。已向 Spring Boot Admin 提出 ISSUE #1925，待修正后再升级</li>
<li>重构 eurynome-cloud-captcha 包代码，将行为验证码与图形验证码以及 Hutool 验证码整合并统一，目前支持滑块拼图、文字点选、算数类型、中文类型、字母类型、GIF 类型，以及 Hutool 圆圈干扰、扭曲干扰、线段干扰等多种类型验证码。</li>
<li>使用独立的、统一的接口进行验证码调用以及验证码的验证。通过修改接口即可动态变更验证码的显示和调用。</li>
<li>通过 Spring Boot 配置，可以动态修改验证码的大小、内容、字体等多维度参数，让验证码的显示更具多样性和灵活性。</li>
<li>重构 OAuth2 授权码模式登录页面代码，替换已有验证码，更换为新的统一验证码接口。不再使用已经不再维护的第三方开源验证码包。</li>
<li>修正前端验证码组件，在重新设置滑块大小参数情况下，滑块显示位置错误并导致验证失败问题。</li>
<li>调整部分代码日志级别，减少日志在 Debug 模式下不必要的日志输出</li>
<li>依赖包升级：
<ul>
<li>Redisson 版本升级至 3.16.7</li>
<li>Springdoc 版本升级至 1.6.2</li>
<li>Fastjson 版本升级至 1.2.79</li>
</ul>
</li>
</ol>
<h2>v2.6.2.40</h2>
<ul>
<li>重大更新
<ul>
<li>Spring Boot 版本升级至 2.6.2</li>
<li>lombok 版本升级至 1.18.22</li>
<li>docker-maven-plugin 版本升级至 0.38.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>Jetcache 回滚至 2.6.0 版本，规避 Jetcache 2.6.1 版本在 redis 中生成缓存 key 中包含 null 的问题</li>
</ul>
</li>
</ul>
<h2>v2.6.2.30</h2>
<ul>
<li>重大更新
<ul>
<li>Spring Boot Admin 版本升级至 2.5.5</li>
</ul>
</li>
<li>其它更新
<ul>
<li>Apache Log4j2 版本升级至 2.17.0，解决第三个安全漏洞 CVE-2021-45105</li>
<li>独立的 eurynome-cloud-upms-api 包，已经失去单独提取的意义，将其与 eurynome-cloud-upms-logic 包整合。</li>
<li>新增认证成功后，登录信息日志记录。</li>
</ul>
</li>
</ul>
<h2>v2.6.2.20</h2>
<ol>
<li><strong>Apache Log4j2 版本升级至 2.16.0，彻底根除安全漏洞问题</strong></li>
<li>新增组合式、可配置化的图形验证码功能。独立出单独验证码模块，为后续更多的验证码集成奠定基础。<br>
<strong>主要特点</strong>：
<ul>
<li>同时支持滑块拼图验证码和文字点选验证码。</li>
<li>后端可通过配置，灵活定制和修改验证码细节；</li>
<li>前端组件化封装，可通过参数动态指定所使用的验证码</li>
<li>使用统一接口实现不同验证码的生成与验证。</li>
<li>与平台自定义 Session 高度融合，整合幂等、防刷等管控机制，前后端数据加密传输，提升验证码使用安全性。</li>
<li>验证码反馈信息，与平台统一错误体系有机结合，用户体验进一步提升。</li>
</ul>
</li>
<li>优化 Feign 请求信息传输，修正 UserAgent 信息被修改后未回置问题。</li>
<li>前端工程升级大量依赖包，重新编译组件库代码。</li>
<li>后端核心依赖包版本升级
<ul>
<li>Springdoc 版本升级至 1.6.1</li>
<li>Mybatis 版本升级至 3.5.8</li>
</ul>
</li>
</ol>
<h2>v2.6.2.10</h2>
<ol>
<li>修复 Apache Log4j2 的远程代码执行漏洞安全问题</li>
<li>进一步优化调整核心代码模块依赖关系，降低模块间的耦合程度，减少部分核心模块被过度依赖的情况。</li>
<li>升级依赖包版本
<ul>
<li>Logstash Logback Encoder 版本升级至 7.0.1</li>
<li>Hutool 版本升级至 5.7.17</li>
<li>aliyun-java-sdk-core 版本升级至 4.5.30</li>
<li>dysmsapi20170525 版本升级至 2.0.8</li>
<li>qiniu-java-sdk 版本升级至 7.9.0</li>
</ul>
</li>
</ol>
<h2>v2.6.2.0</h2>
<ol>
<li>重构平台基础核心代码，调整部分核心代码归属模块，让各个核心代码模块逻辑更加清晰、职责更加内聚。</li>
<li>调整核心代码模块依赖关系，降低模块间的耦合程度，减少部分核心模块被过度依赖的情况。</li>
<li>升级大量基础核心依赖包版本
<ul>
<li>Fastjson 版本升级至 1.2.78</li>
<li>JetCache 版本升级至 2.6.1</li>
<li>Redisson 版本升级至 3.16.6</li>
<li>SpringDoc 版本升级至 1.5.13</li>
<li>Minio 版本升级至 8.3.4</li>
<li>Okhttps 版本升级至 3.4.1</li>
</ul>
</li>
<li>前端工程升级使用依赖包版本，重新编译组件库。</li>
</ol>
<h2>v2.6.1.0</h2>
<ul>
<li>
<p>重大更新</p>
<ol>
<li>Spring Boot 版本升级至 2.6.1</li>
<li>Spring Cloud 版本升级至 2021.0.0</li>
<li>新增 Sentinel 自动降级处理机制。</li>
</ol>
</li>
<li>
<p>其它更新</p>
<ol>
<li>解决 JetCache 2.6.0 在 Spring Boot 2.6.X 环境下，Bean 循环依赖问题。</li>
<li>解决 Spring Cloud Alibaba Sentinel 2021.1 在 Spring Boot 2.6.X 环境下，Bean 循环依赖问题。</li>
<li>解决 Spring Boot 2.6.X 环境下，由于代码方法变更，导致接口自动化扫描抛空错误问题。</li>
<li>解决 Sentinel 与 Feign 冲突问题。</li>
<li>解决 Spring Cloud OAuth2 由于无用代码的注入，导致的 Bean 循环依赖问题。</li>
<li>前端 Vuetify 版本升级至 2.6.1，升级相关依赖包版本，重新编译组件库</li>
</ol>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>v2.7.X</title>
      <link>https://www.herodotus.cn/logs/2.7.html</link>
      <guid>https://www.herodotus.cn/logs/2.7.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">v2.7.X</source>
      <description>v2.7.18.Final 重要说明 Spring Boot 2.7.X 于 2023 年 11 月 24 日 停止维护，最后发布一次 Dante Cloud 2.7.X 版本以示纪念 主要更新 [升级] Spring Boot 版本升级至 2.7.18 [升级] Spring Boot Admin 版本升级至 2.7.13 [升级] spring-s...</description>
      <pubDate>Thu, 26 Dec 2024 07:36:25 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v2.7.18.Final</h2>
<ul>
<li>重要说明
<ul>
<li>Spring Boot 2.7.X 于 2023 年 11 月 24 日 停止维护，最后发布一次 Dante Cloud 2.7.X 版本以示纪念</li>
</ul>
</li>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.18</li>
<li>[升级] Spring Boot Admin 版本升级至 2.7.13</li>
<li>[升级] spring-security-oauth2-authorization-server 版本升级至 0.4.4</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] 同步更新最新版本 Antisamy XSS 防护配置文件</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] antisamy 版本升级至 1.7.4</li>
<li>[升级] commons-io 版本升级至 2.15.0</li>
<li>[升级] guava 版本升级至 32.1.3-jre</li>
<li>[升级] redisson 版本升级至 3.24.3</li>
<li>[升级] minio 版本升级至 8.5.7</li>
<li>[升级] fastjson2 版本升级至 2.0.42</li>
<li>[升级] hutool 版本升级至 5.8.23</li>
<li>[升级] okhttps 版本升级至 4.0.2</li>
<li>[升级] wxjava 版本升级至 4.5.7.B</li>
<li>[升级] mybatis plus 版本升级至 3.5.4.1</li>
<li>[升级] mybatis 版本升级至 3.5.14</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.893</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.140.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.17</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.2</li>
<li>[升级] xnio 版本升级至 3.8.12.Final</li>
<li>[升级] log4j 版本升级至 2.21.1</li>
<li>[升级] jackson 版本升级至 2.15.3</li>
</ul>
</li>
</ul>
<h2>v2.7.16.Final</h2>
<ul>
<li>重要说明
<ul>
<li>因 Spring Boot 2.7.X 和 3.0.X 将分别于 2023 年 11 月 18 日 和 2023 年 11 月 24 日 停止维护，考虑到技术发展趋势，决定停止 Dante Cloud 2.7.X 版本代码的维护，专注 3.1.X 维护及 3.2.X 开发。2.7.16.Final 版本是 Dante Cloud 2.7.X 系列最后一个版本，代码仍旧会保留，可在工程 2.7.X 分支中获取。</li>
</ul>
</li>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.16</li>
<li>[升级] Spring Boot Admin 版本升级至 2.7.11</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.23.5</li>
<li>[升级] fastjson2 版本升级至 2.0.40</li>
<li>[升级] hutool 版本升级至 5.8.22</li>
<li>[升级] wxjava 版本升级至 4.5.5.B</li>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.6.4</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.853</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.85.ALL</li>
<li>[升级] snakeyaml 版本升级至 2.2</li>
</ul>
</li>
<li>其它说明
<ul>
<li>还没拥抱 JDK 17 么？JDK 21 都已经来到了！</li>
</ul>
</li>
</ul>
<h2>v2.7.15.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.15</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 默认数据库名称进行变更，修改为与项目名称一致，方便记忆和使用</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] docker-maven-plugin 版本升级至 0.43.4</li>
<li>[升级] bcprov-jdk15to18 版本升级至 1.76</li>
<li>[升级] guava 版本升级至 32.1.2-jre</li>
<li>[升级] redisson 版本升级至 3.23.3</li>
<li>[升级] minio 版本升级至 8.5.5</li>
<li>[升级] fastjson2 版本升级至 2.0.39</li>
<li>[升级] hutool 版本升级至 5.8.21</li>
<li>[升级] wxjava 版本升级至 4.5.4.B</li>
<li>[升级] mybatis-plus 版本升级至 3.5.3.2</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.835</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.14.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.61.ALL</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.1</li>
<li>[升级] snakeyaml 版本升级至 2.1</li>
</ul>
</li>
</ul>
<h2>v2.7.14.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.14</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 删除 Gateway 服务中无用的 RedisRouteDefinitionRepository 类以及无用的依赖。避免使用该类进行无防护的业务功能开发，导致产生安全问题。</li>
<li>[修复] 修复前端 bpmn-designer 模块在新版 vite 环境下编译模块出错问题。</li>
<li>[修复] 修复前端在新版本 vite-plugin-dts 环境下编译模块出错问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] guava 版本升级至 32.1.1-jre</li>
<li>[升级] redisson 版本升级至 3.23.1</li>
<li>[升级] fastjson2 版本升级至 2.0.36</li>
<li>[升级] transmittable-thread-local 版本升级至 2.14.3</li>
<li>[升级] wxjava 版本升级至 4.5.2.B</li>
<li>[升级] dysmsapi20170525 版本升级至 2.0.24</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.800</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.10.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.13.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.13</li>
<li>[升级] Spring Cloud 版本升级至 2021.0.8</li>
<li>[升级] Debezimu 版本升级至 2.3</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] guava 版本升级至 32.1.0-jre</li>
<li>[升级] bcprov-jdk15to18 版本升级至 1.75</li>
<li>[升级] minio 版本升级至 8.5.4</li>
<li>[升级] wxjava 版本升级至 4.5.1.B</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.787</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.171.ALL</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.0</li>
</ul>
</li>
</ul>
<h2>v2.7.12.1</h2>
<ul>
<li>[升级] spring-security-oauth2-authorization-server 版本升级至 0.4.3</li>
<li>[升级] nacos 版本升级至 2.2.4</li>
<li>[升级] hutool 版本升级至 5.8.20</li>
<li>[升级] docker-maven-plugin 版本升级至 0.43.0</li>
<li>[升级] commons-io 版本升级至 2.13.0</li>
<li>[升级] guava 版本升级至 32.0.1-jre</li>
<li>[升级] redisson 版本升级至 3.22.1</li>
<li>[升级] logstash-logback-encoder 版本升级至 7.4</li>
<li>[升级] skywalking 版本升级至 8.16.0</li>
<li>[升级] minio 版本升级至 8.5.3</li>
<li>[升级] fastjson2 版本升级至 2.0.34</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.781</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.13.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.166.ALL</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.16.3</li>
<li>[升级] jackson 版本升级至 2.15.2</li>
</ul>
<h2>v2.7.12.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.12</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重新配置并统一 maven repositories 和 pluginRepositories。以 dante-engine dependencies 为核心，删除其它工程中，重复和冲突的配置。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] mapstruct-processor 版本升级至 1.5.5.Final</li>
<li>[升级] maven-gpg-plugin 版本升级至 3.1.0</li>
<li>[升级] commons-io 版本升级至 2.12.0</li>
<li>[升级] redisson 版本升级至 3.21.3</li>
<li>[升级] fastjson2 版本升级至 2.0.32</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.757</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.132.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.16</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.16.3</li>
<li>[升级] jackson 版本升级至 2.14.3</li>
</ul>
</li>
</ul>
<h2>v2.7.11.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2021.0.7</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.21.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.748</li>
</ul>
</li>
</ul>
<h2>v2.7.11.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.11</li>
<li>[升级] Spring Authorization Server 版本升级至 0.4.2</li>
<li>[升级] Debezium 版本及相关基础设施版本升级至 2.2</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[漏洞] 修复 Snakeyaml (CVE-2022-1471) 存在反序列化漏洞 和 (CVE-2022-41854) 存在缓冲区溢出漏洞</li>
<li>[新增] 新增服务优雅停机支持</li>
<li>[优化] 优化数据自动初始化脚本放置位置，与新版本代码创建数据表需要启动两个服务机制进行统一。</li>
<li>[修复] 修复自定义社交登录模式中，微信小程序参数获取未补充错误。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] snakeyaml 版本升级至 2.0</li>
<li>[升级] fastjson2 版本升级至 2.0.29</li>
<li>[升级] wxjava 版本升级至 4.5.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.744</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.110.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.10.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 2.2.2</li>
<li>[升级] Spring Authorization Server 版本升级至 0.4.2</li>
<li>[升级] Camunda 版本升级至 7.19.0，同步更新数据库脚本</li>
<li>[升级] Skywalking Agent 版本升级至 8.15.0</li>
<li>[升级] Antisamy 版本升级至 1.7.3，同步升级 XSS 攻击防护策略配置文件</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化 Docker Compose 脚本配置</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] bcprov-jdk15to18 版本升级至 1.73</li>
<li>[升级] fastjson2 版本升级至 2.0.28</li>
<li>[升级] hutool 版本升级至 5.8.18</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.737</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.107.ALL</li>
</ul>
</li>
<li>升级说明
<ul>
<li>因仓库提交文件大小限制，所以本次发布不在上传 Skywalking Agent 相关 Jar 包，有需要请自行下载。</li>
</ul>
</li>
</ul>
<h2>v2.7.10.1</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增 MongoDB 基础 Entity、Repository、Service、Controller 和 MybatisPlus 基础 Controller，方便业务接口代码编写。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] docker-maven-plugin 版本升级至 0.42.1</li>
<li>[升级] redisson 版本升级至 3.20.1</li>
<li>[升级] springdoc 版本升级至 1.7.0</li>
<li>[升级] fastjson2 版本升级至 2.0.27</li>
<li>[升级] hutool 版本升级至 5.8.16</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.731</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.13.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.101.ALL</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.16.2</li>
<li>[升级] xnio 版本升级至 3.8.9.Final</li>
</ul>
</li>
</ul>
<h2>v2.7.10.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.10</li>
<li>[升级] Spring Cloud Alibaba 版本升级至 2021.0.5.0</li>
<li>[漏洞] 修复 Spring DoS 安全漏洞 (CVE-2023-20861)</li>
<li>[漏洞] 修复 commons-fileupload 安全漏洞 (CVE-2023-24998)</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复授权码模式登录页面在最新版本 Spring Authorization Server 下被拦截问题</li>
<li>[修复] 优化前端 Vite 生产模式下 chunk 打包策略，解决 chunk 不合理设置，产生编译后的代码运行异常，导致前端页面无法正常显示问题。</li>
<li>[修复] 修复 docker-compose 脚本中，最新版本 Nacos 缺少新增默认环境变量，导致 Nacos 镜像无法正常启动错误。fix：#I6OMKH (ISSUED by 乌拉松)</li>
<li>[修复] 修复 mysql 数据库初始化脚本。fix：#I6OMKH (ISSUED by 乌拉松)</li>
<li>[修复] 修复在线文档内容与当前最新版本内容不匹配问题。fix：#I6P1F3 (ISSUED by tao)</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-embedder 版本升级至 3.9.1</li>
<li>[升级] maven-compat 版本升级至 3.9.1</li>
<li>[升级] tencentcloud-sdk-java 版本升级至 3.1.720</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.83.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.9.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] nacos 版本升级至 2.2.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 SecurityUtils 无法获取当前登录用户信息问题 fix: #I6KOG6 (ISSUED by 大叔丨小巷)</li>
<li>[修复] 修复 Content/Authorize 页面，页面缩放时，页面可能出现空白 fix: #I6KT7R (ISSUED by dens)</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] springdoc 版本升级至 1.6.15</li>
<li>[升级] fastjson2 版本升级至 2.0.26</li>
<li>[升级] hutool 版本升级至 5.8.15</li>
<li>[升级] wxjava 版本升级至 4.4.9.B</li>
<li>[升级] mybatis 版本升级至 3.5.13</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.715</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.79.ALL</li>
<li>[升级] postgresql 版本升级至 42.6.0</li>
</ul>
</li>
</ul>
<h2>v2.7.9.1</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复单体版 MySQL 数据库初始化脚本错误。fix:#I6HRDP (ISSUED by 柳敏莘)</li>
<li>[修复] 修复查询接口中包含 properties 数组属性会导致 Gateway UrlDecode 失败的问题 fix:#I6HOLA (ISSUED by dens)</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] docker-maven-plugin 版本升级至 0.42.0</li>
<li>[升级] redisson 版本升级至 3.20.0</li>
<li>[升级] hutool 版本升级至 5.8.13</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.706</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.65.ALL</li>
<li>[升级] log4j 版本升级至 2.20.0</li>
</ul>
</li>
</ul>
<h2>v2.7.9.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.9</li>
<li>[升级] Spring Cloud 版本升级至 2021.0.6</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 菜单管理页面增加标题过滤条件</li>
</ul>
</li>
</ul>
<h2>v2.7.8.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Authorization Server 版本升级至 0.4.1</li>
<li>[升级] Skywalking Agent 版本升级至 8.14.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] Debezimu 相关组件版本升级至 2.1</li>
<li>[删除] 删除 yunpian 短信模块及相关依赖</li>
<li>[修复] Dashboard 页面，在切换 Tab 后，echarts 宽度不再自适应，显示成 100px 问题。</li>
<li>[修复] 根据新版 axios typescript 定义，修改 axios 核心代码解决类型提示错误。</li>
<li>[修复] 修复在最新版本 vite-plugin-dts 环境下，编译模块出现较多提示信息问题。</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] docker-maven-plugin 版本升级至 0.41.0</li>
<li>[升级] maven-embedder 版本升级至 3.9.0</li>
<li>[升级] maven-compat 版本升级至 3.9.0</li>
<li>[升级] redisson 版本升级至 3.19.3</li>
<li>[升级] logstash-logback-encoder 版本升级至 7.3</li>
<li>[升级] minio 版本升级至 8.5.2</li>
<li>[升级] fastjson2 版本升级至 2.0.24</li>
<li>[升级] hutool 版本升级至 5.8.12</li>
<li>[升级] okhttps 版本升级至 4.0.1</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.697</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.45.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.14</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.16.1</li>
<li>[升级] dom4j 版本升级至 2.1.4</li>
<li>[升级] jackson 版本升级至 2.14.2</li>
<li>[升级] postgresql 版本升级至 42.5.4</li>
</ul>
</li>
</ul>
<h2>v2.7.8.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.8 fix：#I6B4UT</li>
<li>[重构] 重构 Athena 工程模块结构，简化该工程多模块结构，删除多余无意义的结构示例性结构，各模块的用途及含义更简洁清晰</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 针对某一类权限校验并不严格的接口，新增只校验是否认证、不校验授权的权限校验策略，以提高权限校验的灵活度，降低权限配置维护的工作量。</li>
<li>[新增] 只校验是否认证、不校验授权的权限校验策略配置</li>
<li>[修复] 修复极端情况下，权限缓存数据丢失，接口请求将跳过权限验证的潜在安全问题。</li>
<li>[修复] 修复 Spring Cloud Gateway 长期运行后出现 io.netty.util.internal.OutOfDirectMemoryError 问题。fix：#I6AZJX (ISSUED by 狂练胸肌李大懒)</li>
<li>[优化] 将默认 WebSocket 连接地址设置为 permitAll 权限，跳过资源服务器检测，由 WebSocket 模块自主进行权限校验。</li>
<li>[优化] 基于 axios 最新版本 typescript 定义，优化前端 @herodotus/core 模块 axios 核心代码，避免编译过程中出现类型校验错误。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] jetcache 版本升级至 2.7.3</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.681</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.37.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.7.5</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增融合 Stomp WebSocket、私信、公告等功能的独立消息服务。支持前端与后端采用 WebSocket 和 REST 接口互发消息。</li>
<li>[新增] 新增私信、公告发送，及新消息提醒、基于私信对话浏览信息功能</li>
<li>[新增] 新增手工解析 Token 信息机制，同时支持 JWT Token 和 Opaque Token。</li>
<li>[新增] WebSocket 模块，支持 WebSocket Token 鉴权及登录用户信息解析功能</li>
<li>[新增] 新增实时在线用户统计及同步实时刷新功能</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构基于 Redis 的 Session 共享配置代码，采用更合理的配置实现分布式微服务 Session 共享</li>
<li>[修复] 优化微服务分布式 Session 共享配置，解决共享 Session 不一致问题。</li>
<li>[修复] Spring Authorization Server 授权码模式下，WebSocket 获取的 Principal 类型无法解析问题。</li>
<li>[修复] Antisamy XSS 校验结果包含换行符，导致 WebSocket 连接异常问题。</li>
<li>[修复] 修复 WebSocket 握手阶段获取 Session 异常抛出 NullPointException 问题。</li>
</ul>
</li>
</ul>
<h2>v2.7.7.4</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 重构 Spring Authorization Server OAuth2 相关代码分包和模块结构，逻辑更内聚、职责更清晰、模块引用依赖更简洁</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 XSS Request 包装器 Parameter 方法错误，导致无法获取参数错误</li>
<li>[新增] 新增手工解析 Token 信息机制，同时支持 JWT Token 和 Opaque Token。</li>
</ul>
</li>
</ul>
<h2>v2.7.7.3</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 删除 engine-protect 模块，相关代码合并至 engine-rest 模块中</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 在 JWT Token、Opaque Token 和 IdToken 新增用户信息扩展字段，以方便前端直接使用，减少与后端的交互。</li>
<li>[修复] 修复 前端使用授权码模式登录，授权码地址多出 &quot;/api&quot; 代理问题。fix: #I69XZC</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-embedder 版本升级至 3.8.7</li>
<li>[升级] maven-compat 版本升级至 3.8.7</li>
<li>[升级] redisson 版本升级至 3.19.1</li>
<li>[升级] minio 版本升级至 3.5.1</li>
<li>[升级] fastjson2 版本升级至 2.0.23</li>
<li>[升级] wxjava 版本升级至 4.4.8.B</li>
<li>[升级] tencentcloud-sdk-java 版本升级至 3.1.678</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.32.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.7.2</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复条件查询 Token 参数错误，导致操作异常抛出 Unable to locate Attribute with the the given name [authorizationCode] 异常错误。fix：#I69CN8</li>
</ul>
</li>
</ul>
<h2>v2.7.7.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 2.7.10</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复遗漏 Spring Authorization Server 0.4.0 以后新增字段 authorized_scopes 问题</li>
<li>[修复] 修复自定义授权模式使用 Refresh Token 重新申请 Token 抛错问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] fastjson2 版本升级至 2.0.22</li>
<li>[升级] hutool 版本升级至 5.8.11</li>
<li>[升级] wxjava 版本升级至 4.4.7.B</li>
<li>[升级] mybatis-plus-boot-starter 版本升级至 3.5.3.1</li>
<li>[升级] mybatis-plus-generator 版本升级至 3.5.3.1</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.667</li>
<li>[升级] Redisson 版本升级至 3.19.1</li>
</ul>
</li>
</ul>
<h2>v2.7.7.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.7</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] docker-maven-plugin 版本升级至 0.40.3</li>
<li>[升级] redisson 版本升级至 3.19.0</li>
<li>[升级] springdoc 版本升级至 1.6.14</li>
<li>[升级] fastjson2 版本升级至 2.0.21</li>
<li>[升级] jetcache 版本升级至 2.7.2</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.657</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.9.ALL</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.12.1</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.13</li>
</ul>
</li>
</ul>
<h2>v2.7.6.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 2.2.0</li>
<li>[升级] Spring Boot Admin 版本升级至 2.7.9</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 前端 API 接口调用，修改为 proxy 代理模式，解决前后端跨域导致前端不创建 Cookie，后端 Session Id 不一致，Session 共享不生效问题。</li>
<li>[修复] 修复华为云短信发送请求体类型设置错误</li>
<li>[修复] 修复 MySQL 数据库初始化脚本中 JSON 数据格式错误，导致运行抛出参数校验错误问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.18.1</li>
<li>[升级] wxjava 版本升级至 4.4.6.B</li>
<li>[升级] dysmsapi20170525 版本升级至 2.0.23</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.648</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.0.ALL</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.16.0</li>
</ul>
</li>
</ul>
<h2>v2.7.6.0</h2>
<ul>
<li>主要更新
<ul>
<li>Spring Boot 版本升级至 2.7.6</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 解决使用 edge 浏览器并使用 feign，报错 Unexpected char 0x0a（PR by 狂练胸肌的李大懒）</li>
<li>[修复] 修复前端唯一性字段在新建、编辑不同状态下校验不准确、状态不合理问题。解决导致编辑状态下唯一性字段数据未修改仍会校验失败问题。</li>
<li>[修复] 优化微服务分布式 Session 共享配置，解决共享 Session 不一致问题。</li>
<li>[新增] 数据初始化脚本补充 OIDC 客户端注册相关信息</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] fastjson2 版本升级至 2.0.20</li>
<li>[升级] dysmsapi20170525 版本升级至 2.0.23</li>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.6.3</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.641</li>
<li>[升级] alipay-sdk-java 版本升级至 4.34.71.ALL</li>
<li>[升级] aliyun-java-sdk-green 版本升级至 3.6.6</li>
<li>[升级] postgresql 版本升级至 42.5.1</li>
<li>[升级] jackson 版本升级至 2.14.1</li>
</ul>
</li>
</ul>
<h2>v2.7.5.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Authorization Server 版本升级至 0.4.0</li>
<li>[重构] 按照 Spring Authorization Server 0.4.0 最新代码结构和 API 用法，重构认证授权相关代码。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 Token Settings 授权码模式 Code 有效时间字段。</li>
<li>[优化] OAuth2 Application 中 accessTokenTimeToLive、refreshTokenTimeToLive、authorizationCodeTimeToLive 属性的默认值，修改为与 Spring Authorization Server TokenSettings 一致。</li>
<li>[优化] 优化数据库初始化脚本中的初始数据，与 Spring Authorization Server 0.4.0 最新代码适配</li>
<li>[优化] 优化分布式微服务 Session 共享配置。解决 Session 共享在 Gateway 中，出现注入重复导致无法启动问题。</li>
<li>[升级] 升级 Antisamy XSS 攻击防护规则脚本。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] antisamy 版本升级至 1.7.2</li>
<li>[升级] springdoc 版本升级至 1.6.13</li>
<li>[升级] minio 版本升级至 8.4.6</li>
<li>[升级] fastjson2 版本升级至 2.0.19</li>
<li>[升级] Hutool 版本升级至 5.8.10</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.633</li>
<li>[升级] alipay-sdk-java 版本升级至 4.34.52.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.5.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2021.0.5</li>
<li>[升级] Skywalking Agent 版本升级至 8.13.0</li>
<li>[新增] 在 Nacos 中增加 logback 配置，新增服务可外部化动态读取 logback.xml 配置模式。以便于更加灵活的进行日志输出配置。</li>
<li>[新增] 外部配置 logback.xml 中，增加 Skywalking 日志上报、ELK 日志中心日志收集、Skywalking TraceId 等支持。同时提供常规及 MDC 两种配置。</li>
<li>[新增] 在统一响应实体 Result 增加 TraceId 信息，在开启 Skywalking Tracing 的情况下，可在返回结果中，统一增加 TraceId，方便跟踪和调试。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 优化 Gateway 请求信息拦截机制，增加对 Feign 内部无权限标记的拦截，修复内部接口被外部直接调用的安全漏洞</li>
<li>[升级] 升级 Camunda Open API 描述文件版本至 7.18.0</li>
<li>[新增] 新增 logback-spring.xml 和 logback-spring-mdc.xml 两个 Nacos 配置文件</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] jackson 版本升级至 2.14.0</li>
<li>[升级] fastjson2 版本升级至 2.0.18</li>
<li>[升级] wxjava 版本升级至 4.4.4.B</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.627</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.12.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.34.47.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.5.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本更新至 2.7.7</li>
<li>[修复] 修复在 Opaque Token 格式下，通过客户端模式(Client Credentials) 获取 Token 后，基于 Scope 进行接口权限验证失败，出现&quot;OAuth2ClientAuthenticationToken is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin&quot; 问题。</li>
</ul>
</li>
<li>其它更新<br>
前端尝鲜版（dante-cloud-ui monorepo 分支）增加体验性功能
<ul>
<li>[新增] API 模块封装 Camunda Deployment、ProcessDefinition、ProcessInstance 相关操作 API，结合 Camunda API 说明封装相关 Typescript 声明文件。</li>
<li>[新增] 新增 Camunda Modeler 在线编辑结果上传部署至服务器端功能。</li>
<li>[新增] 前端增加 Apps Widgets 列表功能，可方便快捷地进入流程编辑器和动态表单设计器界面。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] fastjson2 版本升级至 2.0.17</li>
<li>[升级] jackson 版本强制升级至 2.14.0-rc3</li>
<li>[升级] wxjava 版本升级至 4.4.3.B</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.616</li>
<li>[升级] alipay-sdk-java 版本升级至 4.34.22.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.12</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.12.0</li>
</ul>
</li>
<li>友情提示
<ul>
<li>如使用阿里 Maven 镜像，出现本项目或其它依赖包无法下载的情况，请切换其它 Maven 镜像，比如腾讯 Maven 镜像。</li>
<li>在 Maven Settings 文件中，将所需使用的 Maven Mirror 设置为第一个即可。</li>
</ul>
</li>
</ul>
<h2>v2.7.5.0</h2>
<ul>
<li>
<p>重要更新</p>
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.5</li>
<li>[升级] Debezimu 版本升级至 2.0，相关 Docker Compose 中配置 Debezimu Kafka Zookeeper 镜像版本同步升级至 2.0。</li>
<li>[变更] 代码开发、编译以及 Docker 基础镜像所使用 JDK 全面更换为 Bellsoft Liberica OpenJDK 发行版，以避免 Oracle JDK 版权问题。仍旧兼容 Oracle JDK，原有自主封装 Oracle JDK 已不再使用。fix：#I5WRXI (ISSUED by 疯狂的狮子 Li)</li>
<li>[变更] 修改部分核心代码结构和依赖关系，严格遵循最小依赖的原则，规避模块依赖关系中引入过多的不必要依赖，引起冗余的组件或 Bean 注入问题。</li>
</ul>
</li>
<li>
<p>其它更新</p>
<ul>
<li>[新增] 增加腾讯备选 Maven 仓库配置，以临时解决本月阿里仓库停更问题</li>
<li>[优化] 优化 Recluse SMS 短信模块依赖配置，将通用依赖改为仓库下载方式。</li>
<li>[优化] 修改 MySQL JDBC Driver 依赖 ArtifactId，替换为最新 ID，以规避 Maven 编译出现 WARN 提示</li>
<li>[修复] 修复前端工程中，Scope 配置接口权限功能抛错导致操作异常问题。</li>
</ul>
</li>
<li>
<p>依赖升级</p>
<ul>
<li>[升级] transmittable-thread-local 版本升级至 2.14.2</li>
<li>[升级] fastjson2 版本升级至 2.0.16</li>
<li>[升级] hutool 版本升级至 5.8.9</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.611</li>
</ul>
</li>
</ul>
<h2>v2.7.4.5</h2>
<ul>
<li>
<p>重要更新</p>
<ul>
<li>[核心组件升级] Spring Boot Admin 版本升级至 2.7.6</li>
<li>[核心组件升级] Nacos 版本升级至 2.1.2</li>
<li>[核心组件升级] Camunda 版本升级至 7.18.0，同时更新 Camunda Open API 描述文件</li>
<li>[核心组件升级] 自主封装 Oracle JDK 版本升级至 8u341, 系统所涉及的所有 Docker 均已修改为使用该版本。该镜像已经上传至 Docker Hub，可以直接使用。</li>
</ul>
</li>
<li>
<p>其它更新</p>
<ul>
<li>[修复] 修复防刷机制控制范围过窄，缺少对用户标识的区分问题。fix: #I5WF5O (ISSUED by <code>我问这瓜保熟吗</code>)</li>
<li>[优化] 优化系统登录成功记录代码，增加代码健壮性。</li>
</ul>
</li>
<li>
<p>依赖更新</p>
<ul>
<li>[升级] springdoc 版本至 1.6.12</li>
<li>[升级] transmittable-thread-local 版本至 2.14.1</li>
<li>[升级] tencentcloud-sdk-java 版本至 3.1.609</li>
</ul>
</li>
<li>
<p>友情提示</p>
<ul>
<li>本月阿里云 Maven 仓库升级，暂停同步更新，新发布的包均无法从阿里云 Maven 仓库更新。请直接使用中央仓库或增加其它备份 Maven 仓库镜像</li>
</ul>
</li>
</ul>
<h2>v2.7.4.4</h2>
<ul>
<li>重要更新
<ul>
<li>[新增] 扩展 Spring Authorization Server 授权码模式(AuthorizationCode), 在授权码模式返回 Token 中增加系统用户信息，减少二次请求获取用户信息</li>
<li>[新增] 在原有 ResourceOwnershipPassword、SocialCredentials 认证模式基础上，补充增加 OIDC IdToken 支持。</li>
<li>[新增] 扩展 OIDC IdToken 及 OIDC /userinfo 端点信息内容，与现有用户权限体系融合，支持使用 Opaque Token 读取并解析用户信息。</li>
<li>[优化] 整体优化 AuthorizationCode、ClientCredentials、ResourceOwnershipPassword、SocialCredentials 四种认证模式，除保留原有 JWT Token 的所有支持内容外，对 OIDC IdToken、Opaque Token 与现有权限体系进行了全面融合与支持</li>
<li>[安全] 修复 jackson-databind 拒绝服务漏洞 (CVE-2022-42003)</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构自定义 AuthorizationCode、ClientCredentials、ResourceOwnershipPassword、SocialCredentials 四种认证模式代码，抽象创建 AccessToken、RefreshToken、IdToken 等操作公共代码，代码逻辑更清晰易懂，同时减少重复冗余代码。</li>
<li>[重构] 重构 oauth2ResourceServer 配置代码，统一资源服务器 Opaque Token 和 JWT Token 切换策略化配置逻辑。</li>
<li>[修复] 在 OAuth2 OIDC 认证方式下，/userinfo 接口调用始终为 401 问题</li>
<li>[修复] 修复开启 Session 共享功能后，Gateway Session 相关内容注入重复冲突问题。</li>
<li>[修复] 在 AuthorizationCode、SocialCredentials 认证模式下，前端菜单加载异常问题。</li>
<li>[新增] 前端新增 Spring Authorization Server 授权码模式(AuthorizationCode) 登录方式。</li>
<li>[新增] 优化前端 OAuth2 认证所有接口代码，新增 OIDC IdToken 开关配置，在前端即可根据使用需求决定使用 OIDC (OpenID Connect) 模式还是传统 OAuth2 模式。</li>
<li>[优化] 优化前后端用户基本信息提供和使用机制。后端会根据前端 IdToken 开启与否状态，策略化提供 IdToken 或自定义 Token 补充用户信息(注：两种方式均无需通过二次请求后端获取用户信息)。</li>
<li>[优化] 前端 echarts 使用方式，变更为按需加载，解决前端工程调试过程中，Dashboard 页面加载缓慢问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] commons-text 版本至 1.10.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版本至 3.1.608</li>
<li>[升级] alipay-sdk-java 版本至 4.34.14.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.4.3</h2>
<ul>
<li>
<p>主要更新</p>
<ul>
<li>[新增] 新增 Opaque Token (不透明令牌) 支持，并将其设置为默认 Token 格式，降低 JWT Token 被捕获解析的风险。仍旧保留 JWT Token 格式支持，可通过修改配置参数进行切换。</li>
<li>[重构] 使用 Spring Authorization Server 标准的 Token 废弃机制，重构同一账号在同类型终端异处登录自动踢出(Kick Out)前一个登录用户功能。</li>
<li>[修复] 结合 Opaque Token 修复 Kick Out 不生效问题。(注：Kick Out 仅在 Opaque Token 格式下有效。因 JWT Token 自身机制以及 Spring Authorization Server Token 验证实现方式与 Spring Security OAuth 的不同，使用 JWT Token 仅能实现 Token 过期验证，是无法实现 Kick Out 功能 )</li>
<li>[新增] Monorepo 版前端工程新增 Dante Cloud 授权码登录预览功能</li>
</ul>
</li>
<li>
<p>其它更新</p>
<ul>
<li>[重构] 重构部分枚举类，新增 Target 枚举，统一所有涉及本地及远程判断的枚举。</li>
<li>[重构] 由于 Okhttps 大版本升级，包名进行了变更，因此涉及一部分代码修改重构。</li>
<li>[删除] 删除无用的、自定义的 OAuth2ClientAuthorizationToken 类。</li>
<li>[删除] 删除 AuthorizationServerConfiguration 中，冗余的 Authorization Server 配置</li>
<li>[修复] 修复单体版参数配置错误，导致启动抛错问题。</li>
<li>[优化] 结合 Opaque Token 机制，优化前端工程应用管理功能。</li>
</ul>
</li>
<li>
<p>依赖更新</p>
<ul>
<li>[升级] mapstruct-processor 版本至 1.5.3.Final</li>
<li>[升级] bcprov-jdk15to18 版本至 1.72</li>
<li>[升级] okhttps 版本至 4.0.0</li>
<li>[升级] dysmsapi20170525 版本至 2.0.22</li>
<li>[升级] alipay-sdk-java 版本至 4.34.8.ALL</li>
</ul>
</li>
<li>
<p>升级指南</p>
<p>本次更新涉及系统核心逻辑变化，升级新版代码涉及核心参数和数据的变更。如果是全新部署，可以跳过以下内容；如果是在系统，可以参考以下内容进行最小化修改。</p>
<ol>
<li>修改 dante-cloud-platform.yaml 配置，增加 opaque 相关配置，具体修改内容参见工程中对应的配置文件。</li>
<li>手动修改数据和参数
<ul>
<li>修改数据表 oauth2_registered_client 中 token_settings 字段 JSON 数据，将其中 OAuth2TokenFormat 的值 “self-contained” 修改为 “reference”。</li>
<li>修改 数据表 oauth2_application 中 access_token_format 字段的值，将其值从“0”改“1”</li>
<li>修改完成之后，需要清理 Redis 缓存，并重新启动服务。</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2>v2.7.4.2</h2>
<ul>
<li>重要更新
<ul>
<li>[新增] 基于 pnpm 的 monorepo 模式新版前端应用，预览版上线
<ul>
<li>该版本基于 pnpm，采用 monorepo 模式对前端工程进行重构</li>
<li>抽取 utils、components、apis、bpmn-designer 等相关代码，形成共享模块。</li>
<li>共享模块已进行优化配置，可编译成独立的组件，单独以组件形式进行发布。</li>
<li>代码以共享模块的方式进行单独维护开发，降低现有工程代码复杂度，便于后续功能的扩展和代码的复用。</li>
</ul>
</li>
<li>[重构] 重构现有前端工程 TabView 相关代码和实现逻辑
<ul>
<li>完全基于 Quasar QRouteTab 组件的自身特性对 TabView 进行重构，将 TabView 与 Vue-Router 更好的融合，实现逻辑更清晰也更易维护。</li>
<li>新增 Tab 页局部刷新、关闭左侧、关闭右侧以及 Tab 页面保留等 TabView 操作功能</li>
<li>重新设计常规 Tab 页面、详情 Tab 页面，开启、关闭、跳转、显示位置等操作逻辑。</li>
<li>将 TabView 操作与 Tab 当前各种状态有机结合，规避不合理 Tab 操作出现。</li>
<li>重构 TabView Pinia Store 操作代码逻辑，合并相近逻辑，删除重复或无用代码。</li>
</ul>
</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 在最新 Vite 和 Vue-tsc 环境下出现 <code>Write operation failed: computed value is readonly</code> 问题。</li>
<li>[修复] 优化单体版本应用配置，修正第三方登录配置错误，补充单体版多租户配置</li>
<li>[优化] 优化 Nacos 配置文件，默认关闭多租户配置。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本至 3.17.7</li>
<li>[升级] fastjson2 版本至 2.0.15</li>
<li>[升级] dysmsapi20170525 版本至 2.0.21</li>
<li>[升级] alipay-sdk-java 版本至 4.34.0.ALL</li>
</ul>
</li>
<li>额外说明
<ul>
<li>想要尝鲜 pnpm monorepo 版本前端，请检出 dante-cloud-ui 工程中，monorepo 分支代码。</li>
<li>构建 monorepo 版本前端，是为扩展更多功能、增加应用级功能做铺垫。</li>
<li>微前端架构技术已经走通，未在 monorepo 版本中直接实现，主要考虑到微前端更适合大型前端拆解、多前端项目整合情况。构建一套完善的微前端应用，研发投入大、潜在问题多、使用复杂度高，并不适合本项目目前大多数用户的实际用途。采用一些性价比更高的替代方案，比如说 Nginx 的多应用似乎更可取。因此，目前尝试推出 monorepo 版前端，作为基础或过渡版本。在此版本基础之上，构建&quot;微前端&quot;应用，特别是基于 Micro-App 的微前端会非常容易。会适时并结合用户需求，再考虑是否转换为微前端架构前端应用。</li>
</ul>
</li>
</ul>
<h2>v2.7.4.1</h2>
<ul>
<li>重要更新
<ul>
<li>[新增] 新增 Spring Authorization Server 同一账号，在同类型终端异处登录自动踢出(Kick Out)前一个登录用户功能。可通过配置参数修改是否开启。该项功能与应用安全合规性检查项：同一终端不允许重复登录互斥(即不能同时开启，同一时间只能开启一项)。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化 Nacos 配置文件，删除 Redisson 统一开关配置，默认不启用 Redisson。</li>
<li>[优化] 改进在线文档部分内容描述不正确问题 。fix: #I5TSEM (ISSUED by 等待是如此漫长)</li>
<li>[优化] 优化数据自动初始化脚本放置位置，与新版本代码创建数据表需要启动两个服务机制进行统一。</li>
<li>[修复] 增加 Redisson 密码配置，修复在 Redis 5 版本下设置密码后 Redisson 无法连接 Redis 问题。fix: #I5TS0Y (ISSUED by 等待是如此漫长)</li>
<li>[修复] 修复前端工程，配置人员归属功能人员选择页面无法点开的问题。</li>
<li>[修复] 修复前端工程，使用最新版 vue-router 会在控制台抛出 <code>You are running the esm-bundler build of vue-i18n. It is recommended to configure your bundler to explicitly replace feature flag globals with boolean literals to get proper tree-shaking in the final bundle.</code> 问题</li>
<li>[修复] 修复删除无用 Token 抛出 No EntityManager with actual transaction available for current thread 错误</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] minio 版本至 8.4.5</li>
<li>[升级] hutool 版本至 5.8.8</li>
<li>[升级] wxjava 版本至 4.4.2.B</li>
<li>[升级] dysmsapi20170525 版本至 2.0.20</li>
<li>[升级] tencentcloud-sdk-java 版本至 3.1.600</li>
<li>[升级] alipay-sdk-java 版本至 4.33.60.ALL</li>
<li>[升级] aliyun-sdk-oss 版本至 3.15.2</li>
<li>[升级] snakeyaml 版本至 1.33</li>
<li>[升级] 前端工程所有依赖包均升级至最新版本</li>
</ul>
</li>
</ul>
<h2>v2.7.4.0</h2>
<ul>
<li>重要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.4</li>
<li>[修复] 优化 Spring Authorization Server 客户端模式下，根据 Scope 分配权限逻辑。使用统一逻辑修复客户端模式下，Scope 权限对应接口始终返回“没有权限访问”问题</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 OAuth2 Scope 分配权限接口实现逻辑错误问题。</li>
<li>[修复] 修复前端 OAuth2 Scope 分配权限功能传递参数不足，导致权限无法增加问题。</li>
<li>[修复] 修复在新版本 Spring Boot 环境下，@EntityListeners 复发获取 ApplicationContext 导致角色权限变更抛空错误</li>
<li>[修复] 优化前端 Token 过期校验逻辑，修复在使用 Refresh Token 的情况下，不应该再进行 Token 是否过期校验，导致 Refresh Token 无法使用问题。fix： #I5SKFS (ISSUED by 狂练胸肌的李大懒)</li>
<li>[修复]修复前端工程打卡编辑页面后，调整浏览器大小后，所有 tab 都显示空白问题。fix：#I5SB49 (ISSUED by SimonLiu)</li>
</ul>
</li>
</ul>
<h2>v2.7.3.6</h2>
<ul>
<li>重要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 2.7.5</li>
<li>[升级] 前端工程 Camunda Bpmn 在线编辑器核心组件大版本升级</li>
<li>[安全] 强制升级 xnio 版本至 3.8.8.Final，修复安全漏洞 (CVE-2022-0084)</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 Spring Authorization Server 历史 Token 清理逻辑</li>
<li>[修复] 前端工程编译结果与 Nginx 一起打包为 Docker 后，在浏览器刷新页面出现 404 问题。</li>
<li>[优化] 优化 Token 过期检测逻辑，调整时钟偏移(Clock Skew)，与 Spring Security OAuth2 Jose 默认实现逻辑保持一致。fix: #I5RWGA（ISSUED by 狂练胸肌李大懒）</li>
<li>[优化] 优化前端封装 Axios 代码中阻止重复提交属性名称，将其修改为更容易理解的变量名称。</li>
<li>[优化] 优化自定义 Spring Authorization Server JPA 模块部分查询操作，启用基于 Jetcache 自定义的 JPA 多级缓存支持，提升数据查询效率</li>
<li>[优化] 优化部分代码日志输出级别，提升控制台日志输出内容的可聚焦性</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>log4j2 版本升级至 2.19.0</li>
<li>minio 版本升级 8.4.4</li>
<li>fastjson2 版本升级至 2.0.14</li>
<li>hutool 版本升级至 5.8.7</li>
<li>mybatis 版本升级至 3.5.11</li>
<li>tencentcloud-sdk-java-sms 版本升级至 3.1.597</li>
</ul>
</li>
</ul>
<h2>v2.7.3.5</h2>
<ul>
<li>重要更新
<ul>
<li>Spring Boot Alibaba 版本升级至 2021.0.4.0</li>
<li>自主封装 Oracle JDK 版本升级至 8u333, 系统所涉及的所有 Docker 均已修改为使用该版本。该镜像已经上传至 Docker Hub，可以直接使用。</li>
<li>自主封装 Sentinel Dashboard 基于 Sentinel 1.8.5 进行了全面升级。
<ul>
<li>Sentinel Dashboard 基础 Spring Boot 版本已升级至 2.5.14 (Spring Boot 版本升级至 2.6.X 或 2.7.X，会出现无法登陆的情况，所以暂时只能升级至 2.5. X)</li>
<li>使用 Dante Engine 统一代码，优化自定义的基于 InfluxDB 的 Sentinel Dashboard 数据持久化存储代码及 Dockerfile 参数，可通过修改 docker-compose 中的参数动态开启或关闭持久化功能</li>
<li>使用 Dante Engine 统一代码，优化自定义的基于 Nacos 的 Sentinel Dashboard 流控规则存储及 Dockerfile 参数，可通过修改 docker-compose 中的参数动态开启或关闭保存流控规则至 Nacos 功能。</li>
<li>该镜像已经上传至 Docker Hub，通过命令 docker push herodotus/sentinel-dashboard:latest(或者 1.8.5) 可以直接获取使用。</li>
</ul>
</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 用户文档列表中无结果时也要更新列表数据,防止不在第一页时发两遍请求。(感谢 狂练胸肌李大懒提交的 PR)</li>
<li>[修复] 强制升级部分组件依赖的 snakeyaml 版本， 修复低版本 snakeyaml 携带的 CVE-2022-25857，CVE-2022-38752，CVE-2022-38749 安全漏洞问题。</li>
<li>[优化] 增加是否使用自动获取 Refresh Token 判断，在使用 Refresh Token 模式下，禁用关闭浏览器自动最退出系统功能。</li>
<li>[升级] 前端工程 Vite 版本升级至 3.1.1</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>log4j 版本升级至 2.18.0</li>
<li>wxjava 版本升级至 4.4.1.B</li>
<li>aliyun-java-sdk-core 版本升级至 4.6.2</li>
<li>tencentcloud-sdk-java-sms 版本升级至 3.1.593</li>
<li>alipay-sdk-java 版本升级至 4.33.42.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.3.4</h2>
<ul>
<li>重要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2021.0.4</li>
<li>[升级] Skywalking Agent 版本升级至 8.12.0</li>
<li>[新增] 基于 JPA 的多租户系统支持，支持 Database 和 Schema 两种模式，可通过配置进行开启和关闭。</li>
<li>[重构] 基于 JetCache 的自定义 Hibernate 二级缓存，支持多租户模式下数据的分布式多级缓存。</li>
<li>[重构] 重构前端详情页面参数的传递方式，解决 vue-router 自 4.1.4 版本不再建议使用 push param 传递参数而导致的新增、编辑功能不可用问题。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化部分代码日志输出内容及日志输出级别</li>
<li>[优化] 优化基于 JetCache 的 Hibernate 二级缓存代码</li>
<li>[升级] 升级 antisamy XSS 防护配置文件</li>
<li>[修复] 临时修复 BPMN.js 在线工作流编辑器，在第一次加载页面时抛错无法显示 Canvas 和 Property Panel 问题。</li>
<li>[修复] 第三方社交登录 logo 在生产环境下无法正常显示问题。</li>
<li>[优化] 优化服务配置，将第三方社交登录相关配置移至 Nacos 方便修改。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>antisamy 版本升级至 1.7.1</li>
<li>hutool 版本升级至 5.8.6</li>
<li>tencentcloud-sdk-java-sms 版本升级至 3.1.590</li>
<li>fastjson2 版本升级至 2.0.13</li>
<li>alipay-sdk-java 版本升级至 4.33.39.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.3.3</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 前端根据后端配置的第三方社交登录信息，在前端登录页面自动生成第三方社交登录按钮。新增常见第三方社交登录 logo 图标，根据后端配置自动显示。</li>
<li>[新增] 新增 JustAuth 的第三方社交登录前端 Callback 处理逻辑，让第三方社交登录逻辑更合理清晰</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修改社交登录示例配置格式，解决错误配置格式导致第三方社交登录核心配置信息无法注入问题</li>
<li>[修复] 社交登录处理器 Bean 名称与 微信小程序登录处理器 Bean 名称混淆问题。</li>
<li>[优化] 优化外部登录接入模块 (Access 模块) 部分 Exception 代码，将其融入平台整体错误体系。</li>
<li>[优化] 去除 dante-engine 中无用的依赖配置</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>tencentcloud-sdk-java 版本升级至 3.1.587</li>
</ul>
</li>
<li>友情提示
<ul>
<li>目前前端工程支持第三方社交登录，必须改为 History 模式，暂不支持 Hash 模式。</li>
<li>因很多常用第三方登录，需要以企业信息进行注册。所以第三方登录并未覆盖所有情况以及参数测试，需根据实际使用情况自行增减。也欢迎提交 PR。</li>
</ul>
</li>
</ul>
<h2>v2.7.3.2</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 强制升级 postgresql jdbc driver 依赖包版本，解决 spring boot 2.7.3 默认依赖低版本 postgresql jdbc driver 携带的 SQL 注入漏洞（CVE-2022-31197）问题。</li>
<li>[修复] 修复前端工程 keep-alive 不生效问题。fix：#I5ODHH （ISSUE By jokeway）</li>
<li>[修复] 修复前端工程因升级 vue-router 最新版本，导致修改和编辑功能不可用问题。</li>
<li>[修复] 修复前端工程因升级 Bpmn 和 Camunda 相关依赖包导致工作流在线编辑不可用问题。</li>
<li>[重构] 将 Influxdb 相关模块调整至新增的 NoSQL 模块组，删除原有 temporal 相关模块。</li>
<li>[新增] 新增 CouchDB 数据库集成模块</li>
<li>[删除] 删除已有 Cmdb 相关模块和代码，以及 SQL 脚本配置，减少用户对该模块的误解。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>redisson 版本升级至 3.17.6</li>
<li>springdoc 版本升级至 1.6.11</li>
<li>alipay-sdk-java 版本升级至 4.33.26.ALL</li>
</ul>
</li>
<li>友情提示
<ul>
<li>最新版本 vue-router、Bpmn 和 Camunda 相关组件存在破坏性升级，会导致前端工程已有功能不可用，因此不要随意升级依赖包版本，使用工程指定版本。</li>
</ul>
</li>
</ul>
<h2>v2.7.3.1</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 前端工程新增用户名非法字符检查，仅支持数字、字符、下划线和减号</li>
<li>[新增] 前端工程新增日志审计数据导出为 Excel 功能</li>
<li>[修复] 前端 Tabview 组件，当只剩一个 Tab 时，不应允许再关闭问题。(感谢 jokeway 提交的 PR)</li>
<li>[修复] 前端解决当打开的 Tab 超出当前显示区域时，新开的 Tab 无法定位并显示的问题。(感谢 jokeway 提交的 PR)</li>
<li>[修复] 优化应用安全合规性检查项：统一终端不允许重复登录相关代码逻辑。解决原有逻辑删除已有认证信息，导致 refresh token 失效问题。fix：#I5NYX2 ( ISSUE By 狂练胸肌李大懒)</li>
<li>[修复] 修复系统错误反馈信息配置错误。fix：#I5N1VA ( ISSUE By 未来)</li>
<li>[优化] 优化基于 JPA 的自定义 Spring Authorization Server AuthorizationService 数据访问代码。</li>
<li>[优化] 优化 Spring Authorization Server 错误信息描述和错误码，可以更加准确的区分 Access Token 和 Refresh Token 过期的状态。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>jetcache 版本升级至 2.7.0 正式版</li>
<li>fastjson2 版本升级至 2.0.12</li>
<li>weixin-java-sdk 版本升级 4.4.0</li>
<li>alipay-sdk-java 版本升级 4.33.12.ALL</li>
<li>com.baidu.aip 版本升级至 4.16.11</li>
</ul>
</li>
</ul>
<h2>v2.7.3.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 2.7.3</li>
<li>[新增] 新增 OAuth2 Token 过期后，用户可无感知自动获取新 Token 机制。(感谢 <code>狂练胸肌李大懒</code> 提交的 PR)</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 应用安全合规代码，在容器环境下，受监控 Agent 影响，产生 Consider injecting the bean as one of its interfaces orforcing the use of CGLib-based proxiesby setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching 错误问题。</li>
<li>[修复] 修复自定义 Spring Authorization Server 认证模式，因 Token 必要参数缺失，导致通过 Refresh Token 获取新 Access Token 时抛出空指针错误。fix: #I5MF3B</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>protobuf-java 版本升级至 3.21.5</li>
<li>springdoc 版本升级至 1.6.10</li>
<li>tencentcloud-sdk-java-sms 版本升级至 3.1.572</li>
</ul>
</li>
</ul>
<h2>v2.7.2.6</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 增加手机短信验证码登录沙盒验证测试模式。在此模式下，无须开通短信通道，就可以进行手机短信验证码注册和登录验证测试。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 强制升级 tea-xml 和 dom4j 依赖包版本，去除低版本 dom4j 携带的安全漏洞问题。</li>
<li>[修复] 修复 Spring Authorization Server 自定义 Social Credentials 认证模式下，认证类型校验错误，导致手机验证码登录失败问题。</li>
<li>[修复] 修复自定义 Social Credentials 社会化登录参数解析器配置遗漏，导致无法正确解析参数错误。</li>
<li>[修复] 在远程访问获取用户数据模式下，Social Credentials 社会化登录第三方用户交换处理器未正确注入问题。</li>
<li>[修复] 自定义 Social Credentials 认证模式，前后端数据传输的加密数据解密失败问题。</li>
<li>[修复] OpenFeign 远程调用接口，传递实体对象数据丢失问题。</li>
<li>[优化] 优化自定义 Social Credentials 认证模式手机验证码登录等参数合规性校验逻辑。</li>
<li>[优化] 优化 Nacos 输出至日志中心的服务日志数据级别及包含内容配置</li>
<li>[优化] 优化前端登录页面，开放手机验证码登录功能。</li>
<li>[优化] 优化前端工程生产模式打包脚本配置</li>
</ul>
</li>
</ul>
<h2>v2.7.2.5</h2>
<ul>
<li>重要更新
<ul>
<li>Nacos 版本升级至 2.1.1。已提供最新版本 Nacos 数据库脚本。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 前端分页组件，选择&quot;全部&quot;选项，导致后端接口校验返回参数错误问题。fix #I5M1G6</li>
<li>[修复] 前端登录认证过期后，浏览器存储无效 Token，再次启动应用过期提示框会被 loading 效果覆盖问题。fix #I5LKYI</li>
<li>[修复] 在 MySQL 数据库环境下，如果编码为非 utf-8 的情况下，导致 sys_social_user 表无法创建的问题。fix #I5LWGK</li>
<li>[修复] 以 jar 包形式运行单体版状态下，找不到依赖问题。</li>
<li>[优化] Dockerfile 中增加默认编码设置，防止在未设置语言环境的操作系统中，出现乱码导致请求内容包含特殊字符被 XSS 拦截而产生反序列化问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>dysmsapi20170525 版本升级至 2.0.18</li>
<li>tencentcloud-sdk-java-sms 版本升级至 3.1.568</li>
<li>alipay-sdk-java 版本升级至 4.33.1.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.2.4</h2>
<ul>
<li>主要更新
<ul>
<li>Spring Boot Admin 版本升级至 2.7.4</li>
<li>JetCache 版本升级至 2.7.0.RC3，以支持服务多实例环境下，服务本地缓存的同步。</li>
<li>根据 JetCache 2.7.0 版本的变化，调整 Nacos 相关配置。</li>
<li>优化重构所有 Stamp 相关代码，将所有标记为过时的 @CreateCache 相关代码，全部修改为最新的代码化配置形式。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>WxJava 版本升级至 4.3.9.B</li>
<li>Aliyun-java-sdk-core 版本升级至 4.6.1</li>
<li>Dysmsapi20170525 版本升级至 2.0.17</li>
<li>Tencentcloud-sdk-java 版本升级至 3.1.565</li>
<li>Alipay-sdk-java 版本升级至 4.31.84.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.2.3</h2>
<p>重磅！Dante Cloud 所有核心代码全部开源。</p>
<ul>
<li>
<p>主要更新</p>
<ul>
<li>接口权限鉴权：全面整合 <code>@PreAuthorize</code> 注解权限与 <code>URL</code> 权限，通过后端动态配置，无须在代码中配置 <code>Spring Security</code> 权限注解以及权限方法，即可实现接口鉴权以及权限的动态修改。采用分布式鉴权方案，规避 Gateway 统一鉴权的压力以及重复鉴权问题</li>
<li>动态权限数据分发：采用分布式服务独立鉴权方案，<code>Spring Security</code> <code>@PreAuthorize</code> 的权限注解、权限方法以及 <code>URL</code> 权限，通过后端动态配置后，实时动态分发至对应服务。</li>
<li>User 数据策略访问：<code>OAuth2</code> <code>UserDetails</code> 核心数据支持直连数据库获取和 <code>Feign</code> 远程调用两种模式。<code>OAuth2</code> 直连数据库模式性能更优，<code>Feign</code> 访问远程调用可扩展性更强。可通过配置动态修改采用策略方式。</li>
<li>手机短信验证码注册认证：采用自定义 <code>OAuth2</code> 授权模式，使用统一 <code>Token</code> 接口，实现手机验证码登录认证，与平台为统一体系，统一返回<code>OAuth2</code> Token，支持服务接口鉴权</li>
<li>第三方系统社交注册认证：集成 <code>JustAuth</code>，采用自定义 <code>OAuth2</code> 授权模式，使用统一 <code>Token</code> 接口，实现基于 <code>JustAuth</code> 实现第三方系统社交登录认证，与平台为统一体系，统一返回 <code>OAuth2</code> Token，支持服务接口鉴权。所有 <code>JustAuth</code> 支持的第三方系统均支持。</li>
<li>微信小程序注册认证：采用自定义 <code>OAuth2</code> 授权模式，使用统一 <code>Token</code> 接口，实现支持微信小程序登录认证，与平台为统一体系，统一返回 <code>OAuth2</code> Token，支持服务接口鉴权。</li>
<li>其它方式注册认证：采用策略模式对外部系登录认证和用户注册进行接入支持，采用 <code>OAuth2</code> 默认认证接口。目前未集成的外部系统，可参考标准，适当增减参数，即可支持接入。</li>
<li>多通道 SMS 集成：集成阿里，百度，中国移动，华为，京东，极光，网易，七牛，腾讯，又拍，云片等平台短信发送通道。可通过配置动态选择具体使用通道。支持多模版定义以及模版参数顺序控制</li>
<li>微信小程序订阅消息：支持微信小程序订阅消息发送。提供订阅消息模版工厂，可根据自身业务需求，编写少量代码既可以拓展支持新订阅消息模版。</li>
</ul>
</li>
<li>
<p>依赖更新：</p>
<ul>
<li>hutool 版本升级至 5.8.5</li>
<li>docker-maven-plugin 版本升级至 0.40.2</li>
<li>tencentcloud-sdk-java-sms 版本升级至 3.1.561</li>
</ul>
</li>
</ul>
<h2>v2.7.2.2</h2>
<h3>[1]、特别说明</h3>
<p><code>Dante Cloud</code> (但丁，原 Eurynome Cloud) 正式加入 <code>Dromara</code> 开源社区。<code>Dante Cloud</code> 将继续秉承“简洁、高效、包容、务实”的理念，不断地深耕细作、去粗取精，用心打造一款适应未来信息化建设需求的精致产品。同时，与 Dromara 开源社区以及社区中所有的优秀人才一起互相扶持、并肩前行，创造更多、更好、更精的产品以回馈社会，促进软件开源的发展。</p>
<p>谢谢大家对 Eurynome Cloud 支持与厚爱，希望大家继续给与 Dante Cloud  以及  Dromara 开源社区关注与支持</p>
<h3>[2]、为什么更名为  Dante Cloud</h3>
<p>原项目名称 Eurynome Cloud，很多朋友都反映名字太长、读起来拗口、不容易记等问题。因此在加入 Dromara 开源社区之际，将名字进行了变更。</p>
<p>Dante，即但丁·阿利基耶里(公元 1265 年-公元 1321 年)，13 世纪末意大利诗人，现代意大利语的奠基者，欧洲文艺复兴时代的开拓人物之一，以长诗《神曲》(原名《喜剧》)而闻名，后来一位作家叫薄伽丘将其命名为神圣的喜剧。</p>
<p>他被认为是中古时期意大利文艺复兴中最伟大的诗人，也是西方最杰出的诗人之一，最伟大的作家之一。恩格斯评价说：“封建的中世纪的终结和现代资本主义纪元的开端，是以一位大人物为标志的，这位人物就是意大利人但丁，他是中世纪的最后一位诗人，同时又是新时代的最初一位诗人”</p>
<p>更名为 Dante Cloud，寓意本项目会像恩格斯对但丁的评价一样，在行业变革的时期，可以成为一款承上启下，助力企业信息化建设变革的产品。</p>
<h3>[3]、本次更新内容</h3>
<p>由于项目名称的变更，为了降低和规避使用者产生不必要的误解，因此对项目代码、模块、Nacos 配置、初始数据进行了同步修改。</p>
<ul>
<li>主要更新
<ul>
<li>项目地址变更为：<a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://gitee.com/dromara/dante-cloud</a></li>
<li>主工程所有模块名称，使用最新名称进行了修改。</li>
<li>主工程所有代码所在的包，使用最新名称进行了修改。</li>
<li>Nacos 配置文件名称及相关配置项，使用最新名称修改。</li>
<li>数据库初始化脚本默认数据，涉及项目名称的内容进行了修改。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>修复 MySQL 数据初始化脚本错误</li>
<li>OkHttps 版本升级至 3.5.3</li>
<li>Tencentcloud-sdk-java-sms 版本升级至 3.1.559</li>
</ul>
</li>
</ul>
<h3>友情提示</h3>
<ol>
<li>因代码模块名和代码包名存在变更，直接更新代码会在 IDE 工具中产生冗余结构，可以直接删除，建议全新检出工程以彻底规避该问题。</li>
<li>因  Nacos  配置文件名称存在变更，需要重新导入  Nacos  配置。</li>
</ol>
<h2>v2.7.2.1</h2>
<ul>
<li>
<p>主要更新</p>
<ul>
<li>[修复] 用户账号登录超出错误次数被锁定以后，自动恢复账号状态代码使用错误参数导致恢复出错问题。fix: #I5J8QS</li>
<li>[修复] 系统页面长时间停留在登录页面，自定义 Session 过期，后端错误提示不友好问题。fix: #I5J9S8</li>
<li>[修复] 修改密码传递至后端加密数据无法正确解密，出现缓存反序列化错误问题。fix: #I5JEF3</li>
<li>[修复] 使用全名替换 dependencies dependencyManagement 配置中使用变量配置的 groupId，修复外部新建服务工程无法准确获取主工程生成的依赖包问题。</li>
<li>[删除] 删除 JPush 相关的信息推送和短信发送相关组件模块</li>
<li>[优化] 优化权限数据同步分发数据存储逻辑，将本地和远程缓存同时存储，修改为只在本地缓存存储。</li>
</ul>
</li>
<li>
<p>依赖更新</p>
<ul>
<li>JetCache 版本升级至 2.6.7</li>
<li>WxJava 版本升级至 4.3.8.B</li>
<li>Tencentcloud-sdk-java-sms 版本升级至 3.1.558</li>
</ul>
</li>
</ul>
<h2>v2.7.2.0</h2>
<ul>
<li>
<p>主要更新</p>
<ul>
<li>Spring Boot 版本升级至 2.7.2</li>
<li>Spring Boot Admin 版本升级至 2.7.3</li>
<li>Vite 版本升级至 3.0.2</li>
<li>使用 JetCache 重构已有自定义 JPA 多级二级缓存代码，将系统中所有多级缓存统一为使用 JetCache。也为后续升级 JetCache 2.7.X，实现服务本地缓存自动同步更新功能做铺垫。</li>
<li>新增极简 CMDB 配置管理服务及前端管理功能，用于管理应用系统、服务器、数据库等基础配置信息。</li>
</ul>
</li>
<li>
<p>其它更新</p>
<ul>
<li>[修复] 修复用户密码过期状态判断条件使用错误，fix #I5I7ZZ</li>
<li>[修复] 修复后端配置前端菜单角色信息，前端工程读取错误，导致菜单无法过滤问题。</li>
<li>[新增] 优化数据库初始化脚本，增加菜单默认初始化数据以及测试用切换账号数据。</li>
<li>[新增] 增加后端菜单数据节点排序功能，可通过 Ranking 字段进行配置。</li>
<li>[优化] 优化服务间权限数据同步代码逻辑，删除不合理代码、简化同步实现逻辑。</li>
<li>[删除] 删除 cache-sdk-layer 和 cache-layer-spring-boot-starter 两个模块</li>
</ul>
</li>
<li>
<p>依赖更新</p>
<ul>
<li>redisson 版本升级至 3.17.5</li>
<li>tencentcloud-sdk-java-sms 版本升级至 3.1.557</li>
<li>alipay-sdk-java 版本升级至 4.31.72.ALL</li>
<li>com.baidu.aip 版本升级至 4.16.10</li>
</ul>
</li>
</ul>
<h2>v2.7.1.3</h2>
<ul>
<li>
<p>主要更新</p>
<ul>
<li>[新增] 增加菜单管理功能，支持后端数据动态转换为前端菜单，根据用户角色动态切换菜单</li>
<li>[新增] 增加基于 Spring Authorization Server 的 Token 信息扩展，客户端在获取 Token 时就可以同时获取用户的相关信息，方便客户端区分用户身份信息，减少重复请求。使用前后端数据加密，进一步保护敏感信息。</li>
<li>[升级] 前端工程 Vite 版本升级至 3.0.1</li>
</ul>
</li>
<li>
<p>其它更新</p>
<ul>
<li>[优化] 优化系统用户登录合规性记录代码，去除重复性逻辑及部分硬代码</li>
<li>[优化] 优化 ServiceContext 代码逻辑，增加统一的 HTTP 地址 Protocol 头处理机制，实现代码中 HTTP 或 HTTPS 协议头的统一配置和修改</li>
<li>[修复] 修复 Swagger 认证功能不生效无法在 Swagger 中进行测试问题，以及多环境下 Swagger 是否开启控制失效问题。</li>
<li>[修复] 修复单体版 Swagger 多是否开启控制失效问题， 以及 Swagger 界面中自动生成的接口地址错误问题。</li>
<li>[升级] 升级 Antisamy XSS 拦截分析配置文件版本</li>
</ul>
</li>
<li>
<p>依赖更新</p>
<ul>
<li>antisamy 版本升级至 1.7.0</li>
<li>minio 版本升级至 8.4.3</li>
<li>jetcache 版本升级 2.6.6</li>
<li>dysmsapi20170525 版本升级至 2.0.16</li>
<li>tencentcloud-sdk-java 版本升级 3.1.552</li>
<li>alipay-sdk-java 版本升级至 4.31.64.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.1.2</h2>
<ul>
<li>
<p>主要更新：</p>
<ul>
<li>Spring Boot Admin 版本升级至 2.7.2</li>
<li>增加应用安全合规检查相关支持功能
<ol>
<li>[新增] 用户账号过期时间和用户密码过期时间。优化系统用户是否可用、是否锁定以及账号过期时间和密码过期的状态控制</li>
<li>[新增] 在同一类型终端下，允许同一账号重复登录限制。可通过配置修改默认允许重复登录数值，默认值为 1.</li>
<li>[新增] 用户登录错误次数限制，超过最大错误次数系统将自动锁定该用户账号（前提是系统中已存在的用户）。提供自动解锁功能和管理员解锁支持。</li>
<li>[新增] 新增用户登入、登出系统记录功能。包含，登录账号、时间、IP、使用的终端类型、浏览器类型、操作系统等相关信息。</li>
</ol>
</li>
<li>增加基于 Minio 对象存储的大文件分片上传功能。</li>
</ul>
</li>
<li>
<p>其它更新</p>
<ul>
<li>[优化] 进一步优化 Spring Authorization Server 认证过程错误体系与 Spring Security 登录错误体系的融合。解决 Spring Authorization Server 认证错误信息无法触发 Spring Security 自身特性问题。</li>
<li>[优化] 优化 Spring Authorization Server 错误提示信息，让登录错误反馈信息更加友好和准确。</li>
<li>[优化] 自定义认证模式代码，进行详细的用户信息校验，用户登录相关错误抛出机制和逻辑。在登录出错的情况下，抛出合理的 OAuth2 认证错误信息，便于区分错误类型以及进行应用安全合规性校验。</li>
<li>[优化] 优化基于 JPA 的自定义 Spring Authorization Server 数据访问相关代码，优化 OAuth2AuthorizationService 删除逻辑处理，增加过期 Token 清理逻辑。</li>
<li>[优化] 优化基于 JPA 的自定义 Spring Authorization Server 数据访问相关代码，补充部分常用字段索引的自动创建配置，提升数据查询效率。</li>
<li>[优化] 优化涉及 @RequestBody 注解接口的前后端数据加密加密传输的解密逻辑，除了维持原有整个 JSON 加解密外，增加 JSON 属性遍历解密，以提升前后端数据加解密的灵活性。</li>
<li>[优化] 优化部分 starter 自动配置文件，改用 Spring Boot 最新的 org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置文件</li>
<li>[新增] 新增 Spring Authorization Server 主要接口，是真正的退出接口，取代原有使用 Spring Authorization Server 默认提供的撤销 Token 接口。</li>
<li>[新增] 调用开放型接口前（无须鉴权的接口），新增对客户端的有效性的校验，以增强接口调用的安全性</li>
<li>[新增] 增加登录界面自定义 Session 检测，Session 未创建成功或后端无法正常连接，则禁止登录。</li>
<li>[新增] 因代码中多处使用基于多级缓存的计数逻辑，由此新增基于缓存的计数逻辑抽象类，方便此类功能代码的编写。</li>
<li>[新增] 前端设计接口或权限的功能，增加权限接口的排序查询支持。</li>
<li>[新增] 前端修改用户密码功能，密码复杂度校验。</li>
<li>[新增] 增加 Redis 事件监听配置，实现 Redis 过期数据的监听和逻辑处理。</li>
<li>[新增] 增加 ZonedDateTime 时间类型转换工具类，方便 Minio API 代码的使用。</li>
<li>[新增] 增加 Minio 对象存储容器化部署 Docker-compose 脚本</li>
<li>[修复] 涉及排序的接口，默认参数值设置错误问题。</li>
<li>[修复] 修复由于默认缓存时间设置过短，导致前后端数据加解密失效问题。修改为默认与客户端配置 accessToken 失效相同。</li>
</ul>
</li>
<li>
<p>依赖更新：</p>
<ul>
<li>WxJava 版本升级至 4.3.7.B</li>
<li>Dysmsapi20170525 版本升级至 2.0.15</li>
<li>Tencentcloud-sdk-java-sms 版本升级至 3.1.546</li>
<li>Alipay-sdk-java 版本升级至 4.31.48.ALL</li>
<li>Jpush-client 版本升级至 3.6.6</li>
<li>Jiguang-common 版本升级至 1.2.2</li>
</ul>
</li>
</ul>
<h2>v2.7.1.1</h2>
<ul>
<li>新版前端发布</li>
</ul>
<p>基于 Vue3、Vite2、Pinia、Quasar2、Typescript、Hooks 等最新技术栈，全新构建前端工程正式发布</p>
<p>新版前端特点：</p>
<ol>
<li>未使用任何流行开源模版，使用全新技术栈，完全纯&quot;手写&quot;全新前端工程。</li>
<li>借鉴参考流行开源版本的使用和设计，新版前端界面风格和操作习惯尽量与当前流行方式统一。</li>
<li>充份使用 Typescript 语言特性，解决大量类型校验问题，尽可能规避 &quot;any&quot; 式的 Typescript 编程语言使用方式。</li>
<li>充份使用 Composition Api 和 Hooks 等 Vue3 框架新版特性进行代码编写。</li>
<li>充份利用 Component、Hooks 以及 Typescript 面向对象等特性，抽取通用组件和代码，尽可能降低工程重复代码。</li>
<li>对较多 Quasar 基础组件和应用功能组件进行封装，以方便代码的统一修改维护和开发使用。</li>
<li>对生产模式下，对基于 Vite2 的工程打包进行深度性能优化。</li>
<li>提供以 docker-compose 方式，对工程生产代码进行容器化打包和部署。</li>
</ol>
<ul>
<li>
<p>其它更新</p>
<ul>
<li>[优化] 基于 commons-pool2, 将 Minio 对象存储的 MinioClient 代码进行池化，以提升与 Minio 服务端的交互效率。</li>
<li>[优化] 补充和优化 MinioProperties 对象池配置参数。多数配置默认参数，与 commons-pool2 包提供的默认值统一。</li>
<li>[优化] 重新梳理对象存储模块错误错误 Exception，将其融入平台整体错误体系，返回给前端更友好错误信息提示。</li>
<li>[重构] 重构 Minio Oss 操作基础 Service 代码，丰富 Minio 操作 Service 代码，涵盖大部分 Minio SDK 支持的 API。</li>
<li>[修复] 修复 Oss 模块组件代码包中，Minio 模块条件注解不生效，导致 Minio 包无法通过注解开启问题。</li>
</ul>
</li>
<li>
<p>依赖更新</p>
<ul>
<li>Hutool 版本升级至 5.8.4</li>
<li>Mybatis-plus-generator 版本升级至 3.53</li>
<li>Tencentcloud-sdk-java-sms 版本升级至 3.1.540</li>
<li>Com.baidu.aip 版本升级至 4.16.9</li>
<li>Aliyun-sdk-oss 版本升级至 3.15.1</li>
</ul>
</li>
</ul>
<h2>v2.7.1.0</h2>
<ul>
<li>重要更新
<ul>
<li>Spring Boot 版本升级至 2.7.1</li>
<li>Spring Boot Admin 版本升级至 2.7.1</li>
<li>Skywalking Java Agent 版本升级至 8.11.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 增加通过范围代码查询应用范围接口</li>
<li>[新增] 增加自定义打包的 JDK 镜像 herodotus/oracle-jdk:8u331，彻底根除服务在 Docker 环境下由于使用 OpenJDK 版本不同的差异导致无法读取字体出错，以及打包成镜像过程中下载字体慢的问题。该镜像已经上传至 Docker Hub，可以直接下载使用。</li>
<li>[新增] 对几款常用的 OpenJDK 基础镜像进行了验证，在 Dockerfile 中，新增了适合使用的两个版本 JDK 基础镜像配置，放开注释即可选择使用。</li>
<li>[优化] 优化涉及字体读取的行为验证码和图形验证码代码，增强在不同系统和不同 Java 版本下的兼容性。</li>
<li>[修复] 调整 Skywalking Docker Compose 配置参数，解决 8.7.0-es7 以后版本 Skywalking Docker 无法正常启动问题。</li>
<li>[删除] 删除项目核心 dependencies 中，与新版本 spring-boot-dependencies 中重复的依赖及版本 pom 配置，尽量使用 spring-boot-dependencies 对项目大部分依赖版本进行管控，减少依赖的重复配置以及版本不兼容问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>WxJava 版本升级至 4.3.6.B</li>
<li>mapstruct-processor 版本升级至 1.5.2.Final</li>
<li>mybatis-plus-boot-starter 版本升级至 3.5.2</li>
<li>tencentcloud-sdk-java 版本升级至 3.1.537</li>
<li>alipay-sdk-java 版本升级至 4.31.28.ALL</li>
<li>jpush-client 版本升级至 3.6.3</li>
</ul>
</li>
</ul>
<h2>v2.7.0.60</h2>
<ul>
<li>重要更新
<ul>
<li>Spring Authorization Server 版本升级至 0.3.1</li>
<li>系统使用 Java 版本降级兼容 Java 8，经验证目前本系统在 Java 8、11、17 环境下均可以正常稳定运行。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复人员归属管理功能中，删除已配置人员归属会出现 Jackson 反序列化问题。</li>
<li>[优化] 优化单位树和部门树，树形数据组织代码，对不同类型的根节点数据，统一处理为系统定义标准根节点，便于前端组件使用和封装。</li>
<li>[优化] 优化社交登录接口相关代码，同时增加 @Inner 注解，解决该接口内部调用跳过权限验证问题。</li>
<li>[优化] 优化自定义 OAuth2 Security 权限元数据管理接口相关代码</li>
<li>[重构] 重构权限表达式枚举类代码，与系统已有枚举常量体系统一。调整该类所在包，便于前端将其常量使用。</li>
<li>[删除] Spring Security 5.7.0 版本以前，Spring Security WebAuthenticationDetails 类只支持 HttpServletRequest 做为参数，这导致无法在 Spring Security 自身的登录页面增加验证码等额外信息。因此，提取了 WebAuthenticationDetails 代码进行扩展，以支持登录验证码功能。随着 Spring Security 的版本升级至 5.7.1，该问题已经被修复，因此删除原有自行扩展代码。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>OkHttp 版本升级至 4.10.0</li>
</ul>
</li>
</ul>
<h2>v2.7.0.50</h2>
<ul>
<li>重要更新
<ul>
<li>Spring Boot Admin 版本升级至 2.7.0</li>
</ul>
</li>
<li>主要更新
<ul>
<li>[新增] 新增读取全部角色和读取全部 Scope 接口，删除已有基于权限类别读取权限接口</li>
<li>[新增] 自定义 Validation 注解 EnumeratedValue，支持对指定枚举的 name 或 ordinal 值进行校验，提升接口的健壮性。</li>
<li>[新增] Spring Data JPA 分页查询数据排序支持。通过接口动态传递额外参数，实现分页数据的排序。</li>
<li>[修正] 修复幂等和防刷拦截器读取配置不正确问题。优先读取注解配置参数，如注解为设置参数值，则默认使用统一配置参数。</li>
<li>[优化] 优化 Security 工具类，使用 Spring Security 最新的获取 PasswordEncoder 工厂类重构密码创建方法和密码校验方法。</li>
<li>[优化] 优化幂等和防刷注解和配置的默认参数值，设置更合理的参数，解决幂等和防刷过于敏感问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>docker-maven-plugin 版本升级至 0.40.1</li>
<li>maven-embedder 版本升级至 3.8.6</li>
<li>maven-compat 版本升级至 3.8.6</li>
<li>redisson 版本升级至 3.17.4</li>
<li>minio 版本升级至 8.4.2</li>
<li>hutool 版本升级至 5.8.3</li>
<li>weixin-java-sdk 版本升级至 4.3.5.B</li>
<li>tencentcloud-sdk-java 版本升级至 3.1.530</li>
<li>qiniu-java-sdk 版本升级至 7.11.0</li>
</ul>
</li>
</ul>
<h2>v2.7.0.40</h2>
<ul>
<li>主要更新：
<ul>
<li>[修复] 修改幂等防护注解默认配置，解决行为验证码验证过程中，频繁出现幂等错误提示问题。</li>
<li>[修复] 修正因统一 Jackson ObjectMapper 配置，产生时间类型反序列化不正确，导致 Spring Authorization Server 认证相关操作抛错问题。</li>
<li>[优化] 优化 Protect 包内相关 Exception 及配置，将其融入到系统的错误体系中，让返回错误信息更加直观也更加人性化。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>Docker-maven-plugin 版本升级至 0.40.0</li>
<li>Mapstruct-processor 版本升级至 1.5.1.Final</li>
<li>WxJava 版本升级至 4.3.5.B</li>
<li>Mybatis-plus-boot-starter 版本升级至 3.5.2</li>
<li>Tencentcloud-sdk-java 版本升级至 3.1.526</li>
<li>Alipay-sdk-java 版本升级至 4.31.7.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.0.30</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 统一 OkHttp 、HttpClient 自定义配置，实现 OkHttp 、HttpClient 与 RestTemplate 、Openfeign 整合。统一使用 Feign 配置参数，对 OkHttp 、HttpClient 进行参数设定。可通过配置参数，策略化设置使用 OkHttp 还是 HttpClient 作为 RestTemplate 、Openfeign 的基础 HttpClient。</li>
<li>[新增] 增加 OkHttp 、HttpClient 平滑退出机制，服务退出前自动销毁已经创建的 Client。将 OkHttp 错误响应与已有错误体系整合，使用统一响应结构返回数据。</li>
<li>[新增] 重新定义 Feign 配置，实现 Feign 调用日志输出。统一 Feign 错误解码器，并与已有错误体系整合。</li>
<li>[新增] 重构 Feign 与 Sentinel 整合代码，补充 Feign 自动降级处理。增加统一的 fallback 处理机制，无须再进行 FeignClient Fallback 配置。</li>
<li>[新增] 增加 @Inner 注解，实现 Feign 内部接口调用无须进行权限验证，同时 Gateway 以外调用仍需权限验证的机制。无须在 Feign 接口中传递额外参数，注意：该注解只能在 @FeignClient 定义类中使用。</li>
<li>[修复] 调整 WebMvc 配置，解决原有配置方式下自定义 interceptor 不生效，导致防刷、前后端数据传输加解密不工作问题。</li>
<li>[修复] Dockerfile 和 Docker Compose 配置文件错误。升级依赖的 JDK 版本，去除原有字体下载配置。</li>
<li>[优化] 调整 JacksonConfiguration 代码所在的包，让代码归类放置更合理。解决自定义 JacksonConfiguraiton 注入顺序不当而不生效问题。</li>
<li>[优化] 用更优的办法，解决在 JDK 17 环境下，因 okhttp 和 okio 版本不匹配，而导致的运行错误问题。</li>
<li>[优化] 修改 Nacos 配置，去除上一版本为临时解决 JDK 17 下 Okhttp 兼容问题增加的配置。</li>
<li>[优化] 使用 Spring Boot 2.7.0 新的自动配置注册文件，替换 facility-spring-boot-starter、web-spring-boot-starter 等模块 spring.factories 自动配置文件</li>
<li>[文档] 补充部分组件简要说明 Readme，帮助快速了解组件用途。</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>Redisson 版本升级至 3.17.3</li>
<li>SpringDoc 版本升级至 1.6.9</li>
<li>Hutool 版本升级 5.8.2</li>
<li>Qiniu-java-sdk 版本升级至 7.10.4</li>
<li>Alipay-sdk-java 版本升级至 4.27.1.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.0.20</h2>
<ul>
<li>重要更新
<ul>
<li>Dante Cloud 版本使用 JDK 从 8 升级至 17 (注意：现有版本不在兼容 JDK 8)</li>
</ul>
</li>
<li>主要更新
<ul>
<li>Spring Cloud 版本升级至 2021.0.3</li>
<li>Spring Authorization Server 版本升级至 0.3.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>根据 Spring Authorization Server 0.3.0 代码的变化，修改和调整现有系统代码</li>
<li>为支持 JDK 17，同时解决老旧依赖包安全漏洞问题，临时删除现有 baidu 短信模块</li>
<li>升级 Kryo 版本至 5.3.0，增加自定义拓展代码，解决 Kryo 现有版本与 JDK 17 不兼容问题而导致的 Jetcache 无法使用问题。</li>
<li>升级 Xnio 版本，解决 Undertow 在 JDK 17 环境下运行，抛出 <code>Could not initialize class org.xnio.channels.Channels</code> 错误，导致服务无法运行问题。</li>
<li>修复 docker-compose 配置错误。</li>
<li>修复现有工程中残留的 FastJson 使用代码。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>JetCache 版本升级至 2.6.5</li>
<li>Tencentcloud-sdk-java 版本升级至 3.1.516</li>
</ul>
</li>
<li>额外说明
<ul>
<li>因 Spring Authorization Server 0.3.0 默认 JDK 已升级至 11，在 JDK 8 环境下编译出错，已无法兼容 JDK 8，所以考虑升级系统使用 JDK 版本。</li>
<li>2022 年 11 月，Spring Boot 3 将会发布，最低版本要求 JDK 17。因此，直接将 JDK 版本升级至 17，为升级至 Spring Boot 3 提前做铺垫准备。</li>
</ul>
</li>
</ul>
<h2>v2.7.0.10</h2>
<ul>
<li>主要更新
<ul>
<li>Fastjson 版本升级至 1.2.83。消除 Fastjson 反序列化任意代码执行漏洞。</li>
<li>使用 Jackson 全面替换工程中涉及的 Fastjson 代码。保留工程 Fastjson 依赖，用于统一控制其它第三方依赖包中依赖的 Fastjson 版本。</li>
<li>合并原有 assistant-sdk-json 包至 assistant-core。新增 Gson 通用工具类。</li>
<li>重构缓存相关模块，将 redis 和 caffeine 模块独立出来。使用标准化方式重新配置 redis，解决早期在 jar 包模式下，自定义 Redis 配置不生效问题</li>
<li>使用 Spring Boot 2.7.0 新的自动配置注册文件，调整部分 starter 自动注入配置文件</li>
<li>增加基于 Redis 的 Spring Cloud Session 共享基础配置。同时支持 Servlet 和 Webflux</li>
<li>去除重复定义的 JPA 实体自定义 KEY Generator 名称</li>
</ul>
</li>
<li>其它更新
<ul>
<li>Redisson 版本升级 3.17.2</li>
<li>Logstash Logback Encoder 版本升级至 7.2</li>
<li>WxJava 版本升级至 4.3.4.B</li>
<li>MyBatis 版本升级至 3.5.10</li>
<li>Tencentcloud-sdk-java 版本升级 3.1.514</li>
<li>Qiniu-java-sdk 版本升级至 7.10.3</li>
<li>Alipay-sdk-java 版本升级至 4.23.26.ALL</li>
<li>com.baidu.aip 版本升级至 4.16.8</li>
</ul>
</li>
</ul>
<h2>v2.7.0.0</h2>
<ul>
<li>主要更新
<ul>
<li>Spring Boot 版本升级至 2.7.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>修正新版 Spring Boot 下 applicationContext 获取 Bean RequestMappingHandlerMapping 出错问题。</li>
<li>修正权限数据通过消息队列传递， Jackson 反序列化出错问题。</li>
<li>修正新版 Spring Boot 下，OkHttp 与已有 Spring Cloud 版本不兼容，导致服务无法启动问题。</li>
<li>将 RestTemplate 底层客户端组件，临时由 OkHttp 改为 HttpClient，以规避 Okhttp 与 Spring Cloud 不兼容问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>Maven Invoker 版本升级至 3.2.0</li>
<li>Minio 版本升级至 8.4.1</li>
<li>Hutool 版本升级至 5.8.1</li>
<li>Okhttps 版本升级至 3.5.2</li>
<li>WxJava 版本升级至 4.3.3.B</li>
<li>Alipay-sdk-java 版本升级至 4.23.21.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.0.RC1</h2>
<ul>
<li>主要更新
<ul>
<li>Spring Boot Admin 版本升级至 2.6.7</li>
<li>Nacos 版本更新至 2.1.0, 同步更新 Nacos Docker 版本，新增 Nacos 2.1.0 SQL 脚本。(提示：该版本有数据结构变化，建议备份已有配置信息，重新导入数据库脚本)</li>
<li>增加 Spring Authorization Server 自定义密码认证模式前后端数据加密传输。</li>
<li>增加国密算法 SM2 SM4，采用数字信封技术融合国密 SM2 SM4 算法，实现前后端数据加密传输。保留基于 RSA 和 AES 的前后端加密传输方式，可通过配置选择切换。</li>
<li>整合 rest-sdk-crypto、rest-sdk-secure、assistant-sdk-secure 代码包，形成统一的防护组件，包含前后端数据加密、接口幂等、防刷、Xss 和 SQL 注入防护，以及国密 SM2、SM4、RSA、AES 等加密逻辑。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>Hutool 版本升级至 5.8.0</li>
<li>Bce-java-sdk 版本升级至 0.10.208</li>
<li>Minio 版本升级至 8.4.0</li>
<li>Alipay-sdk-java 版本升级至 4.23.0.ALL</li>
</ul>
</li>
</ul>
<h2>v2.7.0.Beta4</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Authorization Server 版本升级至 0.2.3 版本</li>
<li>[升级] Spring Boot 版本升级至 2.6.6</li>
<li>[新增] 基于 Spring Authorization Server 0.2.3 最新代码调用方式，重构自定义 OAuth2 密码模式</li>
<li>[新增] 对 OAuth2 OIDC 认证模式的支持，补充前端 OIDC 认证相关配置操作</li>
<li>[新增] OAuth2 OIDC 认证方式下，对应的 /userinfo 接口调用支持 和 客户端注册支持</li>
<li>[新增] OAuth2 Authorization Code PKCE 认证模式支持</li>
<li>[新增] OAuth2 Client Credentials 模式下，提供 Refresh Token。</li>
<li>[新增] OAuth2 Client Credentials 模式下，支持使用 Scope 权限对接口进行验证。</li>
<li>[新增] 在已有的 oauth2-sdk-authorization-server 模块下，增加客户端 Scope 的权限配置功能，并与已有的用户权限体系解耦</li>
<li>[新增] 自定义 Social Credentials 认证模式，支持手机短信验证码、微信小程序、第三方应用登录</li>
<li>[修复] 在 OAuth2 OIDC 认证方式下，/userinfo 接口调用始终为 401 问题。</li>
<li>[修复] 自定义 Spring Authorization Server JPA 存储模式参数丢失，遗漏 Scope 设置，导致 /userinfo 调用失败问题</li>
<li>[修复] No AuthenticationProvider found for UsernamePasswordAuthenticationToken 问题导致的用户名无法登录问题</li>
<li>[修复] 服务启动过程中，由于异步操作与事务操作冲突，导致的 JetCache 数据存储产生并发异常</li>
<li>[修复] 改进 @Async 注解调用代码，解决由 jdk 自动代理与 CGlib 代理两种代理方式的区别造成代码抛出异常</li>
<li>[修复] 改进 OAuth2 基础操作代码，解决 Spring Authorization Server 部分配置不能设置为空值的问题</li>
<li>[修复] Spring Cloud Bus 进行远程事件发布时，所有事件监听代码均会接收数据，无法根据服务 Destination 定向处理的问题。</li>
<li>[优化] 优化 SecurityGlobalExceptionHandler，支持 OIDC 场景下对错误内容的拦截，并将其融合进整体错误体系</li>
<li>[优化] EndpointProperties 配置，区分系统涉及的 url 以及 endpoint，以便支持更多用途</li>
<li>[优化] 重构并抽象出策略事件 Event，以规范化大量使用的混合本地时间和远程事件的各类操作。</li>
<li>[优化] 规范化所有 Enum 类，使用统一方式为前端提供常量</li>
<li>[优化] 优化 OAuth2 应用管理前后端交互相关代码及 OpenApi 文档说明</li>
</ul>
</li>
<li>其它更新
<ul>
<li>Spring boot admin 版本升级至 2.6.4</li>
<li>Redisson 版本升级至 3.17.0</li>
<li>Antisamy 版本升级至 1.6.6</li>
<li>Fastjson 版本升级至 1.2.80</li>
<li>Okhttps 版本升级至 3.4.6</li>
<li>Bce-java-sdk 版本升级至 0.10.202</li>
<li>Alipay-sdk-java 版本升级至 4.22.75.ALL</li>
<li>Qiniu-java-sdk 版本升级至 7.9.5</li>
<li>Logback 版本升级至 1.2.11</li>
</ul>
</li>
<li>尝鲜注意事项
<ol>
<li>建议新建目录、单独检出 Dante Cloud 2.7.0 分支代码，以防对现有代码产生影响。</li>
<li>数据表结构以及 Nacos 存在较大变化，建议重新建库、重新导入 Nacos 配置。</li>
<li>支持 MySQL 数据库，但是尚未进行充份的验证和测试，为规避不必要的问题，建议直接使用 PostgreSQL 数据库。</li>
<li>Herodotus Engine 是独立的、可编译的、组件库式的工程，具体使用需要在其它 Spring Boot 工程中引入相关的组件模块。独立出的各个模块，已经同步至 Maven 中央仓库，检出 Dante Cloud 2.7.0 分支代码既可以直接使用。当然，也可以先检出 Herodotus Engine 工程，编译后再进行 Dante Cloud 项目的使用。</li>
<li>想要研究、学习、了解已有的模块代码，可以访问 Herodotus Engine 代码库，地址：<a href="https://gitee.com/herodotus/herodotus-engine" target="_blank" rel="noopener noreferrer">https://gitee.com/herodotus/herodotus-engine</a></li>
</ol>
</li>
</ul>
<h2>v2.7.0.Beta3</h2>
<ul>
<li>
<p>重大更新</p>
<ol>
<li>全面拥抱 Spring Authorization Server。基于 Spring Authorization Server 重新改版，替换即将停止维护的 Spring Security OAuth2。再也不用担心 Spring Security OAuth2 停止维护了。</li>
<li>基于 Spring Data JPA，重新构建 Spring Authorization Server 基础数据存储代码。替代原有 JDBC 数据访问方式，破除 Spring Authorization Server 原有数据存储局限，扩展为更符合实际应用的方式。配合自定义多级缓存加持，认证过程更加顺滑。</li>
<li>基于 Spring Authorization Server，在 OAuth 2.1 规范基础之上，增加自定义“密码”认证模式，以兼容现有基于 OAuth 2 规范的、前后端分离的应用可以平滑使用。</li>
<li>基于 Spring Authorization Server 新的数据存储结构，重新定义应用管理、客户端管理功能，同步修改前后端代码，管理更加便捷。</li>
<li>完全遵照 Spring Security 5 以及 Spring Authorization Server 的代码规范，进行 OAuth2 认证服务器核心代码的开发。</li>
<li>除了支持 Spring Authorization Server 的标准的 Token 加密校验方式外，还了增加支持自定义证书的 Token 加密方式，可通过配置动态修改。</li>
<li>重新梳理并调整优化已有配置参数，让工程配置参数更加清晰，层级更加合理。同时，拆分原有使用内部类定义的配置参数，进一步由配置参数导致的代码耦合。</li>
<li>同步优化 Nacos 配置内容，采用 Spring Authorization Server 标准 Token 校验方式，新服务增加无须再增加配置文件和进行 Client 配置。</li>
<li>重新梳理本微服务架构内的错误体系及相关代码，已有的 Exception 类放入更合理的保重，无须再经过修改通用基础包中代码，即可便捷的将新的 Exception 融入到系统的错误体系中。同时，仍旧支持自定义错误码以及人机交互友好的自定义错误提示。</li>
<li>对已有代码进行了深度的“庖丁解牛”。严格遵照“单一职责”原则，根据各个组件的职责以及用途，拆解细化为多个各自独立组件模块，在最大程度上降低代码间的耦合。降低工程代码编译耗时，改进 CICD 效率，提升代码可维护性。</li>
<li>除已有的组件模块外，对现有工程代码分包也进一步调整，分包和逻辑更加清晰。</li>
</ol>
</li>
<li>
<p>其它更新</p>
<ul>
<li>优化接口权限鉴权逻辑，解决通配符类型权限与全路径权限冲突或重复的问题，实现重复权限剔除并以最大化匹配方式进行权限匹配逻辑。</li>
<li>由于 Spring Authorization Server 机制和模式的变化，原有团队管理功能已不符合实际，相关功能已删除。</li>
<li>前端部分功能配合后端功能变化进行同步修改和改进。</li>
<li>原有 Herodotus Engine 工程中的模块，根据实际代码变更。代码包以及代码进行了一定的优化和整合。</li>
<li>核心依赖 dependencies 采用参数方式，统一定义版本号方便其它依赖工程覆盖和修改版本号。</li>
<li>改进错误信息展示，同时支持 Mvc 和 Json 两种方式，通过浏览器操作的 Mvc 方式错误也可以通过界面展示了。</li>
</ul>
</li>
<li>
<p>尝鲜注意事项</p>
<ol>
<li>建议新建目录、单独检出 Dante Cloud 2.7.0 分支代码，以防对现有代码产生影响。</li>
<li>数据表结构以及 Nacos 存在较大变化，建议重新建库、重新导入 Nacos 配置。</li>
<li>支持 MySQL 数据库，但是尚未进行充份的验证和测试，为规避不必要的问题，建议直接使用 PostgreSQL 数据库。</li>
<li>Herodotus Engine 是独立的、可编译的、组件库式的工程，具体使用需要在其它 Spring Boot 工程中引入相关的组件模块。独立出的各个模块，已经同步至 Maven 中央仓库，检出 Dante Cloud 2.7.0 分支代码既可以直接使用。当然，也可以先检出 Herodotus Engine 工程，编译后再进行 Dante Cloud 项目的使用。</li>
<li>想要研究、学习、了解已有的模块代码，可以访问 Herodotus Engine 代码库，地址：<a href="https://gitee.com/herodotus/herodotus-engine" target="_blank" rel="noopener noreferrer">https://gitee.com/herodotus/herodotus-engine</a></li>
</ol>
</li>
</ul>
<p><strong>友情提示：</strong></p>
<p>本次代码发布，为尝鲜预览版，请结合自己的实际需求，谨慎选择使用！</p>
<p>Spring Authorization Server 也在不断的改进中，0.2.3 和 0.2.2 代码就有较大差异，因此暂时不要将该版本用于生产，随着 Spring Authorization Server 升级，代码还会进行较大的修改。</p>
<h2>v2.7.0.Beta2</h2>
<ol>
<li>更正工程 Readme 文档表述错误内容。</li>
<li>优化自定义 Hibernate Dialect，增加 PostgreSQL 环境下对 CLOB 和 BLOB 数据类型的统一支持。为 Spring Authorization Server 的使用奠定基础</li>
<li>完善大量 Herodotus Engine 代码中的注释内容，解决代码编译生成 Javadoc 显示大量告警信息问题。</li>
<li>由于使用组件库的方式，源代码包和 Javadoc 包均已生成。已有微服务工程无须再进行源代码的编译，因此去掉 Dante Cloud 主工程源代码编译配置和相关依赖。</li>
</ol>
<h2>v2.7.0.Beta1</h2>
<ul>
<li>
<p>重要变更</p>
<ul>
<li>将现有 Dante Cloud 微服务架构，进行了深度的“庖丁解牛”。将完整的微服务架构，根据各个组件的职责以及用途，拆解细化为多个各自独立组件模块，在最大程度上降低代码间的耦合。并将组件模块单独提取为一个独立的工程项目。</li>
<li>已有 Dante Cloud 微服务架构，根据新的模块化代码结构，进行了重构。</li>
</ul>
</li>
<li>
<p>升级目的</p>
<ul>
<li>2021 年 11 月 8 日 Spring 官方已经强烈建议使用 Spring Authorization Server 替换已经过时的 Spring Security OAuth2.0。距离 Spring Security OAuth2.0 结束生命周期还有小半年的时间，所以准备用 Spring Authorization Server 对已有的 Dante Cloud 微服务架构进行升级</li>
<li>Dante Cloud 微服务架构，一直遵循“高内聚、低耦合”的原则，在开发和维护的过程中不断优化已有代码，尽一切可能降低代码的耦合性。但是，毕竟所有的代码都堆积在同一个工程中，代码间的过度依赖和互相耦合还是较为严重。这为 Spring Authorization Server 替换 Spring Security OAuth2.0 带来了较大的阻力和难度。</li>
<li>为了进一步降低代码与代码间、模块与模块间的耦合度，进行了本次版本更新，并衍生了 Herodotus Engine 工程。</li>
<li>同时，本次版本迭代，也是为了后期升级使用 Spring Boot 3.X 和 JDK 17，做先期主备。</li>
</ul>
</li>
<li>
<p>新模式特点</p>
<ol>
<li>严格遵照“单一职责”原则，进行各个模块的划分和代码拆解。</li>
<li>严格遵循 Spring Boot 编码规则和命名规则。</li>
<li>大多数模块均支持 @EnableXXX 注解 和 starter，不仅提升了模块使用的便捷性，同时在开发使用过程中，让 Spring Bean 的注入顺序更加可控和便于理解。</li>
<li>借鉴 Spring Boot 模块化设计思想，通过接口化编程、策略化 Bean 注入 以及丰富的自定义 @ConditionalXXX 注解，让模块的添加和使用更加灵活便捷。</li>
<li>各模块既可以综合在一起使用，也可以在其它 Spring Boot 工程中独立使用</li>
</ol>
</li>
<li>
<p>新模式优势</p>
<ol>
<li>虽然模块看似很多，但是每个模块职责单一、代码清晰，更有利于聚焦和定位问题。</li>
<li>通过对微服务架构的“庖丁解牛”，初学者不再需要在代码的海洋里“遨游”，通过针对性地了解各个模块，以点带面快速掌握微服务架构整体结构。</li>
<li>模块间的依赖极大的降低，想要替换为 Spring Authorization Server，影响到的代码和范围将会很小。该工程也是使用 Spring Authorization Server 的前序工作</li>
<li>每个模块均是最小化依赖第三包，规避依赖包过度依赖，特别是 starter 过多依赖，导致不可预知、难以调试、不好修改等问题。</li>
<li>降低微服务系统代码量，独立组件可提前编译并上传至 Maven 仓库，降低工程代码编译耗时，改进 CICD 效率。</li>
</ol>
</li>
<li>
<p>尝鲜注意事项</p>
<ol>
<li>建议新建目录、单独检出 Dante Cloud 2.7.0 分支代码，以防对现有代码产生影响。</li>
<li>独立出的各个模块，已经同步至 Maven 中央仓库，检出 Dante Cloud 2.7.0 分支代码既可以直接使用。当然，也可以先检出 Herodotus Engine 工程，编译后再进行 Dante Cloud 项目的使用。</li>
<li>想要研究、学习、了解已有的模块代码，可以访问 Herodotus Engine 代码库，地址：<a href="https://gitee.com/herodotus/herodotus-engine" target="_blank" rel="noopener noreferrer">https://gitee.com/herodotus/herodotus-engine</a></li>
<li>如果之前已经使用、部署过 Dante Cloud 微服务系统，那么尝鲜使用 2.7.0.Beta1 版，无须修改和变更数据库。但是需要更新 Nacos 配置，具体变化可自行详细对比工程中 Nacos 配置文件。前端工程无须做任何修改，即可使用该版本后端系统。</li>
<li>Herodotus Engine 是独立的、可编译的、组件库式的工程，具体使用需要在其它 Spring Boot 工程中引入相关的组件模块。</li>
</ol>
</li>
</ul>
<p><strong>友情提示：</strong><br>
本次代码发布，为尝鲜预览版，请结合自己的实际需求，谨慎选择使用！</p>
]]></content:encoded>
    </item>
    <item>
      <title>v3.0.X</title>
      <link>https://www.herodotus.cn/logs/3.0.html</link>
      <guid>https://www.herodotus.cn/logs/3.0.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">v3.0.X</source>
      <description>v3.0.7.0 主要更新 [升级] Spring Boot 版本升级至 3.0.7 其它更新 [优化] 优化 Webjars 相关组件的依赖关系，规避通过 rest 模块引入过多无用的依赖 依赖更新 [升级] maven-gpg-plugin 版本升级至 3.1.0 [升级] commons-io 版本升级至 2.12.0 [升级] redisson...</description>
      <pubDate>Thu, 26 Dec 2024 07:36:25 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v3.0.7.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.0.7</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化 Webjars 相关组件的依赖关系，规避通过 rest 模块引入过多无用的依赖</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-gpg-plugin 版本升级至 3.1.0</li>
<li>[升级] commons-io 版本升级至 2.12.0</li>
<li>[升级] redisson 版本升级至 3.21.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.132.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.16</li>
</ul>
</li>
</ul>
<h2>v3.0.6.4</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Authroization Server 版本升级至 1.1.0</li>
<li>[升级] Spring Security 版本升级至 6.1.0</li>
<li>[新增] 新增支持智能电视、IOT 设备等输入受限设备的 Device Flow 认证模式</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 Device Flow 认证系统内置页面</li>
<li>[新增] 新增 IOT 产品、设备管理 SDK</li>
<li>[重构] 重构 Spring Authorization Server 授权确认页面，与内置认证页面统一风格。</li>
<li>[重构] 除特殊依赖外，将所有内置页面静态资源引用改为 Webjars 方式。</li>
<li>[重构] 所有内置页面均改用页面嵌入 Vue 方式重新实现</li>
<li>[优化] 优化客户端动态自动注册相关功能代码及配置方式</li>
<li>[优化] 优化数据库初始化脚本，增加 Spring Authorization Server 内置默认 Scope 数据及关联数据</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] fastjson2 版本升级至 2.0.32</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.756</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.16.3</li>
</ul>
</li>
</ul>
<h2>v3.0.6.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.0.4</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.11.4-2022.0.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构自定义实体转换 RegisteredClient 代码，抽取公共转换适配器，减少重复代码方便后续 SAS 属性变更统一修改。</li>
<li>[重构] 重构 OAuth2 Authentication 模块，缩减包名，明晰定位。</li>
<li>[重构] 重构 Spring Authorization Server 相关自定义配置类，根据相关模块职责与定位，重新梳理所有配置参数，并整合已有配置类，调整和精简配置参数，减少配置在各模块间交叉引用，提升和增强模块内聚性。</li>
<li>[重构] 合并 oauth2-sdk-compliance 模块至 oauth2-sdk-management。</li>
<li>[优化] 减少 Dante Cloud 统一 dependencies 中配置的镜像仓库配置，避免过多 matadata 信息引起的 maven 下载 混乱</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.754</li>
</ul>
</li>
</ul>
<h2>v3.0.6.2</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增 Webjars 支持，将内置页面引用的 JavaScript 和 CSS 全部改为 Webjars 方式引入，同时删除不必要的静态资源降低生成 jar 大小</li>
<li>[新增] SQLite 数据库集成 JPA 和 p6spy 支持</li>
<li>[重构] 重构统一错误体系代码，统一使用 FeedbackFactory 管理自定义错误信息，降低自定义 Exception 手动输入错误代码产生错误类型与 HttpStatus 不一致或不配问题。</li>
<li>[重构] 重构后端 Spring Authorization Server 核心数据实体，补充新版本缺失参数及字段</li>
<li>[重构] 抽取 Spring Authorization Server 通用工具代码，以支持更多认证模式。</li>
<li>[重构] 使用 Thymeleaf 模版统一内置页面布局，统一设备认证页面，提升页面代码易维护性。</li>
<li>[重构] 重新配置并统一 maven repositories 和 pluginRepositories。以 dante-engine dependencies 为核心，删除其它工程中，重复和冲突的配置。</li>
<li>[修复] 修复单体版 Swagger 参数层级配置错误，导致 SpringDoc Authorize 按钮不显示问题</li>
<li>[修复] 修复前端代码与后端不匹配以及字段参数缺失问题</li>
<li>[修复] 优化前端 OAuth2 应用管理功能，修复分页面内容显示错误问题。</li>
<li>[修复] 修改 Spring Authorization Server 默认的 SessionRepository 配置。解决使用 Spring Session 配置 SessionRepository 导致设备认证流程错误问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] mapstruct-processor 版本升级至 1.5.5.Final</li>
<li>[升级] maven-gpg-plugin 版本升级至 3.1.0</li>
<li>[升级] redisson 版本升级至 3.21.1</li>
<li>[升级] fastjson2 版本升级至 2.0.31</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.752</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.126.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.6.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Alibaba 版本升级至 2022.0.0.0-RC2</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.11.2-2022.0.1</li>
<li>[升级] Spring Boot Admin 版本升级至 3.0.3</li>
<li>[升级] Debezium 版本及相关基础设施版本升级至 2.2</li>
<li>[重构] 重构 Jackson 全局配置以及相关自定义代码
<ol>
<li>不再使用 @Primary ObjectMapper Bean 的方式全局配置 Jackson。</li>
<li>改用更灵活的、支持扩展的 Customizer 方式全局配置 Jackson。</li>
<li>同时兼顾全局配置、yaml 配置和自定义扩展，支持模块代码独立设置 Jackson 参数。</li>
<li>解决原有配置方式，只能通过修改源代码进行配置变更，无法自定义扩展问题。</li>
<li>解决原有配置方式，导致 yaml 配置失效、Spring Boot Jackson 默认 Bean 不注入等问题。</li>
</ol>
</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] Jackson XSS 防护代码迁移至 rest-sdk-protect 模块，降低 XSS 相关代码耦合性。</li>
<li>[重构] 部分重复代码、类似代码提取为公共方法或类</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.21.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.748</li>
</ul>
</li>
</ul>
<h2>v3.0.6.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.0.6</li>
<li>[升级] Spring Security 版本升级至 6.1.0-RC1</li>
<li>[升级] Spring Authorization Server 版本升级至 1.1.0-RC1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[漏洞] 修复 Snakeyaml (CVE-2022-1471) 存在反序列化漏洞 和 (CVE-2022-41854) 存在缓冲区溢出漏洞</li>
<li>[新增] 新增服务优雅停机支持</li>
<li>[新增] 新增 Nacos Server Postgresql 数据源支持插件及数据库初始化脚本</li>
<li>[修复] 修复 Access Token Scope 设置不正确导致 OAuth2 Client 使用授权码模式登录出错问题。</li>
<li>[修复] 修复组件模块编译错误</li>
<li>[修复] 修复自定义社交登录模式中，微信小程序参数获取不到错误。</li>
<li>[优化] Security 相关代码适配 Spring Security 最新用法。</li>
<li>[优化] 使用最新版 Bpmn-js、Diagram-js 内部 Typescript 定义替换自定义 Typescript 定义，并优化重构相关代码以适配最新定义</li>
<li>[重构] 采用页面嵌入 Vue 方式，重构授权页面。同时兼容 Device Authorization 授权页面</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] snakeyaml 版本升级至 2.0.</li>
<li>[升级] spring-security-cas 版本升级至 5.8.3</li>
<li>[升级] fastjson2 版本升级至 2.0.29</li>
<li>[升级] wxjava 版本升级至 4.5.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.743</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.110.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.5.5</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 1.11.1-2022.0.1</li>
<li>[升级] Spring Authorization Server 版本升级至 1.1.0-RC1</li>
<li>[升级] Spring Security 版本升级至 6.1.0-RC1</li>
<li>[升级] Camunda 版本升级至 7.19.0，同步更新数据库脚本</li>
<li>[升级] Skywalking Agent 版本升级至 8.15.0</li>
<li>[升级] Antisamy 版本升级至 1.7.3，同步升级 XSS 共计防护策略配置文件</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构各个模块 Constants 常量，清除无用代码，让各个常量归属更合理、命名更统一规范。</li>
<li>[重构] 重构 Minio 核心服务代码</li>
<li>[优化] 优化 Spring Authorization Server JPA 存储相关代码</li>
<li>[优化] 优化 Docker Compose 脚本</li>
<li>[修复] 修复针对 “text/html” 头的统一错误信息页面不显示问题</li>
<li>[删除] 删除重复放置的静态错误页面代码</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] antisamy 版本升级至 1.7.3</li>
<li>[升级] bcprov-jdk15to18 版本升级至 1.73</li>
<li>[升级] fastjson2 版本升级至 2.0.28</li>
<li>[升级] hutool 版本升级至 5.8.18</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.737</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.107.ALL</li>
</ul>
</li>
<li>升级说明
<ul>
<li>因仓库提交文件大小限制，所以本次发布不在上传 Skywalking Agent 相关 Jar 包，有需要请自行下载。</li>
<li>Camunda 7.19.0 尚未适配 Spring Boot 3，还无法正常使用，当前仅做依赖升级，</li>
</ul>
</li>
</ul>
<h2>v3.0.5.4</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 2.2.2</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 收 DeviceController 类的 success()方法影响，导致授权码模式页面跳转错误问题。fix: #I6US82 (ISSUED by 大叔丨小巷)</li>
</ul>
</li>
</ul>
<h2>v3.0.5.3</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 优化简单数据审计内容，新增实时数据审计、数据的变更记录、查看特定历史数据等数据审计功能</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 单位删除时增加参数接收设定 (PR by 未来)</li>
<li>[新增] 新增 MongoDB 基础 Entity、Repository、Service、Controller 和 MybatisPlus 基础 Controller，方便业务接口代码编写。</li>
<li>[优化] 设备认证授权模式激活页面调整</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] fastjson2 版本升级至 2.0.27</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.731</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.13.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.101.ALL</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.16.2</li>
<li>[升级] xnio 版本升级至 3.8.9.Final</li>
</ul>
</li>
</ul>
<h2>v3.0.5.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 1.10.3-2022.0.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复数据库初始化数据 sql 脚本错误</li>
<li>[修复] 修复多租户数据源租户 ID 异步校验不生效问题</li>
<li>[修复] 修复前端应用新建多租户页面跳转错误问题</li>
<li>[优化] 优化数据自动初始化脚本放置位置，与新版本代码创建数据表需要启动两个服务机制进行统一。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] springdoc 版本升级至 2.1.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.726</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.92.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.5.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2022.0.2</li>
<li>[重构] 重构多租户功能，优化代码实现
<ol>
<li>多租户代码提取为单独代码模块，支持独立数据库(Database)、共享数据库(Schema)、共享数据表(Discriminator) 三种模式</li>
<li>默认开启共享数据表(Discriminator)模式, 取消所有表默认添加 TenantId 字段方式，改为按需添加以规避不必要的设计混乱</li>
<li>优化共享数据库(Schema)模式数据源切换逻辑，与独立数据库(Database)分离实现，避免冲突。</li>
<li>增加多租户数据源管理功能，多租户数据源从配置文件配置方式变更为通过数据表进行管理。</li>
<li>优化多租户数据库连接池、数据库事务支持。</li>
</ol>
</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增前端工程 Authorization Code + PKCE 支持工具代码。</li>
<li>[新增] 增加 Spring 默认仓库，解决未使用腾讯镜像仓库无法下载 snapshot 或 milestone 版本问题。(PR by tao)</li>
<li>[优化] 删除 @herodotus/plugins 模块，将其代码移动到主工程。解决基础样式编译后过大以及编译新版本 quasar 问题出错。</li>
<li>[重构] 删除 data-sdk-p6spy 模块，将相关代码合并至 data-sdk-jpa 模块。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.20.1</li>
<li>[升级] hutool 版本升级至 5.8.16</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.724</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.87.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.5.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.0.5</li>
<li>[升级] Spring Boot Admin 版本升级至 3.0.2</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.10.2-2022.0.1</li>
<li>[升级] Spring Authorization Server 版本升级至 1.1.0-M2</li>
<li>[升级] Spring Security 版本升级至 6.1.0-M2</li>
<li>[升级] Nacos 版本升级至 2.2.1</li>
<li>[漏洞] 修复 Spring DoS 安全漏洞 (CVE-2023-20861)</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复授权码模式登录页面在最新版本 Spring Authorization Server 下被拦截问题</li>
<li>[修复] 优化前端 Vite 生产模式下 chunk 打包策略，解决 chunk 不合理设置，产生编译后的代码运行异常，导致前端页面无法正常显示问题。</li>
<li>[修复] 修复 docker-compose 脚本中，最新版本 Nacos 缺少新增默认环境变量，导致 Nacos 镜像无法正常启动错误。fix：#I6OMKH (ISSUED by <code>乌拉松</code>)</li>
<li>[修复] 修复 mysql 数据库初始化脚本缺少 sys_user 数据问题。fix：#I6PNHB (ISSUED by <code>ustcck</code>)</li>
<li>[优化] Polaris 配置添加注册中心地址,docker 环境变量适配 (PR by <code>我问这瓜保熟吗</code>)</li>
<li>[优化] spring cloud alibaba 相关配置增加适配 docker 环境变量配置</li>
<li>[新增] 支持字段级多租户模式</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-embedder 版本升级至 3.9.1</li>
<li>[升级] maven-compat 版本升级至 3.9.1</li>
<li>[升级] fastjson2 版本升级至 2.0.26</li>
<li>[升级] mybatis 版本升级至 3.5.13</li>
<li>[升级] tencentcloud-sdk-java 版本升级至 3.1.720</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.83.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.4.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] SpringDoc 版本升级至 2.0.4</li>
</ul>
</li>
</ul>
<h2>v3.0.4.1</h2>
<ul>
<li>主要更新
<ul>
<li>[改版] 基于 SAS 的动态接口权限全新改版
<ul>
<li>不再直接使用接口作为权限数据，提取接口数据单独管理，额外增加权限层，支持同一权限配置多个接口。</li>
<li>用户权限 和 OAuth2 Scope 权限，均已支持同一权限配置多个接口。提升权限配置和使用的便捷性，同时解决以接口作为权限导致 Token 过大过长问题。</li>
<li>全面支持 Ant 风格 REST 接口转权限数据校验，支持静态配置权限与动态权限重叠冲突检查，包括通配符及占位符形式接口重叠冲突检查。</li>
<li>权限重叠冲突检查会自动选取最大匹配规则作为权限校验元数据。例如：静态权限 <code>/message/**</code> 和动态权限 <code>/message/send</code> 重叠，会自动选取 <code>/message/**</code> 作为权限校验元数据。</li>
<li>无须重启服务、修改代码，支持在系统后台动态修改某个接口的表达式权限，提升接口权限设置的便捷性和灵活性。</li>
<li>结合最新版本 <code>Spring Authorization Server</code> 和 <code>Spring Security</code>，重新梳理当前支持动态表达式权限，去除冗余、重复以及不再支持的表达式。目前支持的表达式动态表达式权限包括：<code>permitAll</code>, <code>anonymous</code>, <code>rememberMe</code>, <code>denyAll</code>, <code>authenticated</code>, <code>fullyAuthenticated</code>, <code>hasRole</code>, <code>hasAnyRole</code></li>
<li>极大地简化了接口数据汇总、权限转换、重叠冲突校验、权限分发等核心代码逻辑。</li>
</ul>
</li>
<li>[升级] Spring Boot 版本升级至 3.0.4</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.10.0-2022.0.1</li>
<li>[升级] Spring Boot Admin 版本升级至 3.0.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[漏洞] 强制升级 commons-fileupload 版本至 1.5 ，修复 CVE-2023-24998 安全漏洞</li>
<li>[重构] 结合各模块组件的职责及定位，重新划分和调整部分模块的归属。</li>
<li>[重构] 原 dante-cloud-upms-logic 和 dante-cloud-upms-rest 代码调整至 dante-engine，后续以组件形式调用。</li>
<li>[重构] 原 dante-module-upms-logic 和 dante-module-upms-rest 模块，名称分别变更为 dante-module-social 和 dante-module-metadata，职责和用途更加清晰</li>
<li>[重构] 重新定义系统部分管理功能接口路径，增加统一标识方便查看及数据库管理操作。</li>
<li>[优化] 优化多级缓存 Key 生成逻辑，解决原有以 JSON 格式作为缓存 Key，导致缓存数据在可视化工具中查看混乱问题。</li>
<li>[优化] 前端主要管理功能配合新版动态权限同步优化</li>
<li>[修复] 新增在服务多实例情况下，多级缓存权限数据本地同步支持。解决在同一消费组下，未能消费到数据的服务实例校验权限错误问题。</li>
<li>[修复] 彻底解决初次搭建过程中因操作步骤错误，产生冗余缓存数据，会出现 <code>Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1</code> 错误问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] springdoc 版本升级至 2.0.3</li>
<li>[升级] fastjson2 版本升级至 2.0.25</li>
<li>[升级] hutool 版本升级至 5.8.15</li>
<li>[升级] wxjava 版本升级至 4.4.9.B</li>
<li>[升级] mybatis 版本升级至 3.5.12</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.712</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.71.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.4.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.0.4</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.20.0</li>
<li>[升级] hutool 版本升级至 5.8.13</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.706</li>
</ul>
</li>
</ul>
<h2>v3.0.3.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.0.3</li>
<li>[升级] 适配 Spring Authorization Server 1.1.0-M1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复单体版 MySQL 数据库初始化脚本错误。fix:#I6HRDP (ISSUED by 柳敏莘)</li>
<li>[修复] 修复查询接口中包含 properties 数组属性会导致 Gateway UrlDecode 失败的问题 fix:#I6HOLA (ISSUED by dens)</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] spring-security-cas 版本升级至 5.8.2</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.702</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.65.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.2.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Authorization Server 版本升级至 1.0.1</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] logstash-logback-encoder 版本升级至 7.3</li>
<li>[升级] minio 版本升级至 8.5.2</li>
<li>[升级] fastjson2 版本升级至 2.0.24</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.697</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.45.ALL</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.16.1</li>
<li>[升级] dom4j 版本升级至 2.1.4</li>
</ul>
</li>
</ul>
<h2>v3.0.2.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 1.9.0-2022.0.1</li>
<li>[升级] Debezimu 相关组件版本升级至 2.1</li>
<li>[变更] 本地权限数据存储修改为多级缓存，以支持服务多实例权限数据共享。</li>
<li>[删除] 删除云片短信发送模块</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构权限存储 Key 对象结构，解决 AntPathMatcher 无法序列化导致多级缓存模式下存储权限数据失败问题。</li>
<li>[修复] Dashboard 页面，在切换 Tab 后，echarts 宽度不再自适应，显示成 100px 问题。</li>
<li>[修复] 根据新版 axios typescript 定义，修改 axios 核心代码解决类型提示错误。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.19.3</li>
<li>[升级] maven-embedder 版本升级至 3.9.0</li>
<li>[升级] maven-compat 版本升级至 3.9.0</li>
<li>[升级] hutool 版本升级至 5.8.12</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.45.ALL</li>
<li>[升级] tencentcloud-sdk-java 版本升级至 3.1.691</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.14</li>
</ul>
</li>
</ul>
<h2>v3.0.2.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2022.0.1</li>
<li>[升级] Spring Boot Admin 版本升级至 3.0.0</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.8.5-2022.0.1</li>
<li>[升级] Skywalking Agent 版本升级至 8.14.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 增加本项目涉及的 Skywalking 常用 Agent Plugins，方便生产环境直接打包使用。</li>
<li>[修复] Dashboard 页面，在切换 Tab 后，echarts 宽度不再自适应，显示成 100px 问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] Okhttps 版本升级至 4.0.1</li>
</ul>
</li>
</ul>
<h2>v3.0.2.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.0.2 fix：#I6B4UT</li>
<li>[升级] Spring Boot Admin 版本升级至 3.0.0-M9 fix：#I6B4WO</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.8.4-2022.0.0 fix：#I6B4WM</li>
<li>[重构] 重构 Athena 工程模块结构，简化该工程多模块结构，删除多余无意义的结构示例性结构，各模块的用途及含义更简洁清晰</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 针对某一类权限校验并不严格的接口，新增只校验是否认证、不校验授权的权限校验策略，以提高权限校验的灵活度，降低权限配置维护的工作量。</li>
<li>[新增] 只校验是否认证、不校验授权的权限校验策略配置</li>
<li>[修复] 修复极端情况下，权限缓存数据丢失，接口请求将跳过权限验证的潜在安全问题。</li>
<li>[修复] 修复 Spring Cloud Gateway 长期运行后出现 io.netty.util.internal.OutOfDirectMemoryError 问题。fix：#I6AZJX (ISSUED by 狂练胸肌李大懒)</li>
<li>[修复] 修复遗漏 Spring Authorization Server 0.4.0 以后新增字段 authorized_scopes 问题</li>
<li>[修复] 修复自定义授权模式使用 Refresh Token 重新申请 Token 抛错问题。fix：#6</li>
<li>[优化] 将默认 WebSocket 连接地址设置为 permitAll 权限，跳过资源服务器检测，由 WebSocket 模块自主进行权限校验。</li>
<li>[优化] 基于 axios 最新版本 typescript 定义，优化前端 @herodotus/core 模块 axios 核心代码，避免编译过程中出现类型校验错误。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-embedder 版本升级至 3.8.7</li>
<li>[升级] maven-compat 版本升级至 3.8.7</li>
<li>[升级] redisson 版本升级至 3.19.1</li>
<li>[升级] minio 版本升级至 3.5.1</li>
<li>[升级] fastjson2 版本升级至 2.0.23</li>
<li>[升级] wxjava 版本升级至 4.4.8.B</li>
<li>[升级] jetcache 版本升级至 2.7.3</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.681</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.37.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.1.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.0.1</li>
<li>[升级] Spring Cloud Alibaba 版本升级至 2022.0.0.0-RC1</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.8.2-2022.0.0</li>
<li>[升级] Nacos 版本升级至 2.2.1-RC</li>
<li>[升级] MyBatis Plus 版本升级至 3.5.3.1</li>
</ul>
</li>
<li>新增特性
<ul>
<li>[新增] 新增融合 Stomp WebSocket、私信、公告等功能的独立消息服务。支持前端与后端采用 WebSocket 和 REST 接口互发消息。</li>
<li>[新增] 新增私信、公告发送，及新消息提醒、基于私信对话浏览信息功能</li>
<li>[新增] 新增手工解析 Token 信息机制，同时支持 JWT Token 和 Opaque Token。</li>
<li>[新增] WebSocket 模块，支持 WebSocket Token 鉴权及登录用户信息解析功能</li>
<li>[新增] 新增实时在线用户统计及同步实时刷新功能</li>
<li>[新增] 前端新增基于用户 ID，动态生成默认 Avatar 功能。</li>
<li>[新增] 前端新增独立个人设置页面，包含私信、公告查阅功能</li>
<li>[新增] 前端采用新版 Stomp JS 实现 WebSocket 支持。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 去除额外增加的 JetCache 自动注入配置代码，改为使用新版 JetCache 自身配置。</li>
<li>[优化] 在 Token 中增加额外用户信息，方便前端使用，减少重复查询。</li>
<li>[优化] 调整部分模块 DTO 代码放置目录</li>
<li>[优化] 恢复 Spring Cloud Alibaba 相关代码，删除 Sentinel 临时支持新版 Spring Boot 代码。</li>
<li>[优化] 将默认基础设施修改为 Alibaba，删除 Spring Cloud Alibaba 基础设施环境中 Zookeeper 服务发现配置。</li>
<li>[重构] 删除 engine-protect 模块，相关代码合并至 engine-rest 模块中</li>
<li>[修复] 修复 XSS Request 包装器 Parameter 方法错误，导致无法获取参数错误。</li>
<li>[修复] 修复 Anti 萨摩耶 XSS 防护代码额外增加的标识符引起的 WebSocket Token 无法正确解析问题。</li>
<li>[修复] 修复因 Hibernate 6 代码变化，导致基于 JetCache 自定义 JPA 二级缓存分页查询数据不正确问题。</li>
<li>[修复] 修复前端异常提示框弹出后，在不同时机下，特别是调试阶段，vue-router 实例不存在导致的页面不会刷新问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] jetcache 版本升级至 2.7.2</li>
<li>[升级] hutool 版本升级至 5.8.11</li>
<li>[升级] spring-boot-admin 版本升级至 3.0.0-M8</li>
<li>[升级] spring-security-cas 版本升级至 5.8.1</li>
<li>[升级] springdoc 版本升级至 2.0.2</li>
<li>[升级] wxjava 版本升级至 4.4.7.B</li>
<li>[升级] mybatis-plus-boot-starter 版本升级至 3.5.3.1</li>
<li>[升级] mybatis-plus-generator 版本升级至 3.5.3.1</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.667</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.9.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.13</li>
</ul>
</li>
</ul>
<h2>v3.0.0.0</h2>
<ul>
<li>
<p>重要更新</p>
<ul>
<li>Spring Boot 版本升级至 3.0.0</li>
<li>Spring Cloud 版本升级至 2022.0.0</li>
<li>Spring Authorization Server 版本升级至 1.0.0</li>
<li>Spring Cloud Tencent 版本恒机制 1.8.2-2022.0.0</li>
<li>Spring Boot Admin 版本升级至 3.0.0-M6</li>
<li>Spring Doc 版本升级至 2.0.1</li>
</ul>
</li>
<li>
<p>其它更新</p>
<ul>
<li>[重构] 根据新版 Spring Authorization Server 代码结构及类命名，对 Spring Authorization Server 相关代码进行重构适配。</li>
<li>[重构] OAuth2 Application 实体类增加 authorization code time-to-live 字段。</li>
<li>[重构] OAuth2 Application 中 accessTokenTimeToLive、refreshTokenTimeToLive、authorizationCodeTimeToLive 属性的默认值，修改为与 Spring Authorization Server TokenSettings 一致。</li>
<li>[重构] 重构 Spring Authorization Server OAuth2 相关代码分包和模块结构，逻辑更内聚、职责更清晰、模块引用依赖更简洁</li>
<li>[重构] 使用 SpringSecurity 6 最新代码逻辑，重新实现接口鉴权。</li>
<li>[重构] 重构基于 Redis 的微服务 Session 共享配置代码，采用更合理的配置实现分布式微服务 Session 共享</li>
<li>[新增] 新增 Token Settings 授权码模式 Code 有效时间字段。</li>
<li>[新增] 在 Nacos 中增加 logback 配置，新增服务可外部化动态读取 logback.xml 配置模式。以便于更加灵活的进行日志输出配置。</li>
<li>[新增] 外部配置 logback.xml 中，增加 Skywalking 日志上报、ELK 日志中心日志收集、Skywalking TraceId 等支持。同时提供常规及 MDC 两种配置。</li>
<li>[新增] 在统一响应实体 Result 增加 TraceId 信息，在开启 Skywalking Tracing 的情况下，可在返回结果中，统一增加 TraceId，方便跟踪和调试。</li>
<li>[变更] 服务注册临时改为使用 zookeeper。</li>
<li>[变更] 数据库初始化脚本中的初始数据，与 Spring Authorization Server 0.4.0 最新代码适配</li>
<li>[变更] 默认 JDK 版本及 pom.xml 中 &lt;java.version&gt; 指定为变更为 JDK 17。</li>
<li>[变更] Docker 默认 JDK 基础镜像变更为 JDK 17。</li>
<li>[变更] spring.factories 全部替换为新版格式并启用 @AutoConfiguration。原有 ...AutoConfiguration 形式命名配置类，变更为...Configuration 形式命名。</li>
<li>[变更] 取消自定义登录页面中使用 Thymeleaf 内置 Reqeust、Session 对象，改用 Model 方式进行传递。</li>
<li>[变更] 适配新版本依赖注解引用及代码用法</li>
<li>[变更] 删除 Knife4j 相关依赖，后续版本不再提供 Knife4j API 支持。</li>
<li>[变更] 不再提供 docker-maven-plugin 支持，去除相关配置</li>
<li>[修复] 使用 Feign 调用接口时，上游接口 content_type 会传递至下游，错误的 content_type 导致下游接口调用出错问题。</li>
<li>[修复] 在 3.0.0 环境下，Thymeleaf 不再支持 Request、Session 对象调用，导致自定义 Spring Authorization Server 登录页面解析错误</li>
<li>[修复] OpenFeign 使用 OkHttp 做基础请求组件时，启动服务抛出对象无法注入错误问题。</li>
<li>[优化] 优化 git-commit-id 配置，变更为只在服务类型模块中生成相关信息，提升主工程编译速度</li>
<li>[优化] 优化 dependencies 及 pom 配置，去除冗余重复配置，降低 maven 内容的重复化配置。减少不必要的 Maven 插件配置，提升工程代码整体编译效率</li>
<li>[优化] 整体调整各类模块 pom build 配置，适当增加冗余重复配置，以支持 Spring Native 编译需要。规避对所有模块进行 Native 编译产生错误问题。</li>
</ul>
</li>
<li>
<p>配置变更</p>
<ul>
<li>javax 属性节点全部变更为 jakarta</li>
<li>spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults 值修改为 true</li>
<li>org.hibernate.dialect.PostgreSQL10Dialect 变更为 org.hibernate.dialect.PostgreSQLDialect</li>
<li>Redis 相关配置全部移动到 data 属性节点下</li>
</ul>
</li>
<li>
<p>依赖升级</p>
<ul>
<li>[升级] Nacos 版本升级至 2.2.0</li>
<li>[升级] wxjava 版本升级至 4.4.6.B</li>
<li>[升级] antisamy 版本升级至 1.7.2</li>
<li>[升级] redisson 版本升级至 3.19.0</li>
<li>[升级] minio 版本升级至 8.4.6</li>
<li>[升级] fastjson2 版本升级至 2.0.21</li>
<li>[升级] Hutool 版本升级至 5.8.10</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.650</li>
<li>[升级] dysmsapi20170525 版本升级至 2.0.23</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.7.ALL</li>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.6.3</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.12.1</li>
<li>[升级] aliyun-java-sdk-green 版本升级至 3.6.6</li>
<li>[升级] postgresql 版本升级至 42.5.1</li>
<li>[升级] jackson 版本升级至 2.14.1</li>
<li>[升级] jasypt-spring-boot-starter 版本升级至 3.0.5</li>
</ul>
</li>
</ul>
<h2>v3.0.0-RC3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2022.0.0-RC3</li>
<li>[新增] Spring Cloud Tencent 熔断、限流、元数据处理支持</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 前端 API 接口调用，修改为 proxy 代理模式，解决前后端跨域导致前端不创建 Cookie，后端 Session Id 不一致，Session 共享不生效问题。</li>
<li>[重构] 调整 Engine 工程模块结构，整合消息模块和 WebSocket 模块。</li>
<li>[修复] 修复华为云短信发送请求体类型设置错误</li>
<li>[修复] 修复模块编译组件时，vite-plugin-dts 提示找不到第三方模块抛出错误提示问题。</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] redisson 版本升级至 3.18.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.34.86.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.0-RC2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 1.8.1-2022.0.0-RC2</li>
<li>[新增] 现有工程新增对 Spring Cloud Tencent 的适配和支持。</li>
<li>[新增] 新增 Spring Cloud Alibaba、Spring Cloud Tencent 和 Spring Cloud 原生全家桶微服基础设施适配切换能力，以相对便捷的方式快捷迁移使用 Alibaba、Tencent、Spring 等基础设施环境</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 解决使用 edge 浏览器并使用 feign，报错 Unexpected char 0x0a（PR by 狂练胸肌的李大懒）</li>
<li>[修复] 修复前端唯一性字段在新建、编辑不同状态下校验不准确、状态不合理问题。解决导致编辑状态下唯一性字段数据未修改仍会校验失败问题。</li>
<li>[修复] 优化微服务分布式 Session 共享配置，解决共享 Session 不一致问题。</li>
<li>[修复] 在 docker 环境下，服务默认选择容器内部 IP 而不会映射本机 IP 问题。</li>
<li>[优化] 优化 Spring Authorization Server 认证服务配置，整理代码逻辑、去除冗余重复的配置</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] springdoc 版本升级至 2.0.0</li>
<li>[升级] fastjson2 版本升级至 2.0.20</li>
<li>[升级] dysmsapi20170525 版本升级至 2.0.23</li>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.6.3</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.641</li>
<li>[升级] alipay-sdk-java 版本升级至 4.34.71.ALL</li>
<li>[升级] aliyun-java-sdk-green 版本升级至 3.6.6</li>
<li>[升级] postgresql 版本升级至 42.5.1</li>
<li>[升级] jackson 版本升级至 2.14.1</li>
</ul>
</li>
</ul>
<h2>v3.0.0-RC1</h2>
<ul>
<li>主要更新
<ul>
<li>Spring Boot 版本升级至 3.0.0</li>
<li>Spring Authorization Server 版本升级至 1.0.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构 Spring Authorization Server 扩展模块，调整代码结构，逻辑定位更清晰。删除无用包。</li>
<li>[重构] 使用 SpringSecurity 6 最新代码逻辑，重新实现接口鉴权。</li>
<li>[重构] 调整部分 Starter 配置类包路径</li>
<li>[变更] 服务注册发现临时改用 zookeeper</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] spring-security-cas 版本升级至 5.8.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.637</li>
<li>[升级] alipay-sdk-java 版本升级至 4.34.64.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.0-M5</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2022.0.0-RC2</li>
<li>[修复] 修复使用 Feign 调用接口时，上游接口 content_type 会传递至下游，错误的 content_type 导致下游接口调用出错问题。</li>
<li>[修复] 在 3.0.0 环境下，Thymeleaf 不再支持 Request、Session 对象调用，导致自定义 Spring Authorization Server 登录页面解析错误</li>
<li>[优化] 优化 Okhttp 与 Feign 整合配置代码</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] jetcache 版本升级至 2.7.1</li>
<li>[升级] redisson 版本升级至 3.18.0</li>
<li>[升级] fastjson2 版本升级至 2.0.19</li>
<li>[升级] wxjava 版本升级至 4.4.5.B</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.633</li>
<li>[升级] alipay-sdk-java 版本升级至 4.34.52.ALL</li>
<li>[升级] antisamy 版本升级至 1.7.2</li>
<li>[升级] hutool 版升级至 5.8.10</li>
</ul>
</li>
</ul>
<h2>v3.0.0-M4</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.0.0-RC2</li>
<li>[升级] SpringDoc 版本升级至 2.0.0-RC1</li>
<li>[升级] Skywalking Agent 版本升级至 8.13.0</li>
<li>[修复] OpenFeign 使用 OkHttp 做基础请求组件时，启动服务抛出对象无法注入错误问题。</li>
<li>[修复] 修复 Spring Cloud Gateway 不兼容低版本 SpringDoc 导致启动出错问题。</li>
<li>[修复] 优化 Gateway 请求信息拦截机制，增加对 Feign 内部无权限标记的拦截，修复内部接口被外部直接调用的安全漏洞</li>
<li>[删除] 删除 Knife4j 组件依赖，后续版本不在提供 Knife4j 支持。</li>
<li>[优化] 优化 SpringDoc 依赖关系及 Swagger UI 包引入。</li>
<li>[新增] 在 Nacos 中增加 logback 配置，新增服务可外部化动态读取 logback.xml 配置模式。以便于更加灵活的进行日志输出配置。</li>
<li>[新增] 外部配置 logback.xml 中，增加 Skywalking 日志上报、ELK 日志中心日志收集、Skywalking TraceId 等支持。同时提供常规及 MDC 两种配置。</li>
<li>[新增] 在统一响应实体 Result 增加 TraceId 信息，在开启 Skywalking Tracing 的情况下，可在返回结果中，统一增加 TraceId，方便跟踪和调试。</li>
<li>[变更] 服务注册临时改为使用 zookeeper。</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] jackson 版本升级至 2.14.0</li>
<li>[升级] fastjson2 版本升级至 2.0.18</li>
<li>[升级] wxjava 版本升级至 4.4.4.B</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.627</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.12.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.34.47.ALL</li>
</ul>
</li>
</ul>
<h2>v3.0.0-M3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.0.0-RC1</li>
<li>[升级] Spring Cloud 版本升级至 2022.0.0-M5</li>
<li>[升级] Spring Authorization Server 版本升级至 1.0.0-RC1</li>
<li>[升级] Spring Boot Admin 版本升级至 3.0.0-M5</li>
<li>[升级] git-commit-id-maven-plugin 插件依赖更新</li>
<li>[重构] Openfeign HttpClient 配置改用 HttpClient5 配置方式</li>
<li>[重构] UUIDGenerator 改为 UuidGenerator</li>
<li>[重构] 所有涉及 javax 的代码，重构为适配 Jakarta。</li>
<li>[重构] 重构 Spring Authorization Server 扩展模块，调整代码结构，逻辑定位更清晰。删除无用包。</li>
<li>[重构] 使用 SpringSecurity 6 最新代码逻辑，重新实现接口鉴权。</li>
<li>[重构] 原有 JPA @GenericGenerator 注解全部使用最新支持方式替换</li>
<li>[重构] 将 JPA 过时查询缓存标记常量修改为最新常量</li>
<li>[重构] spring.factories 全部替换为新版格式</li>
<li>[重构] 重构权限数据存储、分发代码，替换原有 Spring Security 5 逻辑。</li>
<li>[新增] 临时新增 Sentinel Spring WebMvc Adapter，以解决 当前版本 Sentinel 没有没有适配 Jakarta 问题</li>
<li>[删除] 删除原有自定义 Hibernate PostgreSQL Jsonb 支持代码</li>
<li>[优化] hibernate dialect 改为 org.hibernate.dialect.PostgreSQLDialect</li>
<li>[优化] 更新 Redis 和 JetCache 配置，适配最新版 Spring Boot</li>
<li>[优化] 调整 jetcache 依赖，临时解决 Bean 注入顺序不生效问题</li>
<li>[优化] use_jdbc_metadata_defaults: true 修改为 true</li>
</ul>
</li>
</ul>
<h2>v3.0.0-M2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Authorization Server 版本升级至 0.4.0-M2</li>
<li>[重构] 重构核心代码，适配 Spring Authorization Server 0.4.0-M2。</li>
</ul>
</li>
</ul>
<h2>v3.0.0-M1</h2>
<ul>
<li>主要更新
<ul>
<li>[变更] 默认 JDK 版本及 pom.xml 中 &lt;java.version&gt; 指定为变更为 JDK 17。</li>
<li>[变更] Docker 默认 JDK 基础镜像变更为 JDK 17。</li>
<li>[升级] Spring Authorization Server 版本升级至 0.4.0-M1</li>
<li>[重构] 根据新版 Spring Authorization Server 代码结果及变更，对现有工程代码进行重构适配。</li>
<li>[重构] OAuth2 Application 实体类增加 authorization code time-to-live 字段。</li>
<li>[重构] OAuth2 Application 中 accessTokenTimeToLive、refreshTokenTimeToLive、authorizationCodeTimeToLive 属性的默认值，修改为与 Spring Authorization Server TokenSettings 一致。</li>
<li>[变更] 变更数据库初始化脚本中的初始数据，与最新代码适配</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] wxjava 版本升级至 4.4.4.B</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.623</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.12.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.34.43.ALL</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>v3.1.X</title>
      <link>https://www.herodotus.cn/logs/3.1.html</link>
      <guid>https://www.herodotus.cn/logs/3.1.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">v3.1.X</source>
      <description>v3.1.12.0-Final 主要更新 [升级] Spring Boot 版本升级至 3.1.12 其它更新 [修复] 修复日志代码引入类不规范问题。fix: #I9QR01 (ISSUED by 杨运交) [修复] 修复 Webjars JQuery 重复引入造成等登录页面单独访问无法加载问题。fix: #I9QRT0 (ISSUED by 杨运交...</description>
      <pubDate>Thu, 26 Dec 2024 07:36:25 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v3.1.12.0-Final</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.12</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复日志代码引入类不规范问题。fix: #I9QR01 (ISSUED by 杨运交)</li>
<li>[修复] 修复 Webjars JQuery 重复引入造成等登录页面单独访问无法加载问题。fix: #I9QRT0 (ISSUED by 杨运交)</li>
<li>[修复] 修复授权码模式开启确认页面显示 401 无权限问题。fix: #I9QS1H (ISSUED by 杨运交)</li>
<li>[修复] 修复 Dante OSS 单独引入 Aliyun OSS 配置不会注入错误</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.728</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.19</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.79.ALL</li>
</ul>
</li>
<li>升级说明
<ul>
<li>因 Spring Boot 3.1.X 于 2024年5月停止维护，所以 Dante Cloud 也同步停止维护。3.1.12.0-Final 为 3.1.X 系列最后一个版本</li>
</ul>
</li>
</ul>
<h2>v3.1.11.3</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复 Spring Authorization Server 自定义登录页面静态内容 webjars 加载错误</li>
<li>[修复] 修复 commons text 版本依赖错误导致运行抛错问题</li>
<li>[修复] 修复内置授权码登录页面，控制台抛错错误问题</li>
<li>[修复] 修复内置授权码登录页面脚本依赖模块丢失问题</li>
<li>[修复] 修复接口扫描条件配置默认值错误导致系统登录没有权限问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.725</li>
<li>[升级] influxdb-client 版本升级至 7.1.0</li>
<li>[升级] fastjson2 版本升级至 2.0.50</li>
<li>[升级] quasar webjars 版本升级至 2.16.4</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.74.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.11.2</h2>
<ul>
<li>主要更新
<ul>
<li>[优化] 代码适配 Hutool 6.0.0-M12</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-05-10T01-41-38Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] guava 版本升级至 33.2.0-jre</li>
<li>[升级] redisson 版本升级至 3.30.0</li>
<li>[升级] Hutool 版升级至至 6.0.0-M12</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.720</li>
<li>[升级] font-awesome webjars 版本升级至 6.5.2</li>
<li>[升级] quasar 版本升级至 2.16.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.70.ALL</li>
<li>[升级] mysql 版本升级至 8.4.0</li>
<li>[升级] checker-qual 版本升级至 3.43.0</li>
</ul>
</li>
</ul>
<h2>v3.1.11.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 1.13.2-2022.0.4</li>
<li>[发布] 发布基于 Nacos 2.3.2、使用 Postgresql 数据库作为存储、支持amd、arm 等多CPU架构的 Herodotus Nacos Server Docker 镜像已发布至 Docker Hub。</li>
<li>[发布] 基于 Dante 最新代码重新打包，herodotus/ sentinel-dashboard 镜像。优化打包方式，新增 amd、arm 等多种 CPU 架构支持。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复前端组件库编译出现 Error: Cannot find module @rollup/rollup-win32-x64-msvc. 错误 fix: #I9IYT3</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.4</li>
<li>[升级] redisson 版本升级至 3.29.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.709</li>
<li>[升级] quasar webjars 版本升级至 2.15.4</li>
<li>[升级] vue webjars 版本升级至 3.4.23</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.58.ALL</li>
<li>[升级] xnio 版本升级至 3.8.14.Final</li>
<li>[升级] minio 版本升级至 8.5.10</li>
</ul>
</li>
</ul>
<h2>v3.1.11.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.11</li>
<li>[升级] Spring Authorization Sever 版本升级至 1.1.7</li>
<li>[升级] Debezium 版本升级至 2.6</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 当前的CRUD模式局限于从列表页发起编辑详情页，不能从左侧菜单或其它链接入口发起问题 （感谢 James7 PR）</li>
<li>[修复] 面包屑中间节点对应左侧菜单目录节点，删除链接避免与左侧展开动作逻辑不一致问题（感谢 James7 PR）</li>
<li>[新增] 添加了审批流程和任务流程空页面，搭配前端修复后端加载的Workbench路由，保障保障菜单与标签页整体界面交互逻辑完整展示（感谢 James7 PR）</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-04-18T19-09-19Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.3</li>
<li>[升级] commons-io 版本升级至 2.16.1</li>
<li>[升级] redisson 版本升级至 3.28.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.704</li>
<li>[升级] fastjson2 版本升级至 2.0.49</li>
<li>[升级] sms4j-spring-boot-starter 版本升级至 3.2.1</li>
<li>[升级] vue webjars 版本升级至 3.4.22</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.42.ALL</li>
<li>[升级] commons-text 版本升级至 1.12.0</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.78.1</li>
<li>[升级] bcpkix-jdk18on 版本升级至 1.78.1</li>
<li>[升级] sqlite-jdbc 版本升级至 3.45.3.0</li>
</ul>
</li>
</ul>
<h2>v3.1.10.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.2.3</li>
<li>[升级] Skywalking 版本升级至 9.2.0</li>
<li>[升级] Camunda 版本升级至 7.21.0 正式版。同步更新对应版本 OpenApi 和 SQL 脚本</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 前端 Dashboard 新增后端基础设施管理页面快速访问入口</li>
<li>[修复] 处理了TabsView标签页锁定不固定、详情页与列表管理页位置关系不一致问题（感谢 James7 提交的 PR）</li>
<li>[优化] 添加了标签右键操作菜单、添加了ToolBar独立刷新按钮，支持再关闭标签页布局时仍支持刷新当前页（感谢 James7 提交的 PR）</li>
<li>[优化] 优化前端 Dashboard 默认图表文字显示。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-04-06T05-26-02Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.696</li>
<li>[升级] mybatis plus 版本升级至 3.5.6</li>
<li>[升级] mybatis 版本升级至 3.5.16</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.31.ALL</li>
<li>[升级] maven-source-plugin 版本升级至 3.3.1</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.78</li>
</ul>
</li>
</ul>
<h2>v3.1.10.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 2.3.2</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] Nacos docker 镜像版本升级至 v2.3.2</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-03-30T09-41-56Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.2</li>
<li>[升级] commons-io 版本升级至 2.16.0</li>
<li>[升级] springdoc 版本升级至 2.5.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.692</li>
<li>[升级] fastjson2 版本升级至 2.0.48</li>
<li>[升级] hutool 5.X 版本升级至 5.8.27</li>
<li>[升级] sms4j 版本升级至 3.2.0</li>
<li>[升级] animate.css 版本升级至 4.1.1</li>
<li>[升级] jquery-backstretch 版本升级至 2.1.17</li>
<li>[升级] mdi/font webjars 版本升级至 7.4.47</li>
<li>[升级] quasar webjars 版本升级至 2.15.1</li>
<li>[升级] vue webjars 版本升级至 3.4.21</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.19.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.10.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.10</li>
<li>[升级] Spring Authorization Server 版本升级至 1.1.6</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复自定义 FeignErrorDecoder 处理代码，判断逻辑错误问题 fix: #I99O2G (ISSUED by 杨交运)</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-03-15T01-07-19Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] guava 版本升级至 33.1.0-jre</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.683</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.2.ALL</li>
<li>[升级] okio 版本升级至 3.9.0</li>
<li>[升级] git-commit-id-maven-plugin 版本升级至 8.0.2</li>
<li>[升级] sqlite-jdbc 版本升级至 3.45.2.0</li>
</ul>
</li>
</ul>
<h2>v3.1.9.2</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] Nacos 2.3.1 SQL 脚本</li>
<li>[优化] 明确各个版本及分支代码版权信息</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复前端设计自定义组件模块在新版本 vue 和 vite 环境下，因 Typescirpt 类型错误导致编译失败问题</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-03-07T00-43-48Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.0</li>
<li>[升级] redisson 版本升级至 3.27.2</li>
<li>[升级] springdoc 版本升级至 2.4.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.676</li>
<li>[升级] alipay-sdk-java 版本升级至4.38.221.ALL</li>
<li>[升级] org.json 版本升级至 20240303</li>
<li>[升级] git-commit-id-maven-plugin 版本升级至 8.0.1</li>
</ul>
</li>
</ul>
<h2>v3.1.9.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 2.3.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[安全] 增加 Hutool 5.X pom 配置，修复 SMS4J 依赖 Hutool 低版本携带的 CVE 问题。</li>
<li>[修复] 修复前端粒子效果卡顿问题</li>
<li>[升级] Nacos docker 镜像版本升级至 v2.3.1</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-03-03T17-50-39Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.27.1</li>
<li>[升级] minio 版本升级至 8.5.9</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.671</li>
<li>[升级] fastjson2 版本升级至 2.0.47</li>
<li>[升级] bootstrap webjars 版本升级至 5.3.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.221.ALL</li>
<li>[升级] xnio 版本升级至 3.8.13.Final</li>
</ul>
</li>
</ul>
<h2>v3.1.9.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.9</li>
<li>[升级] Spring Authrization Server 版本升级至 1.1.5</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.663</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.212.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.8.4</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 1.13.1-2022.0.4</li>
<li>[升级] Spring Boot Admin 版本升级至 3.2.2</li>
<li>[回滚] Spring Cloud 版本降级至 2022.0.4</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 代码适配 Hutool 6.0.0-M11</li>
<li>[回滚] 回滚 Spring Cloud 版本至 2022.0.4，以解决新版本 Spring Cloud 升级 Feign 版本导致的 Spring Cloud Tencent 版本不兼容问题</li>
<li>[修复] 修复前端 tsParticles 代码问题，导致整个前端无法运行问题</li>
<li>[修复] 修复前端静态路由自动校验错误</li>
<li>[修复] 修复伴随 Spring Boot 版本，起的 Netty 版本升级，导致的 Spring Cloud Tencent 代码不兼容运行出错问题。</li>
<li>[修复] 修复 Spring Cloud Tencent 配置逻辑问题，导致服务启动出现 <code>The bean 'restTemplateCustomizer', defined in class path resource [com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerAutoConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration$RetryInterceptorAutoConfiguration.class] and overriding is disabled.</code></li>
<li>[优化] 优化以 Spring Cloud Tencent 作为微服务基础设置的模式下日志输出内容</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-02-17T01-15-57Z</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] redisson 版本升级至 3.27.0</li>
<li>[升级] minio 版本升级至 8.5.8</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.661</li>
<li>[升级] hutool 版本升级至 6.0.0-M11</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.18</li>
<li>[升级] org.json 版本升级至 20240205</li>
<li>[升级] okio 版本升级至 3.8.0</li>
</ul>
</li>
</ul>
<h2>v3.1.8.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 1.13.0-2022.0.4</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复伴随 Spring Boot 版本，起的 Netty 版本升级，导致的 Spring Cloud Tencent 代码不兼容运行出错问题。</li>
<li>[修复] 修复前端提示，在 “module” 模式下无法读取 .eslintrc.js 问题</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-02-04T22-36-13Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.652</li>
</ul>
</li>
</ul>
<h2>v3.1.8.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] 升级 Antisamy XSS 防护策略配置文件</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-01-31T20-20-33Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] antisamy 版本升级至 1.7.5</li>
<li>[升级] zxing 版本升级至 3.5.3</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.650</li>
<li>[升级] influxdb-client 版本升级至 7.0.0</li>
<li>[升级] fastjson 版本升级至 2.0.46</li>
<li>[升级] sqlite-jdbc 版本升级至 3.45.1.0</li>
</ul>
</li>
</ul>
<h2>v3.1.8.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2022.0.5</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 去除核心 Dependencies 中无用的依赖配置</li>
<li>[优化] 优化 Cache 相关模块代码，修改部分包名、代码以及注解的使用，符合 Spring 规范的命名和使用方式</li>
<li>[优化] 临时去除 Sentinel 相关依赖及自动注入，规避 Sentinel 与最新 Openfeign 不兼容问题。</li>
<li>[安全] 修复 Jayway JsonPath 安全漏洞(CVE-2023-51074) fix: #I8XWGJ</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-01-18T22-51-28Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.645</li>
<li>[升级] JetCache 版本升级至 2.7.5</li>
<li>[升级] vue webjars 版本升级至 3.4.15</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.200.ALL</li>
<li>[升级] sqlite-jdbc 版本升级至 3.45.0.0</li>
</ul>
</li>
</ul>
<h2>v3.1.8.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.8</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 彻底清除系统中 facility 相关模块依赖的 bcpkix-jdk15on，解决 bcpkix 不同版本依赖冲突导致的前后端数据加密异常问题。fix: #I8XHFK</li>
<li>[优化] 清除为临时解决 SMS4J 启动输出错误信息的相关配置</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-01-16T16-07-38Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.638</li>
<li>[升级] sms4j 版本升级至 3.1.1</li>
<li>[升级] vue webjars 版本升级至 3.4.14</li>
<li>[升级] mysql-connector-j 版本升级至 8.3.0</li>
</ul>
</li>
</ul>
<h2>v3.1.7.5</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 排除所有 bcprov-jdk15on 依赖，彻底解决模拟 SmUtil 抛出 java.lang.NoClassDefFoundError: Could not initialize class org.dromara.hutool.crypto.bc.SmUtil 问题 fix: #I8WPZZ</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 yml 中 @ 占位符编译时不会被替换问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.637</li>
<li>[升级] redisson 版本升级至 3.26.0</li>
<li>[升级] vue webjars 版本升级至 3.4.13</li>
</ul>
</li>
</ul>
<h2>v3.1.7.4</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] sms4j 依赖 hutool 5.x 和 hutool 6.x crypto 国密SMUtil 模块放在一个工程中会产生冲突 fix: #I8W59R</li>
<li>[修复] 升级hutool 版本至 6.0.0-M10 抛出 java.lang.NoClassDefFoundError: Could not initialize class org.dromara.hutool.crypto.bc.SmUtil fix: #I8W5AN</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 适配 Hutool 6.0.0-M10</li>
<li>[升级] minio 镜像版本升级至 RELEASE.2024-01-13T07-53-03Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] hutool 版本升级至 6.0.0-M10</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.636</li>
<li>[升级] vue webjars 版本升级至 3.4.12</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.192.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.7.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.2.1</li>
<li>[重构] 改用 SMS4J 作为系统短信发送组件，重构相关代码，增加 access-sdk-sms 模块，删除已有 SMS 相关所有代码模块</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[安全] 修复 CVE-2023-22102 未经身份验证的攻击者通过多个协议发送恶意请求，最终接管MySQL Protocol漏洞 fix: #I8T9LR</li>
<li>[优化] 优化数据库初始化脚本</li>
<li>[升级] minio 镜像版本升级至 RELEASE.2024-01-11T07-46-16Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.634</li>
<li>[升级] fastjson 版本升级至 2.0.45</li>
<li>[升级] vue webjars 版本升级至 3.4.7</li>
</ul>
</li>
</ul>
<h2>v3.1.7.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Debezium 版本升级至 2.5</li>
<li>[重构] 全面改用 Nacos V2 API，支持微服务流量监控数据持久化存储到 Influxdb 时序数据库，支持通过 Sentinel Dashboard 界面管理存储在 Nacos 中的流量控制配置。支持 Nacos 认证模式。可通过配置开启或关闭相关支持。注意：该版本仅适用于 Nacos 2.2.2 及以上版本。</li>
<li>[发布] 基于 Sentinel 1.8.7 扩展改造的 Dante Sentinel Dashboard Docker 镜像已发布并上传至 Docker Hub。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] Mybatis Plust 版本升级至 3.5.5，修复 Bean named 'ddlApplicationRunner' is expected to be of type 'org.springframework.boot.Runner' but was actually of type 'org.springframework.beans.factory.support.NullBean' 错误问题</li>
<li>[修复] 修复自主封装 Sentinel Dashboard 配置持久化至 Nacos 不支持认证问题。fix: #I6HZJI</li>
<li>[升级] Dockerfile 基础镜像 bellsoft/liberica-openjdk-debian 版本升级至 17.0.9-11</li>
<li>[升级] Dockerfile 镜像 herodotus/sentinel-dashboard 版本升级至 1.8.7</li>
<li>[优化] 优化 Nacos API 返回值处理，支持错误信息返回</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.628</li>
<li>[升级] transmittable-thread-local 版本升级至 2.14.5</li>
<li>[升级] mybatis-plus 版本升级至 3.5.5</li>
<li>[升级] mybatis 版本升级至 3.5.15</li>
<li>[升级] wxjava 版本升级至 4.6.0</li>
<li>[升级] font-awesome webjars 版本升级至 6.5.1</li>
<li>[升级] vue webjars 版本升级至 3.4.0</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.4</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.183.ALL</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.15.0</li>
</ul>
</li>
</ul>
<h2>v3.1.7.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] spring-boot-admin 版本升级至 3.2.0</li>
<li>[修复] 采用临时版本，解决 mybatis-plus 与 Spring Boot 3.1.7 和 3.2.1 版本不兼容，启动抛出 Bean named 'ddlApplicationRunner' is expected to be of type 'org.springframework.boot.Runner' but was actually of type 'org.springframework.beans.factory.support.NullBean' 错误问题</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] Camunda 版本升级至 2.5</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-12-23T07-19-11Z</li>
<li>[新增] 在线文档和项目 Readme 新增出厂安全测试说明</li>
<li>[优化] 代码适配 Hutool 6.0.0-M9</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.625</li>
<li>[升级] hutool 版本升级至 6.0.0-M9</li>
<li>[升级] fastjson2 版本升级至 2.0.44</li>
</ul>
</li>
</ul>
<h2>v3.1.7.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.7</li>
<li>[升级] Spring Authorization Server 版本升级至 1.1.4</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复在不使用系统统一 Session 环境下，，单独调用接口特别是测试接口时，提示 Session 过期的问题。fix：#I8PZY1</li>
<li>[新增] 新增系统现有错误体系，发现未能识别的错误时，在日志中打印提醒功能。fix: #I8Q187</li>
<li>[重构] 重构自定义 OAuth 2 授权模式代码，提取公共重复代码，去除 IDE 中代码重复提示。</li>
<li>[修复] 临时去除 mybatis-plus 相关依赖，解决 mybatis-plus 与 Spring Boot 3.1.7 和 3.2.1 版本不兼容，启动抛出 Bean named 'ddlApplicationRunner' is expected to be of type 'org.springframework.boot.Runner' but was actually of type 'org.springframework.beans.factory.support.NullBean' 错误问题。fix: #I8QJ9V</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-12-20T01-00-02Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.622</li>
<li>[升级] redisson 版本升级至 3.25.2</li>
<li>[升级] influxdb-client 版本升级至 6.12.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.170.ALL</li>
<li>[升级] okio 版本升级至 3.7.0</li>
</ul>
</li>
</ul>
<h2>v3.1.6.3</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 重构自定义错误体系代码，去除 Feedback 类型多余的构造函数方法。fix:#I8PFQH</li>
<li>[修复] 修复自定义错误体系中，自定义的非 HttpStatus 类型错误不生效问题。fix: #I8PFQK</li>
<li>[修复] 修复自定义错误体系中，自定义类型错误，自动计算的错误码不正确问题。fix: #I8PFQP</li>
<li>[修复] 修复自定义错误体系抛出“Cannot invoke &quot;java.lang.Integer.intValue()&quot; because the return value of ...” 错误问题。fix: I8PNJ0</li>
<li>[优化] 更正自定义错误体系中，Validation 校验失败抛出错误的错误类型和错误编码 fix: #I8PFQT</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化核心 Controller 定义，增加数组类型数据转换为统一响应实体 Result 支持。</li>
<li>[新增] 新增外部 Open Api 调用失败统一 Exception。</li>
<li>[修复] 修复核心 Controller 定义，返回字符串类型数据，数据设置错误导致不显示结果问题。fix: #I8PFV1</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-12-14T18-51-57Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.619</li>
<li>[升级] wxjava 版本升级至 4.5.9.B</li>
<li>[升级] vue webjars 版本升级至 3.3.11</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.161.ALL</li>
<li>[升级] checker-qual 版本升级至 3.42.0</li>
<li>[升级] redisson 版本升级至 3.25.1</li>
<li>[升级] guava 版本升级至 33.0.0-jre</li>
</ul>
</li>
</ul>
<h2>v3.1.6.2</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复 Spring Authorization Server 客户端配置缺少 MacAlgorithm 类型相关加密算法错误 fix: #I8NWX5</li>
<li>[优化] 优化前端 OAuth2 配置，增加选择 private_key_jwt 或 client_secret_jwt 模式时，加密算法选择的联动处理，防止错误选择。fix: #I8O0GD</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] Emqx 系统客户端状态转 ApplicationEvent 重构完成。支持系统主题订阅和 Webhook 两种模式。</li>
<li>[重构] 重构 Nacos API 封装 SDK，改变原有登录逻辑及配置参数，适配最新的 2.2.2 以上版本 API。</li>
<li>[优化] 优化基础 Controller result 方法定义，去除以 <code>ID</code> 作为类型的方法定义。规避重载方法类型判断不正确问题。</li>
<li>[优化] 升级前端粒子效果组件版本，采用最新方式重新实现前端粒子显示，并更新粒子效果。fix: #I8NZOX</li>
<li>[修复] 修复前端 OAuth2Application Typescript 属性类型映射错误问题。fix: #I8NWXP</li>
<li>[修复] 修复前端自定义 Datetime组件 v-close-popup 引入错误，在控制台抛出告警信息问题 fix: I8NWY8</li>
<li>[修复] 增强前端登录页面响应式效果，修复在某些环境下登录框过窄的问题。fix: #I8NZMO</li>
<li>[修复] 解决 Nacos API 封装 SDK 在最新版本环境下登录出错问题。</li>
<li>[新增] 新增 Nacos API 登录单元测试</li>
<li>[新增] 新增获取 Nacos 命名空间列表 Rest API 单元测试代码</li>
<li>[新增] 新增 Nacos 配置历史 API 封装</li>
<li>[新增] 新增 Nacos 命名空间 API 封装</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.610</li>
<li>[升级] skywalking agant 版本升级至 9.1.0</li>
<li>[升级] checker-qual 版本升级至 3.41.0</li>
</ul>
</li>
</ul>
<h2>v3.1.6.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 2.3.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 Nacos 配置整体导入包错误</li>
<li>[升级] nacos 镜像版本升级至 v2.3.0</li>
<li>[升级] minio 镜像版本升级至 RELEASE.2023-12-09T18-17-51Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] commons-io 版本升级至 2.15.1</li>
<li>[升级] redisson 版本升级至 3.25.0</li>
<li>[升级] springdoc 版本升级至 2.3.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.609</li>
<li>[升级] influxdb-client 版本升级至 6.11.0</li>
<li>[升级] fastjson2 版本升级至 2.0.43</li>
<li>[升级] transmittable-thread-local 版本升级至 2.14.4</li>
<li>[升级] JustAuth 版本升级至 1.16.6</li>
<li>[升级] wxjava 版本升级至 4.5.8.B</li>
<li>[升级] quasar webjars 版本升级至 2.14.0</li>
<li>[升级] vue webjars 版本升级至 3.3.9</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.157.ALL</li>
<li>[升级] sqlite-jdbc 版本升级至 3.44.1.0</li>
</ul>
</li>
</ul>
<h2>v3.1.6.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.6</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化 message-sdk-mqtt 模块代码，明确入站、出站以及通道相关代码。增加系统统一通道定义类，便于后续其它模块集成使用。 fix: #I8IPWG</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.594</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.144.ALL</li>
<li>[升级] bcprov-jdk15to18 版本升级至 1.77</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.77</li>
</ul>
</li>
</ul>
<h2>v3.1.5.8</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.1.8</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 前端工程适配 Vite 5.0.0，修复 monorepo 模块编译时出现 “The CJS build of Vite's Node API is deprecated” 问题 fix: #I8HLU0</li>
<li>[修复] 清除 Docker Profile 环境下原有的 Native 配置，解决在 Docker Profile 环境下编译错误问题。fix: #I8ICSZ</li>
<li>[优化] 前端工程支持 ES 模块代码的编译生成，以及 ES 模块的加载。fix: #I8HLVI</li>
<li>[优化] 去除所有 Native 相关 pom 配置，待 Spring Boot 后续版本统一进行 Native 处理。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-11-20T22-40-07Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.592</li>
<li>[升级] wxjava 版本升级至 4.5.7.B</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.140.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.17</li>
<li>[升级] sqlite-jdbc 版本升级至 3.44.0.0</li>
</ul>
</li>
</ul>
<h2>v3.1.5.7</h2>
<ul>
<li>主要更新
<ul>
<li>[优化] jetcache 的问题修复及优化。（PR by Kaiser_Li）
<ol>
<li>优化计数缓存签章，增加 maxTimes 作为默认值，简化了 counting 方法，一般情况下只需调用 counting(key)即可</li>
<li>修复 AbstractCountStampManager 中 counting(String identity, int maxTimes)调用报错的问题</li>
<li>优化 AbstractCountStampManager 中对次数的判断，大于 maxTimes 时都返回错误</li>
</ol>
</li>
<li>[新增] 新增 caffeine、jetcache、redis 缓存组件使用详细说明，文档路径：dante-engine/readme/plugins/cache/ 以及 各模块 Readme 说明（PR by Kaiser_Li）</li>
<li>[优化] 代码适配 Hutool 6.0.0-M8</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-11-11T08-14-41Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] minio 版本升级至 8.5.7</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.588</li>
<li>[升级] hutool 版本升级至 6.0.0-M8</li>
<li>[升级] mybatis-plus 版本升级至 3.5.4.1</li>
<li>[升级] mybatis 版本升级至 3.5.14</li>
</ul>
</li>
</ul>
<h2>v3.1.5.6</h2>
<ul>
<li>主要更新
<ul>
<li>优化 cache 模块配置
<ul>
<li>[优化] 优化 cache 模块配置。解决单独使用 cache-sdk-redis 模块时，需要手工引入 CacheProperties 问题。（PR by Kaiser_Li）</li>
<li>[优化] 优化 cache-sdk-redisson 配置，在没有设置 ClusterServersConfig、SentinelServersConfig、SingleServerConfig 时，取 spring.data.redis 下的配置填充（PR by Kaiser_Li）</li>
<li>[优化] 增加 useSslConnection 配置，可通过修改配置参数的方式，实现 redis:// 和 rediss:// 切换。fix: #I8FAGL</li>
</ul>
</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 统一所有 Starter 提示性日志输出级别和输出内容 fix: #I8EFRV</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-11-06T22-26-08Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.583</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.128.ALL</li>
<li>[升级] checker-qual 版本升级至 3.40.0</li>
</ul>
</li>
</ul>
<h2>v3.1.5.5</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 重构系统核心常量所在包，减少包层次，提升代码放置合理性。</li>
<li>[新增] 新增统一对象池定义以及统一池配置参数，减少重复代码，提升代码复用性</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化 dependencies pom 配置，统一为所有模块设置 test 依赖支持，不再采用各个模块单独依赖 test 模块方式。fix: #I8DSIG</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] fastjson2 版本升级至 2.0.42</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.581</li>
<li>[升级] vue webjars 版本升级至 3.3.7</li>
</ul>
</li>
</ul>
<h2>v3.1.5.4</h2>
<ul>
<li>主要更新
<ul>
<li>[回滚] 回滚 commons-text 版本至 1.10.0，解决最新版 commons-text 适配 commons-lang3 版本错误导致服务运行抛错问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.579</li>
</ul>
</li>
</ul>
<h2>v3.1.5.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 1.12.4-2022.0.4</li>
<li>[重构] 将系统前端和后端关键变量和属性命名风格统一，将所有 <code>userName</code> 命名变量方法统一为 <code>username</code>；所有 <code>nickName</code> 命名变量方法统一为 <code>nickname</code>，以规避代码中存在混乱写法产生不可预知问题。fix: #I8DB9N</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复前端因 JWT DECODE 组件大版本更新，使用方法改变，导致系统无法登录问题 fix: #I8BSIL</li>
<li>[修复] 去除部分模块依赖的 commons-logging，解决“Standard Commons Logging discovery in action with spring-jcl: please remove commons-logging.jar from classpath in order to avoid potential conflicts” 问题。fix:#I8D0R5</li>
<li>[优化] 删除本地和 Nacos 配置中，所有的 “cn.herodotus: debug” 日志配置，避免产生 Hibernate SQL 信息输出两遍的误会 fix: #I8COFY</li>
<li>[重构] 重构 Mqtt SDK 模块，优化现有代码实现正常消息发送和接收。增加 @Enable 控制注解，与系统统一消息发送门面融合</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-11-01T18-37-25Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] commons-text 版本升级至 1.11.0</li>
<li>[升级] wxjava 版本升级至 4.5.6.B</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.2</li>
<li>[升级] git-commit-id-maven-plugin 版本升级至 7.0.0</li>
<li>[升级] sqlite-jdbc 版本升级至 3.43.2.2</li>
</ul>
</li>
</ul>
<h2>v3.1.5.2</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增各类消息发送融合门面设计，以统一 API 支持系统集成的、包含 WebSocket、Application Event、Spring Cloud Stream、消息队列等各类消息的发送。解决给类消息组件发送方式、发送入口不一致、代码混杂、使用不方便等问题。fix: #I8BQZR</li>
</ul>
</li>
<li>其他更新
<ul>
<li>[修复] 修复 WebSocket 模块配置参数错误，引起多实例配置不生效，导致 WebSocket 服务多实例消息发送失败问题。fix: #I8BI3I</li>
<li>[修复] 修复前端正常退出系统，WebSocket 抛出“你的主机中的软件中止了一个已建立的连接错误” fix: #I8BIMU</li>
<li>[修复] 修复以 spring-boot-starter-parent 作为 Maven Parent 配置，在当前环境下依赖组件版本错误不会更新问题 fix: #I8BJAB</li>
<li>[重构] 重构 WebSocket 消息发送及多实例消息同步代码，抽取独立 MessageTemplate 作为核心操作代码，规避多实例环境下消息循环发送问题。fix: #I8BIXI</li>
<li>[重构] 抽象策略事件统一发送方法，减少事件发送方法的反复调用。fix: #I8BLD5</li>
<li>[重构] 重构 Security 已有职能定位相关核心代码及模块，合并至统一 OAuth2 资源服务器自动配置模块，提升代码内聚性和模块间依赖逻辑性，减少不合理或重复性依赖关系。fix: #I8BQ94</li>
<li>[重构] 重构 OAuth2 授权服务器相关核心代码及模块，合并至统一 模块中，提升代码内聚性和模块间依赖逻辑性，减少不合理或重复性依赖关系。fix: #I8BQJI</li>
<li>[优化] 优化 Spring Cloud Stream 代码配置，以更优的方式解决单体版环境下 Stream 自启动连接消息队列问题。fix: #I8BKBK</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-10-25T06-33-25Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] commons-io 版本升级至 2.15.0</li>
<li>[升级] redisson 版本升级至 3.24.3</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.576</li>
<li>[升级] okhttps 版本升级至 4.0.2</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.111.ALL</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.893</li>
<li>[升级] xnio 版本升级至 3.8.12.Final</li>
</ul>
</li>
</ul>
<h2>v3.1.5.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 1.12.3-2022.0.4</li>
<li>[新增] 新增 Ip2Region 离线 IP 定位搜索支持模块，同时支持 IPV4 和 IpV6.</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 Ip2Region 模块单元测试代码</li>
<li>[新增] 新增 Dante Engine 统一 Testing Profile 配置，方便后续逐步增加单元测试用例及统一测试</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-10-24T04-42-36Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] guava 版本升级至 32.1.3-jre</li>
<li>[升级] redisson 版本升级至 3.24.2</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.571</li>
<li>[升级] mybatis plus 版本升级至 3.5.4</li>
<li>[升级] mdi__font webjars 版本升级至 7.3.67</li>
<li>[升级] vue webjars 版本升级至 3.3.6</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.105.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.5.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.5</li>
<li>[升级] Spring Authorization Server 版本升级至 1.1.3</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.12.2-2022.0.4</li>
<li>[升级] Debezium 版本升级至 2.4</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化 Maven 相关配置，支持 release 和 milestore 等多源包的下载</li>
<li>[优化] 优化统一 dependencies，尽量使用 Spring Boot 版本依赖，去除重复的 maven 配置。</li>
<li>[优化] 优化自定义 OAuth2 Provider 日志输出，与 SAS 标准代码统一</li>
<li>[修复] 修复 IDEA 和 Maven 3.9.5 环境下，Spring 生态模块版本不一致问题</li>
<li>[安全] 升级 org.json 版本，去除 baidu java sdk 依赖的低版本 org.json 携带的 CVE 漏洞。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-10-07T15-07-38Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.24.1</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.566</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.880</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.98.ALL</li>
<li>[升级] xnio 版本升级至 3.8.11.Final</li>
<li>[升级] sqlite-jdbc 版本升级至 3.43.2.1</li>
<li>[升级] org.json 版本升级至 20231013</li>
</ul>
</li>
</ul>
<h2>v3.1.4.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Camunda 版本升级至 7.20.0 正式版，兼容 Spring Boot 3 的正式版本。同步更新 Camunda OpenAPI 描述文件和 SQL 脚本。</li>
<li>[升级] Antisamy 版本升级至 1.7.4，同步更新最新版本 Antisamy XSS 防护配置文件。</li>
<li>[升级] Skywalking 相关组件版本升级至 9.0.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构外部接入 RestApi 定义。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-10-07T15-07-38Z</li>
<li>[升级] Apache Maven 版本支持 3.9.5</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] fastjson2 版本升级至 2.0.41</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.565</li>
<li>[升级] hutool 版本升级至 6.0.0-M7</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.875</li>
</ul>
</li>
</ul>
<h2>v3.1.4.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] camunda 版本升级至 7.20.0-alpha6</li>
<li>[新增] 新增 rest-sdk-condition 模块，统一管理服务级 REST 相关自定义条件注解</li>
<li>[优化] 优化部分系统级配置参数注入方式，由原来默认使用注入的代用方式改为使用 ServiceContextHolder 统一进行设置以及采用静态方式使用，方便参数的调用降低模块间依赖和耦合。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 前端用户名密码方式登录，增加回车键登录支持</li>
<li>[新增] 新增基础应用默认存储数据源切换配置</li>
<li>[重构] 重新调整基础 Utils 代码，按照用途和类比重新归类，进一步明晰用途以方便使用。</li>
<li>[重构] 重构 REST 相关模块代码，合并原有拆包过细模块，进一步明确职责和定义，按照 Spring Boot 标准重新规范代码配置。</li>
<li>[优化] 优化单体版系统 Endpoint 配置代码，增加单体版模式下默认配置值，以简化原有相关内容需配置大量重复性内容</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-09-20T22-49-55Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] common-io 版本升级至 2.14.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.560</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.872</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.90.ALL</li>
<li>[升级] okio 版本升级至 3.6.0</li>
<li>[升级] checker-qual 版本升级至 3.39.0</li>
<li>[升级] snappy-java 版本升级至 1.1.10.5</li>
</ul>
</li>
</ul>
<h2>v3.1.4.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.1.7</li>
<li>[重构] 采用 Customizer 模式，重新构建错误码体系
<ol>
<li>采用 Customizer 模式，重新构建错误码体系，支持模块级错误码自定义</li>
<li>各模块可灵活的定义错误码，系统会自动聚合并采用统一交互模式进行反馈</li>
<li>新增错误码自动计算支持，无需再通过统一修改基础模块常量定义，无需手动计算基础值。</li>
<li>去除原有错误描述多处定义，改为采用统一管理模式，增加代码可维护性</li>
<li>增强错误码类型以及设置校验，减少认为设置错误。</li>
</ol>
</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 Timestamp 转 LocalDateTime Jackson 反序列化器，方便数字型 Timestamp 时间戳转为标准日期格式的 LocalDateTime 对象。</li>
<li>[修复] 重构 Data 相关代码放置目录，优化 data-sdk-jpa 模块的依赖。修复自定义扩展的 Hibernate QueryKey 无法覆盖默认代码，导致分页失效问题。</li>
<li>[修复] 修复前端部分代码 Typescript 类型校验错误</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-09-23T03-47-50Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] skywalking 版本升级至 9.0.0</li>
<li>[升级] minio-java 版本升级至 8.5.6</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.556</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.85.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.4.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.4</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化工程整体 pom 文件，去除无用的配置及信息</li>
<li>[重构] 重构自定义 JPA UUID 生成器名称，风格与 JPA 定义统一</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-09-20T22-49-55Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版升级至 3.23.5</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.554</li>
<li>[升级] hutool 版本升级至 6.0.0-M6</li>
<li>[升级] bootstrap webjars 版本升级至 5.3.2</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.85.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.3.6</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 融合 Spring Cloud Stream 和 WebSocket，以优雅的方式实现 WebSocket 服务多实例环境下，点对点、广播消息跨实例推送。fix:#I80GHX (FEATURE by jokeway)</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.12.1-2022.0.4</li>
<li>[升级] camunda 版本升级至 7.20.0-alpha5</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复前端日志审计导出 Excel 数据出现空栏与标题不匹配问题</li>
<li>[修复] 修复前端 BaseEntity 基础类型定义属性，与当前后端定义不匹配问题。</li>
<li>[修复] 修复前端升级到 vue-i18n 最新版本后，运行出现 No known conditions for &quot;.&quot; entry in &quot;@intlify/shared&quot; package 错误问题。</li>
<li>[修复] 修复 WebSocket 服务主动关闭连接，后端抛出 java.io.IOException: 你的主机中的软件中止了一个已建立的连接错误</li>
<li>[重构] 重构前端 Excel 导出代码，用最简代码重新实现原有逻辑。提取 hooks 方便代码重用，支持统一设置和增减字段。</li>
<li project.groupid>[优化] 优化所有 pom 配置文件，去除多余的配置信息，改用 Maven 默认；使用明确的 groupid，替代 $</li>
<li>[优化] 优化服务权限数据汇总、同步机制，解决同一服务权限数据反复收取问题，降低启动过程中消息传递消耗。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-09-04T19-57-37Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.549</li>
<li>[升级] jetcache 版本升级至 2.7.4</li>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.6.4</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.853</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.72.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.3.5</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复 WebSocket 模块注入 UserDetailsService 失败，导致 message 服务无法启动问题。fix:#I7ZLGN (ISSUED by jokeway)</li>
<li>[修复] 修复系统统一 Session 过期之后，前端刷新页面不会创建新 Session 问题。</li>
<li>[修复] 修复 Session 过期后，登录页面同时出现错误弹出框和错误提示问题</li>
<li>[修复] 修复查看全部信息跳转至个人主页后，页面不会再次刷新问题。</li>
<li>[修复] 临时修复前端最新版本 vue-i18n 不兼容，导致启动失败问题</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-09-07T02-05-02Z</li>
</ul>
</li>
</ul>
<h2>v3.1.3.4</h2>
<ul>
<li>主要更新
<ul>
<li>[优化] 重构 WebSocket 相关代码，改用使用 Session 的方式获取用户 Principle，以支持多实例 WebSocket 用户信息共享。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复前端工程某个参数名称写入错误导致登录页面接口抛出校验错误问题。</li>
<li>[修复] 修复自定义 BearerTokenResolver 在 message-sdk-websocket 模块注入不正确问题。</li>
<li>[优化] 新增自定义 SessionAuthenticationStrategy，以保证共享 Session 中 principle 信息可以准确设置。</li>
<li>[优化] 优化 WebSocket 模块用户信息获取和传递逻辑不清晰问题。解决高度依赖 BearerTokenResolver 带来的代码耦合性问题。</li>
<li>[优化] 简化和规范化 message-sdk-websocket 配置代码，优化该模块 dependencies 依赖，减少重复的和不必要的依赖。</li>
<li>[优化] 统一控制 httpclient 依赖版本，解决工程中存在多个 httpclient 版本问题。</li>
<li>[优化] 统一控制 checker-qual 依赖版本，解决工程中存在多个 checker-qual 版本，maven 插件提示冲突问题。</li>
<li>[修复] 修复 WebSocket 模块发送消息 Bean 注入错误，导致发送全员消息出现抛空错误。</li>
<li>[新增] 新增系统默认 OIDC Scope，同步更新数据库初始化脚本。</li>
<li>[优化] 使用 Dante Cloud 作为 Minio 控制台 IDentity Provider(身份提供者)进行登录认证实现配置持久化。</li>
<li>[修复] 修复 herodotus.oauth2.authorization.matcher.permit-all 参数配置无效问题。fix: #7Z3O1 (ISSUED by Ryan)</li>
<li>[修复] 修复通过 Social 方式获取的 HerodutusUser 会被解析成字符串&quot;null&quot;，导致存入数据库序列化时保存为 &quot;avatar&quot;:&quot;null&quot; fix: #I7ZIZ3 (ISSUED by jokeway)</li>
<li>[修复] 修复使用 WebSocket 发送私信不成功，界面提示无响应错误 fix: #I7ZJ2E (ISSUED by jokeway)</li>
<li>[重构] 重构所有自定义 Jackson 反序列化 Mixin，将未使用统一工具类的代码，全部改为使用统一工具类以方便维护。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-09-04T19-57-37Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] fastjson2 版本升级至 2.0.40</li>
<li>[升级] okio 版本升级至 3.5.0</li>
<li>[升级] xnio 版本升级至 3.8.10.Final</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.544</li>
<li>[升级] quasar webjars 版本升级至 2.12.6</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.848</li>
</ul>
</li>
</ul>
<h2>v3.1.3.3</h2>
<ul>
<li>主要更新
<ul>
<li>[优化] 将中间件 Session 体系、Spring Session 和 Dante Cloud 自定义 Session 体系深度融合。实现三者 Session 和 Session ID 的统一和一致，高质量的解决微服务架构下 Session 共享和 Session 一致性问题。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 将原有 WebUtils 工具类，拆分为 SessionUtils、CookieUtils、HeadersUtils 三个工具类，补充适配更多的场景的工具方法，用其删除和替换已有重复性代码，提升代码的可维护性。</li>
<li>[优化] 优化单体版 application.yml 配置，以适配 Session 的一致性和 Session 共享统一化配置</li>
<li>[重构] 抽取共性方法，融合 Spring Session 和自定义 Session Session ID 获取逻辑，封装成统一方法，适配各种不同的场景调用需求，使用更方便也更容易进行统一维护。</li>
<li>[重构] 使用多种方式融合的 Session 调用方法，重构前后端数据加密传输、WebSocket 等涉及 Session 的核心逻辑代码，将 Dante Cloud 中所有 Session 解析处理逻辑全部实现统一化。</li>
<li>[重构] 将前端登录页面专用的组件代码，归并至相近文件夹中，以方便代码查找和维护，删除相关代码中的无用变量和引用</li>
<li>[新增] 重新调整 Nacos 配置，优化 Spring Session 存储逻辑配置，新增 Session 统一快捷配置，可同时修改 Servlet 和 Spring Session 有效期。当前默认 2 小时</li>
<li>[升级] minio 镜像版本升级至 RELEASE.2023-08-31T15-31-16Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.543</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.847</li>
</ul>
</li>
<li>升级说明
<ul>
<li>本次更新涉及配置中心配置的变更，系统升级注意配置文件的变更</li>
<li>整合 Session 一致性方案，请参阅文章<a href="https://www.foxitsoftware.cn/bhds/payRead/pmq4wy" target="_blank" rel="noopener noreferrer"><strong>【《Spring Cloud 之 Session 共享及一致性处理》】</strong></a>。具体详情，访问官网<a href="https://www.herodotus.cn/cookbook/" target="_blank" rel="noopener noreferrer"><strong>【Cookbook】</strong></a>。</li>
</ul>
</li>
</ul>
<blockquote>
<p>如官网内容未更新，请注意点击右下角内容更新提示，进行内容刷新。</p>
</blockquote>
<h2>v3.1.3.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.1.6</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 参考 Spring Security 和 Spring Authorization Server 标准代码写法，重构资源服务器和授权服务器配置代码。</li>
<li>[重构] 遵照 Spring Boot 规范，规范化 Dante Cloud 单体版代码。</li>
<li>[重构] 重构自定义授权码模式和客户端模式代码配置代码，采用 Spring Authorization Server 新版本更推荐方式，以更优雅的方式进行扩展授权模式的配置。</li>
<li>[重构] 提取自定义授权模式公共配置实现，按照 Spring Security 标准方式，以更优雅的方式配置自定义授权模式。</li>
<li>[升级] minio 镜像版本升级至 RELEASE.2023-08-31T15-31-16Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.23.4</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.1.2.541</li>
<li>[升级] bootstrap webjars 版本升级至 5.3.1</li>
<li>[升级] font-awesome webjars 版本升级至 6.4.2</li>
<li>[升级] jquery 版本升级至 3.7.1</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.846</li>
<li>[升级] sqlite-jdbc 版本升级至 3.43.0.0</li>
</ul>
</li>
</ul>
<h2>v3.1.3.1</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 大幅重构 Session 相关内容，清晰微服务环境下 Session 共享逻辑，解决 Spring Security 6 环境下 Session 重放防护机制导致 Session 不匹配问题</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复没有代码的空 Starter 模块编译时出现错误警告问题。fix: #I7W54Q</li>
<li>[修复] 修复新版本 Bpmn 组件环境下，properties-panel 样式位置变更，导致 bpmn-designer 组件编译错误问题 fix: #I7W566</li>
<li>[修复] 去除部分频繁输出的日志，减少无意义的频繁输出对问题判断的干扰。</li>
<li>[修复] 修复配置代码错误，导致当 herodotus.swagger.enabled 参数设置为 false 时，系统启动抛错问题 fix: #I7WDQF (ISSUED by Simon Liu)</li>
<li>[重构] 按照 Spring Boot 规范重构部分模块自动配置代码。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.538</li>
<li>[升级] wxjava 版本升级至 4.5.5.B</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.838</li>
<li>[升级] snakeyaml 版本升级至 2.2</li>
<li>[升级] sqlite-jdbc 版本升级至 3.42.0.1</li>
</ul>
</li>
</ul>
<h2>v3.1.3.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.3</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构部分核心包代码依赖逻辑，解决部分包依赖不合理问题。</li>
<li>[重构] 参照 Spring Boot 规范，重构部分自定义 Event 所在模块，以及核心 Event 实现定义。解决 Event 代码放置混乱逻辑不易理解问题。</li>
<li>[重构] 参数 Spring Boot 规范，重构 engine-rest 模块下所有代码模块。同时，调整相关代码，进一步解耦。</li>
<li>[重构] 参照 Spring Boot 规范，重构短信模块</li>
<li>[重构] 将原有实体转换代码，重构为 Converter 形式。</li>
<li>[重构] 将所有 Starter 按照 Spring Boot Starter 规范重构相关代码。</li>
<li>[重构] 为了保证数据一致性，数据库初始化脚本移动至 dante-cloud-oss-ability 服务中。在线文档数据库初始化内容也同步更新。</li>
<li>[修复] 修复 Snakeyaml (CVE-2022-1471) 存在反序列化漏洞 和 (CVE-2022-41854) 存在缓冲区溢出漏洞</li>
<li>[修复] 修复前端自定义 ListItem 组件 directives 设置不正确导致前端控制台抛错问题。</li>
<li>[修复] 修复前端创建存储桶界面校验存储桶是否存在错误</li>
<li>[修复] 修复微服务版本环境下，因自定义代理地址配置错误，导致 OSS 大文件分片上传出错问题。</li>
<li>[删除] 删除 okio 强制版本控制，改为使用统一配置版本。</li>
<li>[删除] 删除 xnio 强制版本控制，改为使用统一配置版本。</li>
<li>[升级] Minio Docker 镜像版本升级至 RELEASE.2023-08-23T10-07-06Z</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] snakeyaml 版本升级至 2.1</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.76</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.835</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.61.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.2.4</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Authorization Server 版本升级至 1.1.2</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.11.9-2022.0.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复单体版数据库初始化脚本存在重复数据问题</li>
<li>[优化] 默认数据库名称进行变更，修改为与项目名称一致，方便记忆和使用。</li>
<li>[优化] 优化对象存储相关 Nacos 配置，将原有配置替换为与 Dante OSS 1.3.0 统一的新版配置。</li>
<li>[优化] 补充 Nacos 2.2.3 Mysql 数据库初始化脚本。</li>
<li>[优化] 补充可外部化配置的 logback.xml 日志配置文件。包含 Skywalking 日志上报、ELK 日志中心日志收集、Skywalking TraceId 等支持。同时提供常规及 MDC 两种配置</li>
<li>[优化] 使用 import 方式，优化 springdoc 依赖包的引入方式，减少过多无用的依赖信息。</li>
<li>[重构] 按照 Spring 生态规范，重构 assistant 和 rest 相关模块自动配置代码，让其既符合 Spring 自动配置规范，又可以提升模块代码的内聚性，减少耦合关联和精简依赖包的依赖。</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] tencentcloud-sdk-java-sms 版升级至 3.1.834</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.60.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.2.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.1.5</li>
<li>[新增] 新增 idea IDE 中显示 Dante Cloud Logo 配置。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构前端 OSS 流式上传、下载 Typescript 相关定义和接口调用服务代码。流式上传、下载替换为使用后端符合 Dante Java OSS API 规范的统一定义 REST API，并完成前后端联调验证。</li>
<li>[重构] 重构前端 OSS 大文件分片上传 Typescript 相关定义和接口调用服务代码。流式上传、下载替换为使用后端符合 Dante Java OSS API 规范的统一定义 REST API，并完成前后端联调验证。</li>
<li>[重构] 因不具备跨业务通用性，调整前端 OSS 相关组件代码放置位置，将其移动到 OSS 页面代码文件夹，以保持业务相关性。</li>
<li>[新增] DateTimeUtils 增加 Date 互转 ZonedDateTime 方法</li>
<li>[新增] 前端工程新增 OSS 普通流式上传、下载进度显示。fix: #I7DO83 (ISSUED by jacky)</li>
<li>[修复] 重构 HDialog 自定义封装组件。修复前端上传对话框操作按钮逻辑不合理，导致前端抛错以及上传成功后不会刷新对象列表问题。</li>
<li>[修复] 修复前端工程页面切换动画不生效问题</li>
<li>[优化] 优化项目 Banner.txt，增加在线文档地址展示</li>
<li>[升级] Minio Docker 镜像版本升级至 RELEASE.2023-08-16T20-17-30Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.23.3</li>
<li>[升级] minio 版本升级至 8.5.5</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.533</li>
<li>[升级] fastjson2 版本升级至 2.0.39</li>
<li>[升级] mybatis-plus-boot-starter 版本升级至 3.5.3.2</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.833</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.55.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.2.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.1.4</li>
<li>[升级] Camunda 版本升级至 7.20.0-alpha4</li>
<li>[升级] Minio 镜像版本升级至 RELEASE.2023-08-04T17-40-21Z</li>
<li>[升级] 支持 Apache Maven 3.9.4 版本</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 完成存储桶新增、删除、列表、是否存在等基础功能定义以及统一 REST API 实现。</li>
<li>[删除] 删除 Minio、S3、Aliyun 与统一实现重复的 Service API 以及无用的代码。</li>
<li>[新增] Dante Cloud Cookbook 专栏</li>
<li>[完成] Dante Cloud 及相关知识学习方法和学习路径的建议</li>
<li>[完成] OAuth 2 中的 Scope 与 Role 深度解析</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] bcprov-jdk15to18 版本升级至 1.76</li>
<li>[升级] guava 版本升级至 32.1.2</li>
<li>[升级] zxing 版本升级至 3.5.2</li>
<li>[升级] redisson 版本升级至 3.23.2</li>
<li>[升级] springdoc 版本升级至 2.2.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.523</li>
<li>[升级] fastjson2 版本升级至 2.0.38</li>
<li>[升级] hutool 版升级至 6.0.0-M5</li>
<li>[升级] wxjava 版本升级至 4.5.4.B</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.820</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.14.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.41.ALL</li>
</ul>
</li>
<li>更新说明
<ul>
<li>Dante Cloud Cookbook 专栏详情请查阅官方文档 Cookbook 栏目</li>
</ul>
</li>
</ul>
<h2>v3.1.2.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2022.0.4</li>
<li>[升级] Spring Cloud Alibaba 版本升级至 2022.0.0.0 正式版</li>
<li>[升级] Spring Boot Admin 版本升级至 3.1.3</li>
<li>[重构] 借鉴 JPA 标准化设计思想，逐步提取和抽象 OSS 标准化操作，形成统一的 Java API 定义，同时封装可操作任意厂商的、统一的 REST API，形成定义统一、动态实现的应用模式（类似于 Hibernate 是 JPA 的一种实现），以方便不同 OSS 的切换和迁移</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修正工程依赖 Dante OSS 版本</li>
<li>[修复] 修复工作流服务的 yml 中出现配置文件不匹配问题。fix: #I7MW91 (ISSUED by 小黑子哪里跑)</li>
<li>[修复] 修复前端 oss 存储对象管理，进入存储桶列表后错误。fix: #I7MM4C (ISSUED by Joyzhou)</li>
<li>[修复] 修复前端封装 Axios 组件，在新版本 Axios 环境下引入类型错误。</li>
<li>[重构] 重构 Sentinel 统一熔断、降级处理代码，适配 Spring Boot 3 环境</li>
<li>[重构] 将前端工程 OSS API 相关代码提取为独立模块，可作为组件独立发布，便于 OSS 代码的管理和维护</li>
<li>[升级] Minio Docker 镜像版本升级至 RELEASE.2023-07-21T21-12-44Z</li>
<li>[升级] Dante OSS 版本升级至 1.2.0</li>
<li>[优化] 前端工程将 Axios 自定义头相关代码提取为共用方法，减少重复代码让代码更简洁。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.514</li>
<li>[升级] fastjson2 版本升级至 2.0.37</li>
<li>[升级] wxjava 版本升级至 4.5.3.B</li>
<li>[升级] webjars jquery 版本升级至 3.7.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.813</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.28.ALL</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.1</li>
<li>[升级] snappy-java 版本升级至 1.1.10.3</li>
</ul>
</li>
</ul>
<h2>v3.1.2.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.2</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.11.8-2022.0.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构静态资源和开放权限资源路径匹配方法，适配最新版 Spring Security</li>
<li>[修复] 修复在新版 Spring Security 环境下，抛出 <code>This method cannot decide whether these patterns are Spring MVC patterns or not. If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); otherwise, please use requestMatchers(AntPathRequestMatcher).</code> 错误，导致无法运行问题</li>
<li>[升级] Minio 镜像版本升级至 RELEASE.2023-07-18T17-49-40Z</li>
</ul>
</li>
</ul>
<h2>v3.1.1.3</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 为所有服务（资源服务器）单独分配 Client ID 和 Client Secret，进一步提升系统安全性以及服务单独管控能力。</li>
<li>[优化] 各服务改为独立配置中心配置，方便单独管理服务独有性配置信息。同时，适配将服务作为独立 OAuth2 客户端设置。</li>
<li>[升级] spring-boot-admin 版本升级至 3.1.2</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 Linux 环境下 快速启动 quick start docker-compose 配置文件。（PR by ：leven-space）</li>
<li>[新增] 新增前端 OAuth2 Redirect URI 设置校验条件，设置为在授权码模式下必须填写。</li>
<li>[优化] 删除 Gateway 服务中无用的 RedisRouteDefinitionRepository 类以及无用的依赖。避免使用该类进行无防护的业务功能开发，导致产生安全问题。</li>
<li>[修复] 修复前端 bpmn-designer 模块在新版 vite 环境下编译模块出错问题。</li>
<li>[修复] 修复前端 components 模块在新版 quasar 环境下编译模块出错问题。</li>
<li>[升级] Dante OSS 版本升级至 1.1.0，新增 Amazon S3 API 模块。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.23.1</li>
<li>[升级] fastjson2 版本升级至 2.0.36</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.506</li>
<li>[升级] wxjava 版本升级至 4.5.2.B</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.800</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.10.ALL</li>
<li>[升级] snappy-java 版本升级至 1.1.10.2</li>
</ul>
</li>
</ul>
<h2>v3.1.1.2</h2>
<ul>
<li>主要更新
<ol>
<li>Minio 控制台可使用 Dante Cloud 作为 IDentity Provider(身份提供者)进行登录认证<br>
Dante Cloud 不仅仅是一套微服务架构开发平台，因其集成了丰富的、符合 OAuth2.1 规范的认证模式，更可作为一套独立的认证授权平台。通过对已有认证模式的完善，Dante Cloud 可以作为 Minio 控制台的身份提供者，使用 Dante Cloud 登录 Minio 控制台。在 Minio 支持的使用 Okta、KeyCloak、Dex、Google、Facebook 等用于用户身份的外部管理方式以外，提供了一种新的方式，也为集成使用 Minio 提供了一种更便捷安全的认证方式。</li>
<li>基于 Camunda 的工作流服务模块已适配 Spring Boot 3.</li>
<li>已完成对新版本 Redis 环境下，系统运行验证，目前支持的 Redis 版本最高到 7.0.12</li>
</ol>
</li>
<li>其它更新
<ul>
<li>[重构] 重构前端 Minio 对象列表，提取组件，去除重复代码，支持文件夹内容显示</li>
<li>[修复] 采用自定义 Typescript 类型定义方式，临时修复新版本 quasar 类型不兼容，导致 IDE 出现类型错误提示问题</li>
<li>[修复] 修复前端部分 Typescript 类型校验错误</li>
<li>[修复] OSS 服务缺少必要配置，导致对象存储连接失败问题。</li>
<li>[修复] 修复系统初始化脚本，默认缺少对象存储相关权限数据问题。</li>
<li>[修复] 修复工作流服务 bootstrap 配置，解决工作流服务配置与当前环境不匹配问题</li>
<li>[优化] 前端重命名 Table 相关 hooks 命名，更加准确定位其用途</li>
<li>[优化] 前端去除重复的自定义 Typescript 类型定义</li>
<li>[优化] 优化前端对象存储参数传递方式，替代原有 pinia 传值方式，删除无用的存储代码</li>
<li>[优化] 优化 linux 环境下，Minio Docker Compose 配置文件</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-07-11T21-29-34Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] camunda 版本升级至 7.20.0-alpha3</li>
<li>[升级] fastjson2 版本升级至 2.0.35</li>
<li>[升级] redisson 版本升级至 3.23.0</li>
<li>[升级] transmittable-thread-local 版本升级至 2.14.3</li>
<li>[升级] dysmsapi20170525 版本升级至 2.0.24</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.794</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.0</li>
</ul>
</li>
</ul>
<h2>v3.1.1.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版升级至 3.1.0</li>
<li>[升级] Debezimu 相关组件及容器版本升级至 2.3</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 调整 @Inner 注解所在模块，提升代码内聚性。</li>
<li>[优化] 优化代码编译配置，增加代码编译过程中，自动生成 spring-autoconfigure-metadata.properties 机制，解决在新版 IDE 中部分跨 Module Bean 注入提示找不到，出现标红问题。</li>
<li>[新增] 前端 OSS 存储桶设置界面，增加版本控制设置功能。</li>
<li>[新增] 前端 OSS 存储桶设置界面，增加保留设置功能</li>
<li>[新增] 前端对象列表界面，增加文件夹显示及查看功能</li>
<li>[修复] Docker Compose 中 Nacos 镜像版本恢复至 v2.2.3。</li>
<li>[修复] 修复前端在 vite-plugin-dts 3.0.X 环境下，编译模块出错问题。</li>
<li>[修复] 修复前端封装 HDialog 关闭操作异常问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] bcprov-jdk15to18 版本升级至 1.75</li>
<li>[升级] guava 版升级至 32.1.1-jre</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.789</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.4.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.1.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.1</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] Minio 版本升级至 8.5.4</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.783</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.171.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.0.8</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Authorization Server 版本升级至 1.1.1</li>
<li>[升级] Nacos 版本升级至 2.2.4</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] Minio Docker 镜像版本升级至 RELEASE.2023-06-16T02-41-06Z</li>
<li>[变更] Minio Docker 镜像源指向 <a href="http://quay.io" target="_blank" rel="noopener noreferrer">quay.io</a></li>
<li>[修复] 修复单体版 JPA 原始配置导致启动时抛出找不到类错误</li>
<li>[优化] 优化单体版 yml 配置，去除失效配置，更新最新配置</li>
<li>[优化] 去除原有旧版静态资源配置方式，去除系统启动过程中出现静态资源配置 warn 日志信息</li>
<li>[优化] 已有代码适配新版本 Spring Authorization Server 内容。提取公共认证 Converter。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] Hutool 版本升级至 6.0.0-M4</li>
<li>[升级] fastjson2 版本升级至 2.0.34</li>
<li>[升级] redisson 版本升级至 3.22.1</li>
<li>[升级] logstash-logback-encoder 版本升级至 7.4</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.781</li>
</ul>
</li>
</ul>
<h2>v3.1.0.7</h2>
<ul>
<li>主要更新
<ul>
<li>[安全] 修复 SQLite JDBC 远程代码执行漏洞 (CVE-2023-32697)</li>
<li>[重构] 统一 OkHttp 、HttpClient 自定义配置，实现 OkHttp 、HttpClient 与 RestTemplate 、Openfeign 整合。统一使用 Feign 配置参数，对 OkHttp 、HttpClient 进行参数设定。同时兼顾 spring cloud 和 非 spring cloud 环境。可通过配置参数，策略化设置使用 OkHttp 还是 HttpClient 作为 RestTemplate 、Openfeign 的基础 HttpClient。</li>
<li>[新增] 新增 RestClient Factory 配置，可条件判断 OkHttp 、HttpClient 环境，动态配置和选择最合适当前的 ClientHttpRequestFactory。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 RestTemplate 初始配置，解决 RestTemplate 默认的 HttpMessageConverter 被清除，导致代理请求转发出错问题。</li>
<li>[修复] 改进接口权限冲突分析逻辑，修复含有通配符的 REST 接口被忽略，导致鉴权失败始终返回 401 错误。</li>
<li>[修复] 修复大文件分片上传功能没有正确传递请求头，导致鉴权失败问题。</li>
<li>[修复] 修复 DateTimeUtils 工具类传递空值转换出错问题。</li>
<li>[重构] 前端抽取统一的方法封装基于 SweetAlert 的删除对话提示框，用最新方法替代前端工程中出现的重复性代码</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] sqlite-jdbc 版本升级至 3.42.0.0</li>
<li>[升级] quasar 版本升级至 2.12.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.779</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.166.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.0.6</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增 REST 接口动态鉴权是否使用严格模式配置，在严格模式下，所有接口必须配置权限才可使用；在非严格模式下，接口只需要携带 Token 即可使用。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增对象存储 Minio 服务器不可用错误代码</li>
<li>[新增] 前端新增对象存储 Bucket 管理界面</li>
<li>[新增] 前端新增对象存储 Bucket 设置界面</li>
<li>[新增] 前端新增对象存储 Object 管理界面</li>
<li>[新增] 前端新增对象存储 Object 设置界面</li>
<li>[新增] 前端新增基于 vue-simple-uploader 的大文件分片存储支持</li>
<li>[优化] 优化基础 Controller 代码，调整判断逻辑，以更好地的支持查询数据成功、未查询到数据、查询失败等三种状态</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] common-io 版本升级至 2.13.0</li>
<li>[升级] guava 版本升级至 32.0.1-jre</li>
<li>[升级] skywalking 版本升级至 8.16.0</li>
<li>[升级] wxjava 版升级至 4.5.1.B</li>
<li>[升级] camunda 版升级至 7.20.0-alpha2</li>
<li>[升级] Webjars Bootstrap 版升级至 5.3.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版升级至 3.1.775</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.154.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.0.5</h2>
<ul>
<li>主要更新
<ul>
<li>[删除] 删除 pay 和 nosql 相关模块，清理系统中独立性较高的模组，以保持系统内核的专注性</li>
<li>[新增] 新增 message-rabbitmq-spring-boot-starter，以方便 RabbitMQ 使用者集成使用。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增对象存储 Minio 服务器不可用错误代码</li>
</ul>
</li>
</ul>
<h2>v3.1.0.4</h2>
<ul>
<li>主要更新
<ul>
<li>Minio 相关代码，从 Dante Engine 中剥离，成为一个独立的项目产品。一方面提升 Dante 项目和 Minio 应用各自应用的独立性，减少互相干扰; 另一方面，在 Minio Java SDK 的基础之上，只做扩展不做改变。同时融合大文件分片上传、秒传、端点续传等常规解决方案，形成开箱即用的、可以快速与应用项目集成的 Spring Boot 组件。目前正在火速完善中，项目地址：<a href="https://gitee.com/herodotus/dante-oss" target="_blank" rel="noopener noreferrer">https://gitee.com/herodotus/dante-oss</a> ，各位兄弟也多多支持，给颗小星星以资鼓励，谢谢哈！</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复在未引入 Spring Cloud OpenFeign 环境下，RestTemplate 配置失效导致启动错误的问题</li>
<li>[新增] 新增 OSS 对象存储服务</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redission 版本升级至 3.22.0</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.769</li>
</ul>
</li>
</ul>
<h2>v3.1.0.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 1.11.7-2022.0.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构 Minio 对象存储核心代码。增加和完善基础操作参数及 API。</li>
<li>[优化] 完善 Minio 大文件分片上传前端和后端代码</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.767</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.150.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.0.2</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 全部代码适配 Hutool 6.0.0-M3</li>
<li>[升级] guava 版本升级至 32.0.0-jre</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构 Validation 相关内容，将自定义校验注解和依赖迁移至 rest-core 模块中，提升模块的内聚性。</li>
<li>[重构] 使用 animated-gif-lib 组件替换 hutool 自带 AnimatedGifEncoder</li>
<li>[重构] 重构树形结构代码拼装逻辑，采用 Converter 方式提取公共方法，极大地简化代码逻辑</li>
<li>[重构] 将所有默认值常量统一归并至 DefaultConstants 类中</li>
<li>[重构] 重构 Minio OSS 核心操作代码，补充 Bucket Object 常规操作请求实体以及前端操作界面。</li>
<li>[优化] 优化数据库初始化脚本，补充对象存储相关内容</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] fastjson2 版本升级至 2.0.33</li>
<li>[升级] vue webjars 版本升级至 3.3.4</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.766</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.13.1</li>
</ul>
</li>
</ul>
<h2>v3.1.0.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2022.0.3</li>
<li>[升级] Nacos 版本升级至 2.2.3</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化接口鉴权策略，不在权限体系中的所有接口和请求将限制访问和使用。</li>
<li>[修复] 修复数据库初始化脚本错误</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.763</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.139.ALL</li>
</ul>
</li>
</ul>
<h2>v3.1.0.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.1.0</li>
<li>[升级] Spring Cloud 版本升级至 2022.0.3</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增多级缓存可按实体独立进行设置的机制。实体独立配置缓存，优先级高于全局统一配置。</li>
<li>[重构] Apache HttpClient 4 相关组件全部修改为使用 Apache HttpClient 5。调整相关依赖包以及代码中引入包</li>
<li>[重构] 大幅优化客户端自动注册功能逻辑，重构相关代码。</li>
<li>[重构] 采用 Spring Converter 接口方式，重构 Spring Authorization Server 数据操作层实体转换代码</li>
<li>[重构] 抽取 Sentinel Gateway 相关代码合并为基础设施 starter。方便 Alibaba 基础设施与其他基础设施环境的切换。</li>
<li>[重构] 将所有业务类型 Service 中，用于标记代码执行的，debug 级别日志删除。仅保部分需要展现关键信息的、方便查看和定位问题的日志输出</li>
<li>[重构] 采用自定义 Jackson 反序列化器和序列化器方式，简化部分管理功能原有 DTO 请求参数转换实体的繁琐代码。</li>
<li>[修复] 修复 UAA 在本地数据访问模式下，修改用户角色权限后，重新获取的用户权限不正确问题。fix: #I718BI (ISSUED by 晏刚)</li>
<li>[修复] 修复 UAA 数据访问策略条件默认状态设置错误问题。</li>
<li>[修复] 调整 rest 相关配置参数，解决原有 feign 开启 okhttp 支持后，openfeign 调用 无法找到服务，出现 UnknownHostException 错误问题。</li>
<li>[修复] 修复 自定义 Login 页面不显示错误信息问题。</li>
<li>[修复] 修复 OAuth2 部分错误信息，脱离系统统一错误处理体系，导致交互错误信息显示不准确问题。</li>
<li>[优化] 清理 rest-sdk-client 包中，自定义 okhttp 和 httpclient 配置代码，统一使用 openfeign 进行配置和管控。</li>
<li>[优化] 升级部分 maven plugin 至最新版本，去除在 maven 3.9.2 下编译代码产生警告信息问题。</li>
<li>[优化] 清理核心 dependencies pom，删除无用的或者与 spring-boot-dependencies 中配置重复的配置</li>
<li>[变更] 临时将 Snakeyaml 版本恢复至 1.33，以保证 Spring Cloud Tencent 可以正常运行，待其兼容 Snakeyaml 2.0 版本发布后再行回复</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] minio 版本升级至 8.5.3</li>
<li>[升级] vue webjars 版本升级至 3.3.3</li>
<li>[升级] tencentcloud-sdk-java-sms 版本升级至 3.1.760</li>
<li>[升级] alipay-sdk-java 版本升级至 4.35.136.ALL</li>
<li>[升级] maven-source-plugin 版本升级至 3.3.0</li>
<li>[升级] git-commit-id-maven-plugin 版本升级至 6.0.0</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>v3.2.X</title>
      <link>https://www.herodotus.cn/logs/3.2.html</link>
      <guid>https://www.herodotus.cn/logs/3.2.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">v3.2.X</source>
      <description>v3.2.6.0 主要更新 [升级] Spring Boot 版本升级至 3.2.6 其它更新 [修复] 修复日志代码引入类不规范问题。fix: #I9QR01 (ISSUED by 杨运交) [修复] 修复 Webjars JQuery 重复引入造成等登录页面单独访问无法加载问题。fix: #I9QRT0 (ISSUED by 杨运交) [修复] 修...</description>
      <pubDate>Thu, 26 Dec 2024 07:36:25 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v3.2.6.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.2.6</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复日志代码引入类不规范问题。fix: #I9QR01 (ISSUED by 杨运交)</li>
<li>[修复] 修复 Webjars JQuery 重复引入造成等登录页面单独访问无法加载问题。fix: #I9QRT0 (ISSUED by 杨运交)</li>
<li>[修复] 修复授权码模式开启确认页面显示 401 无权限问题。fix: #I9QS1H (ISSUED by 杨运交)</li>
<li>[修复] 修复 Dante OSS 单独引入 Aliyun OSS 配置不会注入错误</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.728</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.74.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.19</li>
</ul>
</li>
</ul>
<h2>v3.2.5.4</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复 Spring Authorization Server 自定义登录页面静态内容 webjars 加载错误</li>
<li>[修复] 修复内置授权码登录页面，控制台抛错错误问题。</li>
<li>[修复] 修复内置授权码登录页面脚本依赖模块丢失问题</li>
<li>[优化] 优化数据加解密逻辑，在 session 不统一环境，加解密逻辑不执行直接返回原文。</li>
<li>[优化] 明确抛错错误类型。新增 PlatformException 主要用于非 RuntimeException，确保 Exception 使用合理规范。</li>
<li>[优化] 优化 ServiceContextHolderBuilder 配置，减少在为必要环境必须要注入配置问题</li>
<li>[优化] 优化 Stamp Exception，统一修改为 Exception 类型。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.725</li>
<li>[升级] influxdb-client 版本升级至 7.1.0</li>
<li>[升级] fastjson2 版本升级至 2.0.50</li>
<li>[升级] quasar webjars 版本升级至 2.16.4</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.74.ALL</li>
</ul>
</li>
</ul>
<h2>v3.2.5.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-05-10T01-41-38Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] guava 版本升级至 33.2.0-jre</li>
<li>[升级] redisson 版本升级至 3.30.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.720</li>
<li>[升级] font-awesome webjars 版本升级至 6.5.2</li>
<li>[升级] quasar 版本升级至 2.16.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.70.ALL</li>
<li>[升级] mysql 版本升级至 8.4.0</li>
</ul>
</li>
</ul>
<h2>v3.2.5.2</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复redis密码为空字符串时，redisson创建报错的问题（感谢 Kaiser_Li 提交的 PR）</li>
<li>[修复] 修复单独使用 data-spring-boot-starter组件，使用多租户模式启动报错。fix: #I9L61O (ISSUED by Kaiser_Li)</li>
<li>[修复] 修复 Database 模式多租户，Hibernate 扩展配置信息注入不全问题</li>
<li>[重构] 重构多租户必要扩展 CurrentTenantIdentifierResolver 和 MultiTenantConnectionProvider 相关 Bean 注入方式。</li>
<li>[优化] 代码适配 Hutool 6.0.0-M12</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-05-01T01-11-10Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.4</li>
<li>[升级] hutool 版本升级至 6.0.0-M12</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.713</li>
<li>[升级] sms4j-spring-boot-starter 版本升级至 3.2.1</li>
<li>[升级] quasar webjars 版本升级至 2.15.4</li>
<li>[升级] vue webjars 版本升级至 3.4.25</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.60.ALL</li>
<li>[升级] checker-qual 版本升级至 3.43.0</li>
</ul>
</li>
</ul>
<h2>v3.2.5.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Alibaba 版本升级至 2023.0.1.0</li>
<li>[发布] 发布基于 Nacos 2.3.2、使用 Postgresql 数据库作为存储、支持amd、arm 等多CPU架构的 Herodotus Nacos Server Docker 镜像已发布至 Docker Hub。</li>
<li>[发布] 基于 Dante 最新代码重新打包，herodotus/ sentinel-dashboard 镜像。优化打包方式，新增 amd、arm 等多种 CPU 架构支持。</li>
<li>[优化] 所有 Docker Compose 脚本去除顶部 <code>version</code> 标签（注意：使用该版本 Docker Compose 版本需要使用 1.27+）</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 Docker Compose 文件运行出现 <code>version</code> is obsolete&quot; 问题 fix: #I9IQDG</li>
<li>[修复] 修复前端组件库编译出现 Error: Cannot find module @rollup/rollup-win32-x64-msvc. 错误 fix: #I9IYT3</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.4</li>
<li>[升级] redisson 版本升级至 3.29.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.708</li>
<li>[升级] sms4j-spring-boot-starter 3.2.1</li>
<li>[升级] vue webjars 版本升级至 3.4.23</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.52.ALL</li>
<li>[升级] xnio 版本升级至 3.8.14.Final</li>
</ul>
</li>
</ul>
<h2>v3.2.5.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.2.5</li>
<li>[升级] Spring Authorization Sever 版本升级至 1.2.4</li>
<li>[升级] Debezium 版本升级至 2.6</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 当前的CRUD模式局限于从列表页发起编辑详情页，不能从左侧菜单或其它链接入口发起问题 （感谢 James7 PR）</li>
<li>[修复] 面包屑中间节点对应左侧菜单目录节点，删除链接避免与左侧展开动作逻辑不一致问题（感谢 James7 PR）</li>
<li>[新增] 添加了审批流程和任务流程空页面，搭配前端修复后端加载的Workbench路由，保障保障菜单与标签页整体界面交互逻辑完整展示（感谢 James7 PR）</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-04-18T19-09-19Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.3</li>
<li>[升级] commons-io 版本升级至 2.16.1</li>
<li>[升级] redisson 版本升级至 3.28.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.704</li>
<li>[升级] fastjson2 版本升级至 2.0.49</li>
<li>[升级] sms4j-spring-boot-starter 版本升级至 3.2.1</li>
<li>[升级] vue webjars 版本升级至 3.4.22</li>
<li>[升级] quasar webjars 版本升级至 2.15.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.42.ALL</li>
<li>[升级] commons-text 版本升级至 1.12.0</li>
<li>[升级] maven-source-plugin 版本升级至 3.3.1</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.78.1</li>
<li>[升级] bcpkix-jdk18on 版本升级至 1.78.1</li>
<li>[升级] sqlite-jdbc 版本升级至 3.45.3.0</li>
</ul>
</li>
</ul>
<h2>v3.2.4.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Skywalking 版本升级至 9.2.0</li>
<li>[升级] Camunda 版本升级至 7.21.0 正式版。同步更新对应版本 OpenApi 和 SQL 脚本</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] data-core模块增加了Hibernate雪花主键生成器 SnowFlakeIDGenerator，对应的注解为 @SnowIdGenerator（感谢 Kaiser_Li 提交的 PR）</li>
<li>[优化] 新增 Hibernate 雪花组件生成器适配新版本 Hutool API</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-04-06T05-26-02Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.696</li>
<li>[升级] mybatis plus 版本升级至 3.5.6</li>
<li>[升级] mybatis 版本升级至 3.5.16</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.31.ALL</li>
<li>[升级] maven-source-plugin 版本升级至 3.3.1</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.78</li>
<li>[升级] bcpkix-jdk18on 版本升级至 1.78</li>
</ul>
</li>
</ul>
<h2>v3.2.4.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 2.3.2</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 前端 Dashboard 新增后端基础设施管理页面快速访问入口</li>
<li>[修复] 处理了TabsView标签页锁定不固定、详情页与列表管理页位置关系不一致问题（感谢 James7 提交的 PR）</li>
<li>[优化] 添加了标签右键操作菜单、添加了ToolBar独立刷新按钮，支持再关闭标签页布局时仍支持刷新当前页（感谢 James7 提交的 PR）</li>
<li>[优化] 优化前端 Dashboard 默认图表文字显示。</li>
<li>[升级] Nacos docker 镜像版本升级至 v2.3.2</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-03-30T09-41-56Z</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] commons-io 版本升级至 2.16.0</li>
<li>[升级] springdoc 版本升级至 2.5.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.692</li>
<li>[升级] hutool 5.X 版本升级至 5.8.27</li>
<li>[升级] mdi/font webjars 版本升级至 7.4.47</li>
<li>[升级] quasar webjars 版本升级至 2.15.2</li>
<li>[升级] camunda 版本升级至 7.21.0-alpha5</li>
</ul>
</li>
</ul>
<h2>v3.2.4.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2023.0.1</li>
<li>[升级] Spring Boot Admin 版本升级至 3.2.3</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[验证] 完成 Redis 版本支持验证，已支持 Redis 版本至 7.2.4</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-03-26T22-10-45Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.2</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.688</li>
<li>[升级] fastjson2 版本升级至 2.0.48</li>
<li>[升级] sms4j 版本升级至 3.2.0</li>
<li>[升级] vue webjars 版本升级至 3.4.2</li>
<li>[升级] quasar webjars 版本升级至 2.15.1</li>
<li>[升级] mdi/font webjars 版本升级至 7.4.47</li>
<li>[升级] jquery-backstretch 版本升级至 2.1.17</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.19.ALL</li>
</ul>
</li>
</ul>
<h2>v3.2.4.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.2.4</li>
<li>[升级] Spring Authorization Server 版本升级至 1.2.3</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.13.1-2023.0.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复自定义 FeignErrorDecoder 处理代码，判断逻辑错误问题 fix: #I99O2G (ISSUED by 杨交运)</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-03-15T01-07-19Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] guava 版本升级至 33.1.0-jre</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.683</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.25.11</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.29.12</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.2.ALL</li>
<li>[升级] okio 版本升级至 3.9.0</li>
<li>[升级] git-commit-id-maven-plugin 版本升级至 8.0.2</li>
<li>[升级] sqlite-jdbc 版本升级至 3.45.2.0</li>
</ul>
</li>
</ul>
<h2>v3.2.3.3</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] Nacos 2.3.1 SQL 脚本</li>
<li>[优化] 明确各个版本及分支代码版权信息</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复前端设计自定义组件模块在新版本 vue 和 vite 环境下，因 Typescirpt 类型错误导致编译失败问题</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-03-07T00-43-48Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.0</li>
<li>[升级] redisson 版本升级至 3.27.2</li>
<li>[升级] springdoc 版本升级至 2.4.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.676</li>
<li>[升级] camunda 版本升级至 7.21.0-alpha4</li>
<li>[升级] org.json 版本升级至 20240303</li>
<li>[升级] git-commit-id-maven-plugin 版本升级至 8.0.1</li>
</ul>
</li>
</ul>
<h2>v3.2.3.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 2.3.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[安全] 增加 Hutool 5.X pom 配置，修复 SMS4J 依赖 Hutool 低版本携带的 CVE 问题。</li>
<li>[修复] 修复前端粒子效果卡顿问题</li>
<li>[升级] Nacos docker 镜像版本升级至 v2.3.1</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-03-03T17-50-39Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.27.1</li>
<li>[升级] minio 版本升级至 8.5.9</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.671</li>
<li>[升级] bootstrap webjars 版本升级至 5.3.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.221.ALL</li>
</ul>
</li>
</ul>
<h2>v3.2.3.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Alibaba 版本升级至 2023.0.0.0-RC1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复目前已知的所有 Spring Cloud Alibaba Sentinel 与 Spring Cloud 2023.0.0 不兼容问题和代码</li>
<li>[修复] 恢复所有 Spring Cloud Alibaba Sentinel 相关支持代码及配置</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-02-26T09-33-48Z</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.665</li>
<li>[升级] fastjson2 版本升级至 2.0.47</li>
<li>[升级] xnio 版本升级至 3.8.13.Final</li>
</ul>
</li>
</ul>
<h2>v3.2.3.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.2.3</li>
<li>[升级] Spring Boot Admin 版本升级至 3.2.2</li>
<li>[升级] Spring Authorization Server 版本升级至 1.2.2</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复前端 tsParticles 代码问题，导致整个前端无法运行问题</li>
<li>[修复] 修复前端静态路由自动校验错误</li>
<li>[修复] 修复伴随 Spring Boot 版本，引起的 Netty 版本升级，导致的 Spring Cloud Tencent 代码不兼容运行出错问题。</li>
<li>[修复] 临时修复 Spring Cloud Tencent 配置逻辑问题，导致服务启动出现 `The bean 'restTemplateCustomizer', defined in class path resource [com/tencent/cloud/polaris/loadbalancer/PolarisLoadBalancerAutoConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration$RetryInterceptorAutoConfiguration.class] and overriding is disabled.</li>
<li>[修复] 修复 Spring Cloud Tencent 配置错误，导致 Spring Cloud Tencent 熔断相关代码无法注入问题。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-02-17T01-15-57Z</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] redisson 版本升级至 3.27.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.663</li>
<li>[升级] camunda-bpm-spring-boot-starter-rest 版升级至 7.21.0-alpha3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.212.ALL</li>
</ul>
</li>
</ul>
<h2>v3.2.2.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 1.13.0-2023.0.0-SNAPSHOT</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 临时修复 Spring Cloud Tencent RestTemplateCustomizer bean 冲突导致服务无法正常启动问题</li>
<li>[修复] 修复伴随 Spring Boot 版本，起的 Netty 版本升级，导致的 Spring Cloud Tencent 代码不兼容运行出错问题。</li>
<li>[修复] 修复前端提示，在 “module” 模式下无法读取 .eslintrc.js 问题</li>
<li>[优化] 调整 Spring Cloud Tencent 工程日志输出配置</li>
<li>[优化] 代码适配 Hutool 6.0.0-M11</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-01-31T20-20-33Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] minio 版本升级至 8.5.8</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.656</li>
<li>[升级] hutool 版本升级至 6.0.0-M11</li>
<li>[升级] org.json 版本升级至 20240205</li>
<li>[升级] okio 版本升级至 3.8.0</li>
</ul>
</li>
</ul>
<h2>v3.2.2.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] 升级 Antisamy XSS 防护策略配置文件</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 临时修复前端 tsparticles 组件最新版本自身 ISSUE 导致前端页面打开没有响应问题</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-01-31T20-20-33Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] antisamy 版本升级至 1.7.5</li>
<li>[升级] zxing 版本升级至 3.5.3</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.650</li>
<li>[升级] influxdb-client 版本升级至 7.0.0</li>
<li>[升级] fastjson 版本升级至 2.0.46</li>
<li>[升级] sqlite-jdbc 版本升级至 3.45.1.0</li>
</ul>
</li>
</ul>
<h2>v3.2.2.1</h2>
<ul>
<li>主要更新
<ul>
<li>[安全] 修复 Jayway JsonPath 安全漏洞(CVE-2023-51074) fix: #I8XWGJ</li>
<li>[升级] JetCache 版本升级至 2.7.5</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 去除核心 Dependencies 中无用的依赖配置</li>
<li>[优化] 优化 Cache 相关模块代码，修改部分包名、代码以及注解的使用，符合 Spring 规范的命名和使用方式</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.645</li>
<li>[升级] vue webjars 版本升级至 3.4.15</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.200.ALL</li>
<li>[升级] sqlite-jdbc 版本升级至 3.45.0.0</li>
</ul>
</li>
</ul>
<h2>v3.2.2.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.2.2</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 彻底清除系统中 facility 相关模块依赖的 bcpkix-jdk15on，解决 bcpkix 不同版本依赖冲突导致的前后端数据加密异常问题。fix: #I8XHFK</li>
<li>[优化] 清除为临时解决 SMS4J 启动输出错误信息的相关配置</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-01-18T22-51-28Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.638</li>
<li>[升级] sms4j 版本升级至 3.1.1</li>
<li>[升级] vue webjars 版本升级至 3.4.14</li>
<li>[升级] mysql-connector-j 版本升级至 8.3.0</li>
</ul>
</li>
</ul>
<h2>v3.2.1.7</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 排除所有 bcprov-jdk15on 依赖，彻底解决模拟 SmUtil 抛出 java.lang.NoClassDefFoundError: Could not initialize class org.dromara.hutool.crypto.bc.SmUtil 问题 fix: #I8WPZZ</li>
<li>[修复] 修复封装的 Sentinel-Dashboard Docker 数据库变量不生效问题 fix：#I8WFZC</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 yml 中 @ 占位符编译时不会被替换问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.637</li>
<li>[升级] redisson 版本升级至 3.26.0</li>
<li>[升级] vue webjars 版本升级至 3.4.13</li>
</ul>
</li>
</ul>
<h2>v3.2.1.6</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] sms4j 依赖 hutool 5.x 和 hutool 6.x crypto 国密SMUtil 模块放在一个工程中会产生冲突 fix: #I8W59R</li>
<li>[修复] 升级hutool 版本至 6.0.0-M10 抛出 java.lang.NoClassDefFoundError: Could not initialize class org.dromara.hutool.crypto.bc.SmUtil fix: #I8W5AN</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 去除 pom 中过时的或者无用的配置和依赖。</li>
<li>[优化] 适配 Hutool 6.0.0-M10</li>
<li>[优化] 去除为临时解决 Spring Cloud Tencent 不兼容新版本增加的 maven 配置，等待新版本更新。</li>
<li>[重构] 调整部分核心定义代码所属模块，提升模块代码的内聚性及合理性。fix: #I8W7SU</li>
</ul>
</li>
</ul>
<h2>v3.2.1.5</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.2.1</li>
<li>[重构] 改用 SMS4J 作为系统短信发送组件，重构相关代码，增加 access-sdk-sms 模块，删除已有 SMS 相关所有代码模块 fix: #I8VQ3V</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-01-13T07-53-03Z</li>
<li>[重构] 回滚上一版本提取的提取 Herodotus 软件生态基础核心定义模块</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.633</li>
<li>[升级] fastjson2 版本升级至 2.0.45</li>
<li>[升级] vue webjars 版本升级至 3.4.12</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.4</li>
</ul>
</li>
</ul>
<h2>v3.2.1.4</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.2.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 提取 Herodotus 软件生态基础核心定义模块</li>
<li>[重构] 涉及事务注解的代码，不再使用自定义 TransactionalRollbackException，全部改为使用基础 Exception</li>
<li>[重构] 提取 Herodotus 生态基础通用代码模块，将 assistant-core 中只适用于 Servlet 环境的代码进行隔离和区分</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-01-05T22-17-24Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.633</li>
<li>[升级] fastjson2 版本升级至 2.0.45</li>
<li>[升级] vue webjars 版本升级至 3.4.6</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.4</li>
</ul>
</li>
</ul>
<h2>v3.2.1.3</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复 Nacos 配置文件内容不对应，导致服务启动和系统使用出现异常问题 fix: #I8TE27</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 提取基础核心定义代码模块，清晰依赖和模块定位，规避定义与应用相关代码全部混合在 assistant-core 模块中，不易理解和代码混乱问题。fix: #I8T966</li>
<li>[优化] 优化自定义对象池基础类，去除已经标记为过时的方法设置，同时重新命名相关属性名称 fix: #I8T96W</li>
<li>[安全] 修复 CVE-2023-22102 未经身份验证的攻击者通过多个协议发送恶意请求，最终接管MySQL Protocol漏洞 fix: #I8T9LR</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-01-01T16-36-33Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.629</li>
<li>[升级] vue webjars 版本升级至 3.4.3</li>
</ul>
</li>
</ul>
<h2>v3.2.1.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Debezium 版本升级至 2.5</li>
<li>[重构] 全面改用 Nacos V2 API，支持微服务流量监控数据持久化存储到 Influxdb 时序数据库，支持通过 Sentinel Dashboard 界面管理存储在 Nacos 中的流量控制配置。支持 Nacos 认证模式。可通过配置开启或关闭相关支持。注意：该版本仅适用于 Nacos 2.2.2 及以上版本。</li>
<li>[发布] 基于 Sentinel 1.8.7 扩展改造的 Dante Sentinel Dashboard Docker 镜像已发布并上传至 Docker Hub。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] Mybatis Plust 版本升级至 3.5.5，修复 Bean named 'ddlApplicationRunner' is expected to be of type 'org.springframework.boot.Runner' but was actually of type 'org.springframework.beans.factory.support.NullBean' 错误问题</li>
<li>[修复] 修复自主封装 Sentinel Dashboard 配置持久化至 Nacos 不支持认证问题。fix: #I6HZJI</li>
<li>[升级] Dockerfile 基础镜像 bellsoft/liberica-openjdk-debian 版本升级至 17.0.9-11</li>
<li>[升级] Dockerfile 镜像 herodotus/sentinel-dashboard 版本升级至 1.8.7</li>
<li>[优化] 优化 Nacos API 返回值处理，支持错误信息返回</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.628</li>
<li>[升级] transmittable-thread-local 版本升级至 2.14.5</li>
<li>[升级] mybatis-plus 版本升级至 3.5.5</li>
<li>[升级] mybatis 版本升级至 3.5.15</li>
<li>[升级] wxjava 版本升级至 4.6.0</li>
<li>[升级] font-awesome webjars 版本升级至 6.5.1</li>
<li>[升级] vue webjars 版本升级至 3.4.0</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.4</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.183.ALL</li>
<li>[升级] qiniu-java-sdk 版本升级至 7.15.0</li>
</ul>
</li>
</ul>
<h2>v3.2.1.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] spring-boot-admin 版本升级至 3.2.0</li>
<li>[修复] 采用临时版本，解决 mybatis-plus 与 Spring Boot 3.1.7 和 3.2.1 版本不兼容，启动抛出 Bean named 'ddlApplicationRunner' is expected to be of type 'org.springframework.boot.Runner' but was actually of type 'org.springframework.beans.factory.support.NullBean' 错误问题</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] Camunda 版本升级至 2.5</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-12-23T07-19-11Z</li>
<li>[新增] 在线文档和项目 Readme 新增出厂安全测试说明</li>
<li>[优化] 代码适配 Hutool 6.0.0-M9</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.625</li>
<li>[升级] hutool 版本升级至 6.0.0-M9</li>
<li>[升级] fastjson2 版本升级至 2.0.44</li>
</ul>
</li>
</ul>
<h2>v3.2.1.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.2.1</li>
<li>[升级] Spring Authorization Server 版本升级至 1.2.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复在不使用系统统一 Session 环境下，，单独调用接口特别是测试接口时，提示 Session 过期的问题。fix：#I8PZY1</li>
<li>[新增] 新增系统现有错误体系，发现未能识别的错误时，在日志中打印提醒功能。fix: #I8Q187</li>
<li>[重构] 重构自定义 OAuth 2 授权模式代码，提取公共重复代码，去除 IDE 中代码重复提示。</li>
<li>[修复] 临时去除 mybatis-plus 相关依赖，解决 mybatis-plus 与 Spring Boot 3.1.7 和 3.2.1 版本不兼容，启动抛出 Bean named 'ddlApplicationRunner' is expected to be of type 'org.springframework.boot.Runner' but was actually of type 'org.springframework.beans.factory.support.NullBean' 错误问题。fix: #I8QJ9V</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-12-20T01-00-02Z</li>
<li>[升级] Camunda 版本升级至 7.21.0-alpha2</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.622</li>
<li>[升级] redisson 版本升级至 3.25.2</li>
<li>[升级] influxdb-client 版本升级至 6.12.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.170.ALL</li>
<li>[升级] okio 版本升级至 3.7.0</li>
</ul>
</li>
</ul>
<h2>v3.2.0.2</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 重构自定义错误体系代码，去除 Feedback 类型多余的构造函数方法。fix:#I8PFQH</li>
<li>[修复] 修复自定义错误体系中，自定义的非 HttpStatus 类型错误不生效问题。fix: #I8PFQK</li>
<li>[修复] 修复自定义错误体系中，自定义类型错误，自动计算的错误码不正确问题。fix: #I8PFQP</li>
<li>[修复] 修复自定义错误体系抛出“Cannot invoke &quot;java.lang.Integer.intValue()&quot; because the return value of ...” 错误问题。fix: I8PNJ0</li>
<li>[优化] 更正自定义错误体系中，Validation 校验失败抛出错误的错误类型和错误编码 fix: #I8PFQT</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化核心 Controller 定义，增加数组类型数据转换为统一响应实体 Result 支持。</li>
<li>[新增] 新增外部 Open Api 调用失败统一 Exception。</li>
<li>[修复] 修复核心 Controller 定义，返回字符串类型数据，数据设置错误导致不显示结果问题。fix: #I8PFV1</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-12-14T18-51-57Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.619</li>
<li>[升级] wxjava 版本升级至 4.5.9.B</li>
<li>[升级] vue webjars 版本升级至 3.3.11</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.17.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.161.ALL</li>
<li>[升级] checker-qual 版本升级至 3.42.0</li>
<li>[升级] redisson 版本升级至 3.25.1</li>
<li>[升级] guava 版本升级至 33.0.0-jre</li>
</ul>
</li>
</ul>
<h2>v3.2.0.1</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复 Spring Authorization Server 客户端配置缺少 MacAlgorithm 类型相关加密算法错误 fix: #I8NWX5</li>
<li>[优化] 优化前端 OAuth2 配置，增加选择 private_key_jwt 或 client_secret_jwt 模式时，加密算法选择的联动处理，防止错误选择。fix: #I8O0GD</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] Emqx 系统客户端状态转 ApplicationEvent 重构完成。支持系统主题订阅和 Webhook 两种模式。</li>
<li>[重构] 重构 Nacos API 封装 SDK，改变原有登录逻辑及配置参数，适配最新的 2.2.2 以上版本 API。</li>
<li>[优化] 优化基础 Controller result 方法定义，去除以 <code>ID</code> 作为类型的方法定义。规避重载方法类型判断不正确问题。</li>
<li>[优化] 升级前端粒子效果组件版本，采用最新方式重新实现前端粒子显示，并更新粒子效果。fix: #I8NZOX</li>
<li>[修复] 修复前端 OAuth2Application Typescript 属性类型映射错误问题。fix: #I8NWXP</li>
<li>[修复] 修复前端自定义 Datetime组件 v-close-popup 引入错误，在控制台抛出告警信息问题 fix: I8NWY8</li>
<li>[修复] 增强前端登录页面响应式效果，修复在某些环境下登录框过窄的问题。fix: #I8NZMO</li>
<li>[修复] 解决 Nacos API 封装 SDK 在最新版本环境下登录出错问题。</li>
<li>[新增] 新增 Nacos API 登录单元测试</li>
<li>[新增] 新增获取 Nacos 命名空间列表 Rest API 单元测试代码</li>
<li>[新增] 新增 Nacos 配置历史 API 封装</li>
<li>[新增] 新增 Nacos 命名空间 API 封装</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-12-09T18-17-51Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.610</li>
<li>[升级] skywalking agant 版本升级至 9.1.0</li>
<li>[升级] checker-qual 版本升级至 3.41.0</li>
<li>[升级] wxjava 版本升级至 4.5.8.B</li>
</ul>
</li>
</ul>
<h2>v3.2.0.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2023.0.0</li>
<li>[升级] Nacos Docker 镜像 版本升级至 2.3.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构自定义 ApplicationEvent 命名及使用方式。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-12-06T09-09-22Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.606</li>
<li>[升级] influxdb-client 版本升级至 6.11.0</li>
<li>[升级] redisson 版本升级至 3.25.0</li>
</ul>
</li>
</ul>
<h2>v3.2.0-RC3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 2.3.0</li>
<li>[升级] Maven 版本升级至 3.9.6</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构系统静态权限配置核心代码，统一配置信息出入口，规范调用 API 名称及使用方式。一次性构建解析列表，减少冗余的循环和临时创建 fix: #I8KL29</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2023-12-02T10-51-33Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.604</li>
<li>[升级] alipay-sdk-java 版本升级至 4.38.149.ALL</li>
<li>[升级] sqlite-jdbc 版本升级至 3.44.1.0</li>
<li>[升级] grpc 版本升级至 1.59.1</li>
<li>[升级] springdoc 版本升级至 2.3.0</li>
<li>[升级] transmittable-thread-local 版本升级至 2.14.4</li>
<li>[升级] fastjson2 版本升级至 2.0.43</li>
<li>[升级] commons-io 版本升级至 2.15.1</li>
<li>[升级] JustAuth 版本升级至 1.16.6</li>
<li>[升级] quasar webjars 版本升级至 2.14.0</li>
<li>[升级] vue webjars 版本升级至 3.3.9</li>
</ul>
</li>
</ul>
<h2>v3.2.0-RC2</h2>
<ul>
<li>主要更新
<ul>
<li>[验证] 完成 Spring Cloud Alibaba 在 Spring Boot 3.2 和 Spring Cloud 2023 最新环境的验证。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构 RestTemplate 和 OpenFeign 底层 Engine 及负载均衡统一化配置。去除 Spring Boot 3.2 不再支持的 OkHttp3ClientHttpRequestFactory 相关配置，增加基于 JdkClient 的 RestTemplate 和 OpenFeign 统一配置。fix: #I8JNOK</li>
<li>[适配] 适配 Spring Cloud Alibaba 生态组件。临时去除 Sentinel 相关组件依赖和代码，解决在 Spring Cloud 2023.0.0 环境下，依赖 Sentinel 会引起 Feign 契约配置失效而导致的服务无法启动问题。</li>
</ul>
</li>
</ul>
<h2>v3.2.0-RC1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.2.0</li>
<li>[升级] Spring Cloud 版本升级至 2023.0.0-RC1</li>
<li>[升级] Spring Authorization Server 版本升级至 1.2.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构相关代码，适配 Spring Boot 3.2.0 fix: #I7W5C3</li>
<li>[重构] 重构相关代码，适配 Spring Cloud 2023.0.0-RC1 fix: #7W5C6</li>
<li>[重构] 重构 Spring Authorization Server 自定义 Provider 代码，适配最新的 Spring Authorization Server 1.2.0 版本。fix: #I7W5BY</li>
<li>[重构] 重构 Spring Authorization Server 配置代码，去除过时方法，适配最新代码。</li>
<li>[修复] 修复 Emqx 监控数据转 Influxdb2 的 Spring Integration 流程注入配置条件错误。</li>
<li>[修复] 修复 docker-compose 文件中，polaris 镜像名称不正确问题。</li>
<li>[新增] Spring Cloud Tencent Polaris 配置导入包，方便环境搭建和配置</li>
<li>[优化] 调整 Polaris 本地配置缓存目录，防止与新增配置导入包冲突和混淆</li>
<li>[修复] 调整 polaris docker-compose 默认端口，适配最新版本 Polarismesh Server。</li>
<li>[优化] 优化各个服务中，Spring Cloud Tencent 相关配置，去除无用的或者与默认参数相同的配置。</li>
<li>[新增] 新增 Spring Cloud Tencent 读取和使用本地缓存统一化配置。</li>
<li>[优化] 临时解决 SAS 1.2.0 不兼容问题，后续根据实际情况进行完善和修改。<a href="https://github.com/spring-projects/spring-authorization-server/issues/1435" target="_blank" rel="noopener noreferrer">https://github.com/spring-projects/spring-authorization-server/issues/1435</a></li>
<li>[优化] 删除 dependencies 中重复的或无用的版本控制配置，统一使用 Spring Boot Dependencies 控制依赖版本</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>枚举字典聚合</title>
      <link>https://www.herodotus.cn/develop-guide/design/enum-aggregation.html</link>
      <guid>https://www.herodotus.cn/develop-guide/design/enum-aggregation.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">枚举字典聚合</source>
      <description>概述 Dante Cloud 选择了 Java 枚举的方式来做 字典，同时提供了支持跨模块的聚合管理方式来支持字典管理。这样做的好处有以下几点： Java 枚举可以很好的和 Java 代码融合，不管是作为类型分类、类型判断还是作为实体的参数都很方便。 使用枚举聚合的机制，实现了 Java 枚举的完全动态管理，无需手动录入数据。 利用 JPA 实体支持枚...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>Dante Cloud 选择了 Java 枚举的方式来做 <code>字典</code>，同时提供了支持跨模块的聚合管理方式来支持字典管理。这样做的好处有以下几点：</p>
<ol>
<li>Java 枚举可以很好的和 Java 代码融合，不管是作为类型分类、类型判断还是作为实体的参数都很方便。</li>
<li>使用枚举聚合的机制，实现了 Java 枚举的完全动态管理，无需手动录入数据。</li>
<li>利用 JPA 实体支持枚举属性以及支持使用枚举作为查询参数的特性，将 Java 枚举与实际 SQL 查询进行关联，而无需单独设计 <code>字典表</code> 进行关联查询。</li>
<li>提供统一的接口，前端使用字典时 <code>随用随取</code>，并采用前端缓存避免反复查询后端。既提高的数据使用效率又避免大量查询或者缓存数据影响性能。</li>
</ol>
<h2>[一]Dante Cloud枚举聚合设计</h2>
<p>Dante Cloud 枚举聚合仍旧采用了系统常量的两种方式：<code>Customizer</code> 设计模式和管理与聚合分离的数据模型设计</p>
<h3>[1]<code>Customizer</code> 设计模式</h3>
<p>因为 Dante Cloud 是高度模块化的系统，枚举可能分散在不同的模块之中。采用 <code>Customizer</code> 模式，将服务下的所有枚举聚合。</p>
<p>这一点与 Jackson 和错误码体系的方式完全相同。详见：<a href="/develop-guide/design/jackson.html#%E4%B8%89-spring-boot-%E4%B8%AD%E7%9A%84-customizer" target="_blank">【Spring Boot 中的 <code>Customizer</code> 模式】</a></p>
<h3>[2]管理与聚合分离的数据模型设计</h3>
<p>各服务下的枚举字典，会在服务启动后首先汇总至管理服务中的枚举聚合表（<code>sys_dictionary</code>）中，然后管理服务会分析新增的枚举信息并同步至枚举管理表（<code>sys_enum</code>）中进行管理。</p>
<p>这一点与 REST API 权限的管理与聚合完全相同。详见：<a href="/develop-guide/advance/authentication.html" target="_blank">【OAuth 2 中的鉴权和动态接口鉴权】</a></p>
<h2>[二]枚举聚合开发</h2>
<h3>[1]定义枚举</h3>
<p>Dante Cloud 中使用的枚举主要有两种类型：用于枚举聚合支持前端显示的枚举和仅用于后端代码逻辑的枚举</p>
<h4>用于枚举聚合支持前端显示的枚举</h4>
<p>用于枚举聚合支持前端显示的枚举，必须要继承 <code>DictionaryEnum</code> 接口。该接口主要有两个用途：</p>
<ul>
<li>用途一：标记枚举的值，通过该种方式如果使用该枚举作为实体或者 DTO 的属性，无需编写 Jackson 反序列化处理</li>
<li>用途二：提供统一的聚合信息读取方式，拿到枚举可以直接获取到对应信息，不需要通过反射动态读取，提升代码性能</li>
</ul>
<p>以下为支持枚举聚合的实例代码：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Schema</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "接口映射类别"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">JsonFormat</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">shape</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> JsonFormat</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Shape</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OBJECT</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> enum</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MappingCategory</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> DictionaryEnum</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 权限表达式</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    REST</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"REST"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "REST API"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    GRPC</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"GRPC"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "gRPC"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Map</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MappingCategory</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> INDEX_MAP </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HashMap</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> List</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Dictionary</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> DICTIONARIES </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ArrayList</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        for</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> (</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MappingCategory</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> category </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">:</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> MappingCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">values</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            INDEX_MAP</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">category</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(), category);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            DICTIONARIES</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">category</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDictionary</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">category</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(), </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">category</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ordinal</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Schema</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "枚举值"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> value</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Schema</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "文字"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> label</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    MappingCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> value</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> label</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">value</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> value;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">label</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> label;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MappingCategory</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> get</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> INDEX_MAP</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">get</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(name);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> List</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Dictionary</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getDictionaries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> DICTIONARIES;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getValue</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> value;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getLabel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> label;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>说明：</p>
<ol>
<li><code>value</code> 和 <code>label</code> 是必需的属性，除此以外你可以根据需要灵活增加其它属性。</li>
<li><code>INDEX_MAP</code> 是一个内置字典，与枚举聚合无关，仅是为了快速从值找到对应的枚举。例如：上例中通过字符串 <code>REST</code> 就可以直接找到对应的 MappingCategory.REST 枚举项。</li>
<li><code>DICTIONARIES</code> 是枚举项的快速生成错误，通过静态方法在该枚举实例化时就可以拿到该枚举中的所有条目，避免通过反射之类的操作再次遍历。</li>
</ol>
<h4>仅用于后端代码逻辑的枚举</h4>
<p>仅用于后端代码逻辑的枚举，就是任意形式的 Java <code>enum</code> 类，可以随意定义。因为只用于后端，所以不需要实现 <code>DictionaryEnum</code> 接口。</p>
<h3>[2]定义模块的 <code>Customizer</code> 类</h3>
<p>系统定义了 <code>EnumDictionaryBuilderCustomizer</code> 接口。枚举定义完成之后，需要按照 <code>功能</code> 模块定义该接口的实现类，以实现字典的聚合。</p>
<p>以下为系统枚举聚合的 <code>Customizer</code> 类示例：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> UpmsEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> EnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">EnumDictionaryBuilder</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">append</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Gender</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDictionaries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">append</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">DataItemStatus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDictionaries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">append</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OrganizationCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDictionaries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">append</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Identity</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDictionaries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">append</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ApplicationType</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDictionaries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">append</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ElementCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDictionaries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">append</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MenuScenario</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDictionaries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>Customizer</code> 类定义完成之后，在 <code>@Configuration</code> 类或者 <code>@AutoConfiguration</code> 中，将其定义为 <code>@Bean</code> 即可，示例代码如下所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> EnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> upmsEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">() {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UpmsEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> customizer </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UpmsEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">debug</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Strategy [Upms EnumDictionary Builder Customizer] Configure."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> customizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[三]注意事项</h2>
<h3>[1] <code>Customizer</code> 类的细粒度</h3>
<p>在 Java 多模块工程中，通常都会划分很多的模块，枚举类大概率会分散在不同的模块当中。这时，就需要掌握好 <code>EnumDictionaryBuilderCustomizer</code> 实现类的细粒度。</p>
<p>因为如果定义过多的 <code>EnumDictionaryBuilderCustomizer</code> 实现类，会出现 <code>Customizer</code> 实现类多，但每个类中注册的枚举都很少，徒增维护复杂度。如果 <code>EnumDictionaryBuilderCustomizer</code> 过少，可能就会出现模块间不必要的过度依赖，反而增加了模块与模块间的耦合性。</p>
<p>定义的标准按照：<strong>功能模块的划分定义，而不是按照物理代码模块的划分定义</strong></p>
<p>假设，有一个功能 A（例如：Dante Cloud 中的 OSS），这个功能相关的代码在 <code>物理</code> 层面被划分为 a、b、c、d 四个代码模块。假设这四个代码模块，每个模块中都有枚举定义。那么定义 <code>EnumDictionaryBuilderCustomizer</code> 的细粒度可以参考以下建议规范：</p>
<ol>
<li>假设，a、b、c、d 四个模块是存在依赖关系的，即，在某一个模块中，可以访问到四个模块中所有的枚举。那么统一定义一个 <code>EnumDictionaryBuilderCustomizer</code> 即可。</li>
<li>假设，a、b 模块存在依赖关系，c、d 也存在依赖关系，即其中一个模块可以访问到两个模块中所有的。但是，a、b 与 c、d 之间不存在任何依赖关系，即 a 模块中的枚举，c 和 d 是无法访问到的。这时就可以定义两个 <code>EnumDictionaryBuilderCustomizer</code> 实现类。一个用于注册 a 和 b 模块中的枚举，另一个负责 c 和 d 模块中的枚举。</li>
</ol>
<h3>[2] <code>Customizer</code> Bean的名称</h3>
<p>Spring 中定义 <code>@Bean</code> 有两个关键的要素：Bean 的类型和 Bean 的名称。</p>
<p>如果 Bean 的类型不同，那么多个 Bean 定义为相同的名称也是可以的。如果 Bean 的类型相同，那么多个 Bean 的名称一定要不同。如果应用中出现相同类型、相同名称的 Bean，启动应用时就会出错。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> EnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> upmsEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">() {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UpmsEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> customizer </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UpmsEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">debug</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Strategy [Upms EnumDictionary Builder Customizer] Configure."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> customizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如上所示，定义枚举 <code>Customizer</code> Bean 的类型，可以统一使用 <code>EnumDictionaryBuilderCustomizer</code>，这样方便使用 <code>Spring Boot</code> 中的工厂模式实现自动注入。但是在这种情况下，Bean 的名称就必须不同，否则启动应用就会出错。</p>
<p>Spring 中 Bean 的名称可以通过很多方式指定，其中常用的方式有两种：</p>
<ul>
<li>一种方式也是默认方式，就是通过 Bean 定义方法的 <code>方法名</code> 来指定。例如：前面的例子代码中 <code>upmsEnumDictionaryBuilderCustomizer</code> 就是这个 Bean 的名称</li>
<li>另一种方式，就是在 <code>@Bean</code> 注解上，手动设置 Bean 的名称。</li>
</ul>
<p>Dante Cloud 代码中，默认均是使用第一种方式来指定 Bean 的名称，这种方式好维护、好调试。</p>
<p>正因为如此，如果你定义了自己的 <code>EnumDictionaryBuilderCustomizer</code> 的实现类，那么一定要注意保证定义 <code>EnumDictionaryBuilderCustomizer</code> Bean 的 <code>方法名</code> 与现有代码中其它的 <code>EnumDictionaryBuilderCustomizer</code> 实现类 Bean 定义的方法名不同才行。</p>
<p>假设，你为自己的两个模块 A 和 B 分别定义了 <code>EnumDictionaryBuilderCustomizer</code> 的实现类的 Bean，那么在定义 Bean 时，一定要区分方法名，如下实例代码所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// A 模块 `EnumDictionaryBuilderCustomizer` 的实现类的 Bean</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> EnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> aEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">() {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// B 模块 `EnumDictionaryBuilderCustomizer` 的实现类的 Bean</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> EnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> bEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">() {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> BEnumDictionaryBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>用户体系设计</title>
      <link>https://www.herodotus.cn/develop-guide/design/user.html</link>
      <guid>https://www.herodotus.cn/develop-guide/design/user.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">用户体系设计</source>
      <description>[一]多种类型用户聚合 微服务架构的一大特点：就是一套稳固的后端，适配各种不同的前端。后端稳固，前端灵活变化。就是所谓的“大中台，小前台”。不同的前端可能会涉及到不同的用户体系。 Dante Cloud 作为标准的微服务平台，也是需要支持各种不同类型的前端，以及不同体系的集成，这就会涉及到不同的用户体系，例如： 小程序：小程序都会有自己的用户体系，需要...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]多种类型用户聚合</h2>
<p>微服务架构的一大特点：就是一套稳固的后端，适配各种不同的前端。后端稳固，前端灵活变化。就是所谓的“大中台，小前台”。不同的前端可能会涉及到不同的用户体系。</p>
<p>Dante Cloud 作为标准的微服务平台，也是需要支持各种不同类型的前端，以及不同体系的集成，这就会涉及到不同的用户体系，例如：</p>
<ul>
<li>小程序：小程序都会有自己的用户体系，需要与自己的用户体系集成</li>
<li>第三方登录：如果使用第三方登录，那么就要实现第三方应用系统与自己用户体系的集成</li>
</ul>
<p>除了以上用户体系以外，传统的应用系统还会涉及组织机构等管理功能，这又是另外一种“用户体系”。</p>
<p>本系统将以上所有用户体系进行了融合，相关的 ER 图如下所示：</p>
<figure><img src="/assets/image/design/system-user.png" alt="用户体系ER图" tabindex="0" loading="lazy"><figcaption>用户体系ER图</figcaption></figure>
<ol>
<li>所有的用户，与系统用户关联</li>
<li><code>sys_social_user</code> 表中存储包括：手机短信登录或注册用户、小程序用户、第三方社交登录用户的用户信息</li>
<li><code>sys_employee</code> 表中存储的是常规组织机构或者人力资源管理用户信息。</li>
</ol>
<h2>[二]支持一人多岗多部门的人力资源管理模块</h2>
<p>大多数组织机构或者人力资源模块的设计都会采取类似于 RBAC 权限模型的设计，即 <code>单位</code>、<code>部门</code> 和 <code>人员</code> 三者进行“多对多” 关联。</p>
<p>本系统并未采用这样的设计，而是采用了另外的一种设计方式，当前设计的 ER 图如下所示：</p>
<figure><img src="/assets/image/design/hr.png" alt="组织机构ER图" tabindex="0" loading="lazy"><figcaption>组织机构ER图</figcaption></figure>
<p>在当前的设计中，除了 <code>单位</code>、<code>部门</code> 和 <code>人员</code> 三要素外，额外增加了一项 <code>人员归属</code>。</p>
<p>这样设计是为了解决两方面问题：</p>
<ol>
<li>很多传统系统中，不仅会存在人力资源这类标注机构，大型企业还会存在党团，工会等多种机构类型。采用以上的设计，可以避免为不同机构类型创建重复的人员信息，一个人员信息就可以关联到不同的机构类型或者不同的部门。</li>
<li>此种人力资源结构设计，也是参考Camunda工作流人员体系，与其保持一致，方便人员数据的同步以支持工作流引擎的使用。</li>
</ol>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Dante Cloud 是采用 <code>CDC（Change Data Capture）</code> 机制，实现系统用户与工作流用户的<code>实时</code>同步。具体用法可以参考作者自己的文章：<a href="https://my.oschina.net/pointerv/blog/5135196" target="_blank" rel="noopener noreferrer">《使用 Debezium、Postgres 和 Kafka 进行数据实时采集（CDC）》</a></p>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/design/system-user.png" type="image/png"/>
    </item>
    <item>
      <title>自研多级缓存</title>
      <link>https://www.herodotus.cn/develop-guide/coding/2lcache.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/2lcache.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">自研多级缓存</source>
      <description>概述 补充中... 正在努力补充中，多点点 Star，会写得更快哦！ Gitee：https://gitee.com/dromara/dante-cloud Github：https://github.com/dromara/dante-cloud</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<div class="hint-container info">
<p class="hint-container-title">补充中...</p>
<p>正在努力补充中，多点点 Star，会写得更快哦！</p>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://gitee.com/dromara/dante-cloud</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://github.com/dromara/dante-cloud</a></li>
</ul>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>后端功能开发</title>
      <link>https://www.herodotus.cn/develop-guide/coding/backend.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/backend.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">后端功能开发</source>
      <description>概述 开发服务接口是微服务系统最常规的开发工作之一。编写后端数据层、服务层以及接口层代码，就是常说的 CRUD 工作。 [1]为什么不提供代码生成 这一类工作相对来说比较模式化，所以为了简化重复性工作，很多系统都会提供代码生成工具或者服务，来提升开发效率。 在 Dante Cloud 中并没有提供代码生成工具或服务，仅是提供了各层代码基础性的抽象通用代...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>开发服务接口是微服务系统最常规的开发工作之一。编写后端数据层、服务层以及接口层代码，就是常说的 CRUD 工作。</p>
<h3>[1]为什么不提供代码生成</h3>
<p>这一类工作相对来说比较模式化，所以为了简化重复性工作，很多系统都会提供代码生成工具或者服务，来提升开发效率。</p>
<p>在 Dante Cloud 中并没有提供代码生成工具或服务，仅是提供了各层代码基础性的抽象通用代码，大部分代码还是需要自己生成和编写。</p>
<p>之所以这样做是出于以下几点考虑：</p>
<ol>
<li>不同的公司和团队，都有自己的开发规范和标准。代码生成虽然在一定生可以提高效率，但生成的代码都是模版化的，难以满足所有人的需求，代码生之后还是需要自己手动修改。</li>
<li>代码生成难以适应复杂多变的业务，业务逻辑稍微复杂一些、数据表稍微多一点，代码生成就难以支持。代码生成后还是需要大量修改，这不仅会完全抵消代码生成带来的效率，可能还会比自己写投入的精力更大（写代码比改代码效率更高）。</li>
<li>单体系统开发人员主要考虑的就是与数据库的交互，但是微服务除了考虑数据库，可能还会涉及服务接口的交互、消息队列的交互、不同存储的交互、分布式事务的处理以及服务拆分的合理性等等。那么这种情况下，代码生成的仅有的优势就完全可以忽略。</li>
</ol>
<h3>[2]为什么选择 Spring Data</h3>
<h4>为什么使用 JPA 而不是 MyBatis</h4>
<p>另外一个比较重要的问题就是 ORM 组件的选择问题。在 Dante Cloud 中，默认使用的是 Spring Date JPA，而没有选择大多数开发人员熟悉的 MyBatis 或 MyBatis Plus。</p>
<p>之所以选择 JPA 是从设计和应用的合理性角度考虑的，而不仅仅是从所谓的“码砖”效率角度考虑。</p>
<p>单体系统玩的就是一个数据库，能够快速灵活的堆叠业务功能，便捷的修改以适应用户需求的变化才是要义。所以在这种场景下，MyBatis 这种面向 SQL 的 ORM 框架的优势就非常大。</p>
<p>虽然微服务系统下，开发效率也是需要考虑的因素，但并不是微服务的重点。微服务的重点是以“服务”的形式沉淀“业务”，这就意味着合理的划分业务以及合理的划分服务，并将两者有机的融合才是微服务优先级最高的考虑事项。</p>
<blockquote>
<p>使用微服务架构失败的案例中，最常见的一个典型问题就是“服务”的划分问题。服务划分的过细，就会导致产生大量的服务，不仅大量增加服务间交互加大复杂度，还会增加开发和维护的成本；如果服务划分的过粗，服务数量倒是少了，但是每个服务负责的内容过多，都相当于一个服务就是一个单体项目，这样就失去了微服务系统带来的灵活性和可扩展性。</p>
</blockquote>
<p>JPA 的开发是面向对象的，开发时更多考虑的是实体关系的设计及合理性，这就与领域驱动设计异曲同工。领域设计的越合理性越高，服务的划分也越容易。相反，如果使用 MyBatis 之类面向 SQL 的组件，不是说不可以，但是大概率会干扰代码的划分甚至服务划分的合理性。</p>
<blockquote>
<p>例如：一个功能涉及多个数据表的关联，在单体项目中这不会有任何问题。但是在服务系统中，可能会出现这几个相关联的表中，其中几个表放入 A 服务更合适，另外几个表放入 B 服务更合适的情况。这样就又需要回过头来重新考虑划分的问题，原来单体使用 MyBatis 一个 SQL 可能就搞定的事情，在微服务中就不得重新编写进行代码拆分。如果使用的是 JPA，那么在编写实体模型时，就会自然而然的考虑到划分以及合理性的问题，相当于提前就进行了思考和设计，减少了不必要的反复和消耗。</p>
</blockquote>
<h4>Spring Data 的优势</h4>
<p>微服务相比单体另外一大优势，就是通过集成的便利性，可以快速的集成多种不同的组件或中间件，来提升整个系统性能。并不是说单体无法进行多内容的集成，而是每增加一种集成就会让系统更加臃肿，甚至需要伤筋动骨式的改造。</p>
<p>微服务的易扩展性，就决定了整个系统不再是单纯的以关系型数据库作为核心，可以根据实际需要灵活的增加 NoSQL数据库、时序数据库等多种类型的存储。</p>
<blockquote>
<p>例如：最新版本的 Dante Cloud 中，<code>Spring Authorization Server</code> 组件的核心数据，就不再简单的支持关系型数据库，还支持了 Redis 和 MongoDB。</p>
</blockquote>
<p>选择 Spring Data 的优势就体现出来了，定义好领域模型后，使用相同的模式就可以快速的切换到其它类型的存储上来，</p>
<h2>[一]使用JPA开发服务接口</h2>
<h3>[1]编写实体</h3>
<p>使用 JPA 开发服务接口，第一步就是定义实体，就是领域驱动中的“领域层”</p>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>定义实体是 JPA 最基础的工作，也是 JPA 最复杂也最难掌握的部分，也是许多人不爱使用 JPA 和 MyBatis 的主要原因。相关的知识较多，不是一句话两句话就可以讲解明白的，所以在本文不会过多讲解基础知识，建议用户自己找相关资料或者书籍系统的学习一下。</p>
</div>
<p>下面就以系统中角色相关代码举例，角色的实体定义如下：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Entity</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Table</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "sys_role"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> uniqueConstraints</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">UniqueConstraint</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">columnNames</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"role_code"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">})}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">        indexes</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Index</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "sys_role_rid_idx"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> columnList</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "role_id"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Index</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "sys_role_rcd_idx"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> columnList</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "role_code"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)})</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Cacheable</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">org.hibernate.annotations.Cache</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">usage</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> CacheConcurrencyStrategy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">READ_WRITE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> region</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> LogicUpmsConstants</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">REGION_SYS_ROLE</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysRole</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractSysEntity</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Id</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">UuidGenerator</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Column</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "role_id"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> length</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 64</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> roleId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Column</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "role_code"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> length</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 128</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> unique</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> roleCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Column</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "role_name"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> length</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 128</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> roleName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">org.hibernate.annotations.Cache</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">usage</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> CacheConcurrencyStrategy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">READ_WRITE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> region</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> LogicUpmsConstants</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">REGION_SYS_PERMISSION</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">ManyToMany</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">fetch</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> FetchType</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">EAGER</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Fetch</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">FetchMode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SUBSELECT</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">JoinTable</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "sys_role_permission"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">            joinColumns</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">JoinColumn</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "role_id"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">            inverseJoinColumns</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">JoinColumn</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "permission_id"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">            uniqueConstraints</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">UniqueConstraint</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">columnNames</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"role_id"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "permission_id"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">})}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">            indexes</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Index</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "sys_role_permission_rid_idx"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> columnList</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "role_id"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Index</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "sys_role_permission_pid_idx"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> columnList</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "permission_id"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)})</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Set</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysPermission</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> permissions </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HashSet</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getRoleId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> roleId;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setRoleId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> roleId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">roleId</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> roleId;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getRoleCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> roleCode;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setRoleCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> roleCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">roleCode</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> roleCode;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getRoleName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> roleName;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setRoleName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> roleName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">roleName</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> roleName;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Set</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysPermission</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getPermissions</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> permissions;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setPermissions</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Set</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysPermission</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">permissions</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">permissions</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> permissions;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container info">
<p class="hint-container-title">说明</p>
<ul>
<li><code>@Entity</code> 和 <code>@Table</code> 是使用 Spring Data JPA 定义实体必要的注解，标识该实体对应实际的数据表</li>
<li><code>@UniqueConstraint</code> 用于标识该字段是唯一性字段，例如：表的主键就属于唯一性字段</li>
<li><code>@Index</code> 用于给指定的字段添加数据库索引</li>
<li><code>@Cacheable</code> 开启查询缓存</li>
<li><code>@Cache</code> Hibernate 专有的缓存注解，用于指定缓存的类型以及相关信息</li>
<li><code>AbstractSysEntity</code> 是 Dante Cloud 提供的基础实体定义，其中包含了数据库审计以及常用的字段，继承该类就无需重复定义相关属性。</li>
</ul>
<blockquote>
<p>除了 <code>AbstractSysEntity</code> 基础定义以外，还有多种基础实体定义，例如：支持多租户的 <code>AbstractTenantEntity</code> 等等，可以根据实际需求选择使用。</p>
</blockquote>
</div>
<h3>[2]编写Repository</h3>
<p>Repository 是 Spring Data 中的核心定义，与领域驱动中的 <code>Repository</code> 概念基本一致。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysRoleRepository</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BaseJpaRepository</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysRole</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 根据用户名查找SysUser</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> roleCode</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 角色代码</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> {@link SysRole}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">QueryHints</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">QueryHint</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> AvailableHints</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HINT_CACHEABLE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> value</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "true"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">))</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    SysRole</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> findByRoleCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> roleCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 根据角色ID查询角色</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> roleId</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 角色ID</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> {@link SysRole}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">QueryHints</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">QueryHint</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> AvailableHints</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HINT_CACHEABLE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> value</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "true"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">))</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    SysRole</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> findByRoleId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> roleId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<ol>
<li><code>BaseJpaRepository</code> 是 Dante Cloud 中提供的基础 Repository 定义。继承该类，就可以直接使用常规的增、删、改、查操作。</li>
<li><code>BaseJpaRepository</code> 提供的操作不满足实际需求，就可以在该类中定义自己的方法。</li>
<li><code>@QueryHints</code> 是新版本 Spring Data JPA 中，与缓存配合的注解。该注解会与实体上定义的 <code>@Cacheable</code> 注解配合，开启查询的“二级缓存”</li>
</ol>
</div>
<h3>[3]编写Service</h3>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Service</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysRoleService</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractJpaService</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysRole</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysRoleRepository</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> sysRoleRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> SysRoleService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysRoleRepository</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> sysRoleRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysRoleRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> sysRoleRepository;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BaseJpaRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysRole</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysRoleRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysRole</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> findByRoleCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> roleCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> sysRoleRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">findByRoleCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(roleCode);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysRole</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> findByRoleId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> roleId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> sysRoleRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">findByRoleId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(roleId);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<ol>
<li><code>AbstractJpaService</code> 是 Dante Cloud 中提供的基础 Service 定义。继承该类，就可以直接使用常规的增、删、改、查操作。</li>
<li><code>AbstractJpaService</code> 提供的操作不满足实际需求，就可以在该类中定义自己的方法。</li>
<li>系统中还提供有 <code>JpaWriteableService</code> 和 <code>JpaReadableService</code> 两个基础 Service 定义。可以根据实际需求灵活选择</li>
</ol>
<blockquote>
<p>之所以有这两个 Service 定义，目的是将数据库的“写”操作和“读”操作分开。因为 JPA 除了支持数据表的映射以外，还支持数据库视图的映射，而视图是无法增、删、改的，即使提供了代码操作执行也会抛错。</p>
</blockquote>
</div>
<h3>[4]编写Controller</h3>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">RestController</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">RequestMapping</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"/security/role"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Tags</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">({</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Tag</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "用户安全管理接口"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Tag</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "系统角色管理接口"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">})</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysRoleController</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractJpaWriteableController</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysRole</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysRoleService</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> sysRoleService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> SysRoleController</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysRoleService</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> sysRoleService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysRoleService</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> sysRoleService;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BaseJpaWriteableService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysRole</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getWriteableService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysRoleService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<ol>
<li><code>AbstractJpaWriteableController</code> 是 Dante Cloud 中提供的基础 Controller 定义。继承该类，就可以直接使用常规的增、删、改、查接口就已经实现，无需再外编写。</li>
<li><code>AbstractJpaWriteableController</code> 提供的操作不满足实际需求，就可以在该类中定义自己的接口。</li>
<li>系统中还提供有 <code>AbstractJpaReadableController</code> 以及其它更底层的基础 Controller 定义。可以根据实际需求灵活选择</li>
</ol>
<blockquote>
<p>之所以有这两个 Controller 定义，目的与前面的 Service 一致。是将数据库的“写”操作和“读”操作分开。因为 JPA 除了支持数据表的映射以外，还支持数据库视图的映射，而视图是无法增、删、改的，即使提供了代码操作执行也会抛错。</p>
</blockquote>
</div>
<div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>当前版本的 Dante Cloud 不仅支持阻塞式接口，还支持响应式接口。开发的方法都是类 <code>AbstractJpaWriteableController</code> 类。区别是阻塞式接口，需要继承 <code>cn.herodotus.stirrup.web.api.servlet.AbstractJpaWriteableController</code>，而响应式接口需要继承 <code>cn.herodotus.stirrup.web.api.reactive.AbstractJpaWriteableController</code></p>
</div>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>继承 <code>AbstractJpaWriteableController</code> 抽象类之后，会自动实现新增/修改、根据删除以及分页三个接口。</p>
<p>以系统角色为例，继承 <code>AbstractJpaWriteableController</code> 抽象类之后就会自动实现以下接口：</p>
<ul>
<li><code>POST</code> - <code>/security/role</code>：新增或修改角色</li>
<li><code>DELETE</code> - <code>/security/role/{id}</code>：根据ID删除角色</li>
<li><code>GET</code> - <code>/security/role</code>：角色分页查询</li>
</ul>
</div>
<h3>[5]编写Config</h3>
<p>最后一步，就是编写以上代码的配置类，方便在系统运行时，将相关内容注入到 Spring 中。</p>
<p>示例代码如下所示</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Configuration</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">proxyBeanMethods</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">EntityScan</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">basePackages</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        "cn.herodotus.stirrup.logic.identity.entity"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">})</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">EnableJpaRepositories</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">basePackages</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        "cn.herodotus.stirrup.logic.identity.repository"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">})</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">ComponentScan</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">basePackages</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        "cn.herodotus.stirrup.logic.identity.service"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        "cn.herodotus.stirrup.logic.identity.controller"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">})</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> LogicIdentityConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Logger</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> log </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> LoggerFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getLogger</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">LogicIdentityConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">PostConstruct</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> postConstruct</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">debug</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Module [Logic Identity] Configure."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    ......</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container info">
<p class="hint-container-title">说明</p>
<ul>
<li><code>@Configuration</code> Spring Boot 配置的注解</li>
<li><code>@EntityScan</code> Spring Data JPA 扫描实体注解</li>
<li><code>@EnableJpaRepositories</code> Spring Data JPA 扫描 Repository注解</li>
<li><code>@ComponentScan</code> Spring Boot 扫描 <code>@Service</code> 和 <code>@RestController</code> 注解</li>
</ul>
</div>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>如果开发的是单模块的 Spring Boot 应用，完全不需要向上面例子一样，手动编写 <code>Configuration</code> 类。因为 <code>@SpringBootApplication</code> 注解会自动帮你你完成类的扫描工作。</p>
<p>如果是多模块的 Spring Boot 应用或者微服务，那么建议一定按照上面的方式，为每一个模块定义单独的 <code>Configuration</code> 类。之所以这样做，有以下几个原因：</p>
<ol>
<li>单模块的 Spring Boot 应用，会以 <code>@SpringBootApplication</code> 注解所在的包为基础，根据当前 Class 的路径主动扫描所有的子包。</li>
<li>如果是多模块的 Spring Boot 应用，不同的模块是在不同的 jar 包中。即使在其中一个 jar 包中指定了 <code>@SpringBootApplication</code>，因为包结构以及代码层次的变化，很可能是无法扫描到其它 jar 包中的类的。</li>
<li>有些朋友图方便，在一个类中直接指定扫描基础包，例如：直接扫描 <code>cn.herodotus</code>。这种方式虽然也可以实现跨 jar 包类的注入，但是需要和不需要的类都会去扫描，会大大降低系统启动的效率</li>
<li>按照上面的手动编写 <code>Configuration</code> 类的方式，按需扫描可以极大的降低扫描的范围。而且也更加灵活，在需要的代码中 <code>@Import</code> 该 <code>Configuration</code> 类就可以使用。</li>
</ol>
</div>
<h2>[二]使用MyBatis Plus开发服务接口</h2>
<p>如果你不习惯使用 JPA，那么 Dante Cloud 还支持使用 MyBatis Plus 来编写代码。JPA 和 MyBatis Plus 编写的代码可以在 Dante Cloud 中同时运行。</p>
<div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>这里所说的 JPA 和 MyBatis Plus 编写的代码可以在一起运行，并不说两者可以进行切换，即：使用 JPA 编写的代码，只能自己手动全部修改为 MyBatis Plus 代码。无法做到通过修改配置，直接进行代码的装换，因为两者的机制完全不同，这是无法做到的。</p>
</div>
<p>使用 MyBatis Plus 开发服务接口，与前面使用 JPA 的主要逻辑大同小异，主要的区别在于 MyBatis 的配置。使用 MyBatis Plus 编写完代码之后，要在具体服务的配置文件中，指定 Mapper XML 所在的位置。如下所示：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">mybatis-plus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  mapper-locations</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"classpath*:camunda-extends-mappings/**/*.xml"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[三]使用其它存储开发服务接口</h2>
<p>除了支持常规的关系数据库开发服务接口以外，Dante Cloud 还支持以 Redis、MongoDB 和 Cassandra 作为存储介质开发服务接口。</p>
<p>开发基于 Redis、MongoDB 和 Cassandra 的服务接口，与 JPA 的开发方式完全相同，区别在于底层基础通用代码不同，开发时依赖不同的基础数据模块即可。</p>
<p>当前，已支持的基础数据模块有：</p>
<ul>
<li><code>data-module-cassandra</code>：基于 <code>Spring Data Cassandra</code> 的数据层基础通用代码模块。</li>
<li><code>data-module-mongodb</code>：基于 <code>Spring Data MongoDB</code> 的数据层基础通用代码模块。</li>
<li><code>data-module-redis</code>：基于 <code>Spring Data Redis</code> 的数据层基础通用代码模块。</li>
<li><code>data-module-jpa</code>：基于 <code>Spring Data JPA</code> 的数据层基础通用代码模块。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>事件驱动编码</title>
      <link>https://www.herodotus.cn/develop-guide/coding/event.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/event.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">事件驱动编码</source>
      <description>概述 Spring 事件（Event）机制 是一种基于观察者模式的实现，它允许应用组件在保持松耦合的同时进行通信。在Spring框架中，事件机制主要涉及三个核心概念：事件源（ApplicationEvent）、事件发布器（ApplicationEventPublisher）和事件监听器（ApplicationListener） 事件发布流程中，有三个核...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p><strong>Spring 事件（Event）机制</strong> 是一种基于观察者模式的实现，它允许应用组件在保持松耦合的同时进行通信。在Spring框架中，事件机制主要涉及三个核心概念：事件源（ApplicationEvent）、事件发布器（ApplicationEventPublisher）和事件监听器（ApplicationListener）</p>
<p>事件发布流程中，有三个核心概念，他们之间的关系如下图所示：</p>
<figure><img src="/assets/image/coding/spring-event.png" alt="事件发布流程" tabindex="0" loading="lazy"><figcaption>事件发布流程</figcaption></figure>
<ul>
<li>事件源（<code>ApplicationEvent</code>）：这个就是你要发布的事件对象。</li>
<li>事件发布器（<code>ApplicationEventPublisher</code>）：这是事件的发布工具。</li>
<li>事件监听器（<code>ApplicationListener</code>）：这个相当于是事件的消费者。</li>
</ul>
<p>使用 Spring 事件机制，可以有以下好处：</p>
<ul>
<li>可以快捷地实现代码异步执行，不会影响主线程代码执行逻辑</li>
<li>实现业务逻辑解耦，让代码逻辑更加简洁。例如：事件源（<code>ApplicationEvent</code>）代码在模块A中，事件监听器（<code>ApplicationListener</code>）在模块B中，这样就无须编写强关联代码。</li>
</ul>
<h2>[一]本地事件</h2>
<h3>[1]使用方法</h3>
<p>接下来，让我们通过一个简单的案例来演示一下 Spring 中事件的用法。</p>
<p>首先，我们需要自定义一个事件对象，自定义的事件继承自 <code>ApplicationEvent</code> 类，如下：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MyEvent</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ApplicationEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MyEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Object</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> source</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(source);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> name;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> toString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "MyEvent{"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">                "name='"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> name </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">+</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\'</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">                "} "</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>事件发布方式，通过 <code>ApplicationContext</code> 中的 <code>publishEvent</code> 方法，将事件发送出去即可。还可以使用 <code>ApplicationEventPublisher</code> 中的 <code>publishEvent</code> 方法</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>使用 <code>ApplicationContext</code> 方法，可以有很多种，常用的两种方式如下：</p>
<ol>
<li>实现 <code>ApplicationContextAware</code> 接口</li>
<li>以 @Autowire 的方式，注入 <code>ApplicationContext</code></li>
</ol>
<p>使用 <code>ApplicationEventPublisher</code> 方法，可以有很多种：</p>
<ol>
<li>实现 <code>ApplicationEventPublisherAware</code> 接口</li>
<li>以 @Autowire 的方式，注入 <code>ApplicationEventPublisher</code></li>
</ol>
</div>
<p>为了方便使用，Dante Cloud 在 <code>ServiceContextHolder</code> 中提供便捷获取<code>ApplicationContext</code>方法，可以直接发送 Event，如下所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ServiceContextHolder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getApplicationContext</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">publishEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MyEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"测试"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">，</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"测试名称"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">))</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>事件发布之后，还需要一个事件消费者去消费这个事件，或者也可以称之为事件监听器。</p>
<p>事件监听器有两种定义方式，第一种是自定义类实现 <code>ApplicationListener</code> 接口：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Component</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MyEventListener</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ApplicationListener</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MyEvent</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> onApplicationEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MyEvent</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> event</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        System</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">out</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">println</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"event = "</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> event);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第二种方式则是通过注解去标记事件消费（监听）方法：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Component</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MyEventListener02</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">EventListener</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">value</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> MyEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> hello</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MyEvent</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> event</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        System</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">out</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">println</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"event02 = "</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> event);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>就这样，一个简单的事件发布订阅系统就完成了。现在，如果发布事件，对应的事件监听器中就可以接收到事件。</p>
<h3>[2]系统应用</h3>
<p>Dante Cloud 中，大量使用了 Spring 事件（Event）机制，主要的事件定义如下，你可以在自己的代码中直接使用：</p>
<h4>系统类事件</h4>
<ul>
<li><code>AccountStatusChangedEvent</code>：用户账号状态变更事件，主要用于账号锁定以及自动解锁用途</li>
<li><code>DisableAuthenticationEvent</code>：关闭认证事件</li>
<li><code>EnableAuthenticationEvent</code>：开启认证事件</li>
<li><code>EnumDictionaryGatherEvent</code>：枚举字典收集事件</li>
<li><code>OidcClientRegistrationEvent</code>：OIDC 客户端自动注册事件</li>
<li><code>PrincipalConnectedEvent</code>：用户上线事件</li>
<li><code>PrincipalDisconnectedEvent</code>：用户下线事件</li>
<li><code>RestAuditEvent</code>：Rest 接口审计记录事件</li>
<li><code>RestMappingGatherEvent</code>：Rest 接口映射收集事件</li>
<li><code>SignOutComplianceEvent</code>：系统用户登出合规性记录事件</li>
<li><code>SysAttributeChangeEvent</code>：权限元数据变更事件</li>
<li><code>SignOutComplianceEvent</code>：系统用户登出合规性记录事件</li>
</ul>
<h4>消息类事件</h4>
<ul>
<li><code>DialogueMessageReceivingEvent</code>：接收到私信事件</li>
<li><code>MailNotificationSendingEvent</code>：Email通知发送事件</li>
<li><code>MessageSendingEvent</code>：统一消息发送事件</li>
<li><code>MqttMessageReceivingEvent</code>：Mqtt消息接收事件</li>
<li><code>MqttMessageSendingEvent</code>：Mqtt消息发送事件</li>
<li><code>RSocketBroadcastMessageSendingEvent</code>：RSocket 广播发送事件</li>
<li><code>RSocketFireAndForgetMessageReceivingEvent</code>：RSocket 个人消息接收事件</li>
<li><code>RSocketFireAndForgetMessageSendingEvent</code>：RSocket 个人消息发送事件</li>
<li><code>StreamMessageSendingEvent</code>：Spring Cloud Stream 发送事件</li>
<li><code>WebSocketBroadcastMessageSendingEvent</code>：WebSocket 广播发送事件</li>
<li><code>WebSocketUserMessageSendingEvent</code>：WebSocket 个人消息发送事件</li>
</ul>
<h3>[3]代码抽象</h3>
<p>当我们编写自定义 Event 时会发现，<code>ApplicationEvent</code> 构造函数有一个 <code>Object</code> 类型的参数。这个参数就是我们希望通过 Event 传递数据。<code>Object</code> 类型的数据，在实际使用中并不方便，接收到数据还是需要进行类型转换。</p>
<p>为了方便使用，Dante Cloud 抽象定义了一个 <code>AbstractApplicationEvent</code> 类。这个类在使用时，需要指定数据类型的泛型，那么在拿数据时就可以直接获取到对应的对象。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> abstract</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractApplicationEvent</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">T</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ApplicationEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> T</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AbstractApplicationEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">T</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(data);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">data</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> data;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AbstractApplicationEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">T</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Clock</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> clock</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(data, clock);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">data</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> data;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> T</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> data;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[4]注意事项</h3>
<p>本章节，之所以称之为“本地事件”，是因为标注的 Spring 事件，只能用于单独的服务或者系统之中。不管 <code>ApplicationEvent</code> 和 <code>ApplicationListener</code> 是在相同的代码模块中还是不同代码模块中，必须保证两者在同一个服务或者应用中才能生效。</p>
<p>换句话说，跨应用或者服务之间使用 Spring 事件是无法进行通信的。</p>
<h2>[二]远程事件</h2>
<p>Spring 事件（Event）机制，在 Spring 生态的各个组件被大量使用。但是无法解决跨服务或者跨应用进行事件通信。Spring 生态中的 <code>Spring Cloud Bus</code> 组件对此提供了解决方案。</p>
<p><code>Spring Cloud Bus</code> 是 <code>Spring Cloud Stream</code> 的扩展组件。<code>Spring Cloud Stream</code> 主要用于实现在服务间便捷的使用消息队列，由此可以看出 <code>Spring Cloud Bus</code> 实现远程事件通信，是依赖于外部消息队列实现，同时采用和 Spring 事件机制一致的编码方式，以保持代码风格的一致性。</p>
<h3>[1]使用方法</h3>
<p><code>Spring Cloud Bus</code> 发送远程 Spring 事件，与本地事件用法基本一致。也是需要定义 <code>ApplicationEvent</code> 和 <code>ApplicationListener</code>。</p>
<p>区别在于：</p>
<ol>
<li>自定义远程事件需要实现 <code>RemoteApplicationEvent</code> 而不是 <code>ApplicationEvent</code></li>
<li>不管是事件发送端还是接收端，都需要使用使用注解 <code>@RemoteApplicationEventScan</code> 扫描到所需使用的 <code>RemoteApplicationEvent</code>。否则事件不会被准确接收到。</li>
<li><code>RemoteApplicationEvent</code> 比 <code>ApplicationEvent</code> 多了一个 <code>发送目标</code> 参数。这个参数用于指定 Event 具体发送到的应用或服务。</li>
</ol>
<h3>[2]系统应用</h3>
<p>Dante Cloud 中，大量使用了 <code>Spring Cloud Bus</code> 远程事件，主要的事件定义如下，你可以在自己的代码中直接使用：</p>
<h4>系统类事件</h4>
<ul>
<li><code>RemoteAccountStatusChangedEvent</code>：用户账号状态变更事件，主要用于账号锁定以及自动解锁用途</li>
<li><code>RemoteAttributeTransmitterSyncEvent</code>：权限数据同步事件</li>
<li><code>RemoteDisableAuthenticationEvent</code>：关闭认证事件</li>
<li><code>RemoteEnableAuthenticationEvent</code>：开启认证事件</li>
<li><code>RemoteEnumDictionaryGatherEvent</code>：枚举字典收集事件</li>
<li><code>RemoteOidcClientRegistrationEvent</code>：OIDC 客户端自动注册事件</li>
<li><code>RemoteRestAuditEvent</code>：Rest 接口审计记录事件</li>
<li><code>RemoteRestMappingGatherEvent</code>：Rest 接口映射收集事件</li>
</ul>
<h4>消息类事件</h4>
<ul>
<li><code>RemoteMessageSendingEvent</code>：统一消息发送事件</li>
</ul>
<h3>[3]注意事项</h3>
<p>远程事件也支持数据的传递，传递的数据默认还是为 <code>Object</code> 类型，当然你也可以传递具体的数据对象。<code>Spring Cloud Bus</code> 也是和 Spring 生态其它组件一样，使用 Jackson 来处理序列化和反序列化。</p>
<p>但是这里需要注意的是，由于对象的定义千变万化，任何 JSON 组件都不可能 100% 的、自动化的实现对象的反序列化。在远程事件中传递自定义的对象时，很可能出现接收端反序列化异常。</p>
<p>Dante Cloud 为了规避这方面问题，直接选择了最“笨拙”的方式，发送事件时将对象数据转换成 JSON 字符串，监听器接收到数据时在将 JSON 字符串手动反序列化为对象。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>有些时候选择一些“笨拙”的方式实现代码未必不好，很多让人摸不着头脑、不好解决的问题通常都是由于为了使用某些“简单”的方式而导致。</p>
</div>
<h2>[三]混合事件</h2>
<p>在微服务的开发过程中，本地事件和远程事件往往并不是孤立使用的，为了满足一些需求和场景，可能既需要使用本地事件又需要使用远程事件。</p>
<p>在 Dante Cloud 中就大量存在这样的场景，比如：REST 接口的扫描、枚举字典的聚合等。这种类型的代码往往都是模版化的，实现逻辑大同小异，会导致编写出大量逻辑相似的代码。</p>
<p>为了减少重复代码的出现，针对同时需要本地事件和远程事件场景，抽象出一个同一个的策略化事件接口 <code>cn.herodotus.stirrup.message.core.definition.strategy.StrategyEventManager</code> 以及 <code>cn.herodotus.stirrup.message.core.definition.strategy.ApplicationStrategyEventManager</code>。</p>
<p>通过实现 <code>StrategyEventManager</code> 以及 <code>ApplicationStrategyEventManager</code> 这两个接口就可以大量减少模版化代码的产生，同时便捷实现本地事件和远程事件的混合使用</p>
<p>示例代码，如下所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> DefaultAccountStatusChangedEventManager</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AccountStatusChangedEventManager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getDestinationServiceName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> ServiceContextHolder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getUpmsServiceName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> postLocalProcess</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">AccountStatus</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        publishEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AccountStatusChangedEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(data));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> postRemoteProcess</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> originService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> destinationService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        publishEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> RemoteAccountStatusChangedEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(data, originService, destinationService));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/coding/spring-event.png" type="image/png"/>
    </item>
    <item>
      <title>前端功能开发</title>
      <link>https://www.herodotus.cn/develop-guide/coding/frontend.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/frontend.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">前端功能开发</source>
      <description>[一]新增模块（可选） Dante Cloud 前端工程采用 monorepo 方式，对工程代码进行拆解，划分为多个模块。采用这种方式，类似于后端多模块工程，方便实现代码的重用。 微服务的重要理念之一：大中台、小前台。就在后端以服务的形式沉淀业务，前端承载用户交互。后端接口丰富业务稳定，前端轻量多样简单多变。 也许你会有多个前端系统，使用微服务后，后端...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]新增模块（可选）</h2>
<p>Dante Cloud 前端工程采用 <code>monorepo</code> 方式，对工程代码进行拆解，划分为多个模块。采用这种方式，类似于后端多模块工程，方便实现代码的重用。</p>
<p>微服务的重要理念之一：<strong>大中台、小前台</strong>。就在后端以服务的形式沉淀业务，前端承载用户交互。后端接口丰富业务稳定，前端轻量多样简单多变。</p>
<p>也许你会有多个前端系统，使用微服务后，后端接口是固定不变的。那么使用 <code>monorepo</code> 方式将前后端交互重复的内容进行固化，就可以极大地的方便开发和使用。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Monorepo 是一种项目代码管理方式，指单个仓库中管理多个项目，有助于简化代码共享、版本控制、构建和部署等方面的复杂性，并提供更好的可重用性和协作性。和 Java 多模块工程一样，每个模块的代码单独管理，也可以长传至 NPM 仓库，实现代码的复用。</p>
</div>
<p>开发前端的第一步，就是考虑好新增的代码是否需要拆分为一个单独的模块。如果不需要一个单独模块，可以考虑将代码添加至现在已有的模块中。</p>
<h2>[二]定义类型</h2>
<p>Dante Cloud 的前端采用的是纯 Typescript。使用纯 Typescript 看其来确实没有纯 JavaScript 灵活，但是通过其定义能力，可以让编码更加准确，在编码阶段就很多明显错误问题。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Typescript 并没有想象的那么复杂，本质上还是 JavaScript。Typescript 更多的是用来与 IDE 工具交互，方便在编码过程给与更多的提示和校验。</p>
</div>
<h3>[1]定义实体类型</h3>
<p>这里的实体类，与后端 JPA 实体相对应。后端接口返回的数据，就可以与之一一对应，方便前端的代码的开发以及类型的校验。例如：前端的 Table，每一个实体就对应 Table 里的一条数据。</p>
<p>示例代码如下所示：</p>
<div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-typescript"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MgtCertificateEntity</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractSysEntity</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  certId</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  certName</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 证书所有者的公共名称</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 简称：CN 字段，对于 SSL 证书，一般为网站域名；而对于代码签名证书则为申请单位名称；而对于客户端证书则为证书申请者的姓名；</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   */</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  commonName</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 组织单元</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 简称：OU 字段。组织单位，表示在组织内部负责证书管理的部门或分支</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   */</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  organizationUnit</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 组织</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 简称：O 字段，对于 SSL 证书，一般为网站域名；而对于代码签名证书则为申请单位名称；而对于客户端单位证书则为证书申请者所在单位名称；</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   */</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  organization</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 位置或城市</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 简称：L 字段，进一步细化了CA的所在地。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   */</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  locality</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 州或省</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 简称：ST 字段，标识证书主体的地理位置。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   */</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  stateOrProvince</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 国家或地区</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   * 简称：C 字段，只能是国家字母缩写，如中国：CN 。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   */</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  country</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  distinguishedName</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  startTime</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Date</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  endTime</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Date</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  password</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  bucketName</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  keystoreName</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  pemName</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  parentId</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  keyStoreCategory</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  certificateCategory</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>在 Dante Cloud 前端工程中，因为有大量的 Typescript 类型定义。为了方便查找和使用，除了特殊的 Typescript 类型定义会放在 <code>types</code> 目录以外，其它所有的 Typescript 定义会被放在 <code>declarations</code> 目录。</p>
</div>
<h3>[2]定义条件类型</h3>
<p>定义完实体之后，还需要定义条件类型。</p>
<p>在 Dante Cloud 中，为了方便常规表格类型的开发，对 Table 基础操作进行了封装。除了常规的 Table 数据展示、添加修改删除操作外，条件搜索也是常见的功能。条件类型定义就是为了指定搜索条件涉及字段的类型。示例代码如下：</p>
<div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-typescript"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MgtCertificateConditions</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Conditions</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>如果 Table 功能不涉及搜索条件，定义一个空条件类型即可。但是不能不定义条件类型类，因为封装的基础必须要用到该类。</p>
</div>
<h3>[3]定义属性类型</h3>
<p>正常情况下，实体和条件类型就已经可以满足开发需求。但在使用基于 Typescript 的 Vue3 组件时，很多参数的值只能使用字符串，这就大大增加了出错的几率。</p>
<p>属性类型是 Dante Cloud 额外需要定义的一种类型，这种类型从实体中直接提取属性字符串作为校验类型，这就规避了手动输入字符串出错的问题。</p>
<p>示例代码如下：</p>
<div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-typescript"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> type</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MgtCertificateProps</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#0184BC;--shiki-dark:#C678DD"> keyof</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MgtCertificateEntity</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h2>[三]定义服务</h2>
<p>这里沿用了后端“服务”(<code>service</code>)这个说法，本质上就是对后端 REST API 的封装。通过 Service 层的封装，将后端接口的调用和使用进一步简化。</p>
<h3>[1]定义服务类</h3>
<p>示例代码如下：</p>
<div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-typescript"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> type</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">MgtCertificateEntity</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "/@/declarations"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">HttpConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">BaseService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "../base"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MgtCertificateService</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BaseService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MgtCertificateEntity</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> instance</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MgtCertificateService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> constructor</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">config</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HttpConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-light-font-style:inherit;--shiki-dark:#E5C07B;--shiki-dark-font-style:italic">    super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getInstance</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">config</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HttpConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MgtCertificateService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">instance</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> ==</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">      this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">instance</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MgtCertificateService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">instance</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getBaseAddress</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getManage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">+</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "/security/permission"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">MgtCertificateService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> };</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<ol>
<li>从上面代码可以看出，Typescript 和 Java 很多用法非常像，所以如果您是后端开发出身，没必要对 Typescript 谈虎色变。</li>
<li>Dante Cloud 前端代码大量采用了“单例”模式，以减少对象的重复创建带来的资源损耗。</li>
</ol>
</div>
<h3>[2]编写外部引用接口</h3>
<p>将服务类直接导出就可以直接使用。但是随着 Service 不断增多，服务类会越来越多，每次使用就需要查找，增加代码编写繁琐度。</p>
<p>Dante Cloud 定义了一个统一的对外接口，将所有的 Service 封装成对外接口的一个方法，在 IDE 中也会有提示，在使用时就比较方便。</p>
<p>在 API 模块的 <code>main.ts</code> 中，添加具体的 <code>service</code>，在编写代码时就可以直接调用。示例代码如下：</p>
<div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-typescript"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ApiResources</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> instance</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ApiResources</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  private</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> config</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {} </span><span style="--shiki-light:#383A42;--shiki-dark:#C678DD">as</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HttpConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> constructor</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">config</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HttpConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">config</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getInstance</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">config</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HttpConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ApiResources</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">instance</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> ==</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">      this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">instance</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ApiResources</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">instance</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HttpConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">  ......</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> mgtCertificate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MgtCertificateService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> MgtCertificateService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getInstance</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> createApi</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">  project</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">  clientId</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">  clientSecret</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">  http</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Axios</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">  oidc</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#E5C07B"> boolean</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ApiResources</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> =></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> config</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> HttpConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">project</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">clientId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">clientSecret</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">http</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">oidc</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  return</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> ApiResources</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getInstance</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">createApi</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> };</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>以上代码编写完成之后，一定要使用 <code>pnpm shared:build</code> 命令重新编译各个子模块。否则新代码不会生效，在 IDE 中会标红提示出错。</p>
</div>
<h2>[四]编写页面</h2>
<h3>[1]定义常量</h3>
<p>在前端工程中，很多代码都需要编写一定的字符串作为标识，例如：定义页面或者组件的名字。这类字符串通常会在多个代码中出现，为了减少维护量，定义了专门的常量类，对常量进行统一管理。</p>
<p>找到主工程模块中的文件 <code>packages/ui/src/composables/constants/definition/display.ts</code>。在其中 <code>ComponentName</code> 添加新增模块的名称。如下所示：</p>
<div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-typescript"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> ComponentName</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  IOT_PRODUCT_CATEGORY</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "IotProductCategory"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  IOT_PRODUCT</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "IotProduct"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  IOT_DEVICE</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "IotDevice"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  IOT_TSL_FUNCTION</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "IotTslFunction"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  IOT_TSL_UNIT</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "IotTslUnit"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  MGT_CERTIFICATE</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "MgtCertificate"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">};</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>该常量主要用于定义页面组件的名称。会在多处代码中进行交互使用。也是页面路由的核心元素。</p>
</div>
<h3>[2]导入类型定义</h3>
<p>找到主工程模块中的文件 <code>lib/declaration/base.ts</code>。在其中导入功能所需的类型定义，即前面定义的 <code>MgtCertificateEntity</code>、<code>MgtCertificateConditions</code> 和 <code>MgtCertificateProps</code></p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>之所以在 <code>lib/declaration/base.ts</code> 文件中进行类型的导入，主要是为了构建一个统一的导入入口，修改以及使用的时候会比较方便。当然也可以在各个代码中独自导入，只不过如要修改就会非常麻烦。</p>
</div>
<h3>[3]实现页面</h3>
<p>下面就需要来编写具体的 Vue 页面。建议在主工程模块的 <code>pages</code> 目录下新建目录并创建页面。</p>
<p>Dante Cloud 前端默认的 Vue 名目前有三个：</p>
<ul>
<li><code>Index.vue</code>：功能模块的主页面。如果是 Table 类型，则为 Table 显示页面。</li>
<li><code>Content.vue</code>：功能模块的新增与编辑页面。新增与编辑类似，所以使用同一个页面。</li>
<li><code>Authorize.vue</code>：设计授权之类功能的页面。</li>
</ul>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>之所以有这三个相对固定的页面，是因为一方面需要将三级Router、Tab View以及前端存储数据进行关联；另一方面是 Table 类型的功能模块都大同小异，提取出共性内容方便开发。</p>
<p>如果以上三个页面不满足需求，可以自己根据实际需求进行定义。不过这时就需要对 <code>hooks</code> 中的 Table 内容进行扩展，否则页面跳转会出现问题。</p>
</div>
<h2>[五]页面路由</h2>
<p>Dante Cloud 前端支持 <code>静态路由</code> 和 <code>动态路由</code> 两种路由模式：</p>
<ul>
<li><code>静态路由</code>：<code>vue-router</code> 最标注准的路由方式。需要在前端代码中明确写出，但在前端不便于动态调整，即使支持意义也不大。更多的是用于前端开发和调试。</li>
<li><code>动态路由</code>：由后端提供路由相关数据，前端根据后端数据动态加载相关路由。所以前端菜单的动态变化也是由次来支持。</li>
</ul>
<h3>[1]静态路由</h3>
<p>在主工程的 <code>packages/ui/routers/modules</code> 目录下，根据代码需要新建一个 <code>ts</code> 或者在已有 <code>ts</code> 文件中，添加静态路由。示例代码如下：</p>
<div class="language-typescript line-numbers-mode" data-highlighter="shiki" data-ext="typescript" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-typescript"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> type</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">RouteRecordRaw</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "vue-router"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">CONSTANTS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "/@/composables/constants"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">const</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> routes</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Array</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">RouteRecordRaw</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    path</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "/manage"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    component</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#0184BC;--shiki-dark:#61AFEF"> import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"/@/views/layouts/Index.vue"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    meta</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">title</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "系统信息管理"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">sort</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 8</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">icon</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "mdi-leaf"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    redirect</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "/manage/certificate"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    children</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        path</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "/manage/certificate"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#986801;--shiki-dark:#E5C07B"> CONSTANTS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ComponentName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#986801;--shiki-dark:#E06C75">MGT_CERTIFICATE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        meta</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          title</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "证书管理"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          icon</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "mdi-certificate"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          isHideAllChild</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        },</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        component</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span><span style="--shiki-light:#0184BC;--shiki-dark:#61AFEF"> import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"/@/views/pages/manage/certificate/Index.vue"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        children</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            path</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "/manage/certificate/content"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            name</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "MgtCertificateContent"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            meta</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">              title</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "证书详情"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">              icon</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "mdi-file-certificate"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">              isDetailContent</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            },</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            component</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">=></span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#61AFEF">              import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"/@/views/pages/manage/certificate/Content.vue"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          },</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        ],</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      },</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    ],</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">export</span><span style="--shiki-light:#E45649;--shiki-dark:#C678DD"> default</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> routes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]动态路由</h3>
<p>动态路由已经集成至前端系统之中。通过添加系统菜单就可以实现动态路由的添加。</p>
<h4>添加菜单</h4>
<p>添加菜单的界面如下面所示：</p>
<figure><img src="/assets/image/coding/sys-menu.png" alt="菜单管理" tabindex="0" loading="lazy"><figcaption>菜单管理</figcaption></figure>
<figure><img src="/assets/image/coding/sys-menu-add.png" alt="添加菜单" tabindex="0" loading="lazy"><figcaption>添加菜单</figcaption></figure>
<h4>菜单参数</h4>
<ul>
<li><code>Vue Router 请求路径</code>：对应的是 Vue Router 的 <code>path</code></li>
<li><code>Vue Router Component</code>：路径对应的组件名。这里的组件应一定要与页面上定义名称保持一致</li>
<li><code>显示标题</code>：菜单上显示的标题</li>
<li><code>显示图标</code>：菜单上显示的图标。系统默认使用的 Icon 是 Material Design Icons。只要在输入框输入 Icon 名称就会自动显示可用图标。如下图所示</li>
<li><code>Vue Component 相对路径</code>：对应的是 Vue Router 的 <code>component</code>。如果是节点菜单，那么对应的就是 Layout 组件；如果是子页面，对应的就是实际页面。可以对照上面静态路由的示例</li>
<li><code>Vue Router 重定向地址</code>：对应的是 Vue Router 的 <code>redirect</code>。可以对照上面静态路由的示例</li>
<li><code>上级节点</code>：上级节点用于关联页面和节点菜单，明确上下级关系。即上例中 <code>children</code> 关系结构</li>
<li><code>该页面不需要 Keeplive 缓存</code>：是否要开启当前页面的 Keeplive 缓存</li>
<li><code>该页面不需要权限验证</code>：系统中默认的页面都需要验证权限，对于特殊的不需要权限的页面可以设置该选项</li>
<li><code>该页面下包含子页面</code>：该选项用于区分是节点菜单还是页面功能</li>
<li><code>该页面是三级路由页面</code>：默认的功能页面就是前面所述的 <code>Index.vue</code>。除此以外的 <code>Content.vue</code> 和 <code>Authorize.vue</code> 页面都属于三级路由</li>
<li><code>排序值</code>：用于控制菜单功能显示顺序</li>
<li><code>数据状态</code>：用于控制菜单条目数据的状态</li>
<li><code>是否为保留数据</code>：如果设置为保留数据，那么在列表中该条数据将不会显示【删除】按钮。是一种保护措施，以避免误删除操作。</li>
</ul>
<figure><img src="/assets/image/coding/sys-menu-icon.png" alt="添加菜单" tabindex="0" loading="lazy"><figcaption>添加菜单</figcaption></figure>
<h4>分配权限</h4>
<p>前端菜单是单独的权限管理，主要的权限管控是基于 RBAC 中的角色。分别权限就是为菜单分配角色，如下图所示：</p>
<figure><img src="/assets/image/coding/sys-menu-role.png" alt="分配权限" tabindex="0" loading="lazy"><figcaption>分配权限</figcaption></figure>
<h2>[六]注意事项</h2>
<p>再次重申，不管是从 <code>大中台、小前台</code> 的理念还是从技术复杂度出发，微服务的核心都是后端而不是前端。所以，在使用本项目前端时请注意以下事项：</p>
<ol>
<li>本系统前端并不是一套通用的管理模版，所以通用性肯定不足以满足所有 Web 前端的需求。如果你是想找一套管理模版，本系统前端未必合适</li>
<li>本系统前端也是一套完整的系统，但是更多定位的是作为微服务系统的管理端。本章内容均是基于扩展相关的功能进行编写，并未覆盖所有实现点。</li>
<li>之所以选择 Quasar 作为本系统首选组件，是因为经过多方对比，觉得 Quasar 更适合后端人员，无需编写大量 CSS 就可以写出还看得过去的开发前端</li>
<li>如果本系统的样式风格不满足你的需要，那么完全可以使用自己的前端框架，把本系统权当是前端与后端交互各类功能的示例即可。（微服务系统的前端，本来就应该“轻量”和易于修改以适应多变的需求）。</li>
<li>因为本人并不是专注于前端，所以如果有哪些代码实现的不够理想，有更好、更优的实现或者编码方式，可以提出 ISSUE，在个人能力范围内会尽量实现。</li>
</ol>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/coding/sys-menu.png" type="image/png"/>
    </item>
    <item>
      <title>编程开发辅助</title>
      <link>https://www.herodotus.cn/develop-guide/coding/helper.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/helper.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">编程开发辅助</source>
      <description>概述 除了日常开发、环境调整以及基础设施配置外，系统中还提供了一定的辅助措施，方便开发中使用和定位信息。 [一]配置参数提示 在使用 Spring Boot 框架时，除了 Spring Boot 自身的大量配置参数外，随着功能的增多还会产生大量自定义的配置参数。 这些参数类型各异，值类型也有很大差别。即使有文档说明，使用起来也不够方便。 [1]使用方法...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>除了日常开发、环境调整以及基础设施配置外，系统中还提供了一定的辅助措施，方便开发中使用和定位信息。</p>
<h2>[一]配置参数提示</h2>
<p>在使用 Spring Boot 框架时，除了 Spring Boot 自身的大量配置参数外，随着功能的增多还会产生大量自定义的配置参数。</p>
<p>这些参数类型各异，值类型也有很大差别。即使有文档说明，使用起来也不够方便。</p>
<h3>[1]使用方法</h3>
<p>为了解决上述问题，Dante Cloud 提供了“配置参数”提示功能。</p>
<p>有了配置参数提示，在 IDE 工具中，打开服务下的 bootstrap.yml 或者 application.yml 等配置文件，直接输入一定的英文字符，就会直接显示当前服务可用的参数，以及用途解释。如下图所示：</p>
<figure><img src="/assets/image/herodotus/properties-prompt.png" alt="IDE提示" tabindex="0" loading="lazy"><figcaption>IDE提示</figcaption></figure>
<h3>[2]自定义方法</h3>
<p>配置参数提示功能已经内置在系统代码之中，只要按照规范编写配置属性，新增的配置参数就会自动生成提示。</p>
<p>新增自定义配置参数，只需要注意以下两点：</p>
<ol>
<li>正常编写配置参数定义类，并在类头上标注 <code>@ConfigurationProperties</code></li>
<li>使用 Java 标准的注释格式，给每个属性编写注释。</li>
</ol>
<p>满足以上两点，重新编译代码后，就会自动生成配置参数的提示。示例代码如下所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">ConfigurationProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">prefix</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> CacheConstants</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">PROPERTY_PREFIX_CACHE</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CacheProperties</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CacheSetting</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 是否允许存储空值</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Boolean</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> allowNullValues </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 缓存名称转换分割符。默认值，"-"</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 默认缓存名称采用 Redis Key 格式（使用 ":" 分割），使用 ":" 分割的字符串作为Map的Key，":"会丢失。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 指定一个分隔符，用于 ":" 分割符的转换</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> separator </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "-"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 针对不同实体单独设置的过期时间，如果不设置，则统一使用默认时间。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Map</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CacheSetting</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> instances </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HashMap</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Boolean</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getAllowNullValues</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> allowNullValues;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setAllowNullValues</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Boolean</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> allowNullValues</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">allowNullValues</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> allowNullValues;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Map</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CacheSetting</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getInstances</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> instances;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setInstances</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Map</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">CacheSetting</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">instances</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">instances</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> instances;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getSeparator</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> separator;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setSeparator</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> separator</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">separator</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> separator;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> toString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> MoreObjects</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toStringHelper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"allowNullValues"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, allowNullValues)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"separator"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, separator)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[二]代码打包记录查询</h2>
<p>团队开发过程中，难免出现某次提交代码自身存在异常，测试不足就部署上线的情况。</p>
<p>已经编译为 jar 或者打包为镜像的应用，除了版本号和发布时间等信息以外，就只有通过开发人员调试代码才能快速定位问题。</p>
<h3>[1]查看方式</h3>
<p>为了可以方便更加快捷的定位问题或者了解问题的出处，Dante Cloud 在代码环境中，会将代码提交相关记录信息，在代码编译过程将其打包至服务中。相关的信息包含以下内容：</p>
<div class="language-properties line-numbers-mode" data-highlighter="shiki" data-ext="properties" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-properties"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">#Generated by Git-Commit-Id-Plugin</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.branch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">develop</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.build.host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">DESKTOP-5CBR0MD</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.build.time</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">2024-12-28 23\:36\:32</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.build.user.email</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">pointer_v@qq.com</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.build.user.name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">\u7801\u5320\u541B</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.build.version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">3.4.1.0</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.closest.tag.commit.count</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.closest.tag.name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.author.time</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">2024-12-23 22\:55\:52</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.committer.time</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">2024-12-23 22\:55\:52</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">66de58275027d344d5b0f0a9ee59acbef52b7152</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.id.abbrev</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">66de582</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.id.describe</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">66de582-dirty</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.id.describe-short</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">66de582-dirty</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.message.full</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">\u2728 feat\: v3.4.1.0\n\n- \u4E3B\u8981\u66F4\u65B0\n  - [\u5347\u7EA7] Spring Boot \u7248\u672C\u5347\u7EA7\u81F3 3.4.1\n  - [\u5347\u7EA7] Spring Authorization Server \u7248\u672C\u5347\u7EA7\u81F3 1.4.1\n  - [\u65B0\u589E] Spring Authorization Server \u6838\u5FC3\u6570\u636E\u5B58\u50A8\u65B0\u589E NoSQL \u5B58\u50A8\u652F\u6301\u3002\u53EF\u6839\u636E\u9700\u6C42\u4EE5\u901A\u8FC7\u4FEE\u6539\u914D\u7F6E\u65B9\u5F0F\uFF0C\u52A8\u6001\u53D8\u66F4 JPA\u3001Redis \u548C MongoDB \u4E09\u8005\u4E0D\u540C\u7684\u5B58\u50A8\u4ECB\u8D28\u4F5C\u4E3A Spring Authorization Server \u6838\u6570\u636E\u7684\u5B58\u50A8\u4ECB\u8D28\u3002\n- \u5176\u5B83\u66F4\u65B0\n  - [\u91CD\u6784] data-module-jpa \u6A21\u5757\u540D\u79F0\u4FEE\u6539\u4E3A data-module-tenant\uFF0C\u66F4\u52A0\u660E\u6670\u4EE3\u7801\u7528\u9014\u548C\u6A21\u5757\u5B9A\u4F4D\u3002\n  - [\u91CD\u6784] \u62C6\u5206\u6570\u636E\u57FA\u7840\u6A21\u5757\u4EE5\u53CA\u76F8\u5173\u8054\u6A21\u5757\uFF0C\u4EE5\u652F\u6301\u540E\u7EED\u591A\u4E2D\u6570\u636E\u6E90\u5207\u6362\u3002\n  - [\u91CD\u6784] \u57FA\u7840 Jpa findById \u65B9\u6CD5\uFF0C\u91CD\u6784\u4E3A\u8FD4\u56DE Spring Data \u6807\u51C6\u7684 Optional \u7C7B\u578B\u5BF9\u8C61\u3002\n  - [\u4FEE\u590D] \u4FEE\u590D Spring Authorization Server \u6838\u5FC3\u6570\u636E AccessTokenType \u672A\u4FDD\u5B58\u95EE\u9898\u3002\n  - [\u4FEE\u590D] \u4FEE\u590D\u7F3A\u5931 Spring Authorization Server TLS \u76F8\u5173\u63A7\u5236\u5C5E\u6027\u95EE\u9898\n  - [\u4FEE\u590D] \u4FEE\u590D\u767B\u5F55\u5931\u8D25\u8D85\u51FA\u6307\u5B9A\u6B21\u6570\u8D26\u53F7\u81EA\u52A8\u9501\u5B9A\u6761\u4EF6\u6CE8\u89E3\u4E0D\u751F\u6548\u95EE\u9898\u3002\n  - [\u4FEE\u590D] \u4FEE\u590D\u524D\u7AEF\u5DE5\u7A0B\u5347\u7EA7\u81F3 Vite6 \u540E\u7F16\u8BD1\u51FA\u9519\u95EE\u9898\n  - [\u4FEE\u590D] \u4FEE\u590D\u524D\u7AEF\u4F7F\u7528\u65B0\u7248 Vite \u7F16\u8BD1\u540E\u6837\u5F0F\u5F15\u5165\u9519\u8BEF\uFF0C\u63D0\u793A\u9700\u8981\u5B89\u88C5\u6A21\u5757\u95EE\u9898\u3002\n  - [\u4FEE\u590D] \u4FEE\u590D\u65B0\u7248\u8BFB\u53D6 Token \u903B\u8F91\u5224\u65AD\u9519\u8BEF\uFF0C\u5BFC\u81F4\u65E0\u6CD5\u6B63\u786E\u8BFB\u53D6 Token \u95EE\u9898\u3002\n  - [\u4FEE\u590D] \u4FEE\u590D\u6570\u636E\u5E93\u521D\u59CB\u5316\u811A\u672C\u9519\u8BEF\n  - [\u4F18\u5316] \u91C7\u7528 JDK 17 \u65B0\u8BED\u6CD5\u4F18\u5316 Spring Authorization Server \u6838\u5FC3\u670D\u52A1\u4EE3\u7801\n  - [\u4F18\u5316] \u6269\u5C55 Spring Authorization Server \u6838\u5FC3\u6570\u636E Jackson2 \u5904\u7406\u7C7B\uFF0C\u652F\u6301\u989D\u5916\u6DFB\u52A0 Jackson Module \u548C Mixin \u4EE5\u9002\u914D\u4E0D\u540C\u7C7B\u578B\u6570\u636E\u6E90\u3002\n  - [\u5B89\u5168] \u4FEE\u590D\u5B89\u5168\u6F0F\u6D1E CVE-2024-47535\n  - [\u5B89\u5168] \u4FEE\u590D\u5B89\u5168\u6F0F\u6D1E CVE-2024-12798\n  - [\u5347\u7EA7] minio docker \u955C\u50CF\u7248\u672C\u5347\u7EA7\u81F3 RELEASE.2024-12-18T13-15-44Z\n- \u4F9D\u8D56\u66F4\u65B0\n  - [\u5347\u7EA7] grpc \u7248\u672C\u5347\u7EA7\u81F3 1.69.0\n  - [\u5347\u7EA7] guava \u7248\u672C\u5347\u7EA7\u81F3 33.4.0\n  - [\u5347\u7EA7] redisson \u7248\u672C\u5347\u7EA7\u81F3 3.41.0\n  - [\u5347\u7EA7] software.amazon.awssdk \u7248\u672C\u5347\u7EA7\u81F3 2.29.39\n  - [\u5347\u7EA7] software.amazon.awssdk.crt \u7248\u672C\u5347\u7EA7\u81F3 0.33.7\n  - [\u5347\u7EA7] quasar webjars \u7248\u672C\u5347\u7EA7\u81F3 2.17.5\n  - [\u5347\u7EA7] sweetalert2 webjars \u7248\u672C\u5347\u7EA7\u81F3 11.15.3\n  - [\u5347\u7EA7] lettuce \u7248\u672C\u5347\u7EA7\u81F3 6.5.1.RELEASE\n  - [\u5347\u7EA7] logback \u7248\u672C\u5347\u7EA7\u81F3 1.5.15</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.message.short</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">\u2728 feat\: v3.4.1.0</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.time</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">2024-12-23 22\:55\:52</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.user.email</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">pointer_v@qq.com</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.commit.user.name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">\u7801\u5320\u541B</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.dirty</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">true</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.local.branch.ahead</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">0</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.local.branch.behind</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">0</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.remote.origin.url</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">https\://gitee.com/herodotus/herodotus-cloud.git</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.tag</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.tags</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#C678DD">git.total.commit.count</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#383A42;--shiki-dark:#98C379">106</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>当然，从 jar 包中查看还是不够方便，可以配合使用 Spring Boot Admin 来查看。</p>
<figure><img src="/assets/image/coding/git-commit.png" alt="SBA 查看 Git 信息" tabindex="0" loading="lazy"><figcaption>SBA 查看 Git 信息</figcaption></figure>
<h3>[2]关闭方式</h3>
<p>如果您不想使用 Git 信息记录，可以选择将其关闭。</p>
<p>在 Dante Cloud 代码工程根目录下，找到 <code>pom.xml</code> 文件。根据您当前的多环境，找到下面配置，将值修改为 <code>true</code>, 即可关闭 Git 信息记录</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!--不执行git commit 构建--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.build.git.commit.info</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>false&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.build.git.commit.info</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/herodotus/properties-prompt.png" type="image/png"/>
    </item>
    <item>
      <title>IP属地查询</title>
      <link>https://www.herodotus.cn/develop-guide/coding/ip.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/ip.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">IP属地查询</source>
      <description>概述 补充中... 正在努力补充中，多点点 Star，会写得更快哦！ Gitee：https://gitee.com/dromara/dante-cloud Github：https://github.com/dromara/dante-cloud</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<div class="hint-container info">
<p class="hint-container-title">补充中...</p>
<p>正在努力补充中，多点点 Star，会写得更快哦！</p>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://gitee.com/dromara/dante-cloud</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://github.com/dromara/dante-cloud</a></li>
</ul>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>ORM使用扩展</title>
      <link>https://www.herodotus.cn/develop-guide/coding/orm.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/orm.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">ORM使用扩展</source>
      <description>[一]JPA 本系统使用的核心 ORM 组件是 JPA，即 Spring Data 套件中的 Spring Data JPA。 为什么选用 JPA? JPA 确实有很多可取之处。JPA 是一种规范，Hibernate 也是遵从他的规范的。可以简单理解为 Hibernate 是 JPA 的一个实现。 JPA 规范和 Spring Data 的实现，设计理...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]JPA</h2>
<p>本系统使用的核心 ORM 组件是 JPA，即 <code>Spring Data</code> 套件中的 <code>Spring Data JPA</code>。</p>
<p><strong>为什么选用 JPA?</strong></p>
<p>JPA 确实有很多可取之处。JPA 是一种规范，Hibernate 也是遵从他的规范的。可以简单理解为 Hibernate 是 JPA 的一个实现。</p>
<p>JPA 规范和 <code>Spring Data</code> 的实现，设计理念绝对是超前的。软件开发复杂性的一个解决手段是遵循 DDD（DDD 只是一种手段，但不是唯一手段）。这里着重介绍几点来聊聊 JPA 的设计中是如何体现领域驱动设计思想的，抛砖引玉。</p>
<h3>[1]聚合根和值对象</h3>
<p>领域驱动设计中有两个广为大家熟知的概念，<code>Entity</code>（实体）和 <code>value object</code>（值对象）。</p>
<p><code>Entity</code> 的特点是具有生命周期的，有标识的，而值对象是起到一个修饰的作用，其具有不可变性，无标识。在 JPA 中 ，需要为数据库的实体类添加 <code>@Entity</code> 注解，相信这并不是巧合。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Entity</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Table</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "t_order"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Order</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Id</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> oid</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Embedded</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CustomerVo</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> customer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">OneToMany</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">cascade</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CascadeType</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ALL</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> orphanRemoval</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> fetch</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> F</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    etchType</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">LAZY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> mappedBy</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "order"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> List</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">OrderItem</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> orderItems</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如上述的代码，<code>Order</code> 便是 DDD 中的实体，而 <code>CustomerVo</code>，<code>OrderItem</code> 则是值对象。</p>
<p>程序设计者无需关心数据库如何映射这些字段，因为在 DDD 中，需要做的工作是领域建模，而不是数据建模。<strong>实体和值对象的意义不在此展开讨论，但通过此可以初见端倪，JPA 的内涵绝不仅仅是一个 ORM 框架</strong>。</p>
<h3>[2]仓储</h3>
<p><code>Repository</code> 模式是领域驱动设计中另一个经典的模式。</p>
<p>在早期，我们常常将数据访问层命名为：DAO，而在 <code>Spring Data JPA</code>。 中，其称之 <code>Repository</code>（仓储），这也不是巧合，而是设计者有意为之。</p>
<h3>[3]复杂的多表查询</h3>
<p>JPA 对复杂 SQL 的支持不好，没有实体关联的两个表要做 <code>join</code>，这也是它最大的诟病，但其设计理念上本身如此：</p>
<p><strong>现代微服务的架构，各个服务之间的数据库是隔离的，跨越很多张表的 <code>join</code> 操作本就不应该交给单一的业务数据库去完成。</strong></p>
<blockquote>
<p>参考：<a href="https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&amp;mid=2247503337&amp;idx=1&amp;sn=d4d4d69651aafabbfff84b73370cde68&amp;chksm=ebd5f2c5dca27bd38ac1a6a3d5fac03f246e51efa56407c4f57e6876ac4d6558d553958602fc&amp;scene=21#wechat_redirect" target="_blank" rel="noopener noreferrer">为什么强烈建议你不要做联表查询？</a></p>
</blockquote>
<p>解决方案是：使用 <code>ElasticSearch</code> 做视图查询或者 <code>Mongodb</code> 一类的 NoSQL 去完成。问题本不是问题。</p>
<h3>[4]总结</h3>
<p>真正走进 JPA，真正走进 <code>Spring Data</code> 会发现，并不是在解决一个数据库查询问题，并不是在使用一个 ORM 框架，而是<strong>真正地在实践领域驱动设计</strong>。</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>再次补充：DDD 只是一种手段，但不是唯一手段</p>
</div>
<h2>[二]JPA特性</h2>
<h3>[1]生命周期回调</h3>
<p>JPA 提供很多实体声明周期的回调注解。</p>
<ul>
<li><code>@PrePersist</code>：在实体被持久化前调用的方法。</li>
<li><code>@PostPersist</code>：在实体被持久化后调用的方法。</li>
<li><code>@PreUpdate</code>：在实体被更新前调用的方法。</li>
<li><code>@PostUpdate</code>：在实体被更新后调用的方法。</li>
<li><code>@PreRemove</code>：在实体被删除前调用的方法。</li>
<li><code>@PostRemove</code>: 在实体被删除后调用的方法。</li>
</ul>
<p>利用这些注解，可以辅助我们开发一些特殊的功能支持。</p>
<p>例如：在 Dante Cloud 就利用这个机制，通过简单的代码就实现了权限元数据变更的监听，从而实现权限数据的及时同步。代码如下：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysAttributeEntityListener</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractApplicationContextAware</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Logger</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> log </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> LoggerFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getLogger</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SysAttributeEntityListener</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">PostUpdate</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    protected</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> postUpdate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysAttribute</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> entity</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">debug</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- [1] SysAttribute entity @PostUpdate activated, value is : [{}]. Trigger SysAttribute change event."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">entity</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        publishEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> SysAttributeChangeEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(entity));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]JPA审计</h3>
<p>使用 JPA 提供的便捷机制，就可以很容易实现数据库审计。</p>
<p>这是最简单的审计形式。在这里，不跟踪所做的改变，而只是跟踪谁创建或修改了一个业务实体，以及何时完成的。</p>
<p>具体来说，将跟踪每个业务对象的以下额外字段：</p>
<ul>
<li>创建时间</li>
<li>创建者</li>
<li>修改者</li>
<li>修改时间</li>
</ul>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Column</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "created_by"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">CreatedBy</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> createdBy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Column</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "updated_by"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">LastModifiedBy</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> updatedBy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Column</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "created_on"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">CreatedDate</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Date</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> createdOn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Column</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "updated_on"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">LastModifiedDate</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Date</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> updatedOn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Dante Cloud 实际的应用代码示例如下：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">MappedSuperclass</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">EntityListeners</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">AuditingEntityListener</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> abstract</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BaseJpaEntity</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BaseEntity</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Schema</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "创建人"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Column</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "create_by"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">CreatedBy</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> createBy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Schema</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "最后修改"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Column</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "update_by"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">LastModifiedBy</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> updateBy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Schema</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "排序值"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Column</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "ranking"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Integer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ranking </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Integer</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getRanking</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ranking;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setRanking</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Integer</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> ranking</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ranking</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ranking;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getCreateBy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> createBy;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setCreateBy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> createBy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">createBy</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> createBy;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getUpdateBy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> updateBy;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setUpdateBy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> updateBy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">updateBy</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> updateBy;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> toString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> MoreObjects</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toStringHelper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"createBy"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, createBy)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"updateBy"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, updateBy)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ranking"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, ranking)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[3]实体审计</h3>
<p>尽管JPA审计为基本的审计提供了简单的配置，但它只提供以下信息</p>
<ul>
<li>实体是什么时候创建的，谁创建了它。</li>
<li>实体最后一次修改是什么时候，谁修改了它。</li>
</ul>
<p>它并没有为你提供一个实体的所有修改/更新的细节，例如一个客户实体可能被修改了5次。通过JPA审计，我没有办法找出在这5次更新中，实体的哪些地方被修改了，以及谁做了这些修改。<br>
因此，<code>Hibernate Envers</code>，它为一个实体提供完整的 审计历史为一个实体提供完整的信息</p>
<p>Dante Cloud 已经添加了实体审计支持。使用时，仅需要在需要开启审计的实体类中，加入<code>@Audited</code> 注解即可。例如：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Audited</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Customer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  ....</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Envers 审计会额外的增加审计数据存储表，如果一些配置信息不符合你实际的使用需求，那么可以通过以下配置进行修改：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  jpa</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      org</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        hibernate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">          envers</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            audit_table_suffix</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">_AUDIT</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            revision_field_name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">REVISION_ID</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            revision_type_field_name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">REVISION_TYPE</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[4]软删除（逻辑删除）</h3>
<p>在 6.4 版本中，Hibernate 团队为 Hibernate ORM 引入了官方的软删除功能。现在只需要 <code>@SoftDelete</code> 注解即可激活实体类的软删除。然后，Hibernate 会生成软删除记录所需的 SQL UPDATE 语句，并调整所有查询语句以排除软删除的记录。</p>
<p>示例代码：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Entity</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Table</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "tag"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">SoftDelete</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Tag</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Id</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">GeneratedValue</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Long</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">NaturalId</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[5]注意事项</h3>
<p>以上所述的 JPA 所包含的特性，除了 JPA 审计之外，其它功能均没有进行统一设置，一方面没有必要因为并不是所有业务都需要，另一方面增加过多的处理势必会增加处理损耗影响性能。所以需要您根据自己的实际需求进行个性化设置。</p>
<h2>[三]Mybatis Plus 使用</h2>
<p>因为一部分用户只习惯使用、甚至只会使用 Mybatis Plus，所以本系统也集成了 Mybatis Plus ORM 组件。</p>
<p>Dante Cloud MyBatis Plus 可以与 JPA 同时使用。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>这里所说的<strong>同时使用</strong>是指：新增的代码即可以选择使用 JPA 也可以选择使用 MyBatis Plus，两者不冲突。</p>
<p>但是要注意的是<strong>同时使用</strong>不是指，用 JPA 编写的代码可以自动切换为 MyBatis Plus 的代码。这个是无法做到的，两者的机制完全不同。</p>
<p>所以系统现有使用 JPA 编写代码，如果你不习惯或者不喜欢，想要切换为 MyBatis Plus，唯一的途径就是将所有的 JPA 代码自己用 MyBatis Plus 全部重新写一遍。</p>
</div>
<h2>[四]QueryDSL</h2>
<p>JPA 确实对复杂 SQL 支持不是那么理想，如果您有个查询使用 JPA 使用觉得不方便，那么可以尝试使用 QueryDSL 对查询进行改进。</p>
<p>QueryDSL 是一个框架，它可以通过它提供的的API帮助我们构建静态类型的SQL-like查询，也就是在上面我们提到的组织查询方式。可以通过诸如Querydsl之类的流畅API构造查询。</p>
<p>QueryDSL 是出于以类型安全的方式维护HQL查询的需要而诞生的。 HQL查询的增量构造需要String连接，这导致难以阅读的代码。通过纯字符串对域类型和属性的不安全引用是基于字符串的HQL构造的另一个问题。</p>
<p>随着域模型的不断变化，类型安全性在软件开发中带来了巨大的好处。域更改直接反映在查询中，而查询构造中的自动完成功能使查询构造更快，更安全。</p>
<p>用于Hibernate的 HQL是 QueryDSL 的第一个目标语言，如今 QueryDSL 支持JPA，JDO，JDBC，Lucene，Hibernate Search，MongoDB，Collections和 RDFBean 作为它的后端。</p>
<p>Dante Cloud 中已经集成了 QueryDSL，在使用时仅需要在代码中，注入 <code>BlazeJPAQueryFactory</code> 即可以编写 QueryDSL 代码。</p>
<p>QueryDSL 在 Dante Cloud 中实际应用的示例如下：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Service</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysSocialBindingService</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BaseJpaService</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysSocialBinding</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SysSocialBindingRepository</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> sysSocialBindingRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BlazeJPAQueryFactory</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> blazeJPAQueryFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> SysSocialBindingService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysSocialBindingRepository</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> sysSocialBindingRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">BlazeJPAQueryFactory</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> blazeJPAQueryFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysSocialBindingRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> sysSocialBindingRepository;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">blazeJPAQueryFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> blazeJPAQueryFactory;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BaseJpaRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysSocialBinding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysSocialBindingRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Transactional</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> List</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">AccessSource</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> findAllByUserId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> userId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        QSysUser</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> sysUser</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> QSysUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        QSysSocialUser</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> sysSocialUser</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> QSysSocialUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysSocialUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        QSysSocialBinding</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> socialBinding</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> QSysSocialBinding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysSocialBinding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        // 通过UserId查询到当前用户已经 Binding 的社交信息 子查询</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        SubQueryExpression</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SysSocialUser</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">socialUserSubQuery</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> JPAExpressions</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">select</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(sysSocialUser)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">from</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(sysSocialUser)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">join</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(sysUser)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysSocialUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">users</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">any</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">userId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">eq</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">userId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">))</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">where</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">userId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">eq</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(userId));</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> blazeJPAQueryFactory</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">select</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        Projections</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">bean</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">AccessSource</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                                socialBinding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">bindingId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">as</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"id"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                                socialBinding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">bindingCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">as</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"source"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                                socialBinding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">bindingName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">as</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"name"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                                sysSocialUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">socialId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                                sysSocialUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">updateTime</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">as</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"bindingTime"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                                sysSocialUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">nickname</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">as</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"detail"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                                sysSocialUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">avatar</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                        )</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                )</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">from</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(socialBinding)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">leftJoin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(socialUserSubQuery, sysSocialUser)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">sysSocialUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">source</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">eq</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">socialBinding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">bindingCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">))</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">fetch</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>对象池化管理</title>
      <link>https://www.herodotus.cn/develop-guide/coding/pool.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/pool.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">对象池化管理</source>
      <description>概述 很多常见组件的 SDK 或者 API 的使用方法，均是实例化一个 XXXClient 对象，然后利用该对象对具体的组件进行操作。例如：AWS S3 中的 S3AsyncClient，InfluxDB 中的 InfluxDBClient 等等。常规使用中，通常是将此类对象实例化为一个单例，或者 Spring Boot 中的 Bean，然后在代码中统...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>很多常见组件的 SDK 或者 API 的使用方法，均是实例化一个 XXXClient 对象，然后利用该对象对具体的组件进行操作。例如：AWS S3 中的 <code>S3AsyncClient</code>，InfluxDB 中的 <code>InfluxDBClient</code> 等等。常规使用中，通常是将此类对象实例化为一个单例，或者 Spring Boot 中的 Bean，然后在代码中统一使用。</p>
<p>一般来说，这种使用方式没有任何问题。但是随着技术的发展以及对性能和吞吐能力需求的提升，大量产品或者组件的 SDK 慢慢的逐步改为异步调用方式。异步操作就意味着原来的阻塞式逻辑变为多线程逻辑，那么原有单例方式实例化核心对象的操作方式就很难满足并发需求。</p>
<p>为了解决这个问题，Dante Cloud 对于此类对象的使用均改为&quot;池&quot;化对象方式。即构建一个对象池，在使用时生成多个核心对象实例，随用随取。类似于连接数据库的线程池，这样可以有效规避异步操作带来的时序不一致以及单一实例多线程操作不稳定问题。同时，减少反复创建和关闭连接带来的损耗。</p>
<h2>[一]使用方法</h2>
<p>Dante Cloud 池化对象封装类，主要有两个：</p>
<ul>
<li><code>cn.herodotus.stirrup.core.definition.support.AbstractObjectPool</code></li>
<li><code>cn.herodotus.stirrup.core.definition.support.AbstractPooledObjectService</code></li>
</ul>
<p>使用方法主要有以下几个步骤：</p>
<ol>
<li>定义自己的 <code>BasePooledObjectFactory</code> 实现类</li>
<li>定义自己的 <code>AbstractObjectPool</code> 实现类</li>
<li>将 <code>AbstractObjectPool</code> 实现类注册为 Bean</li>
<li>编写自己的 <code>AbstractPooledObjectService</code> 实现类，在其中实现自己的业务逻辑。</li>
</ol>
<p>如果要同时实现对象池的配置参数，可以在自己的属性定义类中，增加一个 <code>cn.herodotus.stirrup.core.definition.domain.Pool</code> 属性即可。</p>
<h2>[二]应用案例</h2>
<p>下面以 Dante Cloud 中 <code>InfluxDB2</code> 模块的实现作为案例，来说明池化对象的具体使用方法。</p>
<h3>[1]定义配置属性</h3>
<p>如前文所述，在自己的配置属性类型，添加 <code>cn.herodotus.stirrup.core.definition.domain.Pool</code> 对象作为属性，实现对象池的配置。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ConfigurationProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(prefix </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> InfluxDB2Constants</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">PROPERTY_NOSQL_INFLUXDB2</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> InfluxDB2Properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * Influxdb2 连接访问 URL</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> url </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "http://localhost:8086"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * Influxdb2 Token。Influxdb2 不再使用用户名和密码访问服务端</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> token </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "kGTzf2ey86guP0mkwF5s7tfRFdtGyVYcG-QVbTQyP8zmQLigb1i6guHbQ2DGB9Wbadu93b4151NJjKt0vWXQfQ=="</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * Influxdb2 组织, 默认值：herodotus</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> organization </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "herodotus"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * Influxdb2 存储桶。相比V1 移除了database 和 RP，增加了bucket</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> bucket </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "herodotus-bucket"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 对象池设置</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Pool</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> pool </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> Pool</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getUrl</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> url;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setUrl</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> url</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">url</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> url;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getToken</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> token;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setToken</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> token</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">token</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> token;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getOrganization</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> organization;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setOrganization</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> organization</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">organization</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> organization;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getBucket</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> bucket;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setBucket</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> bucket</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">bucket</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> bucket;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Pool</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getPool</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> pool;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setPool</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Pool</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> pool</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">pool</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> pool;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> toString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> MoreObjects</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toStringHelper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"url"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, url)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"token"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, token)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"organization"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, organization)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"bucket"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, bucket)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"pool"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, pool)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]定义 <code>BasePooledObjectFactory</code> 实现类</h3>
<p>定义一个 <code>InfluxDB2ClientPooledObjectFactory</code> 类，作为 <code>BasePooledObjectFactory</code> 实现类</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> InfluxDB2ClientPooledObjectFactory</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BasePooledObjectFactory</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">InfluxDBClient</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> InfluxDB2Properties</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> influxdb2Properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> InfluxDB2ClientPooledObjectFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">InfluxDB2Properties</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> influxdb2Properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">influxdb2Properties</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> influxdb2Properties;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> InfluxDBClient</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> create</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> throws</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Exception</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> InfluxDBClientFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">create</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">influxdb2Properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getUrl</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                influxdb2Properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getToken</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toCharArray</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                influxdb2Properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getOrganization</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                influxdb2Properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getBucket</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> PooledObject</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">InfluxDBClient</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> wrap</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">InfluxDBClient</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> influxDBClient</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> DefaultPooledObject</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;>(influxDBClient);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> destroyObject</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">PooledObject</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">InfluxDBClient</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">p</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> throws</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Exception</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        p</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getObject</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">close</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p><code>BasePooledObjectFactory</code>（池化对象工厂）类，主要实现对象池中，对象的生成和回收。实现必要的方法即可，其它内容根据实际需求添加即可。</p>
</div>
<h3>[3]定义 <code>AbstractObjectPool</code> 实现类</h3>
<p>定义一个 <code>InfluxDB2ClientObjectPool</code> 作为 <code>AbstractObjectPool</code> 实现类。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> InfluxDB2ClientObjectPool</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractObjectPool</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">InfluxDBClient</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> InfluxDB2ClientObjectPool</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">InfluxDB2ClientPooledObjectFactory</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> influxdb2ClientPooledObjectFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">InfluxDB2Properties</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> influxdb2Properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(influxdb2ClientPooledObjectFactory, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">influxdb2Properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getPool</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p><code>AbstractObjectPool</code>类，对象池的主要操作类。实际的业务代码中，可以直接使用 <code>InfluxDB2ClientObjectPool</code> 类，或者该类注入到其它类中，例如：XXXService 类中进行使用。</p>
</div>
<h2>[三]注意事项</h2>
<p>对象池的操作很类似于 JDBC，需要自己手动生成对象，并且手动关闭对象。</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>每次操作完，一定要手动关闭对象。这里的关闭并不是真正关闭，而是将使用后的对象放回对象池。如果忘记关闭，就会像 JDBC 一样，链接被耗尽导致使用一段时间后出现操作失败问题。</p>
</div>
<p>下面以 Dante Cloud 对象存储为例，展示具体的操作方式：</p>
<h3>[1]定义服务的通用操作</h3>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> abstract</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractS3AsyncClientService</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractPooledObjectService</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">S3AsyncClient</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> AbstractS3AsyncClientService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">S3AsyncClientObjectPool</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> objectPool</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(objectPool);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    protected</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> &#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">T</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CompletableFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">T</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> toFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Function</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">S3AsyncClient</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">CompletableFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">T</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">operate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        S3AsyncClient</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> client</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getClient</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        CompletableFuture</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">T</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">future</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> operate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">apply</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(client);</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        close</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(client);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> future;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>手动创建对象，完成操作后，关闭对象。</p>
</div>
<h3>[2]实现具体的业务操作</h3>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Service</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> S3AsyncClientBucketService</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractS3AsyncClientService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> S3AsyncClientBucketService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">S3AsyncClientObjectPool</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> objectPool</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(objectPool);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CompletableFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">HeadBucketResponse</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> headBucket</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">HeadBucketRequest</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> headBucketRequest</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> toFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(client </span><span style="--shiki-light:#C18401;--shiki-dark:#C678DD">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> client</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">headBucket</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(headBucketRequest));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CompletableFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">CreateBucketResponse</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> createBucket</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">CreateBucketRequest</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> createBucketRequest</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> toFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(client </span><span style="--shiki-light:#C18401;--shiki-dark:#C678DD">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> client</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">createBucket</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(createBucketRequest));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CompletableFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">DeleteBucketResponse</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> deleteBucket</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">DeleteBucketRequest</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> deleteBucketRequest</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> toFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(client </span><span style="--shiki-light:#C18401;--shiki-dark:#C678DD">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> client</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">deleteBucket</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(deleteBucketRequest));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CompletableFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ListBucketsResponse</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> listBuckets</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ListBucketsRequest</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> listBucketsRequest</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> toFuture</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(client </span><span style="--shiki-light:#C18401;--shiki-dark:#C678DD">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> client</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">listBuckets</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(listBucketsRequest));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>签章缓存定义</title>
      <link>https://www.herodotus.cn/develop-guide/coding/stamp.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/stamp.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">签章缓存定义</source>
      <description>概述 在实际开发过程中，我们经常会遇到需要将某种标识型数据缓存一定时间的需求。例如：手机短信验证码、登录错误次数记录等。这一类需求代码实现逻辑除了生成标识数据值的方式（有些可能不需要标识值），基本都是一致的。 为了方便开发，Dante Cloud 将这一需求进行抽象，并提取了一个专门的概念【签章】。 [一]使用方法 开发一个“签章”功能非常简单，编写一...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>在实际开发过程中，我们经常会遇到需要将某种标识型数据缓存一定时间的需求。例如：手机短信验证码、登录错误次数记录等。这一类需求代码实现逻辑除了生成标识数据值的方式（有些可能不需要标识值），基本都是一致的。</p>
<p>为了方便开发，Dante Cloud 将这一需求进行抽象，并提取了一个专门的概念【签章】。</p>
<h2>[一]使用方法</h2>
<p>开发一个“签章”功能非常简单，编写一类，继承 <code>cn.herodotus.stirrup.cache.jetcache.stamp.AbstractStampManager</code> 抽象类。然后根据自己指定的签章值的类型，指定值的生成策略即可。</p>
<p>示例代码如下，这个示例是 Dante Cloud 中生成手机验证码的源代码：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> VerificationCodeStampManager</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractStampManager</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SmsProperties</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> smsProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> VerificationCodeStampManager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SmsProperties</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> smsProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">AccessConstants</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CACHE_NAME_TOKEN_VERIFICATION_CODE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">smsProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> smsProperties;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> nextStamp</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> key</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">smsProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getSandbox</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> smsProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getTestCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">else</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> RandomUtil</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">randomNumbers</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">smsProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getLength</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> afterPropertiesSet</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> throws</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Exception</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setExpire</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">smsProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getExpire</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Boolean</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getSandbox</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> smsProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getSandbox</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getVerificationCodeTemplateId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> smsProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getVerificationCodeTemplateId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果 <code>cn.herodotus.stirrup.cache.jetcache.stamp.AbstractStampManager</code> 不能满足你的需求，您可以选择直接实现 <code>cn.herodotus.stirrup.cache.jetcache.stamp.StampManager</code> 接口，来实现你的逻辑。</p>
<h2>[二]缓存方式</h2>
<p>Dante Cloud 中的【签章】，是基于 <code>JetCache</code> 缓存组件实现。 <code>Jetcache</code> 支持本地（Caffeine）和远程（Redis）缓存，也支持多级缓存的混用。</p>
<p>在 <code>cn.herodotus.stirrup.cache.jetcache.stamp.AbstractStampManager</code> 默认实现中，使用的是 <code>JetCache</code> 多级缓存模式，即本地和远程都会存储缓存数据，这样就可以很便捷的支持服务多实例的场景。</p>
<p>当然，您也可以根据自己的需求进行调整，例如：只想将数据存储在服务本地，或者只想将数据存储在远程 Redis 中。</p>
<p>想要进行更改也非常方便，仅需要再你的签章实现类中，实现 <code>cn.herodotus.stirrup.cache.jetcache.stamp.AbstractStampManager</code>，类的不同构造函数。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MyStampManager</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AbstractStampManager</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MyStampManager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> cacheName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(cacheName, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CacheType</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">REMOTE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> nextStamp</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> key</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ""</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> afterPropertiesSet</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> throws</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Exception</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container info">
<p class="hint-container-title">说明</p>
<p><code>CacheType</code> 是 <code>JetCache</code> 中内置的缓存类型枚举。</p>
<ul>
<li><code>CacheType.REMOTE</code> 指的是仅是远程存储（一般为 Redis，具体要看你实际的 JetCache 配置）</li>
<li><code>CacheType.LOCAL</code> 指的是仅本地存储（一般为 Caffeine，具体要看你实际的 JetCache 配置）</li>
<li><code>CacheType.BOTH</code> 指的是本地和远程同时存储，即多级模式。</li>
</ul>
</div>
<h2>[三]系统应用</h2>
<p>在 Dante Cloud 已经大量在使用【签章】定义来简化开发。</p>
<ul>
<li><code>SignInFailureLimitedStampManager</code>：登录失败次数限制签章</li>
<li><code>AccessLimitedStampManager</code>：接口防刷签章</li>
<li><code>EmailVerificationCodeStampManager</code>：Email验证码签章</li>
<li><code>HttpCryptoProcessor</code>：前后端加解密签章</li>
<li><code>IdempotentStampManager</code>：幂等签章</li>
<li><code>JustAuthStateStampManager</code>：JustAuth State 参数签章</li>
<li><code>LockedUserDetailsStampManager</code>：账号锁定签章</li>
<li><code>VerificationCodeStampManager</code>：验证码签章</li>
</ul>
<p>在图形验证码方面，使用的也非常多</p>
<ul>
<li><code>JigsawCaptchaRenderer</code>：滑动拼图验证码签章</li>
<li><code>WordClickCaptchaRenderer</code>：文字点选验证码签章</li>
<li><code>ChineseGifCaptchaRenderer</code>：汉字GIF图形验证码签章</li>
<li><code>SpecGifCaptchaRenderer</code>：字母数字GIF图形验证码签章</li>
<li><code>ChineseCaptchaRenderer</code>：汉字图形验证码签章</li>
<li><code>SpecCaptchaRenderer</code>：字母数字图形验证码签章</li>
<li><code>ArithmeticCaptchaRenderer</code>：算数计算验证码签章</li>
<li><code>CircleCaptchaRenderer</code>：Hutool圆圈干扰验证码签章</li>
<li><code>GifCaptchaRenderer</code>：HutoolGif验证码签章</li>
<li><code>LineCaptchaRenderer</code>：Hutool线型验证码签章</li>
<li><code>ShearCaptchaRenderer</code>：Hutool扭曲干扰验证码签章</li>
</ul>
<h2>[四]用户标识</h2>
<p>在有些签章需求中，需要依赖于可以区分用户身份的标识进行区分。因为签章缓存的时效性较高，而且很多应用场景是在用户未登录的场景。所以，使用标注的用户信息，比如：userId，来区分不同的身份信息明显不现实。</p>
<p>在 Dante Cloud 采用是 <code>SessionID</code> 来区分不同的用户身份信息。这样就保证了不同场景下，用户身份的区分，也支持用户未登录的场景。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>想要了解如何在复杂的分布式架构中彻底解决 Session 一致性问题，可以阅读<code>付费阅读</code>文章 <code>《OAuth 2 中的 Scope 与 Role 深度解析》</code>，购买方式详见：<a href="/support/cookbook.html" target="_blank">【高阶文档】</a></p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>WebSocket 使用</title>
      <link>https://www.herodotus.cn/develop-guide/coding/websocket.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/websocket.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">WebSocket 使用</source>
      <description>概述 补充中... 正在努力补充中，多点点 Star，会写得更快哦！ Gitee：https://gitee.com/dromara/dante-cloud Github：https://github.com/dromara/dante-cloud</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<div class="hint-container info">
<p class="hint-container-title">补充中...</p>
<p>正在努力补充中，多点点 Star，会写得更快哦！</p>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://gitee.com/dromara/dante-cloud</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://github.com/dromara/dante-cloud</a></li>
</ul>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>服务容器构建</title>
      <link>https://www.herodotus.cn/user-guide/docker/docker-build.html</link>
      <guid>https://www.herodotus.cn/user-guide/docker/docker-build.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">服务容器构建</source>
      <description>概述 微服务系统通常都要配合 Docker 或者 K8S 才能发挥更大的效能。将服务打包为 Docker 镜像，是微服务系统必要的构建工作。 [一]Docker 打包设计初衷 Dante Cloud Docker 打包的设计，采取了与大多数开源微服务框架不同的方式。采取这种不同的方式进行打包，主要是考虑解决以下两方面问题： [1]Skwalking A...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>微服务系统通常都要配合 Docker 或者 K8S 才能发挥更大的效能。将服务打包为 Docker 镜像，是微服务系统必要的构建工作。</p>
<h2>[一]Docker 打包设计初衷</h2>
<p>Dante Cloud Docker 打包的设计，采取了与大多数开源微服务框架不同的方式。采取这种不同的方式进行打包，主要是考虑解决以下两方面问题：</p>
<h3>[1]Skwalking Agent 的引入问题</h3>
<p>系统中为了解决服务链路跟踪及监控的问题，引入了 Skywalking 组件。Skywalking 想要正确运行，除了要保证 Skywalking Server 的正常运行外，还要将 Skywalking Agent &quot;注入&quot; 到服务的运行环境中。</p>
<ol>
<li>如果是以<code>fat jar</code>的方式运行服务，需要在服务的运行命令中指定 Skywalking Agent 以及相关的参数，如下命令</li>
</ol>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">java</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -javaagent:/skywalking-agent.jar=agent.service_name=</span><span style="--shiki-light:#E45649;--shiki-dark:#D19A66">${</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">SW_AGENT_SERVICE_NAME</span><span style="--shiki-light:#E45649;--shiki-dark:#D19A66">}</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">,collector.backend_service=</span><span style="--shiki-light:#E45649;--shiki-dark:#D19A66">${</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">SW_COLLECTOR_BACKEND_SERVICE</span><span style="--shiki-light:#E45649;--shiki-dark:#D19A66">}</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF"> ${</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">JAVA_OPTS</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">}</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -jar</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /app.jar</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ol start="2">
<li>如果是以 Docker 的方式运行服务，传统的方式就需要把<code>skywalking-agent.jar</code>一并打入到服务的 docker 中。</li>
</ol>
<p>由于 Docker Compose 打包上下文的问题，就需要在每一个模块中都复制一份 skywalking-agent.jar。很不好管理，看着也不舒服。</p>
<h3>[2]Dockerfile 的编写和放置问题</h3>
<p>常规 Java 微服务的 Docker 打包方式，要么采用 Maven Plugin 的方式，要么采用 Docker Compose 的方式。不管哪一种方式，都需要在该服务代码所在目录下新建一个 Dockerfile 文件。</p>
<ul>
<li>一方面，每个服务下的 Dockerfile 文件中的配置信息，除了服务的 JAR 包名不同以外，其余的所有配置都相同，就是一种重复劳动。</li>
<li>另一方面，如果要增加 Skywalking，那么需要在每一个服务所在目录下都拷贝一份 <code>skywalking-agent.jar</code>。不仅重复工作，代码结构也不美观整洁。</li>
</ul>
<h2>[二]Docker 打包的设计</h2>
<p>为了解决上述两方面问题，Dante Cloud 在所有模块之外之指定了一个统一的目录，将需要打包的 jar 以及 dockerfile 全部放入该目录，通过该目录形成一个统一的上下文环境。在Dante Cloud工程中，<code>${project_home}/configurations/docker/context</code>就是这个上下文环境。</p>
<p>在这个上下文环境中，除了包含通用的 Dockerfile，Skywalking Agent 以外，在使用 Maven 进行编译的过程中，还会将所有需要打包服务的对应的 jar 包拷贝到<code>${project_home}/configurations/docker/context/target</code>目录下。这样打包的所有资源就都已经具备，同时还解决了上述两个问题。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>可以通过修改<code>${project_home}/pom.xml</code> 中的<code>&lt;docker.build.directory&gt;</code>值，来改变拷贝 jar 包的目录</p>
</div>
<h2>[三]Docker 打包使用方法</h2>
<h3>[1]修改信息</h3>
<p>由于使用了一个统一的 Dockerfile 来解决重复定义 Docker 打包配置文件的问题，想要实现使用一个 Dockerfile 打出不同的 Docker，那么就需要通过传参的方式来改变 Dockerfile 中的值。</p>
<p>这里采用的是环境变量的方式，在<code>${project_home}/configurations/docker/docker-compose/.env</code>文件中定义这些参数。其中包含版本号以及具体服务对应的包名。所以如果有新增服务或者想要指定版本号，首先就需要修改这个配置文件。</p>
<h3>[2]执行命令</h3>
<p>执行打包命令</p>
<div class="language-docker line-numbers-mode" data-highlighter="shiki" data-ext="docker" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-docker"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">docker-compose -f ${project home}/configurations/docker/docker-compose/linux/herodotus/services.dev.yml --env-file=${project_home}/configurations/docker/docker-compose/linux/herodotus/.env up -d</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>或者</p>
<div class="language-docker line-numbers-mode" data-highlighter="shiki" data-ext="docker" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-docker"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">docker-compose -f ${project home}/configurations/docker/docker-compose/linux/herodotus/services.prod.yml --env-file=${project_home}/configurations/docker/docker-compose/linux/herodotus/.env up -d</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>上文说到想要以 Docker 的方式运行服务并且关联 Skywalking，就需要在打包时将 Skywalking Agent 打入到 Docker 中。但实际应用中并不是所有环境都需要使用 Skywalking，比如：在生产环境需要使用 Skywalking，在开发环境中并不需要 Skywalking 以降低资源消耗。</p>
<p>因此，Dante Cloud 提供了两种打包配置</p>
<p>使用<code>${project home}/configurations/docker/docker-compose/linux/herodotus/services.dev.yml</code>这个 Docker-compose 配置文件打包时，对应的 Dockerfile 文件是<code>${project home}/configurations/docker/context/development/Dockerfile</code>。这个 Dockerfile 配置中，没有包含 Skywalking Agent 的拷贝命令。</p>
<p>使用<code>${project home}/configurations/docker/docker-compose/linux/herodotus/services.prod.yml</code>这个 Docker-compose 配置文件打包时，对应的 Dockerfile 文件是<code>${project home}/configurations/docker/context/production/Dockerfile</code>。这个 Dockerfile 配置中，包含了 Skywalking Agent 的拷贝命令。</p>
<p>这样通过不同的命令，实现了对不同 Docker 环境需求的支持。</p>
</div>
<h2>[四]注意事项</h2>
<ol>
<li>以上介绍均是在本地环境中进行操作，本地环境需要有 Docker 环境。</li>
<li>如果想要在服务器进行构建，可以在本地编译好代码后，将 <code>${project home}/configurations</code> 目录直接拷贝或者上传到服务器，使用相同的脚本进行构建即可。</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>自主封装镜像</title>
      <link>https://www.herodotus.cn/user-guide/docker/docker-images.html</link>
      <guid>https://www.herodotus.cn/user-guide/docker/docker-images.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">自主封装镜像</source>
      <description>重要 自 Dante Cloud v4.0.5.1 版本起，开始使用全新的 Nacos 3.2.0 版本。该版本已经将关键的 plugin，例如：Postgresql、Oracle 等数据存储插件合并至 Nacos 主工程中，并打包至 Docker 的镜像中，通过修改配置即可更换数据库，无需像从前一样，更换数据库还得自己打包插件。 因此，原来由 Dan...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>自 Dante Cloud v4.0.5.1 版本起，开始使用全新的 Nacos 3.2.0 版本。该版本已经将关键的 plugin，例如：Postgresql、Oracle 等数据存储插件合并至 Nacos 主工程中，并打包至 Docker 的镜像中，通过修改配置即可更换数据库，无需像从前一样，更换数据库还得自己打包插件。</p>
<p>因此，原来由 Dante Cloud 自主打包的 Docker 镜像将不再维护，直接使用 Nacos 官方打包镜像。</p>
<p>如果，您仍旧在使用 Dante Cloud v4.0.5.1 以前的版本，仍旧可以下载使用由 Dante Cloud 自主打包的 Docker 镜像</p>
</div>
<h2>[一]支持 Postgresql 的 Nacos 自主封装镜像</h2>
<p>Dante Cloud 自开源以来一直支持多数据的切换。对于使用 MySQL 的朋友来说，不存在部署多个数据库的问题。但是，对于使用其它数据库（比如：Postgresql）的朋友来说，部署 Dante Cloud 就需要部署两种数据库，因为 Nacos 默认只支持 MySQL。虽然，自 Nacos 2.2.0 版本开始，Nacos 已经支持以插件的方式扩展数据库存储类型，但是官方 Nacos 的 Docker 还是只支持 MySQL。</p>
<p>所以，为了方便用户的使用，Dante Cloud 基于 Nacos 官方 Postgresql 数据库插件和官方代码，重新构建打包了支持 Postgresql 数据库的 Nacos Docker 镜像 <code>herodotus/nacos-server</code>，并已经上传至 Docker Hub。</p>
<div class="hint-container note">
<p class="hint-container-title">注</p>
<p>该版本 Nacos Docker 镜像仅支持 Postgresql，不支持的多数据库的切换。如需使用 MySQL 版本，直接使用官方镜像即可。</p>
</div>
<h3>[1]获取方法</h3>
<ul>
<li>Docker Hub</li>
</ul>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">docker</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pull</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> herodotus/nacos-server:latest</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ul>
<li><a href="http://Quay.IO" target="_blank" rel="noopener noreferrer">Quay.IO</a></li>
</ul>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">docker</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pull</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> quay.io/herodotus-cloud/nacos-server:latest</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h3>[2]Docker Compose</h3>
<p>想要使用 <code>herodotus/nacos-server</code> 也非常简单，对现有 Docker Compose 进行简单修改即可</p>
<p>使用 Docker Compose 运行，示例脚本参见：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">services</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  nacos-postgresql</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    image</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus/nacos-server:latest</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    container_name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">nacos-postgresql</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    environment</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      MODE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">standalone</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      SPRING_DATASOURCE_PLATFORM</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">postgresql</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      POSTGRESQL_SERVICE_HOST</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">192.168.101.10</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      POSTGRESQL_SERVICE_PORT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">15432</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      POSTGRESQL_SERVICE_USER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">nacos</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      POSTGRESQL_SERVICE_PASSWORD</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">nacos</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      POSTGRESQL_SERVICE_DB_NAME</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">nacos</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      NACOS_AUTH_IDENTITY_KEY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">serverIdentity</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      NACOS_AUTH_IDENTITY_VALUE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">security</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      NACOS_AUTH_TOKEN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">SecretKey012345678901234567890123456789012345678901234567890123456789</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    #      NACOS_AUTH_ENABLE: true</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    volumes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">D:\\local-cached\\docker-volumes\\nacos\\datas:/home/nacos/data</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">D:\\local-cached\\docker-volumes\\nacos\\log:/home/nacos/log</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    ports</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"8849:8080"</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> # Nacos 3.X 新版本控台界面端口</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"8848:8848"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"9848:9848"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"9849:9849"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[3]变量说明</h3>
<p>参考 Nacos MySQL 相关 Docker 环境变量，增加了用于支持 Postgresql 的环境变量</p>
<p>| 变量                        | 默认值                                                                       | 用途                            |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>验证码的切换</title>
      <link>https://www.herodotus.cn/user-guide/infrastructure/captcha.html</link>
      <guid>https://www.herodotus.cn/user-guide/infrastructure/captcha.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">验证码的切换</source>
      <description>概述 补充中... 正在努力补充中，多点点 Star，会写得更快哦！ Gitee：https://gitee.com/dromara/dante-cloud Github：https://github.com/dromara/dante-cloud</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<div class="hint-container info">
<p class="hint-container-title">补充中...</p>
<p>正在努力补充中，多点点 Star，会写得更快哦！</p>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://gitee.com/dromara/dante-cloud</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://github.com/dromara/dante-cloud</a></li>
</ul>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>统一网关服务</title>
      <link>https://www.herodotus.cn/user-guide/infrastructure/gateway.html</link>
      <guid>https://www.herodotus.cn/user-guide/infrastructure/gateway.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">统一网关服务</source>
      <description>概述 微服务架构中，后端的各个服务都是可以独立运行的小型应用。在对外提供接口服务时，如果还是按照各个应用独立的方式提供，就好比是多个独立运行的单体系统，不仅失去了微服务架构的意义，而且还提升了管理和使用的复杂度。 微服务网关的作用，就是为后端服务提供一个统一的出入口。后端所有所服务的接口通过网关对外提供服务，就像单体系统中的 核心控制器 一样，拦截所有...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>微服务架构中，后端的各个服务都是可以独立运行的小型应用。在对外提供接口服务时，如果还是按照各个应用独立的方式提供，就好比是多个独立运行的单体系统，不仅失去了微服务架构的意义，而且还提升了管理和使用的复杂度。</p>
<p>微服务网关的作用，就是为后端服务提供一个统一的出入口。后端所有所服务的接口通过网关对外提供服务，就像单体系统中的 <code>核心控制器</code> 一样，拦截所有的请求。并集合服务发现机制，最终将请求分发至指定的服务。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>虽说网关是重要的安全防护措施，但是仅靠网关无法达到生产级的防护的。部署至生产环境时，还需要很多其它的安全防护措施，常见的措施例如：</p>
<ol>
<li>在服务器级别做访问控制，限制其它服务的外部直接访问。（如果外部可以直接访问后端服务，网关就失去了作用）</li>
<li>在网关服务之前，增加反向代理措施，例如：Nginx 等，避免直接通过IP访问网关，特别时涉及到允许公网访问的场景</li>
<li>部署服务器的安全加固等其它安全管控措施</li>
</ol>
</div>
<h2>[一]网关动态路由</h2>
<p>服务网关拦截到请求之后，将请求转发至对应的服务，就叫做网关路由。网关路由是需要配合服务发现机制才能正常运行的，即通过请求中的指定映射，找到对应的服务。</p>
<p>通常网关路由有两种管理方式：静态路由和动态路由。</p>
<ul>
<li>静态路由：手动在网关服务的配置文件中，配置好具体的映射以及相关配置。这种方式灵活性比较高，可以针对不同的服务进行个性化的定制。但是问题也比较突出，一旦需要修改就需要重启网关服务，对整体的运行产生影响。</li>
<li>动态路由：无需在配置文件中进行配置，利用 Spring Cloud 以及生态组件提供的动态能力，动态生成路由。这种方式可能对于服务的个性化定制不够方便，但是对于整体的运行维护来说性价比更高。</li>
</ul>
<p>Dante Cloud 默认采用的就是动态路由方式，只要服务正常启动并且成功注册到服务注册中心，通过服务名就可以实现请求的转发，无需其它的额外配置。当然，如果你有特殊的</p>
<h2>[二]网关轻量级鉴权</h2>
<h3>[1]背景知识</h3>
<p>不管是微服务系统还是单体系统，API 接口的鉴权都是必不可少的安全保障措施。这里，我们不妨先对单体架构和微服务架构中，接口鉴权的相同点与不同点，做一个简单的分析。</p>
<blockquote>
<p>虽然，还没有详细介绍微服务架构的的鉴权逻辑，但是凭借我们个人的经验，完全可以做这个分析。</p>
</blockquote>
<ul>
<li>相同点：不管哪种架构，都会存在自己的接口（REST API），然后通过某种权限框架或者自研的权限体系进行鉴权。</li>
<li>不同点：单体架构就自身一个应用，自己的接口（REST API）自己来鉴权即可。微服务架构是由多个服务组合而成，也一定存在一个地方或者组件对接口（REST API）进行鉴权，但是具体放在哪里、怎么实现会有很大的不同。</li>
</ul>
<p>继续分析一下，即使没有微服务应用设计和开发经验，也可以想到，要么就是在一个服务中统一进行接口的鉴权，要么就是每个服务自己负责自身接口的接口的鉴权。</p>
<p>而实际的微服务架构鉴权设计中，常见的模式也就是两种：</p>
<ol>
<li>方案一，资源提供服务各自鉴权：这种模式下，对外提供接口的服务（OAuth 2 中的资源服务器），自己对自己的接口进行保护。</li>
<li>方案二，服务网关统一认证鉴权：微服务架构中一般都会有微服务网关，比如 Spring Cloud Gateway，作为请求的统一入口，来实现对接口的统一保护。因为是统一入口，那么在网管进行接口的鉴权，就是一个不二的选择。</li>
</ol>
<blockquote>
<p>方案二，就是很多产品宣传的 <code>网关统一鉴权</code></p>
</blockquote>
<h3>[2]方案选择</h3>
<p>Dante Cloud 的鉴权方案选择的是前面所说的 <code>方案一</code>，即由各个服务负责自身 REST 接口的鉴权。之所以选择这个方案，主要由以下几个原因：</p>
<ol>
<li>因为系统使用的是 Spring Security 生态，而 Spring Security 自身的机制和推荐方式，就是由各个服务自身负责自己接口的鉴权</li>
<li>个人观点认为：<code>网关统一鉴权</code> 并不是一个合理的方式
<ul>
<li>因为如果要实现网关统一鉴权，势必需要在网关服务中增加更多的管理功能，管理功能的增加就意味着关键数据在“网关”层面的暴露。这不仅弱化了网关保护措施的定位，反而需要更多保护措施来保护网关服务自身。</li>
<li>从架构角度考虑，<code>网关统一鉴权</code> 就像是将一个传统单体应用作为网关，网关之后又带了诸多无法保护自身接口安全的 Spring Boot 应用。</li>
<li><code>网关统一鉴权</code> ，网关服务不仅要肩负请求拦截、请求路由职责，还要肩负接口鉴权甚至数据鉴权以及权限管理职责，会增大网关服务的压力。</li>
</ul>
</li>
</ol>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>更详细阐述与解释，参见：<a href="/develop-guide/advance/authentication.html" target="_blank">《OAuth 2 中的鉴权和动态接口鉴权》</a></p>
</div>
<h3>[3]轻量级鉴权逻辑</h3>
<p>虽然，Dante Cloud 选择由各个服务自身进行接口的鉴权，但也不代表网关服务什么都不做，Dante Cloud 在网关服务中做了轻量级的鉴权处理。</p>
<p>在微服务架构中，除了个别特定 REST API 以外，剩余 REST API 的访问都需要携带 <code>Token</code> (OAuth2 协议中的 Access Token 或者自定义实现的 Token)。基于这一点考虑，Dante Cloud 在网关服务中增加了 <code>轻量级鉴权</code>。</p>
<p>网关服务 <code>轻量级鉴权</code>，就是网关会对所有请求是否携带 Token 进行分析，除了网关服务中配置的特定请求外，只要是没有携带 Token 的请求都会被网关拦截。这样做的好处是：</p>
<ol>
<li>虽然还是由服务自身进行鉴权，但是通过网关的 <code>轻量级鉴权</code> 就可以帮助后端拦截大量无效的请求，从而降低其它服务的压力</li>
<li>轻量级鉴权，网关并不需要做太复杂的处理，仅需要在转发请求之前判断是否 Token 即可，对网关的性能影响极低。</li>
<li>保持了网关服务的独立和“干净”。网关仍旧是一个 <code>保护器</code> 的定位，并没有引入过多与保护不相干的业务或者管理逻辑</li>
</ol>
<h2>[三]动态文档聚合</h2>
<p>在面向 REST 接口的应用中，通常都会使用 Swagger 来生成接口文档，以方便开发时查看和调试。</p>
<p>微服务和单体系统不同。</p>
<ul>
<li>单体系统直接集成 Swagger 就可以查看所有接口信息。</li>
<li>微服务系统接口分布在不同的服务中。虽然也可以一个服务一个服务的查看，但是不够方便也不便于管理。</li>
</ul>
<p>所以 Dante Cloud 提供了动态文档聚合功能。</p>
<ul>
<li><strong>文档聚合</strong>：在 Gateway 服务聚合了所有服务的文档，可以统一进行查看</li>
<li><strong>动态聚合</strong>：业务服务在如果没有启动，一方面很难聚合该服务的文档，另一方免即使可以聚合也无法查看和调试接口。动态聚合就是可以根据服务的启动状态，动态聚合有效的服务文档。</li>
</ul>
<p>在浏览器中，直接访问 Gateway 服务中的 Swagger 就可以查看聚合的文档信息。默认的地址为：<a href="http://localhost:8847/swagger-ui.html" target="_blank" rel="noopener noreferrer">http://localhost:8847/swagger-ui.html</a>。显示效果如下：</p>
<figure><img src="/assets/image/infrastructure/swagger-message.png" alt="动态文档聚合" tabindex="0" loading="lazy"><figcaption>动态文档聚合</figcaption></figure>
<p>通过右上角就可以切换查看不同服务的接口文档，下图就是切换到 UAA 文档：</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>如果服务没有启动，右上角服务列表中将不会显示。</p>
</div>
<figure><img src="/assets/image/infrastructure/swagger-uaa.png" alt="UAA服务" tabindex="0" loading="lazy"><figcaption>UAA服务</figcaption></figure>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>文档聚合功能，建议仅在开发环境或者测试环境开启，正式环境不要开启，以减少安全风险。可以通过修改参数进行开启和关闭，参见：<a href="/properties/herodotus/platform.html" target="_blank">Swagger参数</a></p>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/infrastructure/swagger-message.png" type="image/png"/>
    </item>
    <item>
      <title>内置请求组件</title>
      <link>https://www.herodotus.cn/user-guide/infrastructure/http-client.html</link>
      <guid>https://www.herodotus.cn/user-guide/infrastructure/http-client.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">内置请求组件</source>
      <description>概述 补充中... 正在努力补充中，多点点 Star，会写得更快哦！ Gitee：https://gitee.com/dromara/dante-cloud Github：https://github.com/dromara/dante-cloud</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<div class="hint-container info">
<p class="hint-container-title">补充中...</p>
<p>正在努力补充中，多点点 Star，会写得更快哦！</p>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://gitee.com/dromara/dante-cloud</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://github.com/dromara/dante-cloud</a></li>
</ul>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>平台运行监控</title>
      <link>https://www.herodotus.cn/user-guide/infrastructure/monitor.html</link>
      <guid>https://www.herodotus.cn/user-guide/infrastructure/monitor.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">平台运行监控</source>
      <description>概述 补充中... 正在努力补充中，多点点 Star，会写得更快哦！ Gitee：https://gitee.com/dromara/dante-cloud Github：https://github.com/dromara/dante-cloud [一]服务调用链监控 [二]应用吞吐量监控 [三]熔断，降级监控 [四]微服务状态监控</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<div class="hint-container info">
<p class="hint-container-title">补充中...</p>
<p>正在努力补充中，多点点 Star，会写得更快哦！</p>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://gitee.com/dromara/dante-cloud</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://github.com/dromara/dante-cloud</a></li>
</ul>
</div>
<h2>[一]服务调用链监控</h2>
<h2>[二]应用吞吐量监控</h2>
<h2>[三]熔断，降级监控</h2>
<h2>[四]微服务状态监控</h2>
]]></content:encoded>
    </item>
    <item>
      <title>服务治理管控</title>
      <link>https://www.herodotus.cn/user-guide/infrastructure/service-manage.html</link>
      <guid>https://www.herodotus.cn/user-guide/infrastructure/service-manage.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">服务治理管控</source>
      <description>[一]服务注册与发现 服务发现和服务注册是微服务架构中的关键组件，用于解决服务实例的自动注册和发现问题。具体如下： 服务发现允许服务实例在启动时自动向中心注册表注册自己的信息，其他服务可以通过查询注册表找到需要通信的服务实例的网络位置。 服务注册让服务提供者方便地将自己的服务注册到系统中心，让使用方更容易找到服务 本系统可以使用 Nacos、Polar...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]服务注册与发现</h2>
<p><strong>服务发现</strong>和<strong>服务注册</strong>是微服务架构中的关键组件，用于解决服务实例的自动注册和发现问题。具体如下：</p>
<ul>
<li><strong>服务发现</strong>允许服务实例在启动时自动向中心注册表注册自己的信息，其他服务可以通过查询注册表找到需要通信的服务实例的网络位置。</li>
<li><strong>服务注册</strong>让服务提供者方便地将自己的服务注册到系统中心，让使用方更容易找到服务</li>
</ul>
<p>本系统可以使用 Nacos、PolarisMesh 和 Zookeeper 来实现服务的注册与发现。Nacos、PolarisMesh 和 Zookeeper 安装部署不会互相影响，但是同一时间只能以其中一个作为服务发现和注册中心，使用时需要根据具体需求，根据不同的环境更换服务代码底层依赖组件。具体切换方法，参见：<a href="/user-guide/infrastructure/switch-facility.html" target="_blank">【基础设施切换】</a></p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>所以在文档的 <a href="/get-started/prepare/environment.html" target="_blank">【本地运行部署概述】</a> 中提到。运行微服务版本的最小化环境中，必须要安装 Nacos 和 Kafka。就因为微服务必须要有服务注册与发现支持。</p>
<blockquote>
<p>在单体系统外，外挂一个可以提供 REST API 的应用，这个不叫微服务，只能叫做接口调用，就是因为没有 <code>服务注册与发现</code></p>
</blockquote>
</div>
<h2>[二]服务配置中心</h2>
<p><strong>配置中心</strong>的思路就是把项目中各种配置、各种参数、各种开关，全部都放到一个集中的地方进行统一管理，并提供一套标准的接口。当各个服务需要获取配置的时候，就来「配置中心」的接口拉取。当「配置中心」中的各种参数有更新的时候，也能通知到各个服务实时的过来同步最新的信息，使之动态更新。</p>
<p>本系统可以使用 Nacos、PolarisMesh 和 Zookeeper 来实作为统一的配置中心。Nacos、PolarisMesh 和 Zookeeper 安装部署不会互相影响，但是想要服务可以准确的读取配置，需要根据不同的环境更换服务代码底层依赖组件。使用时需要根据具体需求，进行切换配置。具体切换方法，参见：<a href="/user-guide/infrastructure/switch-facility.html" target="_blank">【基础设施切换】</a></p>
<h2>[三]服务负载均衡</h2>
<p>负载均衡（Load Balance，简称 LB）是高并发、高可用系统必不可少的关键组件，目标是 尽力将网络流量平均分发到多个服务器上，以提高系统整体的响应速度和可用性。</p>
<p>微服务架构最大的特色之一，就是可以利用服务的编排机制，动态启动服务的多个实例，来提升整体的响应性能。</p>
<p>传统的开发中，我们可以使用 Apache、Nginx 之类的软件，通过修改配置来实现多个应用实例的负载均衡。在微服务架构中，也需要通过负载均衡来解决服务多个实例的负载问题。但是，如果还是使用 Nginx 之类的软件实现负载均衡，一方面需要修改配置不够灵活，另一方面软件层面的负载无法与服务发现和注册进行联动。所以需要更加便捷和灵活的负载均衡机制。</p>
<p>在 Spring Cloud 套件中，早期可以使用 <code>Spring Cloud Netflix</code> 中的 <code>Ribbon</code> 组件来实现服务多实例的负载均衡。目前 <code>Spring Cloud Netflix</code> 已经停止维护，所以改为使用 <code>Spring Cloud Loadbalancer</code> 替换 <code>Ribbon</code> 来实现负载均衡。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Dante Cloud 已经全面使用 <code>Spring Cloud Loadbalancer</code> 支持各类内置组件的“软”负载聚合。同时实现了 Spring Cloud Loadbalancer 请求缓存机制，更有效的提升了接口访问有效性和运行稳定性。各类内置请求组件的融合，参见：<a href="/user-guide/infrastructure/http-client.html" target="_blank">【内置请求组件】</a> 章节</p>
<p>Dante Cloud 已经对负载均衡做了内置处理，常规情况下不需要再做额外的处理</p>
</div>
<h2>[四]服务熔断降级</h2>
<h3>[1]服务降级:系统有限的资源的合理协调</h3>
<ul>
<li><strong>概念</strong>：服务降级一般是指在服务器压力剧增的时候，根据实际业务使用情况以及流量，对一些服务和页面有策略的不处理或者用一种简单的方式进行处理，从而释放服务器资源的资源以保证核心业务的正常高效运行。</li>
<li><strong>原因</strong>：服务器的资源是有限的，而请求是无限的。在用户使用即并发高峰期，会影响整体服务的性能，严重的话会导致宕机，以至于某些重要服务不可用。故高峰期为了保证核心功能服务的可用性，就需要对某些服务降级处理。可以理解为舍小保大</li>
<li><strong>应用场景</strong>：多用于微服务架构中，一般当整个微服务架构整体的负载超出了预设的上限阈值（和服务器的配置性能有关系），或者即将到来的流量预计会超过预设的阈值时（比如双11、6.18等活动或者秒杀活动）</li>
</ul>
<blockquote>
<p>服务降级是从整个系统的负荷情况出发和考虑的，对某些负荷会比较高的情况，为了预防某些功能（业务场景）出现负荷过载或者响应慢的情况，在其内部暂时舍弃对一些非核心的接口和数据的请求，而直接返回一个提前准备好的fallback（退路）错误处理信息。这样，虽然提供的是一个有损的服务，但却保证了整个系统的稳定性和可用性。</p>
</blockquote>
<p>需要考虑的问题：</p>
<ul>
<li>区分那些服务为核心？那些非核心</li>
<li>降级策略（处理方式，一般指如何给用户友好的提示或者操作）</li>
<li>自动降级还是手动降</li>
</ul>
<h3>[2]服务熔断：应对雪崩效应的链路自我保护机制。可看作降级的特殊情况</h3>
<ul>
<li><strong>概念</strong>：应对微服务雪崩效应的一种链路保护机制，类似股市、保险丝</li>
<li><strong>原因</strong>：微服务之间的数据交互是通过远程调用来完成的。服务A调用服务，服务B调用服务c，某一时间链路上对服务C的调用响应时间过长或者服务C不可用，随着时间的增长，对服务C的调用也越来越多，然后服务C崩溃了，但是链路调用还在，对服务B的调用也在持续增多，然后服务B崩溃，随之A也崩溃，导致雪崩效应</li>
</ul>
<blockquote>
<p>服务熔断是应对雪崩效应的一种微服务链路保护机制。例如在高压电路中，如果某个地方的电压过高，熔断器就会熔断，对电路进行保护。同样，在微服务架构中，熔断机制也是起着类似的作用。当调用链路的某个微服务不可用或者响应时间太长时，会进行服务熔断，不再有该节点微服务的调用，快速返回错误的响应信息。当检测到该节点微服务调用响应正常后，恢复调用链路。<br>
服务熔断的作用类似于我们家用的保险丝，当某服务出现不可用或响应超时的情况时，为了防止整个系统出现雪崩，暂时停止对该服务的调用。</p>
</blockquote>
<ul>
<li><strong>应用场景</strong>：微服务架构中，多个微服务相互调用出使用</li>
</ul>
<p>需要考虑问题：</p>
<ul>
<li>如何所依赖的服务对象不稳定</li>
<li>失败之后如何快速恢复依赖对象，如何探知依赖对象是否恢复</li>
</ul>
<h3>[3]服务降级和服务熔断区别</h3>
<ul>
<li><strong>触发原因不一样</strong>：服务熔断由链路上某个服务引起的，服务降级是从整体的负载考虑</li>
<li><strong>管理目标层次不一样</strong>：服务熔断是一个框架层次的处理，服务降级是业务层次的处理</li>
<li><strong>实现方式不一样</strong>：服务熔断一般是自我熔断恢复，服务降级相当于人工控制</li>
<li><strong>触发原因不一样</strong>：服务熔断一般是某个服务（下游服务）故障引起，而服务降级一般是从整体负荷考虑；</li>
</ul>
<ol>
<li>服务熔断是应对系统服务雪崩的一种保险措施，给出的一种特殊降级措施。而服务降级则是更加宽泛的概念，主要是对系统整体资源的合理分配以应对压力。</li>
<li>服务熔断是服务降级的一种特殊情况，他是防止服务雪崩而采取的措施。系统发生异常或者延迟或者流量太大，都会触发该服务的服务熔断措施，链路熔断，返回兜底方法。这是对局部的一种保险措施。</li>
<li>服务降级是对系统整体资源的合理分配。区分核心服务和非核心服务。对某个服务的访问延迟时间、异常等情况做出预估并给出兜底方法。这是一种全局性的考量，对系统整体负荷进行管理。</li>
</ol>
<p>总结：</p>
<ul>
<li>限流：限制并发的请求访问量，超过阈值则拒绝；</li>
<li>降级：服务分优先级，牺牲非核心服务（不可用），保证核心服务稳定；从整体负荷考虑；</li>
<li>熔断：依赖的下游服务故障触发熔断，避免引发本系统崩溃；系统自动执行和恢复</li>
</ul>
<p>在 Spring Cloud 框架里，熔断机制通过 <code>Hystrix</code> 实现。<code>Hystrix</code> 会监控微服务间调用的状况，当失败的调用到一定阈值，缺省是5秒内20次调用失败，就会启动熔断机制。</p>
<p>因为 <code>Hystrix</code> 已经停止维护。Dante Cloud 中采用的是 <code>Sentinel</code> 和 <code>PolarisMesh</code> 实现和管理服务的降级和熔断。</p>
<p><code>Sentinel</code> 和 <code>PolarisMesh</code> 不会同时生效，使用时需要根据具体需求，进行切换配置。具体切换方法，参见：<a href="/user-guide/infrastructure/switch-facility.html" target="_blank">【基础设施切换】</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>消息队列切换</title>
      <link>https://www.herodotus.cn/user-guide/infrastructure/switch-mq.html</link>
      <guid>https://www.herodotus.cn/user-guide/infrastructure/switch-mq.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">消息队列切换</source>
      <description>概述 补充中... 正在努力补充中，多点点 Star，会写得更快哦！ Gitee：https://gitee.com/dromara/dante-cloud Github：https://github.com/dromara/dante-cloud</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<div class="hint-container info">
<p class="hint-container-title">补充中...</p>
<p>正在努力补充中，多点点 Star，会写得更快哦！</p>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://gitee.com/dromara/dante-cloud</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://github.com/dromara/dante-cloud</a></li>
</ul>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>工作流程引擎</title>
      <link>https://www.herodotus.cn/user-guide/infrastructure/workflow.html</link>
      <guid>https://www.herodotus.cn/user-guide/infrastructure/workflow.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">工作流程引擎</source>
      <description>概述 补充中... 正在努力补充中，多点点 Star，会写得更快哦！ Gitee：https://gitee.com/dromara/dante-cloud Github：https://github.com/dromara/dante-cloud</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<div class="hint-container info">
<p class="hint-container-title">补充中...</p>
<p>正在努力补充中，多点点 Star，会写得更快哦！</p>
<ul>
<li><strong>Gitee</strong>：<a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://gitee.com/dromara/dante-cloud</a></li>
<li><strong>Github</strong>：<a href="https://github.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">https://github.com/dromara/dante-cloud</a></li>
</ul>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>应用安全合规</title>
      <link>https://www.herodotus.cn/user-guide/security/compliance.html</link>
      <guid>https://www.herodotus.cn/user-guide/security/compliance.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">应用安全合规</source>
      <description>[一]整体测试结果 Dante Cloud 已通过由第三方公司进行的软件出厂安全测试和国家三级等保测评。 安全测试整体结果安全测试整体结果 说明 因涉及企业和项目信息，所以不便于再此展示具体测试报告。 [二]Web漏洞检测 采用相关扫描工具对应用系统进行漏洞扫描，根据漏洞扫描结果对存在的漏洞进行风险评估。初测共发现 2 个 Web 漏洞，其中高风险漏洞...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]整体测试结果</h2>
<p>Dante Cloud 已通过由第三方公司进行的软件出厂安全测试和国家三级等保测评。</p>
<figure><img src="/assets/image/main/testing.png" alt="安全测试整体结果" tabindex="0" loading="lazy"><figcaption>安全测试整体结果</figcaption></figure>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>因涉及企业和项目信息，所以不便于再此展示具体测试报告。</p>
</div>
<h2>[二]Web漏洞检测</h2>
<p>采用相关扫描工具对应用系统进行漏洞扫描，根据漏洞扫描结果对存在的漏洞进行风险评估。初测共发现 2 个 Web 漏洞，其中高风险漏洞有 0 个，中风险漏洞有 1 个，低风险漏洞有 1 个。经过整改并复测后，高、中风险漏洞已整改完毕。</p>
<h2>[三]应用安全合规</h2>
<p>针对等级保护三级系统的防护要求，对于应用安全涉及的“身份鉴别”、“访问控制”、“安全审计”、“剩余信息保护”、“通信完整性”、“通信保密性”以及“资源控制”等控制点进行</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>具体内容参见国标【GB/T 28448-2019】软件相关部分。官方原文<a href="https://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcno=7E736CDF4502B6FF1258DD250AA3EC8C" target="_blank" rel="noopener noreferrer">【地址】</a></p>
</div>
<p>| 检查类别     | 检查内容                                                                                                           | 检查结果 |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/main/testing.png" type="image/png"/>
    </item>
    <item>
      <title>两种令牌格式</title>
      <link>https://www.herodotus.cn/user-guide/security/token.html</link>
      <guid>https://www.herodotus.cn/user-guide/security/token.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">两种令牌格式</source>
      <description>概述 在 OAuth2 体系中认证通过后返回的令牌信息分为两大类：不透明令牌（Opaque token） 和 JSON Web Token (JWT) 。 [1]什么是不透明令牌 不透明令牌是一种访问令牌，正如其名字所示，对于客户端或任何外部方是不可透明的。这意味着令牌本身不携带关于用户或授权的任何可读信息。 当你收到不透明令牌时，它通常显示为一个看似...</description>
      <pubDate>Wed, 25 Dec 2024 15:03:06 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>在 OAuth2 体系中认证通过后返回的令牌信息分为两大类：不透明令牌（Opaque token） 和 JSON Web Token (JWT) 。</p>
<h3>[1]什么是不透明令牌</h3>
<p>不透明令牌是一种访问令牌，正如其名字所示，对于客户端或任何外部方是不可透明的。这意味着令牌本身不携带关于用户或授权的任何可读信息。</p>
<p>当你收到不透明令牌时，它通常显示为一个看似随机的字符字符串，尝试解码它将不会产生有意义的数据。</p>
<p>这里有一个不透明令牌的例子：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">M-oxIny1RfaFbmjMX54L8Pl-KQEPeQvF6awzjWFA3iq</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>由于令牌的实际内容仅为发行它的授权服务器所知，因此，要验证不透明令牌，客户端必须将其返回给服务器，然后服务器验证其真实性并确定相关的权限。这种方法确保敏感信息保持隐藏，提供了一层额外的安全性，但也需要额外的服务器请求来验证令牌。</p>
<h4>优点</h4>
<ul>
<li><strong>安全</strong>：不透明令牌不暴露任何敏感信息给客户端。令牌的内容仅为授权服务器所知。</li>
<li><strong>可撤消</strong>：由于令牌存储在服务器上，且唯一验证方法是通过授权服务器上的 <code>Introspection</code> 端点，服务器可以轻松撤消令牌以防止未经授权的访问。</li>
<li><strong>小尺寸</strong>：不透明令牌通常比JWT短小，这对于性能和存储考察有利。</li>
</ul>
<h4>缺点</h4>
<ul>
<li><strong>有状态</strong>：不透明令牌要求授权服务器维持状态以验证令牌，这可能引入附加的复杂性和开销。</li>
<li><strong>性能</strong>：验证令牌所需的额外服务器请求可能会影响性能，特别是在高流量场景中。</li>
</ul>
<h3>[2]什么是 JWT</h3>
<p>与不透明令牌相反，JWT（JSON Web Token）是一种自包含的、无状态的令牌，承载信息以结构化和可读格式表达。</p>
<p>JWT 由三部分组成：<code>header</code>，<code>payload</code>，和<code>signature</code>，每个部分都以<code>Base64</code>编码。</p>
<p>这里有一个 JWT 的例子：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ul>
<li><code>header</code>：包含有关令牌类型和用于签名的算法的信息。例如，<code>{&quot;alg&quot;: &quot;HS256&quot;, &quot;typ&quot;: &quot;JWT&quot;}</code>。</li>
<li><code>payload</code>：区段包含声明—关于用户或授权的信息片段，如用户ID、到期时间和范围。由于这些数据是编码的但未加密，任何拥有令牌的人都可以解码以查看声明，但无法在不使签名失效的情况下更改它。根据规范和授权服务器配置，可以在 <code>payload</code>中包括各种声明，这赋予令牌其自包含的特性。例如，<code>{&quot;sub&quot;: &quot;1234567890&quot;, &quot;name&quot;: &quot;John Doe&quot;, &quot;iat&quot;: 1516239022}</code>。</li>
<li><code>signature</code>：是通过使用指定的算法结合<code>header</code>，<code>payload</code>和密钥来生成的。这个签名用于验证令牌的完整性并确保它没有被篡改。</li>
</ul>
<p>由于它们可以由客户端或任何服务在本地验证，而无需与授权服务器互动，JWT得到了广泛使用。这使得JWT对于分布式系统特别高效，其中多个服务可能需要独立验证令牌的真伪。然而，这种便利也带来了确保令牌声明未过度暴露的责任，因为它们对任何有权访问令牌的人都是可见的。此外，JWT 通常是短命的，到期时间包含在令牌的声明中，以确保令牌不会无限期有效。</p>
<h4>优点</h4>
<ul>
<li><strong>无状态</strong>：JWT 是自包含的，无需服务器端状态即可验证。</li>
<li><strong>跨服务兼容性</strong>：JWT 可以轻松在不同服务之间共享和验证，使其对于分布式系统理想。</li>
<li><strong>可扩展</strong>：JWT 的 <code>payload</code> 可以包含自定义声明，允许灵活的授权和信息共享。</li>
<li><strong>标准</strong>：JWT 令牌遵循一个良好定义的标准（<a href="https://datatracker.ietf.org/doc/html/rfc7662" target="_blank" rel="noopener noreferrer">RFC 7519</a>），使其广泛支持和可互操作。</li>
</ul>
<h4>缺点</h4>
<ul>
<li><strong>暴露</strong>：JWT 中的声明对任何拥有该令牌的人都是可见的，因此不应在 <code>payload</code>中包括敏感信息。</li>
<li><strong>大尺寸</strong>：由于携带附加的信息，JWT 可以比不透明令牌更大，这可以影响性能和存储考察。JWT 令牌中的声明应保持在最小以减少令牌大小。</li>
<li><strong>撤消复杂性</strong>：由于JWT是无状态的，通常在一段时间内有效，且没有内置的令牌撤消机制，这意味着被危及的令牌可能会在失效前一直有效。</li>
</ul>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>实际上，JWT 令牌是一个完整的JSON对象，使用 base64 编码</p>
<p>需要明确一点：在不借助外力的情况下，让 JWT 失效的唯一途径就是等 token 自己过期，无法做到主动让 JWT 失效。非要让 JWT 有主动失效的功能只能借助外力，即在服务端存储 JWT 的状态，在请求时添加判断逻辑，这个与 JWT 的无状态化、去中心化特性是矛盾的。</p>
</div>
<h3>[3]不透明令牌验证</h3>
<p>不透明访问令牌通过将其发送回授权服务器进行验证。授权服务器维护已发行令牌的状态，并可以根据其内部存储确定令牌的有效性。</p>
<figure><img src="/assets/image/infrastructure/opaque-token-validate.svg" alt="不透明令牌验证" tabindex="0" loading="lazy"><figcaption>不透明令牌验证</figcaption></figure>
<ol>
<li>客户端向授权服务器请求访问令牌。</li>
<li>授权服务器颁发不透明令牌。</li>
<li>客户端在请求头中发送带有不透明令牌的资源访问请求。</li>
<li>资源提供者向授权服务器发送令牌内省请求以验证令牌。</li>
<li>授权服务器响应令牌信息。</li>
</ol>
<h3>[4]JWT 访问令牌验证（离线）</h3>
<p>JWT 访问令牌可以由客户端或任何拥有令牌公钥的服务离线验证。</p>
<figure><img src="/assets/image/infrastructure/jwt-token-validate.svg" alt="JWT 访问令牌验证" tabindex="0" loading="lazy"><figcaption>JWT 访问令牌验证</figcaption></figure>
<ol>
<li>资源提供者预先从 OpenID Connect (OIDC) 发现 (Discovery) 获取授权服务器的公钥。公钥用于验证令牌的签名并确保其完整性。</li>
<li>客户端向授权服务器请求访问令牌。</li>
<li>授权服务器颁发 JWT 令牌。</li>
<li>客户端在请求头中发送带有 JWT 令牌的资源访问请求。</li>
<li>资源提供者使用从授权服务器获得的公钥解码并验证 JWT 令牌。</li>
<li>资源提供者根据令牌的有效性授予访问权限。</li>
</ol>
<h2>[二]Dante Cloud 中的令牌</h2>
<p>Dante Cloud 完全遵循 OAuth2.1 开发，所以支持 JWT 和 Opaque 两种 Token。在实际应用中，根据使用场景的不同 JWT 和 Opaque 两种 Token 应用也会有所区别。</p>
<h3>[1]AccessToken 和 RefreshToken</h3>
<p><strong>访问令牌（Access Token）和刷新令牌（Refresh Token）</strong>是保障用户安全访问资源的关键组件。它们在OAuth2授权框架中使用，以管理用户的登录状态和访问权限。</p>
<ul>
<li><strong>访问令牌（Access Token）</strong>：访问令牌是一个短期的凭证，用于在用户和资源服务器之间进行身份验证。当用户成功登录后，授权服务器会发放访问令牌给用户，用户随后可以使用此令牌来请求资源服务器上的受保护资源。访问令牌具有有限的有效期，这是为了减少令牌被盗用的风险。一旦访问令牌过期，用户就无法再使用它来访问资源。</li>
<li><strong>刷新令牌（Refresh Token）</strong>：刷新令牌用于在访问令牌过期时获取新的访问令牌，而无需用户重新登录。这提供了更好的用户体验，因为用户不需要频繁地输入登录凭证。刷新令牌通常具有比访问令牌更长的有效期，并且只能用于从授权服务器获取新的访问令牌。</li>
</ul>
<p>Dante Cloud 中 AccessToken 和 RefreshToken 均支持 JWT 和 Opaque 两种 Token 类型，可以通过配置进行变更和指定。</p>
<p>变更 Token 类型的方法，主要有以下两步；</p>
<h4>第一步修改系统配置，主要在 UAA 服务中进行修改</h4>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">herodotus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  oauth2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    authorization</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      token-format</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">jwt</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>更多配置，参见：<a href="/properties/herodotus/authorization.html" target="_blank">【资源服务】</a></p>
</blockquote>
<h4>第二步修改数据表中的数据</h4>
<p>找到 <code>oauth_application</code> 和 <code>oauth2_registered_client</code> 手动修改其中的数据：</p>
<ol>
<li>修改 <code>oauth_application</code> 表中 <code>access_token_format</code> 字段的值。<code>1</code> 表示为 Opaque Token，<code>2</code> 表示为 JWT Token。</li>
<li>修改 <code>oauth2_registered_client</code> 表中 <code>token_settings</code> 字段中，JSON 属性 <code>settings.token.access-token-format</code> 对应的值。<code>reference</code> 表示为 Opaque Token，<code>self-contained</code> 表示为 JWT Token。</li>
</ol>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p><strong>为什么采用这么“笨拙”的方式切换Token？</strong></p>
<p>这个问题主要是受到使用的底层基础组件的局限所致。</p>
<p>Token 类型在系统中主要用于两个地方：授权服务器对Token的签发和资源服务器对Token的验证。</p>
<ul>
<li>授权服务器对Token的签发：是由 <code>Spring Authorization Server</code> 组件负责。通过修改 TokenSettings 中的 <code>OAccess-token-format</code> 就可以变更所签发的 Token 类型。这个是没有任何问题的。</li>
<li>资源服务器对Token的验证：是由 <code>Spring Security</code> 组件负责。根据你配置的 JWT 或 Opaque 策略对 Token 进行校验，但是 JWT 或 Opaque 相关配置不能同时存在</li>
</ul>
<p>这就导致授权服务器对Token的签发和资源服务器对Token的验证出现了矛盾点。参见 ISSUE：<a href="https://github.com/spring-projects/spring-authorization-server/issues/1211" target="_blank" rel="noopener noreferrer">#1211</a></p>
</div>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>正是因为 JWT Token 和 Opaque Token 本质上的区别，所以在 Dante Cloud 中，如果您选择使用 JWT Token，那么很多合规性功能将无法使用。因此，Dante Cloud 中默认使用的是 Opaque Token。</p>
</div>
<h3>[2]IdToken</h3>
<p>在 OIDC 中，ID Token 是一个包含用户信息的 JWT，用于认证用户。通常与访问令牌一起颁发，ID 令牌允许客户端验证用户的身份。例如：</p>
<div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-json"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// ID 令牌的解码有效负载</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "iss"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"&#x3C;https://auth.wiki>"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "sub"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"1234567890"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "aud"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"client_id"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "exp"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1630368000</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "name"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"John Doe"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "email"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"john.doe@mail.com"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "picture"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"&#x3C;https://example.com/johndoe.jpg>"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>客户端可以验证 ID 令牌以确保用户的身份，并提取用户信息以用于个性化或授权目的。ID 令牌仅供一次性使用，不应用于 API 资源授权。</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>IdToken 默认为 JWT 格式 Token，不支持 Opaque 类型。而且从使用用途和使用便捷型角度看，IdToken 也无需支持 Opaque。</p>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/infrastructure/opaque-token-validate.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>数据存储切换(企业版)</title>
      <link>https://www.herodotus.cn/user-guide/persistence/switch-persistence.html</link>
      <guid>https://www.herodotus.cn/user-guide/persistence/switch-persistence.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">数据存储切换(企业版)</source>
      <description>概述 对于常规的应用系统来说，使用一个关系型数据库作为数据存储介质，就可以满足绝大多数应用功能需求。对于某些特殊应用场景来说，比如：更快的查询速度、海量时序数据的存储和查询等，使用关系型数据库虽然也能实现相应的功能，但受数据库软件自身机制的局限实际应用效果却差强人意。 在开发过程中我们会先选择合适的、不同的数据存储介质，比如：NOSQL、TMDB 等，...</description>
      <pubDate>Wed, 25 Dec 2024 03:35:13 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>对于常规的应用系统来说，使用一个关系型数据库作为数据存储介质，就可以满足绝大多数应用功能需求。对于某些特殊应用场景来说，比如：更快的查询速度、海量时序数据的存储和查询等，使用关系型数据库虽然也能实现相应的功能，但受数据库软件自身机制的局限实际应用效果却差强人意。</p>
<p>在开发过程中我们会先选择合适的、不同的数据存储介质，比如：NOSQL、TMDB 等，然后将相应的功能提取出单独的模块，在把原来使用关系型数据库的代码变更为其它异构存储的代码。这种方式通常需要对原有功能或模块进行大量修改、拆分以及数据的迁移。想要恢复为原有数据存储，仍旧需要投入大量时间和精力进行修改。</p>
<p>Dante Cloud 系统平台中也存在这种类型的数据，例如：认证（Token数据）核心数据。默认是使用关系型数据库进行认证数据的存储，加上有多级缓存的支撑，对于常规应用场景式完全可以满足的。对于一些需要高性能应用的场景，特别是数据查询频次高、并发性能要求高的场景，还是会受到关系型数据库自身机制的局限导致难以满足需求。</p>
<p>为了解决这个问题，自 Dante Cloud v3.4.1.0 版本起，新增了系统核心关键数据的多种异构数据源切换功能。可以在不修改代码的情况下，通过修改系统配置，快速实现系统关键性数据底层存储介质的切换。</p>
<h2>[一]Spring Authorization Server 核心认证数据存储切换</h2>
<p><code>Spring Authorization Server</code> 是 Dante Cloud 核心的认证安全组件。默认的是采用关系型数据库存储和管理 Token 以及相关的认证信息。</p>
<ul>
<li>如果您是使用 JWT 自包含 Token，认证服务进需要负责签发 Token，无需在使用时到服务端进行 Token 校验。服务端的压力较小。</li>
<li>如果您需要更高级别的安全保证，使用 Opaque 不透明 Token，每个请求都需要到服务端进行 Token 校验。这就需要服务端产生大量的 Token 查询。</li>
</ul>
<p>NOSQL 类型数据存储相比关系型数据库，在查询效率以及水平扩展方面有其自身独特的优势。</p>
<p>Dante Cloud 对 <code>Spring Authorization Server</code> 核心认证数据的存储新增了 NOSQL 类型数据存储的支持，而且支持无需修改代码通过修改配置参数即可实现。</p>
<h3>[1]使用方法</h3>
<p>目前，Dante Cloud 新增了 Redis 和 MongoDB 两种 NOSQL 存储介质存储 <code>Spring Authorization Server</code> 核心认证数据。原有基于 JPA 实现的关系型数据库存储方式，并没有抛弃仍旧保留。</p>
<blockquote>
<p>默认使用的是 JPA 方式存储，您可以根据需要修改为 Redis 或者 MongoDB。</p>
</blockquote>
<h4>Redis</h4>
<p>想要修改为使用 Redis 作为 <code>Spring Authorization Server</code> 核心认证数据的存储介质。修改方式仅需要在 UAA 服务对应的配置文件中，增加以下配置：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">herodotus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  oauth2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    datastore</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      sas</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">redis</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>MongoDB</h4>
<p>想要修改为使用 MongoDB 作为 <code>Spring Authorization Server</code> 核心认证数据的存储介质。</p>
<p>首先，要在 UAA 服务中，添加 <code>data-mongodb-spring-boot-starter</code> 以开启 MongoDB 支持</p>
<p>然后，在配置文件合适的位置中，添加 Spring Data MongoDB 相关的配置，以保证服务可以正常访问 MongoDB，参考配置如下：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  data</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    mongodb</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">192.168.101.10</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      database</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-reactive</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      username</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      password</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最后，需要在 UAA 服务对应的配置文件中，增加以下配置：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">herodotus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  oauth2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    datastore</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      sas</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">mongodb</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<ol>
<li>注意此种数据存储介质，并不是整体切换整个系统的存储介质，仅仅只是切换<code>认证</code>相关核心数据的存储介质（毕竟不是所有查询都需要高性能），整个系统还是以关系型数据库为主</li>
<li>如果使用 Redis 作为存储介质，因为 Spring Data Redis 不支持根据时间类型的查询，所以【账号多点登录踢出】功能使用受限</li>
</ol>
</div>
<h3>[2]数据初始化</h3>
<p>基于 OAuth2 的 <code>认证</code> 功能依赖于关键的 <code>client</code> 相关数据信息，即数据表 <code>oauth2_registered_client</code> 中的数据，没有这个信息即使系统可以正常运行，登录和认证相关功能都会出错。</p>
<p>系统默认使用关系型数据库，也提供相关数据初始化脚本，在部署搭建系统时就会正常初始化数据，所以不会存在任何问题。</p>
<p>变更为使用 Redis 或 MongoDB 作为<code>认证</code>相关核心数据的存储介质后，Redis 或 MongoDB 库中并没有相关数据初始化，所以就需要进行数据初始化的操作。</p>
<p>为了简化初始化的操作，系统提供了 Redis 或 MongoDB 自动初始化的支持。当存储介质变更为 Redis 或 MongoDB 后，服务在启动时会自动查询存储介质中是否有 OAuth2 关键的 client 数据存在，如果不存在就从关系数据库中查询该信息，然后初始化到 Redis 或 MongoDB 中。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>从前面的描述中可以看出，系统默认初始化实现依赖于关系型数据库中的数据。所以初次安装部署时，还是建议默认选用关系型数据库作为<code>认证</code>相关核心数据的存储介质，后续根据实际使用需要再行修改。否则，就只能自己手动进行数据的初始化工作。</p>
</div>
<h3>[3]自定义扩展</h3>
<p>如果系统当前提供的 JPA、Redis、MongoDB 三种存储方式均不能满足你的需求，那么你可以参考 <code>datastore-module-sas-mongodb</code>、<code>datastore-module-sas-redis</code> 和 <code>datastore-module-sas-jpa</code>三个模块中的代码，自行进行扩展。</p>
<p>三个模块虽然看着多，核心的关键点就是实现以下四个接口即可</p>
<ul>
<li><code>cn.herodotus.stirrup.oauth2.core.sas.definition.EnhanceAuthenticationManager</code></li>
<li><code>org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService</code></li>
<li><code>cn.herodotus.stirrup.oauth2.core.sas.definition.EnhanceOAuth2AuthorizationService</code></li>
<li><code>org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository</code></li>
</ul>
<h2>[二]系统审计数据存储切换</h2>
<p>Dante Cloud 系统中内置了用户使用记录以及 REST 接口审计功能。</p>
<ul>
<li><strong>用户使用记录</strong>：会记录用户登入和登出系统信息。这通常为应用安全合规测评要求功能，并不是系统必要应用功能。</li>
<li><strong>REST 接口审计</strong>：会记录具体哪个接口，在什么时间，由哪个用户调用使用。该功能应用安全合规测评的一种补充。</li>
</ul>
<blockquote>
<p>传统单体系统一般是记录用户的实际操作，主要是界面操作。微服务系统是面向 REST 接口的应用系统，一个操作也许会涉及多个接口。所以，本系统采用的方式是直接记录 REST 接口使用的记录。</p>
</blockquote>
<p>既然是记录数据，常规的做法就是直接使用关系数据库进行存储。Dante Cloud 中 <strong>用户使用记录</strong> 和 <strong>REST 接口审计</strong> 最初的实现就是使用关系数据库。但是，<strong>用户使用记录</strong> 和 <strong>REST 接口审计</strong> 功能记录的数据属于 <code>日志</code> 型数据，如果开启了相关功能，随着系统的使用，数据会越积越多，不仅无法有效利用数据，可能还会影响系统的使用。</p>
<p>为此，Dante Cloud 新增了针对 <strong>用户使用记录</strong> 和 <strong>REST 接口审计</strong> 功能的时序数据存储支持。可根据你的实际需求，通过修改配置，变换实际使用的存储类型。</p>
<div class="hint-container info">
<p class="hint-container-title">介绍</p>
<p>时序数据库和关系型数据库在数据模型、存储方式、查询性能、适用场景等方面存在明显的差异。时序数据库适用于大规模时间序列数据的高效存储和查询，而关系型数据库更适合处理多种类型和复杂关系的数据。根据自身需求，选择合适的数据库类型可以提高数据处理效率和开发维护成本。</p>
</div>
<h3>[1]使用方法</h3>
<p>目前，Dante Cloud 新增了 Cassandra 存储介质存储 <strong>用户使用记录</strong> 和 <strong>REST 接口审计</strong>数据。原有基于 JPA 实现的关系型数据库存储方式，并没有抛弃仍旧保留。</p>
<blockquote>
<p>默认使用的是 JPA 方式存储，您可以根据需要修改为 Cassandra。</p>
</blockquote>
<p>想要修改为使用 Cassandra 作为 <strong>用户使用记录</strong> 和 <strong>REST 接口审计</strong> 功能数据的存储介质。</p>
<p>首先，要在 UAA 服务中，添加 <code>data-cassandra-spring-boot-starter</code> 以开启 Cassandra 支持</p>
<p>然后，在配置文件合适的位置中，添加 Spring Data Cassandra 相关的配置，以保证服务可以正常访问 Cassandra</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  cassandra</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    keyspace-name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    username</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    password</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    local-datacenter</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">datacenter1</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    schema-action</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">create_if_not_exists</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    request</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      timeout</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">10s</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>最后，需要在 UAA 服务对应的配置文件中，增加以下配置：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">herodotus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  oauth2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    datastore</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      sys</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">cassandra</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>注意此种数据存储介质，并不是整体切换整个系统的存储介质，仅仅只是切换<code>认证</code>相关核心数据的存储介质（毕竟不是所有查询都需要高性能），整个系统还是以关系型数据库为主</p>
</div>
<h3>[2]自定义扩展</h3>
<p>如果系统当前提供的 JPA、Cassandra 两种存储方式均不能满足你的需求，那么你可以参考 <code>datastore-module-sys-cassandra</code> 和 <code>datastore-module-sys-jpa</code> 两个模块中的代码，自行进行扩展。</p>
<p>核心的关键点就是实现以下两个接口即可</p>
<ul>
<li><code>cn.herodotus.stirrup.datastore.core.definition.HerodotusInterfaceAuditService</code></li>
<li><code>cn.herodotus.stirrup.datastore.core.definition.HerodotusUserLoggingService</code></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Dante Cloud</title>
      <link>https://www.herodotus.cn/breaking/dante-cloud.html</link>
      <guid>https://www.herodotus.cn/breaking/dante-cloud.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">Dante Cloud</source>
      <description>v4.0.6.0 Dante Cloud v4.0.6.0 版本开始使用 Nacos v3.2.1。改版本存在破坏性升级，特别是数据库数据模型存在变化，因此需要重新建库，导入配置。 v4.0.5.2 自 v4.0.5.2 版本起，微服务版本所有服务以及单体版本的配置文件格式，从 *.yml 统一修改为官方更为推荐的 *.yaml v4.0.5.1 v4...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v4.0.6.0</h2>
<p>Dante Cloud v4.0.6.0 版本开始使用 Nacos v3.2.1。改版本存在破坏性升级，特别是数据库数据模型存在变化，因此需要重新建库，导入配置。</p>
<h2>v4.0.5.2</h2>
<p>自 v4.0.5.2 版本起，微服务版本所有服务以及单体版本的配置文件格式，从 <code>*.yml</code> 统一修改为官方更为推荐的 <code>*.yaml</code></p>
<h2>v4.0.5.1</h2>
<p>v4.0.5.1 开始使用全新的 Nacos 3.2.0 版本，该版本与之前版本不兼容。需要重新建库重新导入配置。</p>
<h2>v4.0.3.0</h2>
<h3>一、Dante Cloud 4.X 新特性</h3>
<h4>[1] 基础依赖全面升级</h4>
<ol>
<li>Dante Cloud 4.X 使用 JDK 版本已经全面升级至 25，充分发挥虚拟线程的能力</li>
<li>Spring Boot 版本升级至 4.0.3</li>
<li>Spring Cloud 版本升级至 2025.1.1</li>
<li>Spring Cloud Alibaba 版本升级至 2025.1.0.0</li>
<li>全面迁移至 Jackson 3，提升序列化/反序列化性能与安全性</li>
</ol>
<h4>[2] 全新模块架构</h4>
<ol>
<li>基于 Spring Boot 4 “New Moduler Design” 体系，重构整个核心组件库模块，适配新体系下的模块划分和使用，进一步强化模块的内聚性和独立使用能力</li>
<li>工程 maven dependencies 体系，由原来“继承”模式变更为 <code>import</code> 模式，增强工程的独立性以及模块引入的便捷性，避免在多工程场景下各个工程只能串行使用效率低下问题</li>
</ol>
<h4>[3] API 版本控制</h4>
<ol>
<li>新增 REST API 版本控制能力，支持请求头、请求路径和请求参数三种 API 版本设置模式，可通过配置修改。默认已开启基于请求头的版本控制。</li>
<li>新增支持带 API 版本的 REST 接口动态接口鉴权能力，接口权限实时在线变更，不同版本接口可独立设置不同权限</li>
</ol>
<h4>[4] 改进 gRPC 通信</h4>
<ol>
<li>使用 Spring gRPC 组件替换原有 net.devh grpc-srping-boot-starter，并扩展 gRPC 的动态服务发现能力，让 gRPC 与系统的融合更加成体系化</li>
<li>新增基于 Spring Security 的 gRPC 方法动态鉴权能力，与 REST 接口权限体系保持一致，让 gRPC 通信更加安全</li>
</ol>
<h4>[5] 安全防护增强</h4>
<ol>
<li>全新实现 PKI 证书管理模块，支持在线生成与管理，无需使用传统命令行方式生成和管理证书</li>
<li>系统基于 Spring Authorization Server 的授权服务器加密逻辑，全部修改为使用系统 PKI 模块生成证书</li>
<li>改造 Spring Boot Admin 监控服务，增加登陆认证保护。使用 Dante Cloud 系统自身提供的 OAuth2 / OIDC 作为 Spring Boot Admin 主要的登录模式</li>
<li>新增静态接口权限 Customizer 聚合模式，服务可根据实际依赖模块<code>按需</code>、<code>动态装配</code>静态接口权限，减少静态权限的重复设置和不必要的解析，进一步提升接口鉴权效率</li>
<li>服务 Docker 镜像基础 JDK 修改为 liberica 加固镜像，以提升镜像整体安全性及性能</li>
</ol>
<h4>[6] OSS对象存储</h4>
<ol>
<li>原 Dante OSS 工程已经停止维护，使用 AWS S3 V2 权限实现 OSS 管理模块，并合并至核心组件库</li>
<li>支持在线生成与管理，基于 AWS S3 V2 API 能够提供的能力，全新实现前端对象存储管理功能</li>
<li>重构本地文件存储和远程文件存储多层文件管理逻辑，让代码逻辑更清晰、运行更稳定、配置修改，也更容易理解和维护。</li>
</ol>
<h4>[7] 前端功能改进</h4>
<ol>
<li>基于 Vuetify Material Design 3 蓝图风格以及 Vue 3 组合式 API，全新编写新版管理界面</li>
<li>采用 monorepo 模块化设计，原版前端(Quasar版)和前端新版(Vuetify版)，共享通用化代码模块，提升代码维护的便捷性</li>
<li>前端菜单权限大幅调整，支持根据多角色动态获取菜单。</li>
<li>支持多类型动态菜单，个人页面菜单从静态化菜单变更为支持动态化配置管理</li>
<li>大幅提升前端代码 Tree shaking 能力和性能</li>
<li>前端组件库模块新增组件 Resolver 支持，方便 IDE 更好的识别组件及其定义</li>
</ol>
<h4>[8] 其它改进提升</h4>
<ol>
<li>Dante Cloud 主工程也支持中央仓库和 Maven 私库发布，服务的开发和代码组织可以更灵活</li>
<li>使用更优雅及更合理的方式重构自定义 JPA 二级缓存实现，彻底解决需要修改 Hibernate 源代码的问题</li>
<li>优化平台和服务配置，按照 Servlet 和 Reactive 不同环境需求进行拆分，以增强不同运行环境配置的独立性</li>
<li>重构本地文件存储及服务间文件传输体系，简化代码逻辑，清晰化代码定位，便于理解和维护</li>
<li>重构基础 Service 和 Controller 定义，在原有 Spring Data 实体绑定基础上，支持 DTO 类型请求和响应实体。同时支持 Spring Data Page 和 Slice 两种分页场景</li>
</ol>
<h3>二、注意事项</h3>
<ol>
<li>因为全新架构了核心组件库中的模块，核心模块坐标已统一变更为 org.dromara.dante（企业版保持不变），建议全新检出代码，避免因模块结构调整导致冲突</li>
<li>数据库和 Nacos 配置等，也存在较多的变化，与之前版本无法兼容，要使用新版本建议重新建库和导入配置文件。</li>
<li>若通过 ZIP 方式导出代码，需注释 git-commit-id-maven-plugin，否则编译会因缺少 .git 文件夹报错。建议通过 Git 克隆代码以保留版本记录功能</li>
</ol>
<h2>v3.5.5.3</h2>
<p>Dante Cloud v3.5.5.3 版本对 Dante Engine 核心组件库以及主工程做了大规模重构</p>
<p><strong>“本次升级绝非简单的代码同步，而是一次从架构理念到实现方式的深度重构。企业版在生产环境中积累的最佳实践，对开源组件库进行了‘淬炼’：</strong></p>
<ol>
<li><strong>结构上</strong>，我们重新划分了模块边界，使得 <strong>组件的内聚性大幅提升</strong>。每个组件的职责更加单一和明确，就像一个功能精湛的‘瑞士军刀’模块，开发者可以真正地像‘组装乐高’一样，按需引入所需功能，极大提升了使用的灵活性与项目的轻量化。</li>
<li><strong>依赖上</strong>，我们极大地 <strong>降低了组件之间以及与第三方库的非必要耦合</strong>。这不仅减少了潜在的依赖冲突，使得与 Spring Boot 生态及其他主流依赖的集成更加平滑稳定，也让每个组件的‘独立使用’成为了可能。你现在可以轻松地将某个组件单独抽离，集成到任何基于 Spring Boot 的技术栈中。</li>
<li><strong>规范上</strong>，我们确保所有组件的设计都 <strong>更严格地遵循 Spring Boot 的官方规范和习惯用法</strong>，特别是新版本 Spring Boot 新增加的用法。这意味着更低的学习成本、更熟悉的配置方式、以及更好的与 IDE 和 Spring Boot Actuator 等工具链的协同工作能力。</li>
</ol>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<ol>
<li>因为代码结构出现了较大规模的变化，如果您未曾在原版基础上进行较多修改，那么建议重新检出工程代码学习和使用。如果有较多修改，建议新建一个工程重新检出代码，与现有代码对照修改</li>
<li>除了代码结构以外，Nacos 配置也同步进行了较大修改，建议新建一个命名空间，重新导入使用。</li>
</ol>
<h2>v3.5.3.0</h2>
<p>Bootstrap 模式（即 Bootstrap.yml）是兼容性处理模式，已经是 Spring 官方不推荐的模式。</p>
<p>Spring Cloud Alibaba 自 2023.0.1.3 版本起改用了新的配置方式，其实本质就是不打算继续兼容 Bootstrap 模式，因此在 Bootstrap 模式下 SCA 会出现一些问题。</p>
<p>因此 Dante Cloud 自 v3.5.3.0 版本起，也改用 Spring Boot 的标准配置模式，将 <code>bootstrap.yml</code> 统一修改为 <code>application.yml</code>，并去除了对模块 <code>spring-cloud-starter-bootstrap</code> 的依赖。</p>
<h2>v3.4.1.0</h2>
<p>本版本为了适配 <code>Spring Authorization Server</code> 1.4.1，在系统中增加了两个 Token 的属性字段，以保持与 <code>Spring Authorization Server</code> 的一致性。</p>
<p>当您更新了最新的代码后启动服务，系统会自动在 <code>oauth2_application</code> 表中增加两个字段：<code>bound_access_token</code> 和 <code>subject_dn</code>。</p>
<p><code>subject_dn</code> 字段不会对系统产生任何影响。但是由于系统使用的数据库组件会自动添加新的字段，不会同步设置已有信息的值，这就导致新增的另外的一个字段 <code>bound_access_token</code>（bool 类型）的值为 <code>null</code>。这就会导致系统登录时出现异常。</p>
<p>解决办法：</p>
<ol>
<li>如果您已经更新了代码并且启动了服务，<code>oauth2_application</code> 表中会新增两个字段：<code>bound_access_token</code> 和 <code>subject_dn</code></li>
<li>打开 <code>oauth2_application</code> 表，找到 <code>bound_access_token</code> 字段，将其值设置为 &quot;false&quot;。</li>
<li>清空 Redis 缓存，或者等待缓存失效后，再次尝试。</li>
</ol>
<h2>v3.4.0.0</h2>
<p>已将 Spring Cloud Alibaba 版本回滚至 2023.0.1.2</p>
<h2>v3.3.6.0</h2>
<p>v3.3.6.0 版本将 Spring Cloud Alibaba 升级至 2023.0.1.3，这将会出现两个问题：</p>
<ol>
<li>只能使用 <code>spring.config.import</code> 方式读取配置</li>
<li>Spring Boot 自身日志配置失效，无法在配置文件中，通过 <code>logging.level.&lt;logger-name&gt;=&lt;level&gt;</code> 方式灵活控制日志显示和输出</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Breaking Change</title>
      <link>https://www.herodotus.cn/breaking/</link>
      <guid>https://www.herodotus.cn/breaking/</guid>
      <source url="https://www.herodotus.cn/rss.xml">Breaking Change</source>
      <description>内容介绍 说明 本章节内容，主要为记录微服务相关核心组件或 Dante Cloud 系统，在升级过程中将会遇到的“破坏性变更”问题以及相应的处理方法。</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>内容介绍</h2>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>本章节内容，主要为记录微服务相关核心组件或 Dante Cloud 系统，在升级过程中将会遇到的“破坏性变更”问题以及相应的处理方法。</p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Boot</title>
      <link>https://www.herodotus.cn/breaking/spring-boot.html</link>
      <guid>https://www.herodotus.cn/breaking/spring-boot.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">Spring Boot</source>
      <description>v4.0.X v4.0.0 Spring Boot 4.0.0 变化较多，而且不予低版本兼容，必需整体升级包括相关的第三方组件，详情可以阅读作者文章：《Dante Cloud 升级 Spring Boot 4 经验分享》 v3.5.X v3.5.2 Spring Boot 3.5.1 中意外引入的一个严重回归问题，该问题在 Spring Boot 3....</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v4.0.X</h2>
<h3>v4.0.0</h3>
<p>Spring Boot 4.0.0 变化较多，而且不予低版本兼容，必需整体升级包括相关的第三方组件，详情可以阅读作者文章：<a href="https://mp.weixin.qq.com/s/6iagPTc3pRLePxa3XGj5jw" target="_blank" rel="noopener noreferrer">《Dante Cloud 升级 Spring Boot 4 经验分享》</a></p>
<h2>v3.5.X</h2>
<h3>v3.5.2</h3>
<p>Spring Boot 3.5.1 中意外引入的一个严重回归问题，该问题在 Spring Boot 3.5.2 中尚未完全修复。所以不要使用 Spring Boot v3.5.1 和 v3.5.2</p>
<h3>v3.5.1</h3>
<p>Spring Boot 3.5.1 中意外引入的一个严重回归问题，该问题在 Spring Boot 3.5.2 中尚未完全修复。所以不要使用 Spring Boot v3.5.1 和 v3.5.2</p>
<h2>v3.4.X</h2>
<h3>[1]版本</h3>
<p>Spring Boot 3.4.X 版本，绑定了 Spring Cloud 版本 2024.X。使用 Spring Boot 3.4.X 相关版本，只能使用 Spring Cloud 2024.0.X 版本。版本使用错误，将无法启动服务。</p>
<h3>[2]Webjars</h3>
<p>Spring Boot 3.4.X 版本默认改用 <code>LiteWebJarsResourceResolver</code> 支持 Webjars。需要新添加依赖 <code>webjars-locator-lite</code>。缺少该依赖原有 Webjars 方式将导致启动抛错。</p>
<h2>v3.2.X</h2>
<p>Spring Boot 3.2.X 版本，绑定了 Spring Cloud 版本 2023.X。使用 Spring Boot 3.2.X~3.3.X 相关版本，只能使用 Spring Cloud 2023.0.X 版本。版本使用错误，将无法启动服务。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Cloud Alibaba</title>
      <link>https://www.herodotus.cn/breaking/spring-cloud-alibaba.html</link>
      <guid>https://www.herodotus.cn/breaking/spring-cloud-alibaba.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">Spring Cloud Alibaba</source>
      <description>v2025.1.0.0 Spring Cloud Alibaba 2025.1.0.0 基于 Spring Boot 4.0.X 以及 Spring Cloud 2025.1.0，与其它版本不兼容。无法在现有工程中直接升级，需要整体功能全部适配最新版本才能使用。 v2025.0.0.0 Spring Cloud Alibaba 已经彻底放弃了原有 Bo...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v2025.1.0.0</h2>
<p>Spring Cloud Alibaba 2025.1.0.0 基于 Spring Boot 4.0.X 以及 Spring Cloud 2025.1.0，与其它版本不兼容。无法在现有工程中直接升级，需要整体功能全部适配最新版本才能使用。</p>
<h2>v2025.0.0.0</h2>
<p>Spring Cloud Alibaba 已经彻底放弃了原有 <code>Bootstrap</code> 模式的配置方式，因此使用该版本需要进行以下修改：</p>
<ol>
<li>服务中的 <code>boostrap.yml</code> 配置文件，修改为 <code>application.yml</code>。</li>
<li>所有的配置文件改用 <code>spring.config.import</code> 方式引用</li>
</ol>
<h2>v2023.0.3.4</h2>
<p>Spring Cloud Alibaba 由于配置方式的变化，改为使用 <code>spring.config.import</code> 方式，因此 <code>Bootstrap</code> 模式也不推荐使用，即服务下不再使用 <code>boostrap.yml</code>，而是修改为 <code>application.yml</code> 配置文件方式。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Spring Cloud <code>Bootstrap</code> 模式，需要依赖于 <code>spring.factories</code> 配置文件启动。而 <code>spring.factories</code> 配置是 Spring Boot 2.X 时代的产物，在 Spring Boot 3及以后版本中都不再使用。</p>
<blockquote>
<p>部分组件还在 Spring Boot 3 中使用 <code>spring.factories</code> 是为了兼容 Spring Boot 2.X。</p>
</blockquote>
<p>所以，本身 Spring Cloud <code>Bootstrap</code> 就是过时的方式，在新版本中不支持此种方式也是合理的。</p>
</div>
<h2>v2023.0.3.2</h2>
<p>使用 Spring Cloud Alibaba 2023.0.1.3 和 2023.0.3.2 版本会导致 Spring Boot 自身日志配置失效，无法在配置文件中，通过 <code>logging.level.&lt;logger-name&gt;=&lt;level&gt;</code> 方式灵活控制日志显示和输出。</p>
<h2>v2023.0.1.3</h2>
<p>Spring Cloud Alibaba 2023.0.1.3 最主要的变化为提取出一个新的模块 <code>spring-alibaba-nacos-config</code>。</p>
<p>提取 <code>spring-alibaba-nacos-config</code> 模块，使得 <code>NacosPropertySourceLocator</code> 类的注入时机产生了异常，导致 <code>NacosConfigProperties</code> 无法按照原有方式正确注入配置，特别是原有使用 <code>shared-config</code> 和 <code>ext-config</code> 方式注入 Nacos 配置将失效。</p>
<p>参见 ISSUE：<a href="https://github.com/alibaba/spring-cloud-alibaba/issues/3882" target="_blank" rel="noopener noreferrer">【#3882】</a></p>
<h3>解决办法</h3>
<p>解决办法就是采用 <code>spring.config.import</code> 方式注入配置。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Spring Cloud</title>
      <link>https://www.herodotus.cn/breaking/spring-cloud.html</link>
      <guid>https://www.herodotus.cn/breaking/spring-cloud.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">Spring Cloud</source>
      <description>v2025.1.X 重要 使用 Spring Cloud 版本 2025.1.X，虽然版本号仍旧是 2025.X 系列，但是对应的 Spring Boot 版本已经是 4.0.X。 Spring Boot 4.0.X 对整体模块结构进行较大的重构，很多类所在包以及包路径都已经变化，这就导致很难进行兼容处理。所以一定要注意版本的对应。 v2025.0.X...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v2025.1.X</h2>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>使用 Spring Cloud 版本 2025.1.X，虽然版本号仍旧是 2025.X 系列，但是对应的 Spring Boot 版本已经是 4.0.X。</p>
<p>Spring Boot 4.0.X 对整体模块结构进行较大的重构，很多类所在包以及包路径都已经变化，这就导致很难进行兼容处理。所以一定要注意版本的对应。</p>
</div>
<h2>v2025.0.X</h2>
<p>使用 Spring Cloud 版本 2025.0.X，对应的 Spring Boot 版本是 3.5.X。版本使用错误，将无法启动服务。</p>
<h2>v2024.0.X</h2>
<p>使用 Spring Cloud 版本 2024.X，对应的 Spring Boot 版本是 3.4.X~3.5.X。版本使用错误，将无法启动服务。</p>
<h2>v2023.0.X</h2>
<p>使用 Spring Cloud 版本 2023.X，对应的 Spring Boot 版本是 3.2.X~3.3.X。版本使用错误，将无法启动服务。</p>
]]></content:encoded>
    </item>
    <item>
      <title>快速开始</title>
      <link>https://www.herodotus.cn/get-started/</link>
      <guid>https://www.herodotus.cn/get-started/</guid>
      <source url="https://www.herodotus.cn/rss.xml">快速开始</source>
      <description>项目简介 Dante Cloud 国内首个支持阻塞式和响应式服务并行的、开箱即用的企业级云原生微服务基座。是采用领域驱动模型(DDD)设计思想，以「高质量代码、低安全漏洞」为核心，基于 Spring 生态全域开源技术，高度模块化和组件化设计，支持智能电视、IoT等物联网设备认证，满足国家三级等保要求，支持接口国密数字信封加解密等一系列安全体系的一站式多...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>项目简介</h2>
<p><strong>Dante Cloud</strong> 国内首个支持阻塞式和响应式服务并行的、开箱即用的企业级云原生微服务基座。是采用<strong>领域驱动模型(DDD)</strong>设计思想，以「<strong>高质量代码、低安全漏洞</strong>」为核心，基于 Spring 生态全域开源技术，高度<strong>模块化和组件化设计</strong>，支持<strong>智能电视、IoT等物联网设备</strong>认证，满足<strong>国家三级等保要求</strong>，支持<strong>接口国密数字信封加解密</strong>等一系列安全体系的一站式多租户微服务解决方案。独创的可以“<strong>一套代码实现微服务和单体两种架构灵活切换</strong>”的企业级应用系统。</p>
<h3>一、项目理念</h3>
<p><strong>Dante Cloud</strong> 一直秉承着“简洁、高效、包容、务实”的理念，使用微服务领域及周边相关的各类新兴技术或主流技术进行建设，不断地深耕细作、去粗取精、用心打造。目标是构建一款<code>代码质量高、维护投入低、安全防护强</code>的微服务基座，可以帮助用户快速跨越架构技术选型、技术研究探索、基础架构搭建阶段，直接聚焦业务开发。极大地降低传统项目中因安全漏洞、技术负债、低质代码等潜在隐患所产生的高维护投入。期望像项目名字寓意一样，构建一套可以在在行业变革的时期承上启下，助力企业信息化建设和数字化转型的产品。</p>
<p><strong>Dante Cloud</strong> 核心关注点是：<strong>「高质量的系统代码」</strong>、<strong>「合理的系统架构」</strong>、<strong>「低耦合的模块划分」</strong>、<strong>「高安全性系统实现」</strong>、<strong>「灵活的功能扩展能力」</strong>，<strong>「优质的微服务实践方案」</strong>。不会像其它一些系统一样，追求 <strong>业务功能</strong> 的 <strong>丰富</strong> 性。堆叠大量无法做到真正通用的功能，反倒会成为负担和干扰，不如由用户自己按照需求灵活设计和实现。</p>
<h3>二、架构设计</h3>
<p><strong>Dante Cloud</strong> 优秀的模块化能力，为系统提供了高度灵活的配置能力、<strong>功能的“可插拔”能力</strong> 以及不同需求场景的适配能力。正因为优秀的模块化体系，使得 <strong>Dante Cloud</strong> 不仅是一套完整的微服务架构，还是一套高质量的 <strong>「单体模块化」</strong> 系统。这里的微服务架构和单体架构并不是分离的两套代码，也不是分离的两个项目。</p>
<p>而是完全融合的一整套代码，使用时可以根据需要选择是以微服务模式或者单体模式运行，配合灵活的模块能力，实现系统的多样化定制和功能的管控。</p>
<p>这是 Dante Cloud 微服务最大的特色之一：<strong>“一套代码、两种架构”</strong>。可以帮助企业在项目早期以单体架构快速建设项目、方便开发人员在本地进行开发以及新技术研究。在项目后期随着用户规模增大以及并发需求提升时，可以快速无缝迁移至微服务架构。</p>
<figure><img src="/assets/image/prepare/architecture.svg" alt="平台架构" tabindex="0" loading="lazy"><figcaption>平台架构</figcaption></figure>
<h3>三、适用用户</h3>
<p>微服务技术并不是 <strong>「落伍」</strong> 了，而是进入了 <strong>「成熟期」</strong>，它的 <strong>「适用场景和边界被更清晰地定义」</strong> 了。微服务不再是一个 <strong>「必须要有」</strong> 的选项，而是一个 <strong>「权衡之后」</strong> 的选择。</p>
<p><strong>Dante Cloud</strong> 也并未使用任何复杂难懂或难以上手掌握的技术，项目中所涉及核心关键组件中，其中 <strong>「近 80% 均为 Spring 生态原生组件」</strong>。技术实现均为各组件标准用法的组合与应用，编码风格和代码设计一直也在极尽努力尽量与 Spring 生态的标准规范用法保持一致，只不过经过大量的版本迭代和重构之后逐渐形成了一定的封装与抽象。</p>
<p>因此，我们推荐以下用户选择和使用 <strong>Dante Cloud</strong></p>
<ul>
<li><strong>「传统项目用户」</strong>：可以先体验和使用单体版，先从“前后端分离”以及“多端适配”开始，尝试不同于传统内嵌页面的开发模式。之后也可以平滑迁移至微服务版。</li>
<li><strong>「数字转型用户」</strong>：如果您正在考虑进行数字化转型，可以直接选择使用微服务版，不用再为“基础组件碎片化，需花大量时间整合、踩坑版本兼容”等问题而苦恼。</li>
<li><strong>「复杂项目用户」</strong>：如果您的业务复杂度上升到一定阶段，可以直接选择使用微服务版，直接聚焦于业务开发，节省大量前期搭建基础设施、解决通用技术问题的时间。</li>
<li><strong>「初创团队用户」</strong>：可以先使用单体版进行开发，只要代码放置规范、模块划分合理，后期可以根据需要无缝迁移至微服务架构</li>
<li><strong>「技术突破用户」</strong>：本项目并不拘泥局限于常规成熟的技术内容，目标是探索新型技术并用其来为业务的创新服务。喜欢技术尝鲜和突破的用户推荐选择使用。</li>
<li><strong>「学习提升用户」</strong>：本项目代码实现优雅和领域划分清晰，编码风格和模块实现尽最大可能与 Spring 生态规范保持一致，是深入学习 Spring 生态组件和提升技能的优秀案例</li>
</ul>
<blockquote>
<p>想要从传统项目转型至微服务项目的用户，<strong>建议详细阅读《企业IT架构转型之道：阿里巴巴中台战略思想与架构实战》一书（可以先读前几章）之后再上手本项目！</strong></p>
</blockquote>
<p>对于以下用户我们不建议选择 <strong>Dante Cloud</strong></p>
<ul>
<li><strong>「单体拥趸用户」</strong>：如果您觉得单体架构可以满足您所有的架构需求，微服务繁琐庞大无法比拟单体的“方便”、“快捷”，那么建议选择其它更专业的单体项目。</li>
<li><strong>「单一前端用户」</strong>：如果您的项目只会有Web端，不会涉及小程序、移动端、桌面端等其它类型终端，未来也不需要考虑并发或者扩展等问题，选择本项目大材小用。</li>
<li><strong>「主流技术用户」</strong>：如果您只擅长 mysql、mybatis 等主流技术，而且并不希望使用任何新技术。那么本项目所涉及的基础技术体系可能会让您觉得格格不入建议选择更适配您技术体系项目。</li>
<li><strong>「功能丰富用户」</strong>：本项目定位是基础平台，自认为没有能力做到功能既丰富又通用，如果您追求拥有丰富的、开箱即用的功能后台系统，那么本项目并不适合。</li>
<li><strong>「极简编程用户」</strong>：如果您希望基于自己现有知识，不看文档不用学习就可以快速搭建应用，或者通过拖拽生成代码简化开发，这与本项目的产品定位、开发理念、设计哲学背道而驰。</li>
<li><strong>「审美品鉴用户」</strong>：本项目前端是使用组件库纯手搓构建，没有专业的美工也没有照搬主流框架，初衷是为后端开发人员接触前端提供一条更友好的途径，所以不能保证符合您的审美。</li>
</ul>
<h2>技术选型</h2>
<h3>[1]后端核心技术栈</h3>
<h4>（1）Spring 相关核心技术及版本</h4>
<p>| 组件                          | 版本             |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/prepare/architecture.svg" type="image/svg+xml"/>
    </item>
    <item>
      <title>配置</title>
      <link>https://www.herodotus.cn/properties/</link>
      <guid>https://www.herodotus.cn/properties/</guid>
      <source url="https://www.herodotus.cn/rss.xml">配置</source>
      <description>说明 Dante Cloud Dante Cloud 微服务所有参数均是以 herodotus 作为前缀，例如：herodotus.oauth2.authentication.XXX，请注意路径的完整性 提示 Dante Cloud 已经在系统中增加了辅助信息生成配置。在 IDE 中，在 bootstrap.yml 中输入相应的字符，就会出现参数提示，...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>说明</h2>
<h3>Dante Cloud</h3>
<p>Dante Cloud 微服务所有参数均是以 <code>herodotus</code> 作为前缀，例如：<code>herodotus.oauth2.authentication.XXX</code>，请注意路径的完整性</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Dante Cloud 已经在系统中增加了辅助信息生成配置。在 IDE 中，在 <code>bootstrap.yml</code> 中输入相应的字符，就会出现参数提示，如下图所示</p>
<figure><img src="/assets/image/herodotus/properties-prompt.png" alt="IDE提示" tabindex="0" loading="lazy"><figcaption>IDE提示</figcaption></figure>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/herodotus/properties-prompt.png" type="image/png"/>
    </item>
    <item>
      <title>v3.3.X</title>
      <link>https://www.herodotus.cn/logs/3.3.html</link>
      <guid>https://www.herodotus.cn/logs/3.3.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">v3.3.X</source>
      <description>v3.3.6.1 主要更新 [升级] Spring Cloud 版本升级至 2023.0.4 [新增] 新增基于 BouncyCastle 的证书管理和 KeyStore 管理工具代码及模块。新增配套测试代码。 [新增] 新增证书存储管理，方便本地证书读取与写入，同时支持远程对象存储的拉取与上传。 [新增] 新增单体版本地文件上传对象存储代码实现。支持...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v3.3.6.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2023.0.4</li>
<li>[新增] 新增基于 BouncyCastle 的证书管理和 KeyStore 管理工具代码及模块。新增配套测试代码。</li>
<li>[新增] 新增证书存储管理，方便本地证书读取与写入，同时支持远程对象存储的拉取与上传。</li>
<li>[新增] 新增单体版本地文件上传对象存储代码实现。支持上传和下载进度日志显示</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复对象存储远程上传组件配置类条件注解使用错误问题</li>
<li>[重构] 重构服务本地文件操作 FileTemplate 定义，减少依赖性，提升使用的便捷性。</li>
<li>[重构] 将证书和 KeyStore 相关代码提取为独立的 PKI 代码模块。</li>
<li>[安全] 修复安全漏洞 CVE-2024-47072</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-11-07T00-52-20Z</li>
<li>[升级] kafka docker 镜像版本升级至 3.9.0</li>
<li>[升级] grafana docker 镜像版本升级至 11.3.1</li>
<li>[升级] grafana loki 镜像版本升级至 3.3.0</li>
<li>[升级] emqx 镜像版本升级至 5.8.2</li>
<li>[升级] clickhouse 镜像版本升级至 24.11.1</li>
<li>[升级] tdengine 镜像版本升级至 3.3.4.3</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] grpc 版本升级至 1.68.2</li>
<li>[升级] json-schema-validator 版本升级至 1.5.4</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.29.23</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.33.3</li>
<li>[升级] sqlite-jdbc 版本升级至 3.47.1.0</li>
<li>[升级] hutool5 版本升级至 5.8.34</li>
</ul>
</li>
</ul>
<h2>v3.3.6.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.3.6</li>
<li>[升级] Spring Boot Admin 版本升级至 3.3.6</li>
<li>[升级] Spring Cloud Alibaba 版本升级至 2023.0.1.3</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增证书 DistinguishedName 构建器</li>
<li>[新增] 新增证书选项设置，方便快捷地生成证书时间</li>
<li>[修复] 修复 Mqtt 消息通道定义重复，导致 ThingsBrain 应用启动异常问题。</li>
<li>[重构] 修改配置中新配置读取方式，适配最新版本 Spring Cloud Alibaba。</li>
<li>[重构] 重构证书生成以及存储和读取相关工具类代码</li>
<li>[重构] 重构系统核心定义模块代码分包，减少不必要的分包，提升代码归类的合理性</li>
<li>[重构] 重构证书生成和 KeyStore 管理工具类，提升代码使用便捷性</li>
<li>[重构] 修改配置中新配置读取方式，适配最新版本 Spring Cloud Alibaba。</li>
<li>[优化] 消除代码中未编写注释的 TODO，补充相应的注释说明。仅保留已经编写了提示内容的 TODO，方便查询提示信息，减少不必要 TODO 带来的干扰和混淆。</li>
<li>[优化] 补充证书工具类注释</li>
<li>[优化] 优化 dependencies 版本定义顺序，方便按照英文字母顺序查找定义</li>
<li>[优化] 优化虚拟线程配置，兼容 Undertow 中间和 Spring 异步操作。</li>
<li>[优化] 去除 dependencies 中 hutool bom 的引入，直接使用 hutool-all依赖</li>
<li>[优化] 优化 dependencies 中，使用到的 maven plugin 默认配置</li>
<li>[优化] 优化 Undertow WebSocket 条件化配置。在非 Undertow 环境以及 Reactive 环境下主动禁用相关配置。</li>
<li>[升级] 升级 Antisamy XSS 防护配置</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] antisamy 版本升级至 1.7.7</li>
<li>[升级] commons-io 版本升级至 2.18.0</li>
<li>[升级] grpc-bom 版本升级至 1.68.1</li>
<li>[升级] jsonschema-generator 版本升级至 4.37.0</li>
<li>[升级] quasar 版本升级至 2.17.4</li>
<li>[升级] redisson 版本升级至 3.39.0</li>
<li>[升级] mapstruct-processor 版本升级至 1.6.3</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.29.18</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.33.2</li>
<li>[升级] weixin-java 版本升级至 4.6.7.B</li>
<li>[升级] blaze-persistence 版本升级至 1.6.14</li>
<li>[升级] bcpkix-jdk18on 版本升级至 1.79</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.79</li>
<li>[升级] vue 版本升级至 3.5.13</li>
</ul>
</li>
</ul>
<h2>v3.3.5.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.3.5</li>
<li>[修复] 修复基于 Camunda 的 Bpmn 服务启动错误问题</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化 Nacos 镜像挂载映射，增加 logs 目录映射，方便查阅 Nacos 使用中是否存在问题</li>
<li>[优化] 适配 Hutool 6.0.0-M18</li>
<li>[修复] 修复 Mybatis Plus 依赖版本错误，导致 Mybatis Plus 使用异常错误</li>
<li>[修复] 修复基于 Camunda 的工作流 Mybatis Plus 配置文件配置错误，导致的无法找到 Mybatis Plus 无法找到映射对象问题。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-10-29T16-01-48Z</li>
<li>[升级] kafka docker 镜像版本升级至 3.8.1</li>
<li>[升级] emqx docker 镜像版本升级至 5.8.1</li>
<li>[升级] clickhouse docker 镜像版本升级至 24.8.6</li>
<li>[升级] cassandra docker 镜像版本升级至 5.0.2</li>
<li>[升级] grafana docker 镜像版本升级至 11.3.0</li>
<li>[升级] loki docker 镜像版本升级至 3.2.1</li>
<li>[升级] promtail docker 镜像版本升级至 3.2.1</li>
<li>[升级] tempo docker 镜像版本升级至 2.6.1</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] json-schema-validator 版本升级至 1.5.3</li>
<li>[升级] redisson 版本升级至 3.38.1</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.29.6</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.33.0</li>
<li>[升级] hutool 6.X 版本升级至 6.0.0-M18</li>
<li>[升级] weixin-java 版本升级至 4.6.6.B</li>
<li>[升级] blaze-persistence 版本升级至 1.6.13</li>
<li>[升级] checker-qual 版本升级至 3.48.2</li>
<li>[升级] hutool 5.X 版本升级至 5.8.33</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.14.5</li>
</ul>
</li>
</ul>
<h2>v3.3.5.1</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增微服务间消息统一发送机制。采用统一发送事件，支持 Mail、Mqtt、RSocket 广播、RSocket 用户、WebSocket 广播、WebSocket 用户和 Stream 消息。</li>
<li>[新增] 新增判断是否为 Message 服务条件注解，实现消息服务的动态判断。</li>
<li>[新增] 新增独立的 Message Service 自动配置。当代码判断当前服务为消息服务时，会自动注入相关配置。</li>
<li>[新增] 新增可以区分是否为 Message 和 Upms 服务 的ConditionXXX 条件代码，用于在配置代码中增加配置的灵活性</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 在 Reactive 环境下，使用 RSocket 发送私信具体信息没有保存问题。</li>
<li>[修复] 修复基于 Postgresql 封装的 Nacos 镜像，历史版本数据查询失败问题。fix: #IB0BBE</li>
<li>[修复] 修复电子邮件内联图片使用内置 Logo 读取方式不对导致发送邮件失败问题。</li>
<li>[重构] 将 oauth2-module-message 模块代码归并至 oauth2-authorization-autoconfigure 模块中，进一步明细模块定位及用途，减少在使用过程中产生歧义或误解</li>
<li>[重构] 将 herodotus-module-metadata 模块代码归并至 oauth2-authorization-autoconfigure 模块中，进一步明细模块定位及用途，减少在使用过程中产生歧义或误解</li>
<li>[重构] 重构 RSocket Security 自动配置内容，将其合并至消息服务专有自动配置中。</li>
<li>[重构] 调整单体版本模块结构，减少不必要的模块以及归类，与微服务版结构进一步统一。</li>
<li>[重构] 系统内置的登录位置IP异常等电子邮件通知，修改为使用系统统一消息发送机制。将电子邮件发送统一归并至 Message 服务进行配置和处理</li>
<li>[重构] 重构 Captcha 字体和图片获取代码，将通用代码归并至系统统一的资源处理工具类中。</li>
<li>[优化] 优化系统核心 Listener 配置，解决在单体版模式下注入非必要的远程事件监听器</li>
<li>[优化] 优化系统核心数据事件化传输逻辑日志显示，明晰跨服务输出日志的逻辑关联性。方便用户更加容易的理解代码间交互逻辑。</li>
<li>[安全] 修复 CVE-2024-31573 安全漏洞</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] archetype-packaging 版本升级至 3.3.1</li>
<li>[升级] maven-archetype-plugin 版本升级至 3.3.1</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.29.2</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.33.0</li>
<li>[升级] quasar webjars 版本升级至 2.17.1</li>
<li>[升级] xmlunit2 版本升级至 2.10.0</li>
</ul>
</li>
</ul>
<h2>v3.3.5.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.3.5</li>
<li>[升级] Spring Authorization Server 版本升级至 1.3.3</li>
<li>[重构] 开源版本工程代码包名由 cn.herodotus 修改为 org.dromara，与社区项目保持一致</li>
<li>[新增] 新增服务间文件上传和下载传输机制，支持 OpenFeign 和 Grpc 两种模式，通过热插拔模式切换。</li>
<li>[新增] 新增 OSS 文件操作 GRPC 定义模块</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构 AWS SDK V2 高阶 OSS 操作代码，增加高阶操作单元测试。</li>
<li>[重构] 服务内文件基本操作变更为使用 NIO 操作</li>
<li>[重构] Mybatis Plus 修改为 Bom 引入，同时适配最新版本 Mybatis Plus</li>
<li>[修复] 修复 Kafka Docker Compose 配置错误，导致 Kafka 镜像启动抛错问题</li>
<li>[修复] 修复前端 Vite CSS 样式配置不兼容，导致页面启动抛错问题。</li>
<li>[修复] 修复微服务环境下，分布式事件使用错误导致字典聚合数据汇总异常问题</li>
<li>[修复] 修复使用 AWS SDK V2 创建的预签名地址中，仍旧使用 AWS 默认服务地址不会定位至自定义主机问题</li>
<li>[优化] 优化 OSS 模型基础操作类命名，以便更容易的区分代码用途。</li>
<li>[优化] 优化 OSS 模块代码，池化 S3Presigner 对象管理提升效能。</li>
<li>[优化] 优化 OSS 模块代码，提取独立的预签名操作 Service</li>
<li>[升级] Liberica JDK 基础镜像版本分别升级至 17.0.13-12 和 21.0.5-11</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-10-13T13-34-11Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.777</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.28.29</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.31.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.234.ALL</li>
<li>[升级] mysql 版本升级至 9.1.0</li>
<li>[升级] mybatis plus 版本升级至 3.5.9</li>
<li>[升级] sqlite-jdbc 版本升级至 3.47.0.0</li>
<li>[升级] quasar webjars 版本升级至 2.17.1</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.14.4</li>
</ul>
</li>
</ul>
<h2>v3.3.4.3</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增客户端动态注册业务信息同步创建功能</li>
<li>[新增] Mqtt 用户账号管理功能</li>
<li>[新增] 新增 NoSQL 相关组件自动配置 Starter。</li>
<li>[新增] 新增 Influxdb 列式存储和行式存储两种存储设备上报数据支持。</li>
<li>[修复] 修复自动配置类引入日志标识 Class 错误问题。</li>
<li>[修复] grpc 编译出现 error: emptyList() is not public in LazyStringArrayList; cannot be accessed from outside package com.google.protobuf.LazyStringArrayList.emptyList() 问题。fix: #IAWQ4C</li>
<li>[修复] 修复 Docker Compose 镜像地址配置错误问题。fix: #IAXUFB</li>
<li>[修复] 修复 Influxdb2 默认配置与系统提供 Docker Compose 默认配置不一致，导致 Influxdb 测试代码部分通过问题。</li>
<li>[修复] 修复前端 <code>Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0</code> 告警错误</li>
<li>[重构] 重构服务本地文件管理定义以及证书生成逻辑代码</li>
<li>[优化] 对照阿里云物联网的使用完善产品和设备管理的接口实现逻辑。</li>
<li>[优化] 优化 OIDC 客户端动态注册逻辑，更好的兼容物联网设备管理需求。</li>
<li>[优化] 去除重复定义的 ApplicationEvent 消息通道定义，改用统一定义消息通道</li>
<li>[优化] 优化 Emqx 客户端状态检测策略化配置方式</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-10-13T13-34-11Z</li>
<li>[升级] emqx 镜像版本升级至 5.8.0</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] grpc 版本升级至 1.68.0</li>
<li>[升级] protobuf 版本升级至 3.25.5</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.775</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.28.25</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.31.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.234.ALL</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.14.3</li>
</ul>
</li>
</ul>
<h2>v3.3.4.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.3.4</li>
<li>[升级] Debezium 版本升级至 3.0</li>
<li>[升级] Camunda 版本升级至 7.22.0</li>
<li>[升级] Nacos 版本升级至 2.4.3</li>
<li>[重构] 单体版系统合并至微服务版本工程中。可以在同一工程启动单体版本或者微服务版。解决原有模式下，需要单独编译微服务版，再在另一个工程中启动单体版。提升开发和使用的便捷性。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增服务本地常用文件管理机制</li>
<li>[新增] 新增跨模块跨服务认证开启或关闭控制单元</li>
<li>[修复] 重新构建支持 Postgresql 的 Nacos Server 镜像。修复创建命名空间失败问题。</li>
<li>[修复] 修复数据加密策略配置未生效问题</li>
<li>[重构] 重构部分 OAuth2 核心代码，提升代码模块的内聚性降低代码耦合</li>
<li>[重构] 重构物联网设备动态开启和关闭认证逻辑，简化和去除原有采用的多重事件跳转方式。</li>
<li>[优化] 合并部分系统配置参数类定义，增强配置参数划分和归类的合理性</li>
<li>[优化] 删除无用重复的常量定义</li>
<li>[优化] 提取通用 Spring ParameterizedTypeReference 定义</li>
<li>[优化] 自定义函数式接口 ListConverter 代码逻辑，去除 IDE 空值警告</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-10-02T17-50-41Z</li>
<li>[升级] loki docker 镜像版本升级至 3.2.0</li>
<li>[升级] promtail docker 镜像版本升级至 3.2.0</li>
<li>[升级] grafana docker 镜像版本升级至 11.2.2</li>
<li>[升级] zipkin docker 镜像版本升级至 3.4.2</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.37.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.773</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.28.21</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.31.3</li>
<li>[升级] hutool 版本升级至 6.0.0-M17</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.218.ALL</li>
<li>[升级] checker-qual 版本升级至 3.48.1</li>
<li>[升级] nacos-client 版本升级至 2.4.3</li>
<li>[升级] opengauss-jdbc 版本升级至 6.0.0-og</li>
<li>[升级] sweetalert2 版本升级至 11.14.2</li>
<li>[升级] vue webjars 版本升级至 3.5.12</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.223.ALL</li>
</ul>
</li>
</ul>
<h2>v3.3.4.1</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复分布式聚合数据字典前端代码在数据类型为字符串时取值错误问题。</li>
<li>[新增] 新增多种 NoSQL 数据源配置开启条件及注解</li>
<li>[重构] 重构核心消息模块，将物模型Mqtt 与系统默认 Mqtt 代码剥离，提升核心消息模块的独立性与适用性，减少代码间耦合。</li>
<li>[重构] 迁移 influxdb 相关模块至 stirrup-nosql 模块下</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 中央库发布插件修改为使用新版 sonatype central 专用 central-publishing-maven-plugin。同步修改 github action 脚本。fix: #IAUTB7</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-09-22T00-33-43Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] archetype-packaging 版本升级至 3.3.0</li>
<li>[升级] maven-archetype-plugin 版本升级至 3.3.0</li>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.7</li>
<li>[升级] guava 版本升级至 33.3.1</li>
<li>[升级] json-schema-validator 版本升级至 1.5.2</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.772</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.28.11</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.31.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.218.ALL</li>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.7.2</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.14.1</li>
<li>[升级] vue webjars 版本升级至 3.5.10</li>
<li>[升级] camunda 版本升级至 7.22.0-alpha6</li>
<li>[升级] sqlite-jdbc 版本升级至 3.46.1.3</li>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.7.2</li>
</ul>
</li>
</ul>
<h2>v3.3.4.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.3.4</li>
<li>[新增] 新增数据字典批量获取接口以及前端调用方式。</li>
<li>[新增] 新增前端数据字典批量去重功能及字典已获取判断功能，解决字典数据频繁或重复获取问题</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增物模型数据属性单位列表</li>
<li>[新增] 新增表单国内用法 label 组件</li>
<li>[修复] 优化前端数据字典数据加载机制，解决数据字典随用随取因读取时机错误导致的数据加载异常以及控制台抛错问题。</li>
<li>[修复] 修复前端批量获取字典去重校验错误问题</li>
<li>[修复] 修复前端树形输入组件初始值设置错误导致异常触发不必要的查询请求问题</li>
<li>[修复] 前端组件编译出现：Deprecation Warning: The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0 问题，fix: #IAS8HE</li>
<li>[优化] 重命名前端自定义数据字典类型定义</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-09-13T20-26-02Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] mapstruct-processor 版本升级至 1.6.2</li>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.6</li>
<li>[升级] commons-io 版本升级至 2.17.0</li>
<li>[升级] redisson 版本升级至 3.36.0</li>
<li>[升级] jetcache 版本升级至 2.7.7</li>
<li>[升级] fastjson2 版本升级至 2.0.53</li>
<li>[升级] mybatis-plus-boot-starter 版本升级至 3.5.8</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.772</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.28.3</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.31.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.208.ALL</li>
<li>[升级] weixin-java 版本升级至 4.6.5.B</li>
<li>[升级] camunda 版本升级至 7.22.0-alpha5</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.14.0</li>
<li>[升级] vue webjars 版本升级至 3.5.6</li>
<li>[升级] quasar 版本升级至 2.17.0</li>
<li>[升级] okio 版本升级至 3.9.1</li>
<li>[升级] snappy-java 版本升级至 1.1.10.7</li>
</ul>
</li>
</ul>
<h2>v3.3.3.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 2.4.2</li>
<li>[升级] 基于 Postgresql 作为存储的自主封装 Nacos 镜像升级至 2.4.2 并发布至 Docker Hub 和 Quay IO</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 前端优化自定义属性组件数据显示方法，解决指定数据节点时，输入框显示信息需要额外查询问题。</li>
<li>[优化] 去除前端无用的代码以及菜单信息，避免不要信息对使用和开发的干扰</li>
<li>[优化] 优化 ResourceResolver 资源定位器，增强更多环境的适应性。</li>
<li>[优化] 清理数据库初始化脚本中无用的菜单及菜单角色数据。</li>
<li>[新增] 新增物模型反序列化测试用例</li>
<li>[修复] 修复数据字典列表以及调用代码，在使用 hooks 方式时，控制台抛出错误问题。</li>
<li>[修复] 修复加密策略自动配置使用注解错误</li>
<li>[修复] 修复 Servlet 环境 OpenFeign 注入条件错误</li>
<li>[修复] 修复主工程 groupid 与实际包名不一致问题</li>
<li>[修复] 修复 Jackson2Utils 在非 Spring Boot 环境下使用，会抛出异常问题</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-08-29T01-40-52Z</li>
<li>[升级] loki 镜像版本升级至 3.1.1</li>
<li>[升级] promtail 镜像版本升级至 3.1.1</li>
<li>[升级] tempo 镜像版本升级至 2.6.0</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.771</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.27.21</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.30.11</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.200.ALL</li>
<li>[升级] Hutool 6.X 版本升级至 6.0.0-M16</li>
<li>[升级] Hutool 5.X 版本升级至 5.8.32</li>
<li>[升级] okhttps 版本升级至 4.0.3</li>
<li>[升级] weixin-java 版本升级至 4.6.4.B</li>
<li>[升级] sms4j 版本升级至 3.3.3</li>
<li>[升级] quasar 版本升级至 2.16.11</li>
<li>[升级] sweetalert2 版本升级至 11.13.3</li>
<li>[升级] vue 版本升级至 3.5.3</li>
<li>[升级] checker-qual 版本升级至 3.47.0</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.18.1</li>
</ul>
</li>
</ul>
<h2>v3.3.3.1</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增分布式枚举数据字典聚合功能，基于 Spring Customizer 模式设计支持跨模块定义。微服务架构和单体架构均支持。</li>
<li>[新增] 新增数据字典“随用随取”模式，去除原有登录时全部加载至前端模式</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 从主工程迁移部分代码至核心代码库，提升代码的内聚性。</li>
<li>[重构] 重构核心类 ServiceContextHolder，调用时不再必须使用 getInstance() 方法</li>
<li>[重构] 前端工程主要组件不再采用 AutoImport 方式，改为传统手工导入，代码逻辑更清晰。</li>
<li>[重构] 重构前端数据字典以及后端枚举值显示方式，去除之前使用的重复代码。</li>
<li>[修复] 修复主工程部分模块包名不统一错误</li>
<li>[修复] 修复手动关闭动态认证监听逻辑错误</li>
<li>[修复] 修复数据字典前端支持顺序错误问题</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-08-29T01-40-52Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] software.amazon.awssdk 版本升级至 2.27.15</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.30.9</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.190.ALL</li>
</ul>
</li>
</ul>
<h2>v3.3.3.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.3.3</li>
<li>[升级] Spring Authorization Server 版本升级至 1.3.2</li>
<li>[升级] Nacos 版本升级至 2.4.1</li>
<li>[升级] 基于 Postgresql 作为存储的自主封装 Nacos 镜像升级至 2.4.1 并发布至 Docker Hub 和 Quay IO</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增物联网相关页面初始化数据</li>
<li>[修复] 修复前端因升级 Sass 版本，运行时出现 Sass’s behavior for declarations that appear after nested rules will be changing to match the behavior specified by CSS in an upcoming version. 告警问题 fix: #IAKN93</li>
<li>[修复] 修复包含占位符 {} 的接口，例如：/iot/product/{id}，在线动态修改权限始终不会生效，一直报没有权限错误。</li>
<li>[修复] 修复涉及分页的 REST 接口，数字类型参数使用的 validation 校验注解错误。</li>
<li>[修复] 修复仅分布式环境使用的 Bus 消息代码，在单体式环境仍旧会配置问题</li>
<li>[重构] 重构前端组件代码放置目录和位置，提升代码放置合理性以及可快速定位性</li>
<li>[重构] 将 message-module-ability 模块合并至 message-core 模块。</li>
<li>[重构] 将基础消息 Starter 从核心库组件依赖迁移至主工程，方便用户根据实际切换消息队列以及相关组件。</li>
<li>[重构] 提取 OAuth2 相关公共消息组件 oauth2-module-message，提升消息相关代码在更多场景的适应性和使用便捷性。</li>
<li>[重构] 提取物联网相关代码和模块至独立工程，减少代码间相互干扰，保持主工程及核心代码库的独立性</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-08-17T01-24-54Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] guava 版本升级至 33.3.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.27.10</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.30.8</li>
<li>[升级] sqlite-jdbc 版本升级至 3.46.1.0</li>
<li>[升级] vue webjars 版本升级至 3.4.38</li>
<li>[升级] quasar webjars 版本升级至 2.16.9</li>
<li>[升级] minio 版本升级至 8.5.12</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.770</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.183.ALL</li>
<li>[升级] redisson 版本升级至 3.35.0</li>
</ul>
</li>
</ul>
<h2>v3.3.2.2</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增物模型 TSL 核心实体及 JSON 解析</li>
<li>[新增] 新增物模型数据接收并转换为 ApplicationEvent 机制。</li>
<li>[新增] 新增物联网业务逻辑模块</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 Emqx 自动配置错误，导致代码中 ClientManager Bean 飘红问题</li>
<li>[修复] 修复 Emqx Webhook 转成 ApplicationEvent 配置错误</li>
<li>[优化] 优化消息系统消息发送事件命名，清晰事件用途和定位。</li>
<li>[优化] 优化Event Integration 配置，定义统一使用的 ApplicationEvent消息发送配置，减少重复定义。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-08-03T04-33-23Z</li>
<li>[升级] kafka docker 镜像版本升级至 3.8.0</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] mapstruct-processor 版本升级至 1.6.0</li>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.5</li>
<li>[升级] influxdb-client 版本升级至 7.2.0</li>
<li>[升级] skywalking 相关 Agent 版本升级至 9.3.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.27.5</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.30.8</li>
<li>[升级] hutool 6.x 版本升级至 6.0.0-M15</li>
<li>[升级] hutool 5.x 版本升级至 5.8.31</li>
<li>[升级] camunda 版本升级至 7.22.0-alpha4</li>
<li>[升级] sms4j 版本升级至 3.3.2</li>
<li>[升级] blaze-persistence 版本升级至 1.16.12</li>
<li>[升级] quasar webjars 版本升级至 2.16.8</li>
<li>[升级] sweetalert2 版本升级至 11.12.4</li>
<li>[升级] vue 版本升级至 3.4.37</li>
<li>[升级] checker-qual 版本升级至 3.46.0</li>
<li>[升级] snappy-java 版本升级至 1.1.10.6</li>
</ul>
</li>
</ul>
<h2>v3.3.2.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.3.3</li>
<li>[升级] 系统代码完成在最新版 Redis 7.4.0 环境运行验证。</li>
<li>[重构] 对象存储代码全部变更为使用 AWS S3 V2。</li>
<li>[重构] 不再使用单独的 OSS 工程，核心对象存储模块合并至核心组件库工程</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 因API变化和差异较大，不再使用原有适配多厂商模式。</li>
<li>[重构] 重新封装对象存储操作 API，同时支持响应式和阻塞式环境，可根据依赖动态切换。</li>
<li>[修复] 修复单体版本对象存储与响应式环境对象存储不兼容问题，导致无法使用同一界面进行操作问题。</li>
<li>[修复] 修复 Docker 环境变量错误，导致服务镜像打包失败问题</li>
<li>[重构] 删除无用的对象存储依赖。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-07-29T22-14-52Z</li>
<li>[升级] kafka docker 镜像版本升级至 3.8.0</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] sms4j 版本升级至 3.3.0</li>
<li>[升级] redisson 版本升级至 3.34.1</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.767</li>
<li>[升级] logstash-logback-encoder 版本升级至 8.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.26.27</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.30.5</li>
<li>[升级] sqlite-jdbc 版本升级至 3.46.0.1</li>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.7.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.158.ALL</li>
<li>[升级] sweetalert2 版本升级至 11.12.3</li>
<li>[升级] vue 版本升级至 3.4.34</li>
</ul>
</li>
</ul>
<h2>v3.3.2.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.3.2</li>
<li>[升级] Spring Cloud Alibaba 版本升级至 2023.0.1.2</li>
<li>[升级] Nacos 版本升级至 2.4.0</li>
<li>[升级] Debezium 相关组件版本升级至 2.7</li>
<li>[新增] 新增基于 Aws SDK V2 版本的、响应式对象存储适配支持</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[发布] 基于 Postgresql 的、重新打包的 Nacos Server 2.4.0 已上传。</li>
<li>[重构] 大幅调整 OSS 工程各模块定位、实现机制以及代码，重新定义统一调用定义及实现。</li>
<li>[重构] 重构和调整 OSS 各模块自动配置机制，自动适配阻塞式和响应式环境。</li>
<li>[重构] 重构 OSS 代码，修正包名、模块名不规范，与工程 groupId 不一致问题。</li>
<li>[重构] 恢复 Sentinel Dashboard 控制台懒加载配置</li>
<li>[修复] 修复升级至 Nacos 2.4.0 运行抛出 <code>java.lang.IllegalStateException: Could not initialize Logback Nacos logging from classpath:nacos-logback14.xml</code> 问题。fix: #IAECYY</li>
<li>[修复] 修复升级至 Nacos 2.4.0 运行抛出 <code>[*][variable] already has an associated action supplierl</code> 问题。fix: #IAECZ5</li>
<li>[修复] Sentinel 适配新版 sentinel-spring-webmvc-v6x-adapter</li>
<li>[升级] liberica 基础镜像版本升级至 21.0.4-9</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-07-16T23-46-41Z</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.762</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.26.21</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.30.0</li>
<li>[升级] redisson 版本升级至 3.33.0</li>
<li>[升级] fastjson2 版升级至 2.0.52</li>
<li>[升级] weixin-java 版本升级至 4.6.3.B</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.134.ALL</li>
</ul>
</li>
<li>注意事项
<ul>
<li>Nacos 2.4.0 版本做了很多的改进和提升。但是当前版本还存在较多问题，已知问题已提交至：<a href="https://github.com/alibaba/nacos/issues/12387" target="_blank" rel="noopener noreferrer">https://github.com/alibaba/nacos/issues/12387</a>。所以，还请谨慎使用。</li>
</ul>
</li>
</ul>
<h2>v3.3.1.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2023.0.3</li>
<li>[新增] 新增 QueryDsl 和 Blaze Persistence 支持，提升 JPA 查询语句编写便捷性</li>
<li>[新增] 新增第三方社会化账号登录手动绑定功能</li>
<li>[新增] 新增第三方社会化信息可根据服务配置自动生成功能</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增社会化账号绑定接口定义。</li>
<li>[修复] 修复图形验证码字体类型后缀编写错误</li>
<li>[修复] 修复外部登录接入错误代码没有配置导致 Access 相关模块自定义错误码不生效问题</li>
<li>[修复] 修复短信验证码登录相关配置开启条件错误问题。</li>
<li>[修复] 修复外部登录接入错误以及相关配置变更。</li>
<li>[修复] 修复在新版 Apache Maven 环境下，编译代码出现 audience-annotations 相关告警问题</li>
<li>[修复] 修复兼容 RSocket 和 WebSocket 的统一用户信息鉴定器类型校验错误，导致 Servlet WebSocket 环境下抛出转型错误问题。</li>
<li>[修复] 修复 Servlet WebSocket 模式下实时在线用户统计接口提示404问题。</li>
<li>[重构] 重构 autoconfigure 模块自动配置代码日志规范输出，以与 starter 相关模块输出日志进行区分。</li>
<li>[重构] 日志中心相关内容通用性较强，合并至 core-autoconfigure 增强其通用性以及不同环境的适配性。</li>
<li>[重构] 重构第三方社会化账号登录定义接口，去除无意义的 Exception 抛出标识</li>
<li>[重构] 合并社会化登录模块和代码相关模块合并至 assistant-module-access，减少不必要的代码模块拆分。重构相关代码修改为采用更符合 Spring 规范的用法。</li>
<li>[重构] 加密处理配置移动至 core-autoconfigure，改用自动配置方式提升加解密处理便捷性</li>
<li>[优化] 优化 Loki 日志记录补充 Label 形式的 traceId 和 spanId</li>
<li>[优化] 优化 Micrometer 轻量升级链路追踪和度量模块，Zipkin 和 Prometheus 解决不同场景下的链路追踪拆不同模块依赖问题</li>
<li>[升级] 更新 Antisamy XSS 防护配置</li>
<li>[升级] 加强 Docker Compose 服务启动顺序控制检查的准确性，以保证服务按照正确顺序启动</li>
<li>[升级] Kafka 镜像版本升级至 3.7.1</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-07-10T18-41-49Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] antisamy 版本升级至 1.7.6</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.760</li>
<li>[升级] loki-logback-appender 版本升级至 1.5.2</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.26.19</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.30.0</li>
<li>[升级] hutool 版本升级至 6.0.0-M14</li>
<li>[升级] hutool 5.x 版本升级至 5.8.29</li>
<li>[升级] camunda 版本升级至 7.22.0-alpha3</li>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.7.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.134.ALL</li>
<li>[升级] quasar webjars 版本升级至 2.16.6</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.12.2</li>
<li>[升级] checker-qual 版本升级至 3.45.0</li>
<li>[升级] mysql 版本升级至 9.0.0</li>
</ul>
</li>
</ul>
<h2>v3.3.1.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos Client 版本升级至 2.3.3</li>
<li>[升级] Spring Cloud Tencent 版本升级至 1.4.0-2023.0.0-RC2</li>
<li>[新增] 新增支持发送基于 Thymeleaf 模版的定制电子邮件消息功能。</li>
<li>[新增] 新增支持发送包含内联图片以及附件电子邮件消息</li>
<li>[新增] 新增用户登录常用位置记录和IP地址检测。</li>
<li>[新增] 新增用户登录常用位置异常或变更发送提醒邮件</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复发送电子邮件消息参数传递错误导致抛错问题</li>
<li>[修复] 修复新版本用户操作信息记录数据保存错误。</li>
<li>[重构] 重构系统用户登录登出记录代码，采用事件机制解耦部分代码</li>
<li>[重构] 调整 logic-core-sas 模块</li>
<li>[重构] 微信小程序相关代码适配最新 Wxjava。获取手机号码API修改为使用 code 方式。</li>
<li>[优化] 优化基于 Spring Integration 的 Email 自动注入条件配置</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-06-29T01-20-47Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] springdoc 版本升级至 2.6.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.754</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.26.13</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.29.25</li>
<li>[升级] wxjava 版本升级至 4.6.2.B</li>
<li>[升级] aliyun-sdk-oss 版本升级至 3.18.0</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.12.1</li>
<li>[升级] vue webjars 版本升级至 3.4.31</li>
</ul>
</li>
</ul>
<h2>v3.3.1.1</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增使用 Docker Compose 启动服务顺序控制</li>
<li>[新增] 新增使用 Docker Compose 打包镜像多系统环境支持</li>
<li>[新增] 新增响应式版本 Docker 演示系统以及部署说明</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 pom 依赖名字错误导致 commons-logging 未被剔除错误</li>
<li>[修复] 修复使用 Docker Compose 打包镜像启动出错问题</li>
<li>[修复] 修复 Spring Boot Admin 数据不会上报错误</li>
<li>[修复] 修复响应式服务无法连接 Spring Boot Admin 问题。</li>
<li>[优化] 系统使用关键镜像同步至 <a href="http://Quay.IO" target="_blank" rel="noopener noreferrer">Quay.IO</a> 方便使用。</li>
<li>[优化] Spring Boot Admin 相关依赖修改为 pom import 方式</li>
<li>[优化] 优化响应式服务 XSS 防护拦截策略，不对 /actuator 相关地址做处理，减少额外消耗</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-06-22T05-26-45Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.748</li>
<li>[升级] minio 版本升级至 8.5.11</li>
<li>[升级] redisson 版本升级至 3.32.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.26.7</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.29.25</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.113.ALL</li>
<li>[升级] nimbus-jose-jwt 版本升级至 9.40</li>
</ul>
</li>
</ul>
<h2>v3.3.1.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.3.1</li>
<li>[升级] Spring Authorization Server 版本升级至 1.3.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增邮件发送模块</li>
<li>[重构] 重构 WebSocket 以及 RSocket 模块，提取在线用户统计共用定义，减少代码重复</li>
<li>[重构] RSocket Integration 定义及配置代码合并至 message-autoconfigure 模块中，提升代码可插拔性</li>
<li>[重构] Mqtt Integration 定义及配置代码合并至 message-autoconfigure 模块中，提升代码可插拔性</li>
<li>[重构] 重新调整 Message 相关代码模块，合并 Message 和 Event</li>
<li>[重构] 重构物联网设备和 Emqx 相关模块</li>
<li>[修复] 修复部分代码包导入错误</li>
<li>[修复] 修复 herodotus/sentinel-dashboard 运行报错 no main manifest attribute, in sentinel-dashboard.jar fix: #IA6J53</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.747</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.26.6</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.29.24</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.110.ALL</li>
</ul>
</li>
</ul>
<h2>v3.3.0.6</h2>
<ul>
<li>主要更新
<ul>
<li>[优化] 自主封装镜像变更为 <a href="http://Quay.Io" target="_blank" rel="noopener noreferrer">Quay.Io</a>，解决 Docker Hub 无法访问问题。</li>
<li>[优化] 变更 Maven Central 认证方式，解决 Maven Central Deploy 401 （Maven Central account migration）问题。</li>
<li>[优化] 增加基于 Github Action 的自动发布配置</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-06-13T22-53-53Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.744</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.26.3</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.29.23</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.104.ALL</li>
<li>[升级] xnio 版本升级至 3.8.16.Final</li>
</ul>
</li>
</ul>
<h2>v3.3.0.5</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.3.2</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增证书生成工具代码</li>
<li>[新增] 全新设计内置授权码模登录页面。</li>
<li>[新增] 实现授权码模式登录页面数据加密传输体系与“一人一码”加密体系的统一。</li>
<li>[新增] 新增忘记密码、用户注册页面的可定制化。配置了自定义忘记密码、用户注册页面，授权码模式页面会自动显示相关连接</li>
<li>[新增] 新增 NO_RESOURCE_FOUND_EXCEPTION 类型错误转换</li>
<li>[优化] 优化自定义 Spring Authorization Server 授权确认页面。选择了对应的 Scope 后，确认按钮才显示为可用状态。</li>
<li>[优化] 去除无用的 Webjars 依赖</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-06-11T03-13-30Z</li>
<li>[升级] 封装的 Sentinel Dashboard 镜像版本升级至 1.8.8</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.741</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.26.0</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.29.20</li>
<li>[升级] camunda 版本升级至 7.22.0-alpha2</li>
<li>[升级] mybatis-plus-boot-starter 版本升级至 3.5.7</li>
<li>[升级] mybatis-plus-generator 版本升级至 3.5.7</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.99.ALL</li>
<li>[升级] sweetalert2 版本升级至 11.11.1</li>
</ul>
</li>
</ul>
<h2>v3.3.0.3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.3.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化 JetCache 改为 Bom Import 模式</li>
<li>[优化] 优化 Hutool 改为 Bom Import 模式</li>
<li>[优化] 优化全局错误输出，在存在异常时，日志中输出错误信息</li>
<li>[优化] 补充 Spring Authorization Server 新增认证模式以及客户端授权方法</li>
<li>[修复] 修复系统架构参数条件注解默认值设置错误</li>
<li>[修复] 修复在新版本环境下，自定义数组转字符串反序列化错误，导致 OAuth2Application 管理操作失败。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] guava 版本升级至 33.2.1-jre</li>
<li>[升级] jetcache 版本升级至 2.7.6</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.735</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.25.65</li>
<li>[升级] fastjson2 版本升级至 2.0.51</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.95.ALL</li>
<li>[升级] checker-qual 版本升级至 3.44.0</li>
</ul>
</li>
</ul>
<h2>v3.3.0.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2023.0.2</li>
<li>[新增] 新增 Token Exchange 授权模式选项</li>
<li>[新增] 新增 Kafka 3 Docker Compose 配置，无需再额外 Zookeeper（如果要使用 Debezium，建议使用 Debezium 套件，这种情况下还需要启动 Zookeeper。目前还没有找到 Debezium Kafka 单点环境脱离 Zookeeper 的方案）</li>
<li>[新增] 新增 opengauss 数据库支持</li>
<li>[新增] 新增 Nacos 鉴权配置。系统默认在 Nacos 鉴权环境运行。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 按照最新版 Spring Authorization Server 重构自定义授权模式以及扩展已有授权模式代码</li>
<li>[修复] 修复授权服务器 Security Security 没有捕获不会按照统一格式输出问题</li>
<li>[修复] 修复 Client Credentials 模式，Scope 授权接口校验越权问题</li>
<li>[优化] 优化 Spring Data 相关配置，采用最新配置替换已过时配置</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-05-28T17-19-04Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] redisson 版本升级至 3.31.0</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.733</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.25.63</li>
<li>[升级] hutool 5.X 版本升级至 5.8.28</li>
<li>[升级] hutool 版本升级至 6.0.0-M13</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.86.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.19</li>
<li>[升级] sqlite-jdbc 版本升级至 3.46.0.0</li>
<li>[升级] vue webjars 版本升级至 3.4.27</li>
</ul>
</li>
</ul>
<h2>v3.3.0.1</h2>
<ul>
<li>主要更新
<ul>
<li>[优化] 提取系统级常量，归置在一处方便管理</li>
<li>[优化] 优化父级模块 pom 中 module 的配置</li>
<li>[优化] 补充各模块用途描述，便于在中央仓库中显示具体用途</li>
</ul>
</li>
</ul>
<h2>v3.3.0.0</h2>
<ul>
<li><code>Spring Boot</code> 已升级至 3.3.0</li>
<li><code>Spring Authorization Server</code> 已升级至 1.3.0</li>
<li>全面采用 Java 21，默认开启虚拟线程，以改善阻塞操作的处理降低系统资源的消耗</li>
<li>支持传统的 <code>阻塞式</code> 微服务与基于 <code>Reactor</code> 和 <code>WebFlux</code> 的 <code>响应式</code> 微服务同时运行在一套系统之中</li>
<li>不强制使用 <code>响应式</code> 方式开发，可根据自身项目对资源吞吐量、资源消耗、特殊功能性能保障的需求，灵活的选择是采用 <code>响应式</code> 还是 <code>阻塞式</code> 来开发对应的服务。</li>
<li>在保持 Dante Cloud 原有 <code>Spring Authorization Server</code> 深度扩展的各种特性的前提下，实现 <code>响应式</code> 服务的动态鉴权与现有体系的完全融合（无需在代码中使用 <code>@PreAuthorize</code> 写死权限，全部通过后台动态管理）</li>
<li>向“响应式编程”转变，基于 <code>Reactor</code> 重构大量核心代码，进一步提升本系统代码质量和运行效能</li>
<li>重新架构所有核心组件模块，进一步降低各模块的耦合性，减少第三方组件依赖深度，简化各模块使用的复杂度，使用更贴近 Spring Boot 生态官方写法，提升模块组件的可插拔性以及 <code>响应式</code> 和 <code>阻塞式</code> 不同环境下自动配置的适配能力</li>
<li>实现 <code>响应式</code> 和 <code>阻塞式</code> 不同类型服务，Session 共享体系以及自定义 Session 体系的完美融合（谁说微服务就一定用不到 Session ：））。</li>
<li>新增 <code>GRPC</code> 服务间调用和通信方式，系统核心服务间调用支持 <code>OpenFeign</code> 和 <code>GRPC</code> 两种方式，可通过修改配置实现两种方式的切换。</li>
<li>基于 <code>RSocket</code> 全面重写 <code>WebSocket</code> 消息系统，实现 <code>WebSocket</code> 的 <code>响应式</code> 改造以及 <code>RSocket</code> 与 Spring Security 体系的全面集成。支持多实例、跨服务的私信和广播</li>
<li>新增 OAuth2 独立客户端，可用于客户端动态注册以及授权码模式</li>
<li>新增基于 <code>Loki + Grafana</code> 生态的轻量级日志中心和链路追踪解决方案，使用 OSS 作为数据存储，极大地降低资源需求，可作为原有 Skywalking 和 ELK 重量级体系的备选方案，根据实际需要切换。</li>
<li>开放纯手写动态表单功能。可实现BPMN、动态表单、Camunda 流程引擎的串联，实现工作流程运转（目前仅支持简单工作流）</li>
<li>开放包含自定义属性面板的 BPMN 在线设计器功能。</li>
<li>开放物联网设备认证和管理模块，支持基于 Emqx 的物联网设备通信和管理。</li>
<li>开放阿里云内容审核、百度 OCR、环信、Emqx、天眼查、Nacos、PolarisMash等第三方 OpenApi 封装模块</li>
<li>前端工程支持 Docker 运行，相关参数可通过配置环境变量修改。已上传至 Docker Hub，可以直接下载运行。</li>
</ul>
<h2>v3.3.0.0-RC5</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 修复 Spring Authorization Server 自定义登录页面静态内容 webjars 加载错误</li>
<li>[修复] 修复 p6spy 消息模型类配置错误</li>
<li>[修复] 修复内置授权码登录页面，控制台抛错错误问题</li>
<li>[修复] 修复内置授权码登录页面脚本依赖模块丢失问题</li>
<li>[修复] 修复接口扫描条件配置默认值错误导致系统登录没有权</li>
<li>[优化] 优化数据加解密逻辑，在 session 不统一环境，加解密逻辑不执行直接返回原文。</li>
<li>[优化] 明确抛错错误类型。新增 PlatformException 主要用于非 RuntimeException，确保 Exception 使用合理规范。</li>
<li>[优化] 优化 Stamp Exception，统一修改为 Exception 类型。</li>
<li>[优化] 优化 ServiceContextHolderBuilder 配置，减少在为必要环境必须要注入配置问题。</li>
<li>[优化] 优化 oauth2-core 模块依赖，减少模块的过度依赖引起 core-autoconfigure 比必要的自动配置。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] software.amazon.awssdk 版本升级至 2.25.55</li>
<li>[升级] camunda 版本升级至 7.22.0-alpha1</li>
<li>[升级] aws-java-sdk-s3 版本升级至 1.12.725</li>
<li>[升级] influxdb-client 版本升级至 7.1.0</li>
<li>[升级] fastjson2 版本升级至 2.0.50</li>
<li>[升级] quasar webjars 版本升级至 2.16.4</li>
<li>[升级] alipay-sdk-java 版本升级至 4.39.74.ALL</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>v3.4.X</title>
      <link>https://www.herodotus.cn/logs/3.4.html</link>
      <guid>https://www.herodotus.cn/logs/3.4.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">v3.4.X</source>
      <description>v3.4.6.0 主要更新 [升级] Spring Boot 版本升级至 3.4.6 其它更新 [新增] 新增组合文件管理器定义，以支持不同用途文件的本地及对象存储组合管理。同时解决原有 FileTemplate 和 FileTransformer 定义逻辑不够清晰问题。 [新增] 新增 JsonSchema 默认组合文件管理器，并在 core-aut...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v3.4.6.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.4.6</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增组合文件管理器定义，以支持不同用途文件的本地及对象存储组合管理。同时解决原有 FileTemplate 和 FileTransformer 定义逻辑不够清晰问题。</li>
<li>[新增] 新增 JsonSchema 默认组合文件管理器，并在 core-autoconfigure 模块中默认注入，以保证代码正确运行。</li>
<li>[新增] 新增平台级 JsonSchema 组合文件存储管理器定义。</li>
<li>[修复] 修复证书工厂测试用例执行错误问题</li>
<li>[重构] 重构 FileTemplate 和 FileTransformer 定义，减少不必要的方法定义和交互。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.227.ALL</li>
<li>[升级] influxdb-client 版本升级至 7.3.0</li>
<li>[升级] redisson 版本升级至 3.47.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.48</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.3</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.21.2</li>
<li>[升级] json 版本升级至 20250517</li>
<li>[升级] weixin-java 版本升级至 4.7.5-20250522.150738</li>
</ul>
</li>
</ul>
<h2>v3.4.5.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.4.6</li>
<li>[升级] 适配 Redis 8.0.1</li>
<li>[升级] Kafka 版本升级至 4.0.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增占位符 ${} 模版字符串替换及逆向解析工具类。</li>
<li>[新增] 新增物联网物模型属性基于 Json Schema 校验。</li>
<li>[修复] 修复微服务版本在 WebFlux 环境下实时在线用户显示异常问题</li>
<li>[修复] Controller 返回实体对于查询为空的数据不再返回 204 状态码，统一修改为返回 200 状态。修复 204 状态码导致前端出现请求 Cancel 问题。</li>
<li>[优化] 优化 Kafka Docker Compose 配置</li>
<li>[重构] 重构 JetCache 缓存工具类，增加重载方法，调整 synclocal 和 cacheNullValue 参数顺序。</li>
<li>[重构] 重构 JetCacheUtils 方法，直接调用 JetCacheCreateCacheFactory 基础方法以保证参数默认值与系统配置统一。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] antisamy 版本升级至 1.7.8</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.217.ALL</li>
<li>[升级] archetype-packaging 版本升级至 3.4.0</li>
<li>[升级] maven-archetype-plugin 版本升级至 3.4.0</li>
<li>[升级] sms4j-spring-boot-starter 版本升级至 3.3.5</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.45</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.3</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.21.1</li>
<li>[升级] weixin java 版本升级至 4.7.5-20250516.201941</li>
<li>[升级] webauthn4j 版本升级至 0.29.2.RELEASE</li>
<li>[升级] hutool 5.X 版本升级至 5.8.38</li>
</ul>
</li>
</ul>
<h2>v3.4.5.1</h2>
<ul>
<li>主要更新
<ul>
<li>[优化] 前端工程支持模块化发布至私有仓库，方便模块代码多工程共享</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] SecurityResources 类改为接口，定义系统安全资源常量，统一修改魔法值。</li>
<li>[修复] 修复前端条件搜索传递空值属性导致查询结果异常问题。</li>
<li>[修复] 修复核心基础 Controller 分页方法对于 Page 和 Slice 区分不准确，导致前端列表操作异常问题。</li>
<li>[升级] zipkin docker 镜像版本升级至 3.5.1</li>
<li>[升级] kafka docker 镜像版本升级至 4.0.0</li>
<li>[升级] grafana docker 镜像版本升级至 12.0.0</li>
<li>[升级] clickhouse docker 镜像版本升级至 25.4.2</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.186.ALL</li>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.9.10</li>
<li>[升级] jetcache 版本升级至 2.7.8</li>
<li>[升级] mybatis-plus 版本升级至 3.5.12</li>
<li>[升级] mysql 版本升级至 9.3.0</li>
<li>[升级] protobuf-bom 版本升级至 3.25.7</li>
<li>[升级] redisson 版本升级至 3.46.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.35</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.1</li>
<li>[升级] springdoc 版本升级至 2.8.8</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.21.0</li>
<li>[升级] webauthn4j 版升级至 0.29.1.RELEASE</li>
<li>[升级] checker-qual 版本升级至 3.49.3</li>
</ul>
</li>
</ul>
<h2>v3.4.5.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.4.5</li>
<li>[升级] Spring Authorization Server 版本升级至 1.4.3</li>
<li>[升级] Debezium 版本升级至 3.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增物联网物模型模块管理，一个产品支持多个物模型模块。</li>
<li>[新增] 新增 RestClient 基础操作模版类，以 RestClient 作为系统内部核心客户端实现接口调用。</li>
<li>[重构] 规范化 MongoDB 模块、包和类的命名，模块和包使用 mongdb 命名，类使用 mongo 命名，与 Spring Data MongoDB 命名规范统一。</li>
<li>[重构] 规范和统一 Spring Data Module 各模块基础类命名</li>
<li>[重构] 前端工程将系统内需要灵活配置的信息及代码统一归并至 configurations 目录，方便使用前端工程时统一进行修改。</li>
<li>[重构] 物模型单位列表重构完成，支持 MongoDB 数据存储。</li>
<li>[重构] 物模型基础核心定义模块重构完</li>
<li>[修复] 修复各 Spring Data Module 统一转换器定义错误</li>
<li>[优化] 提取多 Spring Data Module 通用属性基础转换器，提升多 Spring Data Module 环境下不同数据源实体与统一定义实体转换的便捷性。</li>
<li>[优化] 优化 Emqx OpenApi 模块，新增阻塞式接口封装。</li>
<li>[优化] 统一系统依赖 joda-time 版本，去该依赖不同版本 jar 的引入。</li>
<li>[优化] 产品品类去除树形结构设计，增加应用场景预留字段。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-04-22T22-12-26Z</li>
<li>[升级] liberica openjdk 基础镜像版本升级至 21.0.7-9</li>
<li>[升级] grafana 镜像版本升级至 11.6.0</li>
<li>[升级] loki 镜像版本升级至 3.5.0</li>
<li>[升级] promtail 镜像版本升级至 3.5.0</li>
<li>[升级] tempo 镜像版本升级至 2.7.2</li>
<li>[升级] cassandra 镜像版本升级至 5.0.4</li>
<li>[升级] emqx 镜像版本升级至 5.8.6</li>
<li>[升级] clickhouse-server 镜像版本升级至 25.4.1</li>
<li>[升级] tdengine 镜像版本升级至 3.3.6.3</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.7.5</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.165.ALL</li>
<li>[升级] bootstrap 版本升级至 5.3.5</li>
<li>[升级] commons-collections4 版本升级至 4.5.0</li>
<li>[升级] commons-io 版本升级至 2.19.0</li>
<li>[升级] commons-text 版本升级至 1.13.1</li>
<li>[升级] grpc-bom 版本升级至 1.72.0</li>
<li>[升级] guava 版本升级至 33.4.8-jre</li>
<li>[升级] protobuf-bom 版本升级至 3.25.6</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.26</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.1</li>
<li>[升级] sweetalert2 版本升级至 11.19.1</li>
<li>[升级] okio 版本升级至 3.11.0</li>
<li>[升级] error_prone_annotations 版本升级至 2.38.0</li>
</ul>
</li>
</ul>
<h2>v3.4.4.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Camunda 版本升级至 7.23.0，同步升级 Camunda SQL 脚本及 Openapi.json</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[优化] 优化使用 alipay-sdk-java 依赖向工程中引入 bcprov-jdk15on 和 commons-logging 问题。</li>
<li>[优化] 优化部分依赖组件的版本，以统一系统中依赖组件版本，减少同一组件不同版本的引入</li>
<li>[优化] 优化 Swagger @Schema 注解名称属性统一设定为 name</li>
<li>[优化] 完善部分基础类的属性注释说明</li>
<li>[修复] 修复前端 Base 路径配置不够合理，导致在 Nginx 环境下使用浏览器刷新会定位到错误的资源。</li>
<li>[重构] 重构 Emqx 系统信息 Event 及实体代码，去除无用的接口定义。</li>
<li>[重构] 重构 Emqx 系统关键事件 Event 和 Webhook 两种模式对应实体，匹配最新版本 Emqx 定义。</li>
<li>[重构] 调整 Emqx 条件注解所在模块位置，提升代码使用的通用性和便捷性。</li>
<li>[重构] 将 Emqx 通用代码从 message-core 中提取出来作为独立模块，减少 message-core 模块非通用代码量。</li>
<li>[重构] 重构基础 Controller 和业务逻辑代码，支持不同数据源的切换。</li>
<li>[重构] 物联网设备连接状态详情重构完成</li>
<li>[重构] 物联网设备动态注册和激活监听器代码重构完成</li>
<li>[新增] 新增物联网数据格式及规范统一处理工具类</li>
<li>[新增] 新增物联网业务数据存储 Jpa 和 MongoDB 介质切换，以便在特殊场景提升数据查询性能</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-04-08T15-41-24Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.133.ALL</li>
<li>[升级] camunda 版本升级至 7.23.0</li>
<li>[升级] guava 版本升级至 33.4.7</li>
<li>[升级] hutool 版本升级至 6.0.0-M21</li>
<li>[升级] hutool 5.X 版本升级至 5.8.37</li>
<li>[升级] logstash-logback-encoder 版本升级至 8.1</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.19</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.1</li>
<li>[升级] weixin-java 版本升级至 4.7.4.B</li>
<li>[升级] webauthn4j 版本升级至 0.29.0.RELEASE</li>
<li>[升级] checker-qual 版本升级至 3.49.2</li>
</ul>
</li>
</ul>
<h2>v3.4.4.1</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 大量重构系统代码：简化数据层核心代码层次，消除重复代码，进一步提升代码质量及易维护性</li>
<li>[升级] Skywalking 版本升级至 10.2.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 补充支付宝社会化登录所需相关依赖，解决找不到 class 问题。</li>
<li>[重构] OpenApi 相关模块 Rest 前缀由 /api 修改为 /openapi</li>
<li>[重构] datastore 相关模块名称变更为persistence</li>
<li>[重构] 重构所有模块 constant 包命名，去除结尾 s 与其它包名保持一致</li>
<li>[重构] 改用 JustAuth 最新支持的第三方系统社交登录 API，去除被标记为已过时系统或API</li>
<li>[重构] 统一 MongoDB 相关模块代码类命名</li>
<li>[重构] 优化系统核心 Entity、Dto 和 Domain 基础定义，进一步明晰各基础类应用场景和用途。删除无用的基础抽象定义类。</li>
<li>[重构] 提取 Spring Data 生态通用 Service 抽象定义，便于后续开发中使用统一的 Service 接口支持异构数据源的切换。</li>
<li>[重构] 简化基础 Service 和 Controller，减少过多的抽象层次定义。提取重复的接口实现代码，同时支持 Page 和 Slice 方式分页以及 Mongodb、Cassandra、JPA 等多种存储</li>
<li>[重构] 重构基础 Controller 方法名称，以减少不必要的误解和混乱</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.112.ALL</li>
<li>[升级] blaze-persistence 版本升级至 1.6.15</li>
<li>[升级] fastjson2 版本升级至为 2.0.57</li>
<li>[升级] guava 版本升级至 33.4.6</li>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.9.9</li>
<li>[升级] jsonschema-generator 版本升级至 4.38.0</li>
<li>[升级] mybatis plus 版本升级至 3.5.11</li>
<li>[升级] skywalking agent 组件版本升级至 9.4.0</li>
<li>[升级] springdoc 版本升级至 2.8.6</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.12</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.37.0</li>
</ul>
</li>
</ul>
<h2>v3.4.4.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.4.4</li>
<li>[升级] Spring Cloud 版本升级至 2024.0.1</li>
<li>[升级] Nacos 版本升级至 2.5.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复使用 Aws SDK V2 异步上传文件出现异常问题</li>
<li>[优化] 改用 Aws SDK V2 高性能客户端替代原有传统文件上传方式。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-03-12T18-04-18Z</li>
<li>[升级] clickhouse 镜像版本升级至 25.2.2</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] guava 版本升级至 33.4.5-jre</li>
<li>[升级] redisson 版本升级至 3.45.1</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.5</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.36.3</li>
<li>[升级] weixin-java 版本升级至 4.7.3.B</li>
<li>[升级] error_prone_annotations 版本升级至 2.37.0</li>
</ul>
</li>
</ul>
<h2>v3.4.3.3</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 系统演示环境上线</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 将授权服务器和资源服务所有异常处理归并至门面配置类中，实现 HttpSecurity 错误配置统一化调用。减少 SAS 核心配置需要注入过多 Bean 问题。</li>
<li>[新增] 新增生成环境浏览器开发工具防护控制环境变量，方便在生产环境关闭防护进行调试。</li>
<li>[新增] 修复前端部署至 Nginx 页面缓存策略化配置。</li>
<li>[修复] 修复 SAS DefaultAuthenticationEventPublisher 如果没有指定默认事件，会抛出跳出系统错误体系不识别异常问题。</li>
<li>[修复] 修复部分 SAS 异常，跳出系统自定义错误体系，抛出不携带自定义错误信息异常问题。</li>
<li>[修复] 修复 OAuth2 自带异常无法转换为系统错误体系标准异常问题。</li>
<li>[修复] 修复调整错误处理类配置方式后，授权码页面被拦截问题。</li>
<li>[修复] 修复获取 IP 时，特殊情况会获取到 0:0:0:0:0:0:0:1 而导致异常问题。</li>
<li>[修复] 修复前端打包为 Docker 镜像，环境变量设置不生效问题。</li>
<li>[修复] 修复前端 Typescript 定义与后端实体不一致，导致前端 OAuth2Appliation 功能显示和操作异常问题。</li>
<li>[修复] 优化前端生产编译配置，修复在指定 Base 场景下 css url 方式引用字体出现 404 问题。</li>
<li>[优化] 去除代码中重复定义的 DefaultOAuth2AuthenticationEventPublisher Bean 配置。</li>
<li>[优化] 请求审计日志新增 js、css、image 等静态资源过滤保护，防止记录过多静态资源调用信息。</li>
<li>[优化] 删除 Vite 配置文件，无用的编译打包设置</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-03-12T18-04-18Z</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.9.5</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.30.38</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.36.2</li>
<li>[升级] audience-annotations 版本升级至 0.15.1</li>
</ul>
</li>
</ul>
<h2>v3.4.3.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.4.5</li>
<li>[新增] SAS 相关的异常和错误反馈不在仅是显示单调的信息，支持浏览器访问场景下以 text/html 类型输出个人人性化界面</li>
<li>[优化] 大幅优化单体版和前端在 Context Path 以及 Nginx 反向代理环境下运行的可用性</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 系统新增 H2 内存数据库支持</li>
<li>[新增] 单体版本新增基于 H2 内存数据库的演示模式 Profile。</li>
<li>[新增] 前端 Vite 配置增加基础路径配置，修复在反向代理指向子路径的配置方式下，出现静态资源 404 问题</li>
<li>[新增] 对原有仅支持 application/json 类型的 SAS 错误响应进行扩展，支持 text/html 类型处理。让通过浏览器访问出现的异常信息展现的更加人性化。</li>
<li>[新增] 新增 Thymeleaf 手动渲染页面处理工具，解决手动渲染不支持 '@{}'，抛出Link base &quot;/error/css/style.css&quot; cannot be context relative (/...) unless the context used for executing the engine implements the org.thymeleaf.context.IWebContext interface 问题。</li>
<li>[重构] 提取授权服务器和认证服务器门面配置类，减少应用端需要注入的 Bean，方便使用和统一维护。</li>
<li>[重构] 使用 Lambda 方式重构 RequestMapping 核心处理逻辑代码。</li>
<li>[修复] 修复 Servlet 环境下 AccessDenied 异常处理逻辑错误，导致抛出信息不够精准问题。</li>
<li>[修复] 修复前端编译时输出类型引入错误告警</li>
<li>[修复] 修复前端 pinia store ts 重复导出引起编译告警问题。</li>
<li>[修复] 修复前端 Vue 页面导出语法错误引起编译告警问题。</li>
<li>[修复] 修复自定义登录页面图片在指定上下文路径环境下不显示问题。</li>
<li>[修复] 修复系统缺少 oauth2_authorization_resource 表初始化脚本问题</li>
<li>[修复] 修复在 Context Path 环境下，Cookie Path 设置异常导致导致登录失败问题。</li>
<li>[修复] 修复在 Context Path 环境下，包含占位符的权限校验错误问题。</li>
<li>[修复] 修复在 Context Path 环境下，接口权限转换没有包含 Context Path，导致权限无法验证通过错误</li>
<li>[升级] emqx docker 镜像版本升级至 5.8.5</li>
<li>[升级] tdengine 镜像版本升级至 3.3.5.8</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] grpc 版本升级至 1.71.0</li>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.9.3</li>
<li>[升级] quasar webjars 版本升级至 2.18.1</li>
<li>[升级] sms4j-spring-boot-starter 版本升级至 3.3.4</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.30.36</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.36.1</li>
<li>[升级] webauthn4j 版本升级至 0.28.6.RELEASE</li>
<li>[升级] checker-qual 版本升级至 3.49.1</li>
</ul>
</li>
</ul>
<h2>v3.4.3.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.4.4</li>
<li>[优化] 优化客户端动态注册以及客户端激活全过程代码和逻辑</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增客户端动态注册不允许重复注册控制</li>
<li>[新增] 新增认证授权资源管理存储，支持多种数据源切换。</li>
<li>[修复] lettuce 依赖版本回滚至与 Spring Boot 一致，以解决退出系统产生异常问题。</li>
<li>[修复] 修复客户端动态注册生成的密码被双重加密导致密码校验错误。</li>
<li>[修复] 解决 spring-security-oauth2-jose 与 spring-security-oauth2-authorization-server 依赖的 nimbus-jose-jwt 版本不一致问题</li>
<li>[修复] 修复客户端动态注册如果使用相同的 clientName 会导致系统查询出错问题。</li>
<li>[修复] 修复 OAuth2 确认页面无法支持多种环境，在物联网环境无法显示页面抛错问题。</li>
<li>[修复] 修复由于 spring-cloud-tencent-dependencies 中 &lt;springdoc.version&gt; 的干扰，会引入低版本 springdoc 导致系统无法启动问题。</li>
<li>[修复] 修复设备认证页面 Logo 无法显示问题</li>
<li>[修复] 修复设备认证成功后没有跳转到指定页面问题。</li>
<li>[优化] 优化动态控制认证开启和关闭生成的默认授权模式配置合理性</li>
<li>[优化] 去除大量代码中的 PasswordEncoder 注入，统一改用静态工具类调用方式。</li>
<li>[优化] 规范化自定义社会化授权模式参数绑定错误 Exception 定义和使用方式。</li>
<li>[重构] 重构应用实体转换为 OAuth2 Client 实体相关代码，明确转换信息，采用统一抽象类实现规避转换代码混乱重复问题。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-02-28T09-55-16Z</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] fastjson2 版本升级至 2.0.56</li>
<li>[升级] redisson 版本升级至 3.45.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.30.31</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.35.0</li>
</ul>
</li>
</ul>
<h2>v3.4.3.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.4.3</li>
<li>[升级] Spring Authorization Server 版本升级至 1.4.2</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复自主封装支持 Postgresql 的 Nacos 镜像查询历史版本错误 fix: #IBLY9R</li>
<li>[修复] 修复基于 Camunda 的 BPMN 工作流服务调用接口出现 java.lang.ClassNotFoundException: javax.ws.rs.core.FeatureContext 错误问题。 fix: #IBNBYE</li>
<li>[优化] 优化不同产品以及不同环境使用的 Redis 数据库，避免产生不必要的冲突。</li>
<li>[优化] 前端去除无用的依赖组件</li>
<li>[优化] 前端集成 Vue DevTools，提升前端调试 Vue 应用便捷性。</li>
<li>[优化] 统一部分第三方依赖组件版本，规避系统中存在同一组件的多个不同版本依赖问题。</li>
<li>[优化] 优化前端模块打包配置，解决部分外部模块被打入导致生成代码过大问题。</li>
<li>[优化] 优化动态控制认证开启和关闭生成的默认配置信息合理性</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-02-18T16-25-55Z</li>
<li>[升级] zipkin 镜像版本升级至 3.5.0</li>
<li>[升级] grafana 镜像版本升级至 11.5.2</li>
<li>[升级] loki 镜像版本升级至 3.4.2</li>
<li>[升级] promtail 镜像版本升级至 3.4.2</li>
<li>[升级] tempo 镜像版本升级至 2.7.1</li>
<li>[升级] node-red 镜像版本升级至 4.0.9-22</li>
<li>[升级] clickhouse 镜像版本升级至 24.12.5</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] hutool 版本升级至 6.0.0-M20</li>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.9.2</li>
<li>[升级] json-schema-validator 版本升级至 1.5.6</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.30.24</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.35.0</li>
<li>[升级] springdoc 版本升级至 2.8.5</li>
<li>[升级] sqlite-jdbc 版本升级至 3.49.1.0</li>
<li>[升级] sweetalert2 版升级至 11.17.2</li>
<li>[升级] hutool 5.X 版本升级至 5.8.36</li>
</ul>
</li>
</ul>
<h2>v3.4.2.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 3.4.2</li>
<li>[优化] 前端工程 Vite、Package.json、Typescript Config 以及依赖和别名等大幅优化，与 Vue 最新版本默认模块进行统一，并优化大量 Typescript 类型不一致以及在新版本环境下出现编译告警等问题。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 MySQL 数据库初始化脚本中，oauth2_registered_client 存储 JSON 数据格式错误问题。fix: #IBKXVK</li>
<li>[优化] 优化前端各项开发辅助组件配置，与最新版本 Vue 模版使用组件和配置方式统一。</li>
<li>[优化] 去除原有过时或不再使用的依赖组件，根据 Vue 新版本模版依赖组件改用新的依赖辅助组件，</li>
<li>[优化] 参考 Vue 最新模版环境，重新调整 Vite 和 tsconfig 配置。</li>
<li>[优化] 优化前端组件模块 package.json 编译后导出配置，避免出现在使用时出现无法找到模块 Typescript 定义问题。</li>
<li>[优化] 统一修改系统以及各模块使用的别名定义，与 sladcn UI 等组件推荐方式保持一致，避免未来引入新组建时还需要额外进行修改</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-02-07T23-21-09Z</li>
<li>[升级] grafana docker 镜像版本升级至 11.5.1</li>
<li>[升级] cassandra 镜像版本升级至 5.0.3</li>
<li>[升级] clickhouse 镜像版本升级至 24.12.4</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] software.amazon.awssdk 版本升级至 2.30.17</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.34.0</li>
<li>[升级] sqlite-jdbc 版本升级至 3.49.0.0</li>
<li>[升级] sweetalert2 版升级至 11.16.0</li>
<li>[升级] checker-qual 版本升级至 3.49.0</li>
</ul>
</li>
</ul>
<h2>v3.4.2.1</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增 Passkey 通行密钥「无密码」登录方式。</li>
<li>[新增] 新增与 OAuth2 融合的自定义 Passkey 授权模式。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 Hypersistence Utils 简化 Hibernate 操作和 JSON 等特殊 PostgreSQL 类型支持工具集</li>
<li>[新增] 新增 Passkey 基础存储定义和 Service 实现。</li>
<li>[新增] 新增 404 相关错误码配置。</li>
<li>[新增] 前端新增 UnoCSS 支持 IDE 提示支持</li>
<li>[重构] 重构自定义 SAS 授权模式，抽取通用代码，减少重复代码编写</li>
<li>[重构] 简化 Spring Authorization Server 认证服务核心配置</li>
<li>[修复] 修复新版本 Spring Authorization Server 默认设置与 Opaque Token 冲突导致应用启动抛错问题。</li>
<li>[修复] 去除不必要的 Jackson Module 定义，修复该定义对反序列化产生转型副作用。</li>
<li>[修复] 修复分页查询缓存异常导致分页操作异常问题。</li>
<li>[优化] 去除 Docker File 中过时标签</li>
<li>[优化] 系统使用各组件 JSON 反序列化组件进一步统一为使用 Jackson，避免不同组件使用不同反序列化组件产生不必要的问题</li>
<li>[优化] 前端打包压缩组件变更为使用 vite-plugin-compression2</li>
<li>[优化] 前端去除多余的 Typescript Config JSON 文件</li>
<li>[优化] 删除早期版本为临时解决 SAS Issuer 不支持微服务服务名增加的覆盖类</li>
<li>[升级] Liberica 基础镜像版本升级至 21.0.6-10</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.9.1</li>
<li>[升级] okhttps 版本升级至 4.1.0</li>
<li>[升级] redisson 版本升级至 3.44.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.30.14</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.33.11</li>
<li>[升级] springdoc 版本升级至 2.8.4</li>
<li>[升级] weixin-java 版本升级至 4.7.2.B</li>
</ul>
</li>
</ul>
<h2>v3.4.3.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.4.2</li>
<li>[升级] Nacos 版本升级至 2.5.0。支持 Postgresql 的 Nacos 镜像同步发布至 Docker hub</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增响应式服务前端对象存储文件下载接口，支持在线下载和打开</li>
<li>[新增] 新增 OSS 模块和 Ip2Region 模块错误码定义。</li>
<li>[新增] 新增 Nacos API 权限初始化脚本</li>
<li>[修复] 修复 Nacos Postgresql 数据库脚本缺少序列器定义问题</li>
<li>[修复] 修复 Reactive 环境下，Xss 防护会将时间字符中的空格去除，导致反序列化出错问题。</li>
<li>[修复] 修复 OSS GRPC 客户端上传文件失败，需要 SSL/TLS 认证问题。</li>
<li>[修复] 修复响应式服务无法将包含有占位符的接口转换为权限问题</li>
<li>[修复] 修复证书文件删除操作不会同步删除本地文件问题</li>
<li>[修复] 修复前端对象存储文件下载地址错误问题</li>
<li>[修复] 修复前端选型自定义组件默认值不会及时设定问题</li>
<li>[修复] 修复读取证书逻辑错误，导致证书无法读取影响其它证书生成问题。</li>
<li>[修复] 修复授权码模式登录出现数据反序列化问题。</li>
<li>[优化] 证书管理页面补充必要信息，增加后端处理提示及防误操作控制。</li>
<li>[升级] zipkin 版本升级至 3.4.4</li>
<li>[升级] tempo 版升级至 2.7.0</li>
<li>[升级] clickhouse 版本升级至 24.12.3</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-02-03T21-03-04Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] grpc 版本升级至 1.70.0</li>
<li>[升级] mysql 版本升级至 9.2.0</li>
<li>[升级] nacos 版本升级至 2.5.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.30.4</li>
<li>[升级] lettuce 版本升级至 6.5.2.RELEASE</li>
</ul>
</li>
</ul>
<h2>v3.4.1.2</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增证书管理模块。无需 Keytool 和 Openssl，可在线生成根证书、CA 证书以及自签名证书。支持 Servlet 和 Reactive 环境动态可拔插。</li>
<li>[新增] 前端新增 VConsole 开发调试工具。可以像小程序一样调试前端页面。</li>
<li>[新增] 前端新增生产环境保护机制。生产环境前端会禁用 F12 以及右键菜单禁用。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增内部服务间从文件服务删除文件支持。支持 Openfeign 和 GRPC 两种模式。</li>
<li>[新增] 服务间文件传输默认实现，并改为在 core-autoconfigure 模块自动配置，解决其它模块引用不方便问题</li>
<li>[新增] 系统管理模块相关数据初始化脚本</li>
<li>[重构] 删除为后续开发预留的、无用的代码模块</li>
<li>[重构] 使用 Java 8 Lambda 代码简化原有复杂代码逻辑</li>
<li>[重构] 将部分 SAS 自定义页面代码，迁移至 REST 模块中，减少和删除不必要的类型重复模块依赖。</li>
<li>[重构] 统一 CRUD 基础类命名规则，Repository 和核心实体统一以 Base 开头，抽象基础类命名全部改为以 Abstract 开头</li>
<li>[重构] 重构前端基础类型定义名称，与后端新版本代码保持一致。</li>
<li>[重构] 重构基于 JPA 的 CRUD 基础类，补充新版本支持的新方法。</li>
<li>[修复] 修复自定义生成服务 Archetype 配置文件与当前版本不一致问题。</li>
<li>[修复] 修复现有 OSS 模块文件传输默认配置与新增系统默认文件传输配置冲突问题。</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-01-18T00-31-37Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] bcpkix-jdk18on 版本升级至 1.80</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.80</li>
<li>[升级] central-publishing-maven-plugin 版本升级至 0.7.0</li>
<li>[升级] fastjson2 版本升级至 2.0.54</li>
<li>[升级] grpc 版本升级至 1.691</li>
<li>[升级] json-schema-validator 版本升级至 1.5.5</li>
<li>[升级] mybatis 版本升级至 3.5.19</li>
<li>[升级] mybatis-plus 版本升级至 3.5.10.1</li>
<li>[升级] redisson 版本升级至 3.43.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.30.2</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.33.9</li>
<li>[升级] springdoc 版本升级至 2.8.3</li>
<li>[升级] sqlite-jdbc 版本升级至 3.48.0.0</li>
<li>[升级] json 版本升级至 20250107</li>
<li>[升级] okio 版本升级至 3.10.2</li>
<li>[升级] logback 版本升级至 1.5.16</li>
<li>[升级] weixin-java 版本升级至 4.7.1.B</li>
<li>[升级] quasar webjars 版本升级至 2.17.7</li>
</ul>
</li>
</ul>
<h2>v3.4.1.1</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增系统应用合规和接口审计数据时序化存储支持，支持与默认 JPA 数据存储介质通过配置切换新特性。提升系统审计类数据存储和性能，提升系统功能扩展性。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 Cassandra NoSQL 存储支持以及相关开发通用代码模块</li>
<li>[修复] 修复权限表达式列表与最新版本 Spring Security 不一致问题。</li>
<li>[修复] 修复 Spring Data MongoDB 开启审计注解冲突导致 BPMN 服务启动错误问题</li>
<li>[修复] 修复 Spring Data Cassandra 在非使用的环境下，自动注入相关配置，导致启动出错问题</li>
<li>[修复] 改变 SAS 核心数据使用非结构化存储开启自动初始化条件实现机制。修复条件注解生效机制错误</li>
<li>[修复] 修复前端升级依赖版本后，编译组件库失败问题。</li>
<li>[重构] 适配 Hutool 6.X 最新版本</li>
<li>[重构] 重构前端 Typescript 定义，适配最新版后端功能定义</li>
<li>[升级] loki docker 镜像版本升级至 3.3.2</li>
<li>[升级] promtail docker 镜像版本升级至 3.3.2</li>
<li>[升级] cassandra docker 镜像版本升级至 5.0.2</li>
<li>[升级] emqx docker 镜像版本升级至 5.8.4</li>
<li>[升级] influxdb docker 镜像版本升级至 2.7.11</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] hutool 版本升级至 6.0.0-M19</li>
<li>[升级] loki-logback-appender 版本升级至 1.6.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.29.45</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.33.7</li>
<li>[升级] springdoc 版本升级至 2.8.0</li>
<li>[升级] sqlite-jdbc 版本升级至 3.47.2.0</li>
<li>[升级] hutool 5.X 版本升级至 5.8.35</li>
<li>[升级] weixin java 版本升级至 4.7.0</li>
<li>[升级] checker-qual 版本升级至 3.48.4</li>
<li>[升级] json 版本升级至 20241224</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.15.10</li>
<li>[升级] quasar webjars 版本升级至 2.17.6</li>
</ul>
</li>
</ul>
<h2>v3.4.1.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.4.1</li>
<li>[升级] Spring Authorization Server 版本升级至 1.4.1</li>
<li>[新增] Spring Authorization Server 核心数据存储新增 NoSQL 存储支持。可根据需求以通过修改配置方式，动态变更 JPA、Redis 和 MongoDB 三者不同的存储介质作为 Spring Authorization Server 核数据的存储介质。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] data-module-jpa 模块名称修改为 data-module-tenant，更加明晰代码用途和模块定位。</li>
<li>[重构] 拆分数据基础模块以及相关联模块，以支持后续多中数据源切换。</li>
<li>[重构] 基础 Jpa findById 方法，重构为返回 Spring Data 标准的 Optional 类型对象。</li>
<li>[修复] 修复 Spring Authorization Server 核心数据 AccessTokenType 未保存问题。</li>
<li>[修复] 修复缺失 Spring Authorization Server TLS 相关控制属性问题</li>
<li>[修复] 修复登录失败超出指定次数账号自动锁定条件注解不生效问题。</li>
<li>[修复] 修复前端工程升级至 Vite6 后编译出错问题</li>
<li>[修复] 修复前端使用新版 Vite 编译后样式引入错误，提示需要安装模块问题。</li>
<li>[修复] 修复新版读取 Token 逻辑判断错误，导致无法正确读取 Token 问题。</li>
<li>[修复] 修复数据库初始化脚本错误</li>
<li>[优化] 采用 JDK 17 新语法优化 Spring Authorization Server 核心服务代码</li>
<li>[优化] 扩展 Spring Authorization Server 核心数据 Jackson2 处理类，支持额外添加 Jackson Module 和 Mixin 以适配不同类型数据源。</li>
<li>[安全] 修复安全漏洞 CVE-2024-47535</li>
<li>[安全] 修复安全漏洞 CVE-2024-12798</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2024-12-18T13-15-44Z</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] grpc 版本升级至 1.69.0</li>
<li>[升级] guava 版本升级至 33.4.0</li>
<li>[升级] redisson 版本升级至 3.41.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.29.39</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.33.7</li>
<li>[升级] quasar webjars 版本升级至 2.17.5</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.15.3</li>
<li>[升级] lettuce 版本升级至 6.5.1.RELEASE</li>
<li>[升级] logback 版本升级至 1.5.15</li>
</ul>
</li>
</ul>
<h2>v3.4.0.1</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增 Reactive 环境下 Indexed 模式的 Spring Session 的配置。</li>
<li>[新增] 新增 Rest 接口审计功能，可以通过配置开启。为减少不必要的性能损耗，默认为关闭状态。</li>
<li>[重构] 使用 Spring Boot 标准的方式和标准的信息输出结构，重构自定义条件注解，以简化相关条件注解数量以及条件类定义。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复响应式服务不支持 Indexed 模式 Session，导致 与阻塞式服务 Session 不统一问题。</li>
<li>[修复] 修复 WebSocket 多实例配置仅能支持 Servlet 环境，以及配置属性不合理问题。</li>
<li>[修复] 修复开启 Rest 接口审计配置不生效问题</li>
<li>[修复] 修复单体版开启和关闭 Swagger 不生效问题。</li>
<li>[修复] 修复前端 package.json 配置未更新导致在最新 Vite 版本下编译组件会打印告警信息问题。</li>
<li>[修复] 修复响应式服务权限校验逻辑异常抛错问题。</li>
<li>[修复] 修复使用 Jackson @JsonFormat 注解序列化时间差8小时问题。</li>
<li>[修复] 修复数据库初始化脚本，去除无用的菜单数据。</li>
<li>[重构] 重构数据库审计核心代码逻辑，去除原有多重判断繁琐实现。</li>
<li>[重构] 调整 Conditional 判断实现类访问权限，避免不必要的引用。</li>
<li>[重构] 提取以枚举作为配置属性的条件注解的通用抽象方法，方便和简化枚举值类型条件注解的编写。</li>
<li>[重构] 系统核心类 RequestMapping 重名为 RestMapping，以减少与 Spring 核心注解 @RequestMapping 不必要的冲突</li>
<li>[重构] 重构用户登录审计功能相关存储信息，减少不必要的字段，以综合性的字段显示信息。</li>
<li>[优化] 优化响应式 Opaque Token Introspector 实现定义，使用最新代码替换已经被标记为过时的方法</li>
<li>[优化] 改用 Spring Authorization Server 新版本标准方式优化自定义扩展授权码模式 Provider</li>
<li>[优化] 优化部分条件注解的检测逻辑，尽可能使用 Spring Boot 标准方式，减少额外的扩展定义类。</li>
<li>[优化] 删除无用的 ComponentScan 包扫描配置</li>
<li>[升级] zipkin 镜像版本升级至 3.4.3</li>
<li>[升级] grafana 镜像版本升级至 11.4.0</li>
<li>[升级] loki 镜像版本升级至 3.3.1</li>
<li>[升级] promtail 镜像版本升级至 3.3.1</li>
<li>[升级] emqx 镜像版本升级至 5.8.3</li>
<li>[升级] tdengine 镜像版本升级至 3.3.4.8</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.7.3</li>
<li>[升级] commons-text 版本升级至 1.13.0</li>
<li>[升级] justauth 版本升级至 1.16.7</li>
<li>[升级] redisson 版本升级至 3.40.2</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.29.34</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.33.7</li>
<li>[升级] weixin-java 版本升级至 4.6.9.B</li>
</ul>
</li>
</ul>
<h2>v3.4.0.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.4.0</li>
<li>[升级] Spring Boot Admin 版本升级至 3.4.1</li>
<li>[升级] Spring Cloud 版本升级至 2024.0.0</li>
<li>[升级] Spring Authorization Server 版本升级至 1.4.0</li>
<li>[回滚] Spring Cloud Alibaba 版本回滚至 2023.0.1.2</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 RestClient 配置并与 RestTemplate 融合，以RestTemplate 作为 RestClient 基础引擎。</li>
<li>[修复] 修复 Hibernate 6.6 对于指定主键的实体无法保存，抛出 <code>Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)</code> 异常错误。</li>
<li>[修复] 修复响应式服务权限数据获取逻辑因采用非响应式操作导致运行出错问题。</li>
<li>[修复] 修复 Spring Cloud Bus 原始服务信息定义错误导致消息无法发送问题。</li>
<li>[修复] 修复以 import 方式导入 springdoc 依赖，会出现 ui 包与 api 包版本不同，从而导致服务启动错误问题。</li>
<li>[修复] 修复 Gateway 服务不兼容新版 Springdoc 引起的启动异常问题。</li>
<li>[重构] 添加 webjars-locator-lite 依赖，改用 webjars lite 方式替换原有 webjars 资源加载方式</li>
<li>[重构] 改用最新的 DelegatingAuthenticationConverter 类替换被标记为过时的 DelegatingAuthenticationConverter 类。</li>
<li>[重构] 适配最新的 KeyStoreKeyFactory 包路径</li>
<li>[重构] 重构 WebClient 配置，去除无用的配置内容</li>
<li>[重构] 变更 HttpClient 与 Openfeign 使用相同配置方式，改用各自独立的方式进行配置。</li>
<li>[重构] 改用 Openfeign SpringMvcContract 新接口定义重写原有配置和方法。</li>
<li>[重构] 重构 Snowflake 主键生成器代码及命名</li>
<li>[重构] 重构所有 IdGenerator 命名，去除无用的 Exception 抛出代码。</li>
<li>[重构] 调整数据相关模块中重复的代码位置，减少不必要的重复定义类</li>
<li>[重构] 改用响应式代码方式重构响应式服务获取权限逻辑</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] mybatis 版本升级至 3.5.17</li>
<li>[升级] redisson 版本升级至 3.40.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.29.29</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.33.5</li>
<li>[升级] weixin-java 版本升级至 4.6.8.B</li>
<li>[升级] checker-qual 版本升级至 3.48.3</li>
<li>[升级] springdoc 版本升级至 2.7.0</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>v3.5.X</title>
      <link>https://www.herodotus.cn/logs/3.5.html</link>
      <guid>https://www.herodotus.cn/logs/3.5.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">v3.5.X</source>
      <description>v3.5.11.0 主要更新 [升级] Spring Boot 版本升级至 3.5.11 [升级] Spring Boot 版本升级至 3.5.7 [升级] Spring Cloud Tencent 版本升级至 2.1.1.0-2024.0.3 依赖更新 [升级] protobuf-maven-plugin 版本升级至 4.1.3 [升级] alipa...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v3.5.11.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.11</li>
<li>[升级] Spring Boot 版本升级至 3.5.7</li>
<li>[升级] Spring Cloud Tencent 版本升级至 2.1.1.0-2024.0.3</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 4.1.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.658.ALL</li>
<li>[升级] fastjson2 版本升级至 2.0.61</li>
<li>[升级] grpc-bom 版本升级至 1.79.0</li>
<li>[升级]hypersistence-utils-hibernate-63 版本升级至 3.15.2</li>
<li>[升级] influxdb-client 版本升级至 7.5.0</li>
<li>[升级] jasypt-spring-boot-starter 版本升级至 4.0.4</li>
<li>[升级] loki-logback-appender 版本升级至 2.0.3</li>
<li>[升级] mysql 版本升级至 9.6.0</li>
<li>[升级] opengauss-jdbc 版本升级至 6.0.3-og</li>
<li>[升级] protobuf-bom 版本升级至 4.33.5</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.41.33</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.43.4</li>
<li>[升级] sqlite-jdbc 版本升级至 3.51.2.0</li>
<li>[升级] weixin java 版本升级至 4.8.1-20260209.141916</li>
<li>[升级] webauthn4j 版本升级至 0.31.0.RELEASE</li>
<li>[升级] vue webjars 版本升级至 3.5.28</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.19</li>
<li>[升级] checker-qual 版本升级至 3.53.1</li>
<li>[升级] error_prone_annotations 版本升级至 2.47.0</li>
<li>[升级] objenesis 版本升级至 3.5</li>
</ul>
</li>
</ul>
<h2>v3.5.10.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.10</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 修复前端 Bpmn moddle 引入方式变化，导致启动应用前端出错问题。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.630.ALL</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.41.12</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.42.2</li>
<li>[升级] weixin java 版本升级至 4.8.0-20260120.134009</li>
<li>[升级] webauthn4j 版本升级至 0.30.2.RELEASE</li>
<li>[升级] vue webjars 版本升级至 3.5.27</li>
</ul>
</li>
</ul>
<h2>v3.5.9.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.9.0</li>
<li>[升级] Spring Cloud 版本升级至 2025.0.1</li>
<li>[升级] Spring Cloud Tencent 版本升级至 2.1.0.3-2024.0.2</li>
<li>[升级] Nacos 版本升级至 3.1.1</li>
<li>[修复] 修复 Openfeign 在当前版本下，调用 RestClient 出现反序列化异常。fix: #IDKKB5</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 3.5.X 版本统一恢复使用 Undertow 作为 Web 中间件</li>
<li>[重构] Openfeign 基础 HttpClient 修改为使用 Http2Client，不再使用 OkHttp</li>
<li>[修复] 修复数据初始化脚本，枚举相关接口权限缺失问题</li>
<li>[升级] 升级 Github Action 插件版本</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 4.1.2</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.626.ALL</li>
<li>[升级] bcpkix-jdk18on 版本升级至 1.83</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.83</li>
<li>[升级] central-publishing-maven-plugin 版本升级至 0.10.0</li>
<li>[升级] commons-text 版本升级至 1.15.0</li>
<li>[升级] grpc-bom 版本升级至 1.78.0</li>
<li>[升级] hutool 版本升级至 7.0.0-M4</li>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.14.1</li>
<li>[升级] io.github.openfeign.querydsl 版本升级至 7.1</li>
<li>[升级] jasypt-spring-boot-starter 版本升级至 4.0.3</li>
<li>[升级] json-schema-validator 版本升级至 2.0.1</li>
<li>[升级] loki-logback-appender 版本升级至 2.0.2</li>
<li>[升级] mybatis-plus 版本升级至 3.5.16</li>
<li>[升级] protobuf-bom 版本升级至 4.33.4</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.41.10</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.42.2</li>
<li>[升级] springdoc 版本升级至 2.8.15</li>
<li>[升级] sqlite-jdbc 版本升级至 3.51.1.0</li>
<li>[升级] weixin java 版本升级至 4.8.0-20260116.172906</li>
<li>[升级] sm-crypto-v2 webjars 版本升级至 1.15.1</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.17</li>
<li>[升级] vue webjars 版本升级至 3.5.26</li>
<li>[升级] checker-qual 版本升级至 3.53.0</li>
<li>[升级] error_prone_annotations 版本升级至 2.46.0</li>
<li>[升级] hutool 5.X 升级至 5.8.43</li>
</ul>
</li>
</ul>
<h2>v3.5.8.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.8</li>
<li>[升级] Spring Cloud Tencent 版本升级至 2.1.0.1-2024.0.2</li>
<li>[重构] 不再使用 Undertow 作为 Web 容器，改为使用 Tomcat 并优化运行参数配置，支持虚拟线程。提前为 4.0 版本开发做准备</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 去除已被标记为过时的 @github/webauthn-json 依赖以及用法，改用浏览器内置的 Passkey 机制重新实现。</li>
<li>[修复] 修复在当前客户端没有配置 Passkey 授权模式时，前端界面不会显示错误提示问题</li>
<li>[修复] 修复因依赖新版 Spring Cloud Tencent 导致 SpringDoc UI 和 API 依赖版本不一致问题</li>
<li>[优化] 优化用户和角色实体 JPA 配置以及数据查询方式，改用更高效方式提升查询性能</li>
<li>[优化] 优化 Maven 配置注释，处理 SCT springdoc 配置属性覆盖系统自身 springdoc 配置属性问题</li>
<li>[优化] 优化 REST 接口 String 类型参数校验使用注解，提升参数校验的准确性</li>
<li>[升级] Redis 版本适配至 8.2.3</li>
<li>[升级] ip 地址数据库更新至 2025.11.19</li>
<li>[升级] kafka docker 镜像版本升级至 4.1.1</li>
<li>[升级] grafana docker 镜像版本升级至 12.4.0-19363970803</li>
<li>[升级] loki promtail docker 镜像版本升级至 3.6.0</li>
<li>[升级] cassandra docker 镜像版本升级至 5.0.6</li>
<li>[升级] clickhouse docker 镜像版本升级至 1.1.3</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 4.0.2</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.523.ALL</li>
<li>[升级] commons-io 版本升级至 2.21.0</li>
<li>[升级] grpc-bom 版本升级至 1.77.0</li>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.12.0</li>
<li>[升级] influxdb-client 版本升级至 7.4.0</li>
<li>[升级] protobuf-bom 版本升级至 4.33.1</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.39.0</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.39.4</li>
<li>[升级] springdoc 版本升级至 2.8.14</li>
<li>[升级] sqlite-jdbc 版本升级至 3.51.0.0</li>
<li>[升级] weixin java 版本升级至 4.7.8-20251117.120146</li>
<li>[升级] webauthn4j 版本升级至 0.30.0.RELEASE</li>
<li>[升级] zxing 版本升级至 3.5.4</li>
<li>[升级] quasar webjars 版本升级至 2.18.6</li>
<li>[升级] vue webjars 版本升级至 3.5.24</li>
<li>[升级] checker-qual 版本升级至 3.52.0</li>
<li>[升级] commons-lang3 版本升级至 3.20.0</li>
<li>[升级] error_prone_annotations 版本升级至 2.44.0</li>
<li>[升级] okio 版本升级至 3.16.4</li>
</ul>
</li>
</ul>
<h2>v3.5.7.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 2.1.0.0-2024.0.2</li>
<li>[升级] Debezium 版本升级至 3.3</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 服务打包为 Docker 镜像，新增 CDS 支持，提升启动性能</li>
<li>[修复] 修复初始化数据库脚本中，权限相关 SQL 不够精准问题</li>
<li>[修复] 修复单体预览镜像构建脚本因 sh 文件格式在 windows 环境下变化，导致镜像无法正常启动问题</li>
<li>[修复] 修复单体预览版 H2 初始数据库文件与当前功能不匹配问题</li>
<li>[修复] 修复在调试代码状态后端重启时，前端未关闭频繁链接后端，出现使用空值异常问题</li>
<li>[优化] 大幅优化在配置了 ContextPath 的环境下，权限扫描和权限校验的逻辑，采用更通用的方式支持 ContextPath 环境</li>
<li>[优化] 优化服务打包以及 Docker 打包配置，进一步支持镜像分层，提升构建效率</li>
<li>[优化] Docker 基础镜像 jre 变更为使用 cds 版本</li>
<li>[升级] ip 地址数据库更新至 2025.10.29</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 4.0.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.498.ALL</li>
<li>[升级] fastjson2 版本升级至 2.0.60</li>
<li>[升级] json-schema-validator 版本升级至 2.0.0</li>
<li>[升级] logstash-logback-encoder 版本升级至 9.0</li>
<li>[升级] mysql 版本升级至 9.5.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.36.3</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.39.4</li>
<li>[升级] weixin java 版本升级至 4.7.8-20251030.194408</li>
</ul>
</li>
</ul>
<h2>v3.5.7.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.7</li>
<li>[升级] Spring Authorization Server 版本升级至 1.5.3</li>
<li>[升级] Spring Cloud Alibaba 版本升级至 2025.0.0.0</li>
<li>[升级] Camunda 版本升级至 7.24.0，SQL 脚本同步升级</li>
<li>[升级] 服务基础 jre 和 jdk 版本升级至 17.0.17-11 和 25.0.1-11</li>
<li>[新增] 新增后端支持跨服务的从 Session 中获取用户信息</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增物联网平台启动时动态添加平台订阅主题功能</li>
<li>[新增] 新增物联网平台默认主题权限数据初始化脚本</li>
<li>[新增] JPA 核心方法定义中，新增 Optional findOne(Specification specification) 方法</li>
<li>[重构] 使用 sealed 接口重构各类型实体定义基类的继承关系，提升继承关系的可控制性，减少基础定义类型无序继承问题。</li>
<li>[修复] 修复授权码模式自定义表单配置类提示使用过时方法设置问题</li>
<li>[修复] 修复系统默认 Mqtt 在出现异常以后不会自动链接问题</li>
<li>[修复] 修复策略工厂统一定义错误引起的死循环问题</li>
<li>[修复] 对 Mqtt 接受数据进行反序列化时存在泛型擦除问题，导致多层嵌套对象被转化成 Map 的问题。</li>
<li>[优化] 优化响应式环境 @EnableHerodotusRestReactiveMessage 注解放置位置，提升代码的规范性及合理性</li>
<li>[优化] 优化前端 Nginx 配置，支持更精准的客户端 IP 获取。</li>
<li>[升级] ip 地址数据库更新至 2025.10.22</li>
<li>[升级] loki docker 镜像版本升级至 3.5.7</li>
<li>[升级] promtail docker 镜像版本升级至 3.5.7</li>
<li>[升级] tempo docker 镜像版本升级至 2.9.0</li>
<li>[升级] node red docker 镜像版本升级至 4.1.1-22</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 3.10.2</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.483.ALL</li>
<li>[升级] camunda 版本升级至 7.24.0</li>
<li>[升级] hutool 版本升级至 7.0.0-M2</li>
<li>[升级] loki-logback-appender 版本升级至 2.0.1</li>
<li>[升级] protobuf-bom 版本升级至 4.33.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.35.11</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.39.4</li>
<li>[升级] weixin-java 版本升级至 4.7.8-20251023.110018</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.3</li>
<li>[升级] error_prone_annotations 版本升级至 2.43.0</li>
<li>[升级] hutool 5.X 版本升级至 5.8.41</li>
<li>[升级] okio 版本升级至 3.16.2</li>
</ul>
</li>
</ul>
<h2>v3.5.6.3</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 改用 sm-crypto-v2 替代原有 sm-crypto 大幅提升加解密性能</li>
<li>[修复] 修复使用新版 Spring Authorization Server，授权码模式配置错误，访问 /oauth2/authorize 页面不会跳转至登录页面问题</li>
<li>[修复] 修复如果先启动 Gateway 再启动其它服务，服务发现就会失败，调用接口出现 404 的问题 fix: #ID18CG</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增一机一密设备签名信息生成逻辑及接口</li>
<li>[新增] 新增一型一密设备基于 Mqtt 动态注册</li>
<li>[修复] 修复服务端在退出时，Mqtt v5 抛出 Mqttv5PahoMessageDrivenChannelAdapter : Error unsubscribing from java.util.ConcurrentModificationException: null 错误</li>
<li>[修复] 修复签名计算参数传递错误，导致签名校验失败问题</li>
<li>[优化] 调整授权服务器和资源服务器配置参数，将 Jwk 相关配置参数归并至更合理位置。</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.467.ALL</li>
<li>[升级] opengauss-jdbc 版本升级至 6.0.2-og</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.35.5</li>
<li>[升级] weixin-java 版本升级至 4.7.8-20251004.014820</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.1</li>
<li>[升级] classgraph 版本升级至 4.8.184</li>
<li>[升级] okio 版本升级至 3.16.1</li>
</ul>
</li>
</ul>
<h2>v3.5.6.2</h2>
<ul>
<li>主要更新
<ul>
<li>[修复] 重新打包 Nacos 3.1.0 镜像，修复 AI 配置缺失以及启动异常问题。</li>
<li>[修复] 修复 Emqx 动态注册响应式和阻塞式配置错误，应用启动时抛出无法找到 Bean 问题</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 基于 WebClient 和 RestClient 的客户端动态注册辅助模块迁移至 IOT 体系</li>
<li>[重构] IOT 代码结构调整，修复因新增签名认证导致的启动错误，提升 Emqx 扩展内容的内聚性</li>
<li>[重构] IOT OAuth2 相关内容进行剥离，单独构建自动配置模块</li>
<li>[优化] 规范化所有 @PostConstruct 注解标注方法的命名</li>
<li>[优化] 规范化日志输出的级别，以及注入提示类型日志的书写规范</li>
<li>[优化] 补充 Emqx 默认 etc 目录配置文件，方便使用 Emqx，无需再手动从 Docker 镜像中拷贝</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] archetype-packaging 版本升级至 3.4.1</li>
<li>[升级] maven-archetype-plugin 版本升级至 3.4.1</li>
<li>[升级] protobuf-maven-plugin 版本升级至 3.10.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.465.ALL</li>
<li>[升级] camunda 版本升级至 7.24.0-alpha3</li>
<li>[升级] grpc 版本升级至 1.76.0</li>
<li>[升级] opengauss-jdbc 版本升级至 6.0.1-og</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.35.1</li>
<li>[升级] weixin-java 版本升级至 4.7.8-20251004.014820</li>
<li>[升级] webauthn4j 版本升级至 0.29.7.RELEASE</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.24.0</li>
<li>[升级] checker-qual 版本升级至 3.51.1</li>
</ul>
</li>
</ul>
<h2>v3.5.6.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 3.1.0。自封装支持 Postgresql 的 Nacos Docker 镜像已经上传至 Docker Hub 和 <a href="http://Quay.IO" target="_blank" rel="noopener noreferrer">Quay.IO</a></li>
<li>[新增] 新增签名算法工具类及验证逻辑，增强抵御重放攻击（Replay Attack）能力</li>
<li>[重构] 重构 StampManager 有效期设置方法，由原来 afterPropertySet 方法统一修改为使用构造函数进行设置</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 变更 Windows 环境 Docker Compose 中默认的 Volumns 地址路径</li>
<li>[重构] Captcha 相关实现类的关键参数设置，由原来使用 setXXX 的方式统一变更为使用构造函数进行设置</li>
<li>[修复] 修复 Endpoint 相关配置参数中，manageServiceUri 值设置不正确问题</li>
<li>[优化] AbstractStampManager 抽象类构造函数访问修饰符修改为 protected</li>
<li>[优化] Servlet 环境前后端加密密钥存储缓存有效期，与 Spring Boot Servlet 环境 Session 配置有效期统一</li>
<li>[优化] StampManager 默认缓存有效期修改为 5 分钟</li>
<li>[升级] ip 位置数据库更新至 2025-09-24</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.461.ALL</li>
<li>[升级] central-publishing-maven-plugin 版本升级至 0.9.0</li>
<li>[升级] fastjson2 版本升级至 2.0.59</li>
<li>[升级] justauth 版本升级至 1.18.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.34.2</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.39.0</li>
<li>[升级] wxjava 版本升级至 4.7.8.B</li>
<li>[升级] commons-lang3 版本升级至 3.19.0</li>
<li>[升级] error_prone_annotations 2.42.0</li>
<li>[升级] jaxb-impl 版本升级至 4.0.6</li>
<li>[升级] vue webjars 版本升级至 3.5.22</li>
</ul>
</li>
</ul>
<h2>v3.5.6.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.6</li>
<li>[升级] Spring Boot Admin 版本升级至 3.5.5</li>
<li>[升级] 企业版默认 JDK 版本升级至 25</li>
<li>[重构] 全面改用 @HttpExchange 声明式客户端作为第三方 OpenAPI 集成方式，去除第三方 okhttps 组件依赖和使用</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 使用 Spring 内置 MediaType 对象中的值替换所有字符串型 ContentType 值</li>
<li>[优化] 开启 SpringDoc default-flat-param-object 配置，以提升文档描述的准确性</li>
<li>[优化] 消除工程中出现依赖同一组件不同版本的情况</li>
<li>[优化] 去除一些早年集成并且多年疏于维护的 OpenApi 集成模块，待有验证条件时再行补充</li>
<li>[升级] JustAuth 组件替换为自主维护的版本，基于 JDK 17 编译，去除低版本依赖</li>
<li>[升级] kafka docker 镜像版本升级至 4.1.0</li>
<li>[升级] grafana docker 镜像版本升级至 12.3.0-17814087142</li>
<li>[升级] loki docker 镜像版本升级至 3.5.5</li>
<li>[升级] clickhouse docker 镜像版本升级至 25.8.3</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 3.9.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.452.ALL</li>
<li>[升级] bcpkix-jdk18on 版本升级至 1.82</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.82</li>
<li>[升级] camunda 版本升级至 7.24.0-alpha2</li>
<li>[升级] guava 版本升级至 33.5.0</li>
<li>[升级] json-schema-validator 版本升级至 1.5.9</li>
<li>[升级] protobuf 版本升级至 4.32.1</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.33.12</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.13</li>
<li>[升级] springdoc 版本升级至 2.8.13</li>
<li>[升级] wxjava 版本升级至 4.7.7-20250915.094526</li>
<li>[升级] sweetalert2 版本升级至 11.23.0</li>
<li>[升级] vue webjars 版本升级至 3.5.21</li>
<li>[升级] quasar webjars 版本升级至 2.18.5</li>
<li>[升级] checker-qual 版本升级至 3.51.0</li>
<li>[升级] dom4j 版本升级至 2.2.0</li>
</ul>
</li>
</ul>
<h2>v3.5.5.3</h2>
<ul>
<li>主要更新
<ul>
<li>[重磅] 对齐开源版和企业版内核代码结构及使用方式</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] ip 位置数据库更新至 2025-09-10</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 3.9.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.440.ALL</li>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.11.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.33.5</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.11</li>
<li>[升级] springdoc 版本升级至 2.8.13</li>
<li>[升级] wxjava 版本升级至 4.7.7-20250904.091948</li>
<li>[升级] webauthn4j 版本升级至 0.29.6.RELEASE、</li>
<li>[升级] sweetalert2 版本升级至 11.23.0</li>
<li>[升级] vue webjars 版本升级至 3.5.21</li>
</ul>
</li>
</ul>
<h2>v3.5.5.2</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 采用新的 protobuf maven 插件替换原有老旧版本插件，以支持使用最新版 protobuf 4.X 编译生成 grpc 代码</li>
<li>[优化] 自定义页面涉及第三方 js 组件全部替换为 webjars 方式</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复在微服务架构下，当用户没有接口权限时，返回的错误信息不准确问题</li>
<li>[修复] 修复系统内引用 jquery webjars maven 坐标已过时问题</li>
<li>[修复] 修复前端页面登录成功后页面控制台页面不显示问题</li>
<li>[修复] 修复应用合规数据存储代码字段长度不足，无法存储 ipv6 地址问题 fix: #ICVJYM</li>
<li>[优化] 优化自定义授权码授权模式登录页面</li>
<li>[优化] 自定义内嵌授权码授权模式 登录页面，补充缺失的 bootstrap 引用</li>
<li>[优化] 服务 Docker 使用的 liberica 镜像由 liberica-openjdk-debian 替换为 liberica-openjre-debian，以减少生成 Docker 镜像的大小</li>
<li>[升级] ip 位置数据库更新至 2025-08-27</li>
<li>[升级] Nodejs 版本升级至 22.19.0</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.427.ALL</li>
<li>[升级] loki-protobuf 版本升级至 0.0.2_pb4.31.0</li>
<li>[升级] mybatis-plus 版本升级至 3.5.14</li>
<li>[升级] protobuf 版本升级至 4.32.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.33.0</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.11</li>
<li>[升级] wxjava 版本升级至 4.7.7-20250831.214433</li>
<li>[升级] checker-qual 版本升级至 3.50.0</li>
<li>[升级] hutool 5.X 版升级至 5.8.40</li>
<li>[升级] zookeeper 版本升级至 3.9.4</li>
<li>[升级] bootstrap webjars 版本升级至 5.3.8</li>
<li>[升级] inter-ui webjars 版本升级至 4.1.1</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.22.5</li>
<li>[升级] @tabler/core webjars 版本升级至 1.4.0</li>
</ul>
</li>
</ul>
<h2>v3.5.5.1</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] 重构系统核心权限验证及去重逻辑，将其核心验证方法有原有基于 AntPathRequestMatcher 扩展思路变更为使用 Spring Security 新版更为推荐的 PathPatternRequestMatcher 方式，效率更高代码逻辑也更加简洁。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构 ip2region 模块为 ip2location，新增大 xdb 文件查询支持。</li>
<li>[重构] 适配 commons-lang3 最新版本，StringUtils.equalsIgnoreCase 替换为 Strings.CI.equals</li>
<li>[重构] 适配 commons-lang3 最新版本，StringUtils.equals 替换为 Strings.CS.equals</li>
<li>[重构] 适配 commons-lang3 最新版本，StringUtils.startsWith 替换为 Strings.CS.startsWith</li>
<li>[重构] 适配 commons-lang3 最新版本，StringUtils.endsWith 替换为 Strings.CS.endsWith</li>
<li>[重构] 适配 commons-lang3 最新版本，StringUtils.remove 替换为 Strings.CS.remove</li>
<li>[重构] 适配 commons-lang3 最新版本，StringUtils.removeStart 替换为 Strings.CS.removeStart；StringUtils.removeEnd 替换为 Strings.CS.removeEnd</li>
<li>[重构] 适配 commons-lang3 最新版本，StringUtils.contains 替换为 Strings.CS.contains；StringUtils.containsAny 替换为 Strings.CS.containsAny</li>
<li>[重构] 适配 commons-lang3 最新版本，StringUtils.replace替换为 Strings.CS.replace</li>
<li>[新增] 新增 qqwry ip 地址查询代码</li>
<li>[升级] ip2region 数据更新至 2025-08-20</li>
<li>[安全] 修复 commons-lang3 CVE-2025-48924 和 zookeeper CVE-2024-51504 漏洞问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.411.ALL</li>
<li>[升级] redisson 版本升级至 3.51.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.32.29</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.11</li>
<li>[升级] springdoc 版本升级至 2.8.11</li>
<li>[升级] vue webjars 版本升级至 3.5.20</li>
</ul>
</li>
</ul>
<h2>v3.5.5.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.5</li>
<li>[升级] Spring Boot Admin 版本升级至 3.5.2</li>
<li>[升级] Nacos 版本升级至 3.0.3。自封装支持 Postgresql 的 Nacos Docker 镜像已经上传至 Docker Hub 和 <a href="http://Quay.IO" target="_blank" rel="noopener noreferrer">Quay.IO</a></li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 前端工程新增主题切换特效</li>
<li>[修复] 修复自定义扩展 Client Credentials 模式 Provider 与 SAS 最新配置方式不匹配，导致 Client Credentials 模式使用不稳定问题</li>
<li>[修复] 修复物联网设备客户端动态注册，因循环开启认证导致客户端注册生的 Registered Client 信息被覆盖问题</li>
<li>[修复] 修复 Spring Session 在退出系统时会抛出 java.lang.IllegalStateException: LettuceConnectionFactory has been STOPPED. Use start() to initialize it 问题。fix：#ICTVGU</li>
<li>[修复] 修复前端设备码验证轮询 API 返回信息错误</li>
<li>[修复] 修复客户端动态注册时 oauth2_authorization_resource 表中，出现多条相同 clientId 信息存在，导致查询出错问题</li>
<li>[修复] 修复设备码授权默认验证成功后跳转地址错误问题</li>
<li>[修复] 修复前端 framework kernel 模块因导入信息错误，导致模块打包过大问题</li>
<li>[修复] 修复 @vue/tsconfig 升级至 0.8.0，默认开启 noUncheckedIndexedAccess 和 exactOptionalPropertyTypes 配置，导致打包编译时出现错误提示问题</li>
<li>[重构] 重构前端 Axios 组件抽象定义中的类型，让类型验证更准确，减少不必要的类型转换</li>
<li>[优化] IP 地址库数据库更新至 2025-08-13</li>
<li>[优化] 优化前端客户端动态注册默认参数，避免注册时生成不必要的授权模式</li>
<li>[优化] 删除 Baidu OCR OpenAPI 封装模块</li>
<li>[优化] 去除 Velocity 组件的依赖以及相关配置</li>
<li>[优化] 优化 Maven 配置，去除早期为控制依赖漏洞而引入的 fastjson 统一版本控制</li>
<li>[优化] 优化 Gitee ISSUE Template</li>
<li>[优化] 删除前端 Bpmn 设计器模块打包配置中的无用配置</li>
<li>[优化] 优化前端 vite.config.mts 和 tsconfig.json 配置，采用更合理的定义配置，同时去除无用或过时的配置内容</li>
<li>[优化] 优化前端应用 Vite 配置，调整自动生成配置文件位置，优化自动导入配置</li>
<li>[优化] 优化前端模块 package.json 导出配置，简化模块样式引入路径长途</li>
<li>[升级] tempo docker 镜像版本升级至 2.8.2</li>
<li>[升级] cassandra docker 镜像版本升级至 5.0.5</li>
<li>[升级] clickhouse docker 镜像版本升级至 25.7.4</li>
<li>[升级] kestra docker 镜像版本升级至 v0.24.2</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.411.ALL</li>
<li>[升级] grpc 版本升级至 1.75.0</li>
<li>[升级] skywalking agent 版升级至 9.5.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.32.27</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.9</li>
<li>[升级] springdoc 版本升级至 2.8.10</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.22.4</li>
<li>[升级] wxjava 版本升级至 4.7.7-20250808.182223</li>
</ul>
</li>
</ul>
<h2>v3.5.4.2</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 前端框架 OAuth2 客户端授权模式及设备授权模式通用操作 API 大幅优化</li>
<li>[优化] 去除早期版本中为了消除 CVE 而增加的 xnio 依赖版本控制配置，改为使用依赖组件传递版本</li>
<li>[优化] 优化 ip2region 数据查询代码，更新 IP 地址库</li>
<li>[优化] 系统以支持 Redis 8.2.0 版本，</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 前端框架模块新增 OAuth2 客户端授权模式及设备授权模式通用操作 API</li>
<li>[新增] 前端框架模块新增设备码授权模式获取 Access Token 轮询验证 API</li>
<li>[新增] 前端新增 OAuth2 协议内置 Scope 枚举定义</li>
<li>[新增] 前端框架模块新增 OIDC 客户端动态注册 API</li>
<li>[重构] 重构前端 OAuth2 各认证模式 API，提取通用代码方便代码维护</li>
<li>[重构] 重构前端 Axios 自定义封装 API 定义，方便更好的理解及使用代码</li>
<li>[重构] 迁移前端 useApplicationStore 相关内容代码至模块 @herodotus-cloud/framework-kernel</li>
<li>[修复] 调整 lettuce 配置参数，修复 lettuce 链接 redis 偶发 io.lettuce.core.RedisCommandTimeoutException: Command timed out after 200 millisecond(s) 错误问题。</li>
<li>[修复] 修复动态开启认证时，OAuth2 客户端信息密码丢失问题。</li>
<li>[优化] 优化前端 OAuth2 各认证模式 API，调整参数顺序和默认值，提升 API 合理性</li>
<li>[优化] 优化动态开启和关闭认证日志，增加统一标识方便代码跟踪和日志分析</li>
<li>[升级] grafana docker 镜像版本升级至 12.2.0-16636675413</li>
<li>[升级] loki docker 镜像版本升级至 3.5.3</li>
<li>[升级] promtail docker 镜像版本升级至 3.5.3</li>
<li>[升级] node-red docker 镜像版本升级至 3.5.3</li>
<li>[升级] clickhouse docker 镜像版本升级至 25.6.6</li>
<li>[升级] kestra docker 镜像版本升级至 v0.23.9</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.382.ALL</li>
<li>[升级] mysql 版本升级至 9.4.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.32.18</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.8</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.22.3</li>
<li>[升级] wxjava 版本升级至 4.7.7-20250808.182223</li>
<li>[升级] webauthn4j 版本升级至 0.29.5.RELEASE</li>
</ul>
</li>
</ul>
<h2>v3.5.4.1</h2>
<ul>
<li>主要更新
<ul>
<li>[优化] 改用 openfeign 维护的 querydsl，替换不再维护的官方 querydsl，同时消除 querydsl 携带的 CVE 漏洞。</li>
<li>[优化] 删除 blaze-persistence 以及相关的低版本组件依赖及配置</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 增加支持不同组件库的菜单处理逻辑定义</li>
<li>[修复] 优化第三方社交登录绑定列表，在未绑定时显示默认时间问题</li>
<li>[修复] 修复数据初始化脚本中，默认通用权限数据欠缺致使切换账号后权限异常</li>
<li>[重构] 使用 openfeign 维护的 querydsl 重构第三方账号绑定查询</li>
<li>[重构] 迁移前端认证安全相关内容代码至模块 @herodotus-cloud/framework-kernel</li>
<li>[重构] 迁移前端菜单相关内容代码至模块 @herodotus-cloud/framework-kernel</li>
<li>[重构] 迁移前端 Passkey 相关内容代码至模块 @herodotus-cloud/framework-kernel</li>
<li>[重构] 迁移前端 Pinia Helper 相关内容代码至模块 @herodotus-cloud/framework-kernel</li>
<li>[重构] 迁移前端 SignOutUtilities 相关内容代码至模块 @herodotus-cloud/framework-kernel</li>
<li>[重构] 迁移前端 useSystemRoute 相关内容代码至模块 @herodotus-cloud/framework-kernel</li>
<li>[重构] 前端使用 Vue3 Setup 和最新方式重构 App.vue 代码</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.354.ALL</li>
<li>[升级] fastjson2 版本升级至 2.0.58</li>
<li>[升级] grpc-bom 版本升级至 1.74.0</li>
<li>[升级] openfeign querydsl 版本升级至 7.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.32.11</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.8</li>
<li>[升级] vue webjars 版本升级至 3.5.18</li>
<li>[升级] wxjava 版本升级至 4.7.7-20250725.114118</li>
<li>[升级] error_prone_annotations 版本升级至 2.41.0</li>
<li>[升级] okio 版本升级至 3.16.0</li>
</ul>
</li>
</ul>
<h2>v3.5.4.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.4</li>
<li>[升级] Spring Cloud Tencent 版本升级至 2.0.2.2-2024.0.1</li>
<li>[重构] 代码升级并适配 Hutool 7.0.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 前端框架提取核心代码至 @herodotus-cloud/framework-kernel 模块，方便不同类型前端共享代码</li>
<li>[重构] 迁移前端 useRouterStore 代码至模块 @herodotus-cloud/framework-kernel</li>
<li>[重构] 迁移前端 useEditFinish 代码至模块 @herodotus-cloud/framework-kernel</li>
<li>[重构] 迁移前端 useSystemTheme 代码至模块 @herodotus-cloud/framework-kernel</li>
<li>[重构] 前端删除 es-tookit 组件依赖，统一修改为使用 lodash</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.333.ALL</li>
<li>[升级] commons-text 版本升级至 1.14.0</li>
<li>[升级] hutool 版本升级至 7.0.0-M1</li>
<li>[升级] quasar 版本升级至 2.18.2</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.32.7</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.7</li>
<li>[升级] sqlite-jdbc 版本升级至 3.50.3.0</li>
<li>[升级] wxjava 版本升级至 4.7.7-20250724.164344</li>
<li>[升级] snappy-java 版本升级至 1.1.10.8</li>
</ul>
</li>
</ul>
<h2>v3.5.3.2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 2.0.2.1-2024.0.1</li>
<li>[升级] Debezium 版本升级至 3.2</li>
<li>[升级] Apache Maven 版本升级至 3.9.11</li>
<li>[升级] liberica jdk 版本升级至 21.0.8-12 和 17.0.16-12</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 Mqtt 客户端动态注册签名密钥校验逻辑</li>
<li>[新增] 新增基于 Mqtt 协议的设备动态注册功能</li>
<li>[重构] 前端框架核心通用代码提取为组件，方便后续以不同的 UI 组件库构建前端应用</li>
<li>[优化] 优化自定义 JPA 多级缓存配置，大幅降低缓存负载和减少多级缓存网络开销</li>
<li>[升级] loki docker 镜像版本升级至 3.5.2</li>
<li>[升级] clickhouse docker 镜像版本升级至 25.6.4</li>
<li>[升级] tdengine docker 镜像版本升级至 3.3.6.13</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.328.ALL</li>
<li>[升级] blaze-persistence 版本升级至 1.6.16</li>
<li>[升级] commons-io 版本升级至 2.20.0</li>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.10.3</li>
<li>[升级] maven-gpg-plugin 版本升级至 3.2.8</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.32.4</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.7</li>
<li>[升级] wxjava 版本升级至 4.7.7-20250716.230806</li>
<li>[升级] webauthn4j 版本升级至 0.29.4.RELEASE</li>
<li>[升级] checker-qual 版本升级至 3.49.5</li>
<li>[升级] classgraph 版本升级至 4.8.181</li>
<li>[升级] error_prone_annotations 版本升级至 2.40.0</li>
<li>[升级] okio 版本升级至 3.15.0</li>
</ul>
</li>
</ul>
<h2>v3.5.3.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 3.0.2</li>
<li>[升级] Spring Boot Admin 升级至 3.5.1</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[修复] 修复前端在设置了 Base Path 情况下，DisableDevtool 禁止页面 URL 跳转错误问题。</li>
<li>[修复] 修复 Emqx Webhook 内置事件实体属性重复导致出现反序列化出现空属性问题。</li>
<li>[重构] 将物联网数据二元组改为域对象定义，以减少数据设置错误，提升数据使用明确性。</li>
<li>[重构] 物联网相关工具类和域定义迁移至 ThingsBrain。</li>
<li>[重构] 调整 @ConditionalOnArchitecture 所在包，以支持更大范围的使用。</li>
<li>[重构] 调整部分代码所在目录，降低模块的交叉使用，提升代码的内聚性</li>
<li>[重构] 提取客户端注册通用代码模块</li>
<li>[优化] 优化 RestClient 配置，支持分布式环境下 LoadBalanced。</li>
<li>[优化] 系统默认 emqx 版本变更为 5.8.6</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-04-22T22-12-26Z</li>
<li>[升级] grafana 镜像版本升级至 12.0.2</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.272.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.22</li>
<li>[升级] hutool 6.X 版本升级至 6.0.0-M22</li>
<li>[升级] json-schema-validator 版本升级至 1.5.8</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.73</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.5</li>
<li>[升级] sqlite-jdbc 版本升级至 3.50.2.0</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.22.2</li>
<li>[升级] wxjava 版本升级至 4.7.6-20250628.230828</li>
<li>[升级] hutool 5.X 版本升级至 5.8.39</li>
<li>[升级] classgraph 版本升级至 4.8.180</li>
<li>[升级] error_prone_annotations 版本升级至 2.39.0</li>
<li>[升级] hutool 5.X 版本升级至 5.8.39</li>
<li>[升级] okio 版本升级至 3.14.0</li>
</ul>
</li>
</ul>
<h2>v3.5.3.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.3</li>
</ul>
</li>
</ul>
<h2>v3.5.2.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.2</li>
</ul>
</li>
</ul>
<h2>v3.5.1.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.1</li>
<li>[升级] Spring Cloud Alibaba 版本升级至 2023.0.3.3</li>
<li>[修复] 临时修复 Spring Cloud Alibaba 2023.0.1.3 及以上版本在 Bootstrap.yml 中配置 logging.level 不工作问题。已经提交 PR 至 Spring Cloud Alibaba，见 ISSUE #3995</li>
<li>[重构] 所有服务默认配置文件从 Bootstrap.yml 统一修改为 application.yml，改用官方更推荐方式。后续将逐步去除 boostrap 依赖。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增物联网设备影子管理功能</li>
<li>[新增] 新增自定义支持 Exception 的 BiFunction 函数式接口</li>
<li>[重构] 全新适配 loki-logback-appender 组件，支持日志批量上传，改用 gRPC 模式传输数据，大幅提升日志聚合性能。</li>
<li>[重构] 重构 Loki Appender 构建代码，提取独立的构建类，避免将 Loki Appender 代码混入 Configuration 类中，提升代码的易维护性。</li>
<li>[修复] 修复单体版默认 Redisson 配置格式错位错误。</li>
<li>[修复] 修复 Spring Boot 3.5.0 环境下，测试用例使用 Jackson2Utils 反序列化出现 Long 类型时间戳无法转换问题。</li>
<li>[修复] 修复 UAA 服务中 Sentinel 配置错误</li>
<li>[优化] 增加 Reactive 环境下 Session 事件发布支持</li>
<li>[优化] 优化 Lettuce 连接池配置，进一步提升吞吐效率</li>
<li>[优化] 优化 Spring Session 配置，进一步降低在高并发环境下的资源损耗</li>
<li>[优化] 统一恢复 Sentinel Eager 配置。</li>
<li>[优化] 去除 shutdown actuator endpoint 配置，提升系统安全性，修复 Spring Boot 3.5.0 环境下兼容性提示问题。</li>
<li>[优化] 优化 RemoteApplicationListener 日志输出内容，增加 Event 源头服务名称，方便定位和明晰数据来源。</li>
<li>[升级] emqx docker 镜像版本升级至 5.10.0</li>
<li>[升级] clickhouse-server docker 镜像版本升级至 25.5.2</li>
<li>[升级] tdengine docker 镜像版本升级至 3.3.6.9</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] aliyun-java-sdk-core 版本升级至 4.7.6</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.251.ALL</li>
<li>[升级] bootstrap webjars 版本升级至 5.3.7</li>
<li>[升级] central-publishing-maven-plugin 版本升级至 0.8.0</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.21</li>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.10.1</li>
<li>[升级] loki-logback-appender 版本升级至 2.0.0</li>
<li>[升级] redisson 版本升级至 3.50.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.66</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.5</li>
<li>[升级] vue webjars 版本升级至 3.5.17</li>
<li>[升级] weixin-java 版本升级至 4.7.6-20250609.143003</li>
<li>[升级] commons-fileupload 版本升级至 1.6.0</li>
<li>[升级] okio 版本升级至 3.13.0</li>
</ul>
</li>
</ul>
<h2>v3.5.0.1</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增物联网设备影子管理功能</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增基于 Spring Boot 的工厂模式抽象定义，方便代码中规范化使用和减少重复代码。</li>
<li>[重构] 重构 JSON 工具类系统统一 ObjectMapper 注入方式，同时调整所在模块以减少过多的依赖层级。</li>
<li>[重构] 重构 MqttTopic.java 支持更多主题的处理，同时简化重复或相似代码提升易维护性。</li>
<li>[修复] 修复 data-core 模块关系配置异常</li>
<li>[修复] 修复系统统一 ObjectMapper 初始化方式错误，导致 Jackson2AutoConfiguration 初始化时机异常，引起系统启动出错问题。</li>
<li>[修复] 修复 Spring Boot 自定义 Banner 显示异常问题</li>
<li>[优化] 所有 pom 文件增加 name 标签，以支持中央仓库更加严格的信息校验</li>
<li>[优化] 删除无用的依赖统一版本控制配置项</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.243.ALL</li>
<li>[升级] bootstrap webjars 版本升级至 5.3.6</li>
<li>[升级] bcpkix-jdk18on 版本升级至 1.81</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.81</li>
<li>[升级] hypersistence-utils-hibernate-63 版本升级至 3.10.0</li>
<li>[升级] redisson 版本升级至 3.49.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.60</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.5</li>
<li>[升级] springdoc 版本升级至 2.8.9</li>
<li>[升级] sqlite-jdbc 版本升级至 3.50.1.0</li>
<li>[升级] weixin-java 版本升级至 4.7.6-20250609.143003</li>
<li>[升级] webauthn4j 版本升级至 0.29.3.RELEASE</li>
<li>[升级] checker-qual 版本升级至 3.49.4</li>
</ul>
</li>
</ul>
<h2>v3.5.0.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 3.5.0</li>
<li>[升级] Spring Authorization Server 版本升级至 1.5.0</li>
<li>[升级] Spring Cloud 版本升级至 2025.0.0</li>
<li>[升级] Spring Cloud Tencent 版本升级至 2.0.1.0-2023.0.3</li>
<li>[升级] Spring Boot Admin 版本升级至 3.5.0</li>
<li>[升级] Nacos 版本升级至 3.0.1。自封装支持 Postgresql 的 Nacos Docker 镜像已经上传至 Docker Hub 和 <a href="http://Quay.IO" target="_blank" rel="noopener noreferrer">Quay.IO</a></li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 增加设置设备属性和调用设备服务参数校验控制</li>
<li>[新增] 新增设置设备属性和调用设备服务接口</li>
<li>[修复] 修复因使用 Import 方式依赖 SCT 导致 Springdoc 版本被干扰导致无法升级问题。</li>
<li>[优化] hikari 和数据库连接相关配置，进一步提升数据库连接和使用效能</li>
<li>[优化] 优化 MqttTopic 定义，支持更多 Mqtt 主题应用场景</li>
<li>[优化] Spring Boot Test Starter 不再采用全局配置，修改为各模块按需引</li>
<li>[优化] 调整对 JustAuth 依赖所在位置及相关代码，提升相关代码内聚性</li>
<li>[重构] 重构核心基础模块代码，调整部分代码包路径和结构，减少各模块间的依赖和耦合。</li>
<li>[重构] 重构 OAuth2 Client 代码适配最新版本 Spring Security OAuth2</li>
<li>[重构] 重构 WebPathUtils 工具类，适配最新版 Spring Security</li>
<li>[重构] 重构 Spring Authorization Server 认证相关代码，支持 DPoP</li>
<li>[重构] 重构 Spring Authorization Server 认证相关代码，支持 PAR</li>
<li>[升级] minio docker 镜像版本升级至 RELEASE.2025-05-24T17-08-30Z</li>
<li>[升级] grafana docker 镜像版本升级至 12.0.1</li>
<li>[升级] loki docker 镜像版本升级至 3.5.1</li>
<li>[升级] promtail docker 镜像版本升级至 3.5.1</li>
<li>[升级] emqx docker 镜像版本升级至 5.9.0</li>
<li>[升级] influxdb docker 镜像版本升级至 2.7.12</li>
<li>[升级] clickhouse docker 镜像版本升级至 25.5.1</li>
<li>[升级] tdengine docker 镜像版本升级至 3.3.6.6</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.237.ALL</li>
<li>[升级] com.baidu.aip 版本升级至 4.16.20</li>
<li>[升级] grpc 版本升级至 1.73.0</li>
<li>[升级] json-schema-validator 版本升级至 1.5.7</li>
<li>[升级] protobuf 版本升级至 3.25.8</li>
<li>[升级] redisson 版本升级至 3.48.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.31.53</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.38.3</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.22.0</li>
<li>[升级] vue webjars 版本升级至 3.5.16</li>
<li>[升级] weixin java 版本升级至 4.7.5-20250529.111829</li>
<li>[升级] okio 版本升级至 3.12.0</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>v4.0.X</title>
      <link>https://www.herodotus.cn/logs/</link>
      <guid>https://www.herodotus.cn/logs/</guid>
      <source url="https://www.herodotus.cn/rss.xml">v4.0.X</source>
      <description>v4.0.6.0 主要更新 [升级] Spring Boot 版本升级至 4.0.6 [升级] Spring Boot Admin 版本升级至 4.0.4 [升级] Nacos 版本升级至 3.2.1 [升级] Debezium 版本升级至 3.5 [新增] 新增 OAuth2 认证动态开启或关闭功能（开源版） [新增] 新增服务本地文件管理功能（开源...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>v4.0.6.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 4.0.6</li>
<li>[升级] Spring Boot Admin 版本升级至 4.0.4</li>
<li>[升级] Nacos 版本升级至 3.2.1</li>
<li>[升级] Debezium 版本升级至 3.5</li>
<li>[新增] 新增 OAuth2 认证动态开启或关闭功能（开源版）</li>
<li>[新增] 新增服务本地文件管理功能（开源版）</li>
<li>[新增] 新增本地文件和 OSS 多级融合的文件文件管理功能（开源版）</li>
</ul>
</li>
<li>其他更新
<ul>
<li>[新增] 新增 Json Schema 多级文件存储配置参数和自动配置</li>
<li>[新增] 新增测试用途菜单场景分类</li>
<li>[新增] 前端新增独立的测试用途 Layout 及菜单</li>
<li>[新增] 新增对象储存启用条件注解及其配置，OSS 按条件启用增强配置灵活性</li>
<li>[新增] 新增证书文件多级下载 REST 接口</li>
<li>[重构] 重构各模块 constants 包名命名，统一修改为 constant</li>
<li>[重构] 重构系统核心数据存储以及 SAS 数据存储相关代码，提取独立模块与企业版对齐，提升扩展为其它存储形式的便捷性</li>
<li>[重构] 重构系统动态开启关闭认证相关类及方法名称增强易读性</li>
<li>[重构] 重构 hibernate 自定义扩展模块包名，去除多余的、与模块名称不匹配的包层次定义</li>
<li>[修复] 修复单体版默认 OSS 配置未使用最新配置问题</li>
<li>[修复] 修复 Axios has Unrestricted Cloud Metadata Exfiltration via Header Injection Chain(CVE-2026-40175)</li>
<li>[修复] 修复前端菜单编辑界面，控制逻辑错误引起选项</li>
<li>[修复] 修复 MessageErrorCodeMapperBuilderCustomizer Bean 定义重复引起服务启动失败问题</li>
<li>[修复] 修复多级文件存储，在未开启 OSS 远程存储的情况下，还会调用远程上传下载引起跑错问题</li>
<li>[修复] 修复前端证书文件下载在后端未配置 OSS 的情况下仍旧使用 OSS 文件下载问题</li>
<li>[优化] 优化各个版本 Redis 默认数据库配置，防止出现不必要的冲突以及序列化问题</li>
<li>[优化] 补充系统关键代码实现逻辑注释说明，以便用户更好的理解整体逻辑</li>
<li>[升级] kafka docker 镜像版本升级至 4.2.0</li>
<li>[升级] zipkin docker 镜像版本升级至 3.6.1</li>
<li>[升级] grafana docker 镜像版本升级至 12.4.3</li>
<li>[升级] alloy docker 镜像版本升级至 1.15.1</li>
<li>[升级] tempo docker 镜像版本升级至 2.10.4</li>
<li>[升级] cassandra docker 镜像版本升级至 5.0.8</li>
<li>[升级] kestra docker 镜像版本升级至 1.3.14</li>
<li>[升级] skywalking 镜像版本升级至 10.4.0-java21</li>
<li>[升级] liberica 基础镜像版本升级至 25.0.3-11-cds</li>
<li>[升级] ip 地址数据库更新至 2026.04.22</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 5.1.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.762.ALL</li>
<li>[升级] bcpkix-jdk18on 版本升级至 1.84</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.84</li>
<li>[升级] commons-io 版本升级至 2.22.0</li>
<li>[升级] guava 版本升级至 33.6.0-jre</li>
<li>[升级] influxdb-client 版本升级至 8.0.0</li>
<li>[升级] json-schema-validator 版本升级至 3.0.2</li>
<li>[升级] langchain4j 版本升级至 1.13.1</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.42.40</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.45.1</li>
<li>[升级] springdoc 版本升级至 3.0.3</li>
<li>[升级] spring grpc 版本升级至 1.0.3</li>
<li>[升级] swagger-core 版本升级至 2.2.47</li>
<li>[升级] weixin java 版本升级至 4.8.2-20260420.104332</li>
<li>[升级] webauthn4j 版本升级至 0.31.3.RELEASE</li>
<li>[升级] quasar 版本升级至 2.19.3</li>
<li>[升级] vue webjars 版本升级至 3.5.33</li>
<li>[升级] error_prone_annotations 版本升级至 2.49.0</li>
<li>[升级] javassist 版本升级至 3.31.0-GA</li>
</ul>
</li>
</ul>
<h2>v4.0.5.2</h2>
<ul>
<li>主要更新
<ul>
<li>[优化] 优化单体版模块及代码的命名，优化核心 dependencies 配置精简配置提升版本控制准确性</li>
<li>[新增] 新增 AI 相关依赖配置</li>
</ul>
</li>
<li>其他更新
<ul>
<li>[修复] 修复 springdoc bom 名称配置错误问题</li>
<li>[修复] 修复使用新的 springdoc bom，缺少swagger-core 依赖定义问题</li>
<li>[优化] 选用更精准和贴切的单体英文单词，重命名相关代码类名及模块名</li>
<li>[优化] 优化系统统一 dependencies repository 配置，增加中央仓库 snapshots。</li>
<li>[优化] application.yml 配置文件 _.yml 后缀统一修改为官方更为推荐、更符合现代规范的 _.yaml 后缀格式</li>
<li>[优化] 消除 grpc 相关模块，间接引入了 bcpkix-jdk15to18，引起工程中出现多个版本 bcpkix 问题</li>
<li>[优化] 删除 jaxb-impl 版本配置，统一使用 Spring Boot Parent 中的版本控制</li>
<li>[优化] 优化自定义 Dependencies 中 grpc 版本控制被 srping grpc 中 grpc 版本覆盖问题</li>
<li>[优化] 优化 springdoc 版本统一控制逻辑，确保整个工程使用正确的 springdoc 版本，避免被 spring-cloud-tencent-dependencies 中定义的 springdoc.version 干扰问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.720.ALL</li>
<li>[升级] redisson 版本升级至 4.3.1</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.42.30</li>
<li>[升级] weixin java 版本升级至 4.8.2-20260322.214559</li>
<li>[升级] webauthn4j 版本升级至 0.31.2.RELEASE</li>
<li>[升级] quasar 版本升级至 2.19.3</li>
<li>[升级] vue webjars 版本升级至 3.5.32</li>
<li>[升级] error_prone_annotations 版本升级至 2.49.0</li>
</ul>
</li>
</ul>
<h2>v4.0.5.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Nacos 版本升级至 3.2.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复响应式 WebSocket 链接请求被网关拦截问题</li>
<li>[优化] 优化前端在 Vite 8 环境下代码拆包逻辑</li>
<li>[优化] 优化前端图片放置目录以及读取方式，改进生产环境打包图片处理逻辑</li>
<li>[优化] 前端工程使用 oxfmt 替换 prettier</li>
<li>[升级] kafka docker 镜像版本升级至 4.1.2</li>
<li>[升级] zipkin docker 镜像版升级至 3.6.0</li>
<li>[升级] grafana docker 镜像版本升级至 12.4.2</li>
<li>[升级] loki docker 镜像版本升级至 3.7.1</li>
<li>[升级] tempo docker 镜像版本升级至 2.10.3</li>
<li>[升级] cassandra docker 镜像版本升级至 5.0.7</li>
<li>[升级] node-red docker 镜像版本升级至 4.1.8-22</li>
<li>[升级] clickhouse docker 镜像版本升级至 25.12.9</li>
</ul>
</li>
<li>依赖升级
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 5.1.2</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.42.25</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.44.0</li>
<li>[升级] quasar webjars 版本升级至 2.19.2</li>
</ul>
</li>
</ul>
<h2>v4.0.5.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 4.0.5</li>
<li>[升级] Nacos 版本升级至 3.1.2</li>
<li>[升级] 前端 Typescript 版本升级至 6.0.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复后端单体版在 Context Path 环境下，鉴权是判断是否为静态资源错误问题</li>
<li>[修复] 修复使用第三方社会化登录无法获取用户信息问题</li>
<li>[修复] 修复从请求中获取默认 0:0:0:0:0:0:0:1 IP 格式变化为 [0:0:0:0:0:0:0:1] 引起的抛错</li>
<li>[修复] 修复单体版及前端合并打包为镜像，设置 Vue 环境变量不生效问题</li>
<li>[修复] 修复前端模块在 Vite 8 环境下打包路径变化，导致引用出错问题</li>
<li>[优化] 优化 Vite 环境下 NODE_ENV 以及 BASE_URL 的配置和使用。</li>
<li>[优化] 优化前端和单体合并打包镜像大量配置以及环境设置，适配当前最新版本</li>
<li>[优化] 优化大量前端 Vite 配置，调整并适配 Vite 8.</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 5.1.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.705.ALL</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.42.21</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.43.9</li>
<li>[升级] weixin java 版本升级至 4.8.2-20260322.214559</li>
<li>[升级] quasar webjars 版本升级至 2.19.1</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.24</li>
<li>[升级] vue webjars 版本升级至 3.5.31</li>
</ul>
</li>
</ul>
<h2>v4.0.4.1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] operaton 版本升级至 2.0.0</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构主工程分组模块名称，以解决 dependencies 模块在 idea 中 显示不协调问题</li>
<li>[升级] ip 地址数据库更新至 2026.03.18</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-bom 版本升级至 4.34.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.700.ALL</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.42.18</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.43.9</li>
<li>[升级] weixin java 版本升级至 4.8.2-20260321.162446</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.24</li>
</ul>
</li>
</ul>
<h2>v4.0.4.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 4.0.4</li>
<li>[升级] 前端工程 Vite 版本升级至 8.0.0，改用 Rolldown 和 Oxc 替代 esbuild 和 Rollup 编译代码</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复网关轻量级鉴权判断逻辑不够严谨问题</li>
<li>[修复] 开放网关拦截 .well-known 端点，修复无法通过 issuer 自动分析 OAuth2 认证相关端点问题</li>
<li>[修复] 修复 components 模块原有配置样式方式在 Vite 8 中会出现编译错误问题</li>
<li>[修复] 修复初始权限数据初始化脚本错误，引起用户切换账号时出现权限异常</li>
<li>[修复] 修复动态权限表达式值与当前版本 Spring Security 内置方法不匹配问题</li>
<li>[修复] 修复前端在 Vite 8 环境下打包失败问题</li>
<li>[修复] 修复前端 BASE_URL 配置出错，不支持相对路径问题</li>
<li>[修复] 修复演示环境版本 h2 控制台无法访问问题</li>
<li>[重构] 合并已有的多个 Customizer 模式顺序定义常量，至同一个文件中</li>
<li>[重构] 所有 monorepo 模块编辑结果为统一输出 es 和 cjs 两种格式</li>
<li>[重构] 前端主工程编译分片配置 manualChunks，修改为 Vite 8 支持方式</li>
<li>[重构] 前端页面刷新和关闭控制修改为监听 pagehide 事件，避免浏览器控制台显示错误告警</li>
<li>[优化] 调整前端静态图片放置目录以及图片读取方式</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] gRPC 版本升级至 1.80.0</li>
<li>[升级] protobuf-maven-plugin 版本升级至 5.0.2</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.700.ALL</li>
<li>[升级] hutool 版本升级至 7.0.0-M5</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.42.16</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.43.8</li>
<li>[升级] weixin java 版本升级至 4.8.2.B</li>
<li>[升级] quasar webjars 版本升级至 2.18.7</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.23</li>
<li>[升级] jaxb-impl 版本升级至 4.0.7</li>
<li>[升级] okio 版升级至 3.17.0</li>
</ul>
</li>
</ul>
<h2>v4.0.3.0</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 4.0.2</li>
<li>[升级] Skywalking agent 版本升级至 9.6.0</li>
<li>[新增] 新增 REST API 接口审计注解，进一步提升接口审计功能描述的准确性</li>
<li>[新增] 系统证书管理生成的证书与 SAS、Spring SSL 的 融合，实现 系统 Spring Authorization Server JwkSet 证书化管理</li>
<li>[新增] 新增 Spring Boot Admin 服务安全防护保证并与系统认证融合，采用系统提供的 OAuth2 OIDC 方式进行登录认证</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 Vuetify 版本前端，升级至 4.0.1 版本样式异常问题</li>
<li>[修复] 修复原有 archetype-packaging extension 配置在 maven 3.9.13 环境下报错问题</li>
<li>[修复] 修复证书管理模块信任库和密钥库信息会互相覆盖问题</li>
<li>[修复] 优化 container 基础 starter 中相关配置条件，修复 Spring Boot Admin 服务引入 container 基础 starter 启动报错问题</li>
<li>[修复] 修复基于 Spring gRPC 自定义的服务发现客户端无法正确链接到服务端问题</li>
<li>[修复] 修复微服务环境下，由于 WebSocket 相关配置启动时机过早，导致相关 Bean 注入失败问题</li>
<li>[修复] 修复 Spring Security 7 环境下，授权码模式页面跳转异常问题</li>
<li>[修复] 修复添加、修改和删除系统应用时，oauth2_registered_client 表数据不会同步变化问题</li>
<li>[重构] 按照 Spring Authorization Server 7 最新配置方式，重构原有配置逻辑</li>
<li>[重构] 提取 feign-spring-boot-starter 模块，专门用于 feign 相关代码的管理，提升 feign 配置的灵活性，以及 feign 的按需使用。解决原有模式下所有服务用不用feign 都需要进行配置扫描问题</li>
<li>[优化] 删除无用的 Permission 远程访问 Openfeign 和 gRPC 定义及模块</li>
<li>[升级] ip 地址数据库更新至 2026.03.11</li>
<li>[升级] hardened-liberica-runtime-container 镜像版本升级至 jdk-25.0.2-cds-glibc</li>
<li>[升级] grafana 版本升级至 12.4.0</li>
<li>[升级] loki 版本升级至 3.6.7</li>
<li>[升级] promtail 版本升级至 3.6.7</li>
<li>[升级] tempo 版本升级至 2.10.1</li>
<li>[升级] node-red 版本升级至 4.1.5-22</li>
<li>[升级] influxdb 版本升级至 2.8.0</li>
<li>[升级] clickhouse 版本升级至 25.12.8</li>
<li>[升级] kestra 版本升级至 1.3.0</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 5.0.2</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.675.ALL</li>
<li>[升级] json-schema-validator 版本升级至 3.0.1</li>
<li>[升级] redisson 版本升级至 4.3.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.42.10</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.43.8</li>
<li>[升级] weixin java 版本升级至 4.8.1-20260310.172740</li>
<li>[升级] webauthn4j 版本升级至 0.31.1.RELEASE</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.22</li>
<li>[升级] vue webjars 版本升级至 3.5.30</li>
<li>[升级] joda-time 版本升级至 2.14.1</li>
<li>[升级] zookeeper 版本升级至 3.9.5</li>
</ul>
</li>
</ul>
<h2>v4.0.3.0-RC2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 4.0.1</li>
<li>[重构] 使用 Spring gRPC 替换原有 net.devh grpc-spring-boot-starter</li>
<li>[新增] 基于 Spring Security 的 gRPC 方法动态权限管理，暂不支持 Reactive 环境</li>
<li>[新增] 新增基于 Spring gRPC 的服务端及客户端扩展 Starter，支持 gRPC 服务发现</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复微服务环境下，前端 WebSocket 显示链接失败问题</li>
<li>[修复] 修复工作流相关配置名称未变更为 operaton 问题</li>
<li>[修复] 修复静态权限修改为聚合模式之后，存在本地权限缓存数据不完整问题</li>
<li>[重构] 重构已有 gRPC 模块 Proto 文件定义，规范编译后生成代码命名</li>
<li>[重构] 重构数字信封前后端加密相关代码，将相关代码合并至同一模块下，提升代码内聚性和易维护性</li>
<li>[重构] 重构验证码模块以及其它相关代码，解决验证码基础定义代码分散在多个模块问题，进一步降低验证码代码与其它模块的耦合性</li>
<li>[重构] 合并 Web 环境自动配置和 Starter 模块，以减少不必要的模块划分</li>
<li>[优化] 优化 core、spring、web 核心模块依赖，去除不必要的依赖，调整部分依赖放置模块，提升内聚性</li>
<li>[优化] 优化前端请求 Canceler 缓存 Key 配置异常问题</li>
<li>[优化] 统一 protobuf 相关依赖版本</li>
<li>[优化] 统一 gRPC 以及 Spring gRPC 相关依赖版本</li>
<li>[优化] 优化 authorization-autoconfigure 模块部分类和方法命名，删除部分类中配置的无用的 @Component 注解</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.662.ALL</li>
<li>[升级] operaton bpmn 版本升级至 2.0.0-M3</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.42.4</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.43.4</li>
<li>[升级] springdoc 版本升级至 3.0.2</li>
<li>[升级] weixin java 版本升级至 4.8.1-20260228.223852</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.20</li>
<li>[升级] vue webjars 版本升级至 3.5.29</li>
<li>[升级] error_prone_annotations 版本升级至 2.48.0</li>
<li>[升级] protobuf-bom 版本升级至 4.34.0</li>
</ul>
</li>
</ul>
<h2>v4.0.3.0-RC1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 2.1.1.0-2024.0.3</li>
<li>[升级] Spring Boot Admin 版本升级至 4.0.0</li>
<li>[新增] 新增基于 Vuetify 组件、与原版本基于 Quasar 组件共享核心模块的的全新前端工程</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增对象存储对于对象版本列表显示接口</li>
<li>[新增] 新增基于 AWS S3 对象存储的分页支持</li>
<li>[修复] 修复 central-publishing-maven-plugin 模块排除配置错误，会发布上传不必要模块问题</li>
<li>[修复] 修复开启 API 版本后，接口动态鉴权失败问题。</li>
<li>[修复] 修复对象存储对象列表中，是否为目录判断逻辑不正确问题</li>
<li>[修复] 修复获取对象存储对象属性信息必要参数缺失以及响应结果转换继承基类错误，出现接口调用抛错问题</li>
<li>[优化] 默认开启 API 版本控制支持，减少用户自己配置出现接口调用异常问题</li>
<li>[重构] 重构对象存储各类实体基类以及继承关系，规范统一各类实体中 ETag 的 set、get 方法命名以及 Jackson 反序列化字段名</li>
<li>[重构] 基于 AWS S3 API 重构前端 OSS 整体页面操作</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 5.0.0</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.658.ALL</li>
<li>[升级] opengauss-jdbc 版本升级至 6.0.3-og</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.41.33</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.43.4</li>
<li>[升级] weixin java 版本升级至 4.8.1-20260209.141916</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.19</li>
</ul>
</li>
</ul>
<h2>v4.0.2.0-M4</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Alibaba 版本升级至 2025.1.0.0</li>
<li>[升级] Spring Boot Admin 版本升级至 4.0.0-M2</li>
<li>[重构] 提取统一 dependencies 工程，方便多工程代码统一管理依赖版本。</li>
<li>[重构] 重新规划 Maven dependencies 配置以及继承关系，工程自身模块单独提取为 Bom，方便以 import 方式使用，为 Maven 4 的 bom 类型的改造预留空间</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 Cloud 主工程非服务类型模块支持 Maven 中央仓库发布配置</li>
<li>[新增] 新增基于 AWS S3 V2 SDK 的主要管理功能 API 的业务逻辑实现</li>
<li>[新增] AWS S3 V2 SDK 统一错误处理以及易交互错误信息，并纳入系统统一错误码提</li>
<li>[新增] 新增 OSS 模块枚举字典聚合配置</li>
<li>[重构] 重构 OSS AWS V2 存储桶相关代码逻辑，补充存储同列表状态信息显示和修改存储桶权限接口</li>
<li>[优化] 优化 OSS 模块接口返回信息状态，在统一响应结果中补充 OSS 服务端执行结果状态</li>
<li>[优化] 自实现相关逻辑代码，去除模块对已经停止维护的 system-lambda 组件的依赖</li>
<li>[优化] 去除已经被标记为过时的 @Temporal 注解使用，改用最新的方式替换</li>
<li>[优化] 删除 nosql-influxdb 模块，暂留 nosql-influxdb2 模块，后期尝试改用 influxdb3</li>
<li>[修复] 修复 OSS 变更对象保留设置请求参数实体时间参数格式化设置错误</li>
<li>[修复] 修复变更存储桶访问权限不生效问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.656.ALL</li>
<li>[升级] fastjson2 版本升级至 2.0.61</li>
<li>[升级] grpc-bom 版本升级至 1.79.0</li>
<li>[升级] hypersistence-utils-hibernate-71 版本升级至 3.15.2</li>
<li>[升级] jsonschema-generator 版本升级至 5.0.0</li>
<li>[升级] operaton 版本升级至 2.0.0-M2</li>
<li>[升级] redisson 版本升级至 4.2.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.41.26</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.43.1</li>
<li>[升级] weixin java 版本升级至 4.8.1-20260209.141916</li>
<li>[升级] error_prone_annotations 版本升级至 2.47.0</li>
<li>[升级] vue webjars 版本升级至 3.5.28</li>
</ul>
</li>
</ul>
<h2>v4.0.2.0-M3</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud 版本升级至 2025.1.1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 前端组件库模块新增组件 Resolver 支持，方便 IDE 更好的识别组件及其定义</li>
<li>[修复] 修复组件库 Resolver 分析范围过宽，干扰主工程相同前缀组件的自动导入问题</li>
<li>[修复] 修复对象存储文件下载返回 GMT 时间格式解析异常，导致失败问题</li>
<li>[修复] 修复部分代码在编译时出现的类型安全告警，以及部分已过时方法告警提示</li>
<li>[修复] 修复对象存储流式文件下载抛出 Service: S3, Status Code: 304 异常问题</li>
<li>[重构] 前端接口 Service 基础定义，增加支持读写分离的定义，以便与后端丰富的 Rest 抽象定义保持一致</li>
<li>[重构] 重构自定义时间工具类，如果参数为空，不再默认返回当前时间，统一返回 null。</li>
<li>[重构] 重构本地和OSS多级文件存储代码逻辑，清晰核心接口定义，规范不同接口定义职责，减少歧义逻辑更加便于理解和维护</li>
<li>[重构] 重构证书管理模块</li>
<li>[优化] 前端组件库模块中的组件代码全部修改为 Composition API 实现</li>
<li>[优化] 去除早前版本为了解决 commons-logging 与 spring-jcl 冲突而设置的 commons-logging 排除设置</li>
<li>[优化] 优化枚举类型字段数据库存储长度，减少数据存储空间浪费</li>
<li>[优化] 数据库初始化脚本，新增证书文件管理和证书吊销列表菜单数据</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 4.1.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.645.ALL</li>
<li>[升级] hypersistence-utils-hibernate-71 版本升级至 3.15.1</li>
<li>[升级] loki-protobuf 版本升级至 0.0.2_pb4.33.0</li>
<li>[升级] protobuf-bom 版本升级至 4.33.5</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.41.19</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.42.2</li>
<li>[升级] webauthn4j 版本升级至 0.31.0.RELEASE</li>
<li>[升级] weixin java 版本升级至 4.8.1-20260131.225507</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.18</li>
<li>[升级] objenesis 版本升级至 3.5</li>
</ul>
</li>
</ul>
<h2>v4.0.2.0-M2</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot Admin 版本升级至 4.0.0-M1</li>
<li>[升级] Operaton 版本升级至 2.0.0-M1</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增 DTO 通用属性抽象实现类</li>
<li>[重构] 去除使用率较低的 rabbitmq starter 模块</li>
<li>[重构] 去除 feign-httpclient 依赖，间接解决工程中会引入 httpclient 4.5.14 问题</li>
<li>[重构] 去除 Maven 中已经无用的版本统一控制配置</li>
<li>[重构] hutool 相关模块改用按需引入方式，以减少比不要的引入</li>
<li>[重构] 前端基础API定义适配后端支持Entity和Dto两种方式</li>
<li>[重构] 前端表格通用 hook 定义适配后端支持Entity和Dto两种方式</li>
<li>[重构] Quasar 版前端主要目录结构，调整至与 Vuetify 版一致</li>
<li>[重构] 前端优化现有 Typescript 定义对 Dto 类型参数和响应的支持</li>
<li>[修复] 修复基础 RestController 定义保存方法缺失 @Validated 和@RequestBody 注解，引起保存失败问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.630.ALL</li>
<li>[升级] influxdb-client 版本升级至 7.5.0</li>
<li>[升级] jasypt-spring-boot-starter 版本升级至 4.0.4</li>
<li>[升级] loki-logback-appender 版本升级至 2.0.3</li>
<li>[升级] operaton 版本升级至 2.0.0-M1</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.41.14</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.42.2</li>
<li>[升级] weixin java 版本升级至 4.8.1.B</li>
<li>[升级] vue webjars 版本升级至 3.5.27</li>
</ul>
</li>
</ul>
<h2>v4.0.2.0-M1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 4.0.2</li>
<li>[重构] 全面改用 Operaton 替换已有 Camunda 相关内容</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 前端 lodash 工具方法不再从 core 模块统一引入，修改为直接从 lodash 模块中 import，以提升代码 Tree shaking 能力和性能</li>
<li>[重构] 重构基础 Service 和 Controller 定义，在原有 Spring Data 实体绑定基础上，支持 DTO 类型请求和响应实体。同时支持 Spring Data Page 和 Slice 两种分页场景。</li>
<li>[重构] 响应式和阻塞式环境 Message Rest API 模块合并为一个模块</li>
<li>[修复] 修复响应式环境 Rest API Version 方法变化，出现编译错误问题</li>
<li>[修复] 修复自定义 Banner 与 Spring Cloud Stream 内置 Banner 冲突，不显示问题。</li>
<li>[优化] 使用占位符变量方式，统一修改 POM 文件 name 标签值，以减少模块名称和 name 不一致情况，提升维护的便捷性</li>
<li>[优化] 完善各模块 POM 中的功能定位和用途描述</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.630.ALL</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.41.12</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.42.2</li>
<li>[升级] weixin java 版本升级至 4.8.0-20260120.134009</li>
<li>[升级] vue webjars 版本升级至 3.5.27</li>
</ul>
</li>
</ul>
<h2>v4.0.1.0-M2</h2>
<ul>
<li>主要更新
<ul>
<li>[重构] PKI 模块证书签发及存储和上传功能重构完成，采用更合理的格式存储证书、私钥，支持私钥加密存储</li>
<li>[重构] 重构本地文件存储及服务间文件传输体系，简化代码逻辑，清晰化代码定位，便于理解和维护</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 新增常用文件后缀常量列表</li>
<li>[新增] 新增 PKI 证书吊销列表（CRL）功能</li>
<li>[新增] 新增 SecurityProvider RsaKeyPair 方法对应单元测试用例</li>
<li>[新增] 新增 CertificateGenerator 根证书生成方法对应单元测试用例</li>
<li>[修复] 修复证书管理证书持有者的身份信息 DistinguishedName，生成顺序与 KeyStore 存储顺序不一致问题。</li>
<li>[修复] 修复 Banner 在 Gateway 服务中不显示问题</li>
<li>[优化] 优化 GRPC 相关模块 proto3 配置</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.624.ALL</li>
<li>[升级] central-publishing-maven-plugin 版本升级至 0.10.0</li>
<li>[升级] hutool 版本升级至 7.0.0-M4</li>
<li>[升级] mybatis plus 版本升级至 3.5.16</li>
<li>[升级] protobuf 版本升级至 4.33.4</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.41.7</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.42.1</li>
<li>[升级] weixin java 版本升级至 4.8.0-20260110.142351</li>
<li>[升级] checker-qual 版本升级至 3.53.0</li>
<li>[升级] error_prone_annotations 版本升级至 2.46.0</li>
<li>[升级] hutool 5.X 版本升级至 5.8.43</li>
</ul>
</li>
</ul>
<h2>v4.0.1.0-M1</h2>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 4.0.1</li>
<li>[升级] Spring Cloud Alibaba 版本升级至 2025.1.0.0-SNAPSHOT</li>
<li>[升级] Spring Cloud Tencent 版本升级至 2.1.0.3-2024.0.2</li>
<li>[升级] Debezium 版本升级至 3.4</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 删除自定义 Async 配置，改用 Spring Boot 4 默认提供的方式支持 Task 的虚拟线程支持</li>
<li>[重构] 重构 Keystore 相关操作封装代码，简化 Exception 抛出逻辑</li>
<li>[修复] 修复静态接口权限，在没有任何配置情况下仍旧会设置 Security 权限问题</li>
<li>[修复] 修复 Reactive 环境，多配置了 Customizer Bean，引起静态权限配置重复喷子hi问题</li>
<li>[修复] 修复 Reactive 环境下，Spring Integration 配置类引用错误，引起的服务启动抛错问题</li>
<li>[优化] Redis 相关配置统一合并至 Cache 中，以精简配置数量，提升维护的便捷性和配置分类的合理性</li>
<li>[优化] 优化平台和服务配置，按照 Servlet 和 Reactive 不同环境需求进行拆分，以增强不同运行环境配置的独立性。</li>
<li>[优化] 去除服务中无用的日志输出配置</li>
<li>[优化] 优化数据库初始化脚本，提升初始化权限的精确度。</li>
<li>[优化] 优化平台 Exception 整体定义，去除无意义的方法重载</li>
<li>[升级] ip 地址数据库更新至 2025.12.24</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] alipay-sdk-java 版本升级至 4.40.607.ALL</li>
<li>[升级] grpc-bom 版本升级至 1.78.0</li>
<li>[升级] loki-logback-appender 版本升级至 2.0.2</li>
<li>[升级] protobuf-maven-plugin 版本升级至 4.1.2</li>
<li>[升级] redisson 版本升级至 4.1.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.41.0</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.41.0</li>
<li>[升级] weixin java 版本升级至 4.7.9-20251227.211054</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.17</li>
<li>[升级] vue webjars 版本升级至 3.5.26</li>
</ul>
</li>
</ul>
<h2>v4.0.0.0-M5</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增轻量级规则引擎模块</li>
<li>[新增] 新增静态接口权限 Customizer 实现方式，服务可根据实际依赖模块按需动态装配静态接口权限，进一步提升接口鉴权效率。</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[新增] 服务 Docker 镜像基础 JDK 修改为 liberica 加固镜像，以提升镜像整体安全性及性能</li>
<li>[重构] 代码中所有 equals 和 hashcode 重载方法统一修改为使用 JDK Objects 对象实现</li>
<li>[优化] 简化默认静态接口权限配置，除去无用或者重复的配置，提升鉴权分析效率</li>
<li>[优化] 除去多余的 Build Resources Maven 配置</li>
<li>[升级] Maven 版本升级至 3.9.12，消除部分在 JDK 25 下出现的告警信息</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] hypersistence-utils-hibernate-71 版本升级至 3.14.1</li>
<li>[升级] jasypt-spring-boot-starter 版本升级至 4.0.3</li>
<li>[升级] json-schema-validator 版本升级至 3.0.0</li>
<li>[升级] redisson 版本升级至 4.0.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.40.11</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.41.0</li>
<li>[升级] weixin java 版本升级至 4.7.9-20251215.153044</li>
<li>[升级] sm-crypto-v2 webjars 版本升级至 1.15.1</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.10</li>
</ul>
</li>
</ul>
<h2>v4.0.0.0-M4</h2>
<ul>
<li>主要更新
<ul>
<li>[优化] 新版自定义 JPA 二级缓存实现，与原有修改 QueryKey 方式保持完全一致</li>
<li>[优化] 优化枚举字典 ID 生成逻辑，提升数据区分的精确性</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复前端新增和编辑界面 overlay 未配置生效问题</li>
<li>[修复] 修复前端 Axios 防重复提交 Canceler 定义未支持参数，导致部分请求被不合理拦截问题</li>
<li>[修复] 修复前端部门管理及人员归属功能逻辑实现问题</li>
<li>[优化] 核心组件库 dependencies 模块修改为 dante-bom，以解决与 Cloud 工程冲突问题</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 4.1.1</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.572.ALL</li>
<li>[升级] commons-text 版本升级至 1.15.0</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.40.3</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.40.3</li>
<li>[升级] weixin java 版本升级至 4.7.9-20251205.162836</li>
<li>[升级] sweetalert2 webjars 版本升级至 11.26.4</li>
</ul>
</li>
</ul>
<h2>v4.0.0.0-M3</h2>
<ul>
<li>主要更新
<ul>
<li>[新增] 新增 Rest API 接口版本支持，支持含版本的接口动态鉴权及配置</li>
<li>[重构] 按照 Spring Security 7 和 Jackson 3 的新方式重构 Security 及 OAuth2 相关序列化代码</li>
<li>[重构] 使用更优雅及更合理的方式重构自定义 JPA 二级缓存实现，彻底解决需要修改 Hibernate 源代码的问题</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[修复] 修复 Servlet 环境 WebSocket 包扫描路径错误</li>
<li>[修复] 修复 Hibernate 二级缓存异常导致分页查询失效问题</li>
<li>[优化] 统一化 asm 相关包的版本，去除 asm 不同版本依赖</li>
<li>[优化] 去除代码中被标记为过时的 @Nullable 注解</li>
<li>[优化] 消除对 feign okhttp3 的依赖以及配置，统一修改为使用 http2client，与 Spring Boot 生态 httpclient 底层组件统一</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 4.0.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.572.ALL</li>
<li>[升级] hypersistence-utils-hibernate-71 版本升级至 3.13.2</li>
<li>[升级] protobuf-bom 版本升级至 4.33.2</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.40.3</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.40.3</li>
<li>[升级] weixin java 版本升级至 4.7.9-20251205.162836</li>
<li>[升级] checker-qual 版本升级至 3.52.1</li>
</ul>
</li>
</ul>
<h2>v4.0.0.0-M2</h2>
<p>说明：</p>
<p>因诸多周边依赖尚未适配 Spring Boot 4，本版本发布仅为了作为代码标记，补充Git变更记录，验证组件库自动发布。便于后续开发，以及未来用户更新代码。目前很多功能不保证可用，如需使用建议使用 3.5.X 分支代码。</p>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Boot 版本升级至 4.0.0</li>
<li>[升级] Spring Cloud 版本升级至 2025.1.0</li>
<li>[升级] Springdoc 版本升级至 3.0.0</li>
<li>[升级] Nacos 版本升级至 3.1.1。支持 Postgresql 的自主封装 Nacos Docker 镜像已经上传至 Docker Hub 和 QuayIO</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[重构] 重构组件库整体结构以及代码以适配 Spring Boot 4 和 Spring Cloud 2025.1.0</li>
<li>[重构] 去除部分用于控制依赖版本的 maven 配置，统一使用 Spring Boot Dependencies 控制版本</li>
<li>[重构] hypersistence-utils-hibernate-63 调整为 hypersistence-utils-hibernate-71</li>
<li>[重构] 重构 Jackson 相关依赖，改为使用 Jackson3</li>
<li>[重构] 重构多租户相关代码，提取出独立的 Starter，服务可以更加灵活的配置多租户支持。可以动态选择以哪个服务作为多租户管理端。</li>
<li>[重构] 重构 XSS 防护相关代码所在模块位置，以减少不必要的模块依赖</li>
<li>[修复] 修复 ServiceContentHolder 初始化时机错误，引起启动异常问题。</li>
<li>[重构] 重构部分 Context 代码，提升代码的内聚性</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 4.0.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.560.ALL</li>
<li>[升级] bcpkix-jdk18on 版本升级至 1.83</li>
<li>[升级] bcprov-jdk18on 版本升级至 1.83</li>
<li>[升级] hutool 版本升级至 7.0.0-M3</li>
<li>[升级] hypersistence-utils-hibernate-71 版本升级至 3.13.1</li>
<li>[升级] mybatis plus 版本升级至 3.5.15</li>
<li>[升级] querydsl 版本升级至 7.1</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.39.6</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.40.1</li>
<li>[升级] weixin java 版本升级至 4.7.9-20251202.120818</li>
<li>[升级] webauthn4j 版本升级至 0.30.1.RELEASE</li>
<li>[升级] vue webjars 版本升级至 3.5.25</li>
<li>[升级] error_prone_annotations 版本升级至 2.45.0</li>
<li>[升级] hutool 5.X 版本升级至 5.8.42</li>
</ul>
</li>
</ul>
<h2>v4.0.0.0-M1</h2>
<p>说明：</p>
<p>本版版本号定义为 v4.0.0.0-M1，可以使用，但并未升级 Spring Boot 4 和 Spring Cloud 2025.1.0。当前增加部分基础功能，主要定位是为开发 v4.0.0.0 做前序准备并于原 v3.5.8.0 版做切割。</p>
<ul>
<li>主要更新
<ul>
<li>[升级] Spring Cloud Tencent 版本升级至 2.1.0.1-2024.0.2</li>
<li>[新增] 新增前端元素支持多种分类以适应不同场景和客户端需求，为后续权限管理扩展做铺垫</li>
<li>[新增] 新增前端页面元素包括菜单和按钮权限，改用根据角色代码获取方式</li>
<li>[重构] 不再使用 Tomcat 作为 Web 容器，改为使用 Jetty 并优化运行参数配置，支持虚拟线程。提前为 4.0 版本开发做准备</li>
</ul>
</li>
<li>其它更新
<ul>
<li>[升级] Redis 版本适配至 8.4.0</li>
</ul>
</li>
<li>依赖更新
<ul>
<li>[升级] protobuf-maven-plugin 版本升级至 4.0.3</li>
<li>[升级] alipay-sdk-java 版本升级至 4.40.546.ALL</li>
<li>[升级] software.amazon.awssdk 版本升级至 2.39.3</li>
<li>[升级] software.amazon.awssdk.crt 版本升级至 0.40.1</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>使用手册</title>
      <link>https://www.herodotus.cn/user-guide/</link>
      <guid>https://www.herodotus.cn/user-guide/</guid>
      <source url="https://www.herodotus.cn/rss.xml">使用手册</source>
      <description>前言 说明 目前，正在根据最新版 Dante Cloud 中的功能使用与系统设计，逐步补充相关知识点的说明与技术讲解文档。 因为，每个人对知识点的需求与理解不同，加之微服务架构本身就纷繁复杂，使用到的组件和基础设施也多，作者本人补充的内容也许未必是你所需要的知道。 那么，你可以在 Gitee 中留言，简述你想要了解的内容。只要是作者掌握的或者知道的，作...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>前言</h2>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>目前，正在根据最新版 Dante Cloud 中的功能使用与系统设计，逐步补充相关知识点的说明与技术讲解文档。</p>
<p>因为，每个人对知识点的需求与理解不同，加之微服务架构本身就纷繁复杂，使用到的组件和基础设施也多，作者本人补充的内容也许未必是你所需要的知道。</p>
<p>那么，你可以在 Gitee 中留言，简述你想要了解的内容。只要是作者掌握的或者知道的，作者会优先安排补充相关的内容。</p>
<p><a href="https://gitee.com/dromara/dante-cloud/issues/I9PBSA" target="_blank" rel="noopener noreferrer">【留言地址】</a></p>
</div>
<h2>说明</h2>
<p>使用指南旨在帮助 Dante Cloud 用户，更快速的了解系统功能使用、设施调整等内容。希望通过对本指南的学习和了解，尽可能全面的掌握和使用 Dante Cloud，而不仅仅只是停留在可以将系统部署运行起来。</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<ol>
<li>本指南中内容，主要面向 Dante Cloud 4.0.X 及以后的版本。除了新特性的差异外，大部分知识点是各个版本兼容的。</li>
<li>本指南中内容，项目名称仍旧使用开源版本名称 <code>Dante Cloud</code>，但其它相关的信息为了兼容开源版和企业版，相关信息（特别是配置文件名称）均由原来的 <code>dante</code> 变更为了 <code>herodotus</code>。</li>
</ol>
</div>
<h2>交流</h2>
<ol>
<li>Dante Cloud 涉及的内容较多，而且相关内容均力争使用最新、最好的技术，所以对技术基础的要求较高。</li>
<li>既然选择了 Dante Cloud、选择了微服务，那么就请抱着更开放的心态来拥抱新的技术知识体系，切忌不要什么问题都拿旧有的知识生搬硬套。</li>
<li>如果在学习的过程中，对介绍的内容有疑惑或者疑问，可以提ISSUE。真心交流的可以进入交流群深入讨论。官方交流群：<a href="/support/communication.html" target="_blank">【点击了解入群方式】</a></li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>授权用户</title>
      <link>https://www.herodotus.cn/support/authorization.html</link>
      <guid>https://www.herodotus.cn/support/authorization.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">授权用户</source>
      <description>企业版 已授权单位 已授权个人 开源版 🐳 已登记个人</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>企业版</h2>
<h3>已授权单位</h3>
<p>| 序号 | 单位           | 日期       | 序号 | 单位 | 日期 |<br>
| :--: |</p>
]]></content:encoded>
    </item>
    <item>
      <title>功能对比</title>
      <link>https://www.herodotus.cn/support/comparison.html</link>
      <guid>https://www.herodotus.cn/support/comparison.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">功能对比</source>
      <description>说明 重要 Dante Cloud 核心关注点是：「高质量的系统代码」、「合理的系统架构」、「低耦合的模块划分」、「高安全性系统实现」、「灵活的功能扩展能力」，「优质的微服务实现」。不会像其它一些系统一样，追求业务功能的丰富性。堆叠大量无法做到真正通用的功能，反倒会变成负担和干扰，不如由用户自己按照需求灵活设计和实现。 如果您关注的是业务功能是否“丰富...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>说明</h2>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>Dante Cloud 核心关注点是：<strong>「高质量的系统代码」</strong>、<strong>「合理的系统架构」</strong>、<strong>「低耦合的模块划分」</strong>、<strong>「高安全性系统实现」</strong>、<strong>「灵活的功能扩展能力」</strong>，<strong>「优质的微服务实现」</strong>。不会像其它一些系统一样，追求<strong>业务功能</strong>的<strong>丰富</strong>性。堆叠大量无法做到真正通用的功能，反倒会变成负担和干扰，不如由用户自己按照需求灵活设计和实现。</p>
<p>如果您关注的是业务功能是否“丰富”，建议看看其它的项目</p>
</div>
<h2>功能对比</h2>
<h3>[1]Dante Cloud 微服务平台</h3>
<p>| 功能                                                 |       开源版       |       企业版       |                           详情                           |<br>
|</p>
]]></content:encoded>
    </item>
    <item>
      <title>高阶文档</title>
      <link>https://www.herodotus.cn/support/cookbook.html</link>
      <guid>https://www.herodotus.cn/support/cookbook.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">高阶文档</source>
      <description>[一]说明 Dante Cloud Cookbook 既不同于其它项目“用户手册”式的学习资料，也不是代码的堆叠，更不是网络常见知识的搬运。而是以相关基础知识做铺垫，配合源码和原理剖析，结合 Dante Cloud 具体实现，融合作者本人经验、认知和理解，对用户最关心的、最难掌握的知识点、设计思想和设计方法进行循序渐进的、由浅入深的、理论联系实际的讲解...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]说明</h2>
<p>Dante Cloud Cookbook 既不同于其它项目“用户手册”式的学习资料，也不是代码的堆叠，更不是网络常见知识的搬运。而是以相关基础知识做铺垫，配合源码和原理剖析，结合 Dante Cloud 具体实现，融合作者本人经验、认知和理解，对用户最关心的、最难掌握的知识点、设计思想和设计方法进行循序渐进的、由浅入深的、理论联系实际的讲解。</p>
<h3>[1]为什么不建议直接上手阅读？</h3>
<p>虽然本系列很多文章会以“基础知识”为名，但并不代表本系列文章是传统意义上的单纯对“基础知识”的讲解（这类知识书籍很多，相关文章网上也是一搜一大把）。其中融合了非常多的作者对知识点的理解、思考、认知、经验和实战方法等方面内容。常规的基础知识会有所提及，但只是铺垫和承上启下的作用，不会是文章的重点。</p>
<p>如果是开发新手或者接触 Spring Boot、Spring Cloud 时间不长的朋友，还是建议先自行对相关的基础知识了解和学习，再来阅读相关的文章，这样才会对你更有帮助，起到画龙点睛、事半功倍的效果。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>有几本本人认为是微服务相关的必读的、强烈推荐的技术书籍，可入 VIP 群了解详情<a href="https://qm.qq.com/q/v9QalTapZm" target="_blank" rel="noopener noreferrer">【Dante Cloud VIP群】</a>。</p>
<p>入群规则，详见：<a href="https://www.herodotus.cn/others/communication.html" target="_blank" rel="noopener noreferrer">Dante Cloud VIP群入群规则</a></p>
<blockquote>
<p>进群前请给本项目一个 Star！</p>
</blockquote>
</div>
<h3>[2]为什么建议“志同道合”者阅读？</h3>
<p>正如《Dante Cloud 及相关知识学习方法和学习路径的建议》文中所说，如果你不赞同作者的思路和方法，或者连阅读的耐心都没有，其实就已经没有学习本项目、阅读本系列文章的必要和意义了。</p>
<p><strong>学习技术是非常难的，因为没有任何捷径，只能靠脚踏实地的、务实专注的投入；学习技术也是非常简单的，只要有所投入就会有所收获</strong>。</p>
<p>所以还请三而后行，购买文章之前，多问问自己是否真的确实想学。不要文章买了，钱也花了，但没有耐心仔细认真看下去。到头来不仅自己没学到东西，还可能出现由于自己没耐心，学得一知半解，回过头埋怨抹黑作者。</p>
<h3>[2]为什么文章收费？</h3>
<p>Dante Cloud Cookbook 绝大部分内容均为独有、独创内容，网络上找不到同样深入度和全面性的文章。虽然部分文章以“基础知识”为题或者包含了部分基础知识，也是为了让大家更好的理解相关内容做的铺垫。同时，为了规避抄袭拷贝、凸显知识的价值，所以采取了付费模式。</p>
<h2>[二]核心文章</h2>
<h3>[1]认知思考</h3>
<p>| 序号 | 文章标题                                                                                                       | 说明                                                           |<br>
| :--: |</p>
]]></content:encoded>
    </item>
    <item>
      <title>授权说明</title>
      <link>https://www.herodotus.cn/support/</link>
      <guid>https://www.herodotus.cn/support/</guid>
      <source url="https://www.herodotus.cn/rss.xml">授权说明</source>
      <description>重要 暂时不再接受授权购买申请，后续根据情况适时开放。 说明 本说明仅适用于 Dante Cloud 响应式版本（企业版，Herodotus Cloud）代码 [一]特别说明 请购买授权前确认相关技术栈、功能点是否满足贵单位需求。建议购买前先自己尝试搭建体验。 因本产品为源码交付，购买交付后不予退换。 [二]商业授权 [1]授权范围 可用于任意数量的商...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>暂时不再接受授权购买申请，后续根据情况适时开放。</p>
</div>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>本说明仅适用于 Dante Cloud 响应式版本（企业版，Herodotus Cloud）代码</p>
</div>
<h2>[一]特别说明</h2>
<ul>
<li><strong>请购买授权前确认相关技术栈、功能点是否满足贵单位需求。建议购买前先自己尝试搭建体验。</strong></li>
<li><strong>因本产品为源码交付，购买交付后不予退换。</strong></li>
</ul>
<h2>[二]商业授权</h2>
<h3>[1]授权范围</h3>
<ul>
<li>可用于任意数量的商业项目</li>
<li>不限制部署机器数量、部署 IP 、请求域名等，仅对源代码的java package 进行约定</li>
<li>如报备授权前缀为 com.baidu ,则二次开发的项目标准包名为：</li>
</ul>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">package</span><span style="--shiki-light:#383A42;--shiki-dark:#C678DD"> com.baidu.项目名.模块名称.gateway.support</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 如授权范围为 com.baidu，则所有以此为前缀的项目均可使用</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>若授权单位或授权包名发生修改，则需要再次购买商业授权。</p>
</blockquote>
<h3>[2]授权解释</h3>
<ul>
<li>给与商业授权，当前版本以及未来新版本的永久使用权。保证在使用过程中，只要按照约定使用，不会因本项目产生任何法律纠纷。</li>
<li>只允许授权单位在授权包的范围内使用, 非一次性买断任意使用。</li>
<li>允许授权单位[①]以可运行jar、镜像等非源码方式向客户交付。</li>
<li>若授权单位[①]个别项目需要向己方客户单位[②]交付源码，请该客户方单位[②]联系购买对该单位的商业授权。</li>
<li>授权单位按市场监督管理局登记实体为准，子公司、分公司、控股公司等属于独立市场经营机构需单独购买授权。</li>
</ul>
<h3>[3]侵权行为</h3>
<div class="hint-container caution">
<p class="hint-container-title">警告</p>
<ul>
<li>不可二次封装开源</li>
<li>不可将代码进行二次销售</li>
<li>不可分享源代码</li>
</ul>
</div>
<h3>[4]侵权处理</h3>
<ul>
<li>永久黑名单，<strong>在内部开源作者群通报单位或个人</strong></li>
<li>永久封禁，贵公司所属 IP 端将被封禁，所有 Dante Cloud 资源不再提供</li>
<li>支付￥ 500000 侵权费用（含我方法律援助费用）</li>
</ul>
<h2>[三]咨询购买</h2>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>暂时不再接受授权购买申请，后续会根据情况适时开放。</p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>OAuth2动态接口鉴权</title>
      <link>https://www.herodotus.cn/develop-guide/advance/authentication.html</link>
      <guid>https://www.herodotus.cn/develop-guide/advance/authentication.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">OAuth2动态接口鉴权</source>
      <description>摘要 OAuth 2 仅是一种授权框架，本身并不负责鉴权，更没有提供相应的鉴权机制，所以，在基于 OAuth 2 的系统架构中，要实鉴权必须由其它组件或者自己扩展代码来实现。 实际的微服务架构鉴权设计中，常见的方案也就是两种： 方案一资源提供服务各自鉴权：这种模式下，对外提供接口的服务（OAuth 2 中的资源服务器），自己对自己的接口进行保护。 方案...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>摘要</h2>
<p>OAuth 2 仅是一种授权框架，本身并不负责鉴权，更没有提供相应的鉴权机制，所以，在基于 OAuth 2 的系统架构中，要实鉴权必须由其它组件或者自己扩展代码来实现。<br>
实际的微服务架构鉴权设计中，常见的方案也就是两种：</p>
<ol>
<li>方案一资源提供服务各自鉴权：这种模式下，对外提供接口的服务（OAuth 2 中的资源服务器），自己对自己的接口进行保护。</li>
<li>方案二服务网关统一认证鉴权：微服务架构中一般都会有微服务网关，比如 Spring Cloud Gateway，作为请求的统一入口，来实现对接口的统一保护。因为是统一入口，那么在网管进行接口的鉴权，就是一个不二的选择。<br>
谓的动态权限管理，即在不停止服务和不修改代码的情况下，通过后台的管理功能实时动态修改接口的权限。这也是 Dante Cloud 微服务架构系统的<strong>亮点</strong>之一。</li>
</ol>
<p>带您跟随 Dante Cloud 源代码，由浅入深全面掌握 Spring Security 5 &amp; 6 以及基于 OAuth2 的微服务动态接口鉴权的原理与实现</p>
<p>全文通读下来，可能一些朋友会觉得原来 OAuth 2 模式下，鉴权功能也不过如此。确实是这样，“难了不会，会了不难”，软件开发很多东西都是如此，所谓的难点就像一层窗户纸，捅破了也就不是什么问题了。但是，学会的这个过程，如果没有他人辅助，全靠自己摸索学习，确实还是一件非常艰苦的事情，而且还要有耐心，能够耐得住寂寞。</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>本文所说的“动态”，是指不需要修改代码、不需要重新服务。使用<code>@PreAuthorize</code>站在本文角度，只能算作是“静态”</p>
</div>
<h2>阅读</h2>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>本篇文章为<code>《OAuth 2 中的鉴权和动态接口鉴权》</code>，是<code>付费阅读</code>文章，购买方式详见：<a href="/support/cookbook.html" target="_blank">【高阶文档】</a></p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>自动配置顺序控制</title>
      <link>https://www.herodotus.cn/develop-guide/advance/autoconfigure.html</link>
      <guid>https://www.herodotus.cn/develop-guide/advance/autoconfigure.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">自动配置顺序控制</source>
      <description>摘要 本文所说的“Bean”注入顺序，不仅仅指的是用@Bean注解标记的 Bean，还包括@Configuration等注解标记的可自动配置的内容。 可能很多朋友会觉得：“这么简单的问题也值得一提么？”。其实，在笔者看来“Bean”注入顺序问题并不简单。严格来说，这可以说是 Spring Boot 最核心的内容，其它方面的知识点的掌握均是偏重应用（知道...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>摘要</h2>
<p>本文所说的“Bean”注入顺序，不仅仅指的是用@Bean注解标记的 Bean，还包括@Configuration等注解标记的可自动配置的内容。</p>
<p>可能很多朋友会觉得：“这么简单的问题也值得一提么？”。其实，在笔者看来“Bean”注入顺序问题并不简单。严格来说，这可以说是 Spring Boot 最核心的内容，其它方面的知识点的掌握均是偏重应用（知道就会用，不知道去查即可）。但是，能否掌握这一知识点决定了对 Spring Boot 整个体系的掌握。特别是目前基于 Spring 生态的微服务架构，涉及的各种组件纷繁众多，暂不说对这些组件的核心源代码的理解和掌握，仅是能让这些组件按照自己的意图正确运行都可以让一些朋友望而却步。而让这众多组件可以有效配合运行起来的关键就是“Bean”注入顺序控制。</p>
<p>掌握了这个知识点，也就掌握了步入 Spring 生态的开关。因为不管是什么微服务框架、Spring 官方的组件或者第三方的组件，不管其设计实现思路如何，只要使用的是 Spring 生态，都是通过 @Bean和@Configuration方式累积起来的。就是所说的：“万变不离其宗”</p>
<h2>阅读</h2>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>本篇文章为<code>《Spring Boot 3 之自动配置与注入顺序控制》</code>，是<code>付费阅读</code>文章，购买方式详见：<a href="/support/cookbook.html" target="_blank">【高阶文档】</a></p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>接口传参方式详解</title>
      <link>https://www.herodotus.cn/develop-guide/advance/rest.html</link>
      <guid>https://www.herodotus.cn/develop-guide/advance/rest.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">接口传参方式详解</source>
      <description>摘要 在现今的软件开发中，特别是在互联网蓬勃发展的今天，HTTP 已经成为不可或缺的必要组成部分。不管是哪种编程语言，不管是面向互联网的浏览器应用、移动应用、小程序，甚至很多桌面端应用，都离不开 HTTP 协议的使用。 在 Java 开发领域，不管技术如何发展、底层组件和框架如何演进，绝大多数企业应用仍旧是围绕 HTTP 协议进行开发和设计。Sprin...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>摘要</h2>
<p>在现今的软件开发中，特别是在互联网蓬勃发展的今天，HTTP 已经成为不可或缺的必要组成部分。不管是哪种编程语言，不管是面向互联网的浏览器应用、移动应用、小程序，甚至很多桌面端应用，都离不开 HTTP 协议的使用。</p>
<p>在 Java 开发领域，不管技术如何发展、底层组件和框架如何演进，绝大多数企业应用仍旧是围绕 HTTP 协议进行开发和设计。Spring 和 Spring Boot 框架，已经成为 Java 开发必需的框架。而基于 Spring 或 Spring Boot 编写基于 HTTP 协议的 Controller 或 RestController，也已经成为 Java 开发日常的主要工作。相信只要从事过相关的开发工作，对此一定不会陌生。</p>
<p>但是，由于需求的多变、场景的复杂、Spring 框架的灵活以及使用 HTTP 传递内容的多样，使得在开发 Controller 或 RestController 的过程，到底该使用哪种请求方式、使用哪种 Content-Type、用什么方式接收参数，经常会让人困惑或者难以选择。并不是说大家对该知识点不熟悉、不了解，而是在开发过程中，在抉择如何使用更合适、更符合需求场景时，还是会让人产生很多困扰和纠结（至少笔者本人就是如此）。</p>
<p>编写本文的目的，一方面是作者本人希望进一步清晰相关知识和逻辑，以提升代码编写的质量和准确性；另一方面，通过总结和梳理形成一个手册，在开发过程中有困惑时，通过该文章很方便的查询到相关的知识。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>如果你对于什么时候使用 <code>application/x-www-form-urlencoded</code> 和 <code>application/json</code> 还比较困惑，还搞不清楚什么场景下该使用 <code>@PathVariable</code>、<code>@RequestParam</code>、<code>@RequestBody</code>、<code>@RequestPart</code>和实体Bean，以及该怎么组合使用它们，那么就该读读本篇文章</p>
</div>
<h2>阅读</h2>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>本篇文章为<code>Spring Boot 3 之 Rest 接口传参方式详解》</code>，是<code>付费阅读</code>文章，购买方式详见：<a href="/support/cookbook.html" target="_blank">【高阶文档】</a></p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>OAuth2中Scope与Role</title>
      <link>https://www.herodotus.cn/develop-guide/advance/scope.html</link>
      <guid>https://www.herodotus.cn/develop-guide/advance/scope.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">OAuth2中Scope与Role</source>
      <description>摘要 OAuth 全称是 Open Authentication。大家都知道 OAuth 是一个开放标准，但是除此以外似乎找不到一个非常准确的而且容易理解的 OAuth 定义。在网络中能够搜索到的 OAuth 的定义，基本都是来源于网络各文章作者自己对 OAuth 的理解。 为此，特别选择了一个笔者本人认为相对来说更容易理解的一个定义，具体如下： 开放...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>摘要</h2>
<p>OAuth 全称是 Open Authentication。大家都知道 OAuth 是一个开放标准，但是除此以外似乎找不到一个非常准确的而且容易理解的 OAuth 定义。在网络中能够搜索到的 OAuth 的定义，基本都是来源于网络各文章作者自己对 OAuth 的理解。</p>
<p>为此，特别选择了一个笔者本人认为相对来说更容易理解的一个定义，具体如下：</p>
<p>开放授权（ OAuth ）是一个开放标准，允许用户让第三方应用访问该用户在某一网站上存储的私密的资源（如照片，视频，联系人列表），而无需将用户名和密码提供给第三方应用。 OAuth 允许用户提供一个令牌，而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站（例如，视频编辑网站)在特定的时段（例如，接下来的2小时内）内访问特定的资源（例如仅仅是某一相册中的视频）。这样，OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息，而非所有内容。</p>
<p>OAuth 主要有 OAuth 1.0 和 OAuth 2.0 两个版本，并且二者完全不同，且不兼容。OAuth 2.0 是目前广泛使用的版本，我们多数谈论 OAuth 时，一般都是指的 OAuth 2.0 版本。随着Spring Authorization Server 逐步普及， OAuth 2.1 版本也逐步地进入了大众的视野。</p>
<p>这里为什么要提及 OAuth 的定义？请注意定义中的关键词“第三方应用”，这个词对于理解后续内容有着至关重要的作用。</p>
<p>笔者认为使用 OAuth 2 最大的难点，是对 OAuth 2 中各种术语以及概念的理解——这里所说的“理解”，指的不仅仅是对概念字面意思的理解，而是对其真正“含义”的理解。能否真正理解这些术语以及概念的“含义”，会直接影响使用 OAuth 2 的系统设计及实现，甚至影响整个系统的安全保障能力。</p>
<p>OAuth 2 中最为特殊的概念就是 Scope。单独来看 Scope其实并不难理解，但因为 Spring Security生态组件中不仅有 Scope 概念还提供了Role概念。在此基础之上，如果再加上 RBAC 权限模型，那么就会让 OAuth 2 的相关概念变得难以理解。</p>
<p>OAuth 2 中 Scope 和 Role 关系和区别到底是什么？在实际设计过程中，如何应用和处理 Scope 与 Role关系？就成为使用 OAuth2 的关键技术难点之一。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>本文全网独家，深度解析 OAuth2 协议中和 Spring Security 生态各组件中 Scope 和 Role 概念与原理，以及在实战中的应用</p>
</div>
<h2>阅读</h2>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>本篇文章为<code>《OAuth 2 中的 Scope 与 Role 深度解析》</code>，是<code>付费阅读</code>文章，购买方式详见：<a href="/support/cookbook.html" target="_blank">【高阶文档】</a></p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>微服务Session共享</title>
      <link>https://www.herodotus.cn/develop-guide/advance/session.html</link>
      <guid>https://www.herodotus.cn/develop-guide/advance/session.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">微服务Session共享</source>
      <description>摘要 不管用什么编程语言、不管用什么组件，只要是基于 HTTP 协议的网络应用，Session 都是一个绕不开的话题。 虽然，在微服务架构中，维持会话和认证方式是基于 Token 的，可以在很大程度上无需考虑会话（Session）问题。但这并不代表可以完全不用考虑，而且是在几个非常关键的场景下还必须考虑会话问题。比较典型的，在 OAuth 2 认证过程...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>摘要</h2>
<p>不管用什么编程语言、不管用什么组件，只要是基于 HTTP 协议的网络应用，Session 都是一个绕不开的话题。</p>
<p>虽然，在微服务架构中，维持会话和认证方式是基于 Token 的，可以在很大程度上无需考虑会话（Session）问题。但这并不代表可以完全不用考虑，而且是在几个非常关键的场景下还必须考虑会话问题。比较典型的，在 OAuth 2 认证过程中，必须要考虑会话问题，否则就会出现认证过程失败的问题。</p>
<p>加之，在微服务架构中，既有前端、基于 Webflux 的 Spring Cloud Gateway 、基于 Web 的 Spring Boot、OAuth 2 和 Spring Security，还有服务发现和服务调用等等。涉及的组件和技术众多，各有各的用法，各有各的实现方式，就会导致会话相关的问题更加复杂。<br>
这也就是撰写本文的初衷，也是本文的重点。</p>
<p>本文会由浅入深的从 Session 的基本概念到 Spring Cloud 微服务架构体系中的 Spring Session 共享进行讲解，帮助各位读者解惑这一关键知识点。</p>
<h2>阅读</h2>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>本篇文章为<code>微服务架构下的 Session 共享及一致性处理</code>，是<code>付费阅读</code>文章，购买方式详见：<a href="/support/cookbook.html" target="_blank">【高阶文档】</a></p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>电子邮件模块</title>
      <link>https://www.herodotus.cn/develop-guide/module/email.html</link>
      <guid>https://www.herodotus.cn/develop-guide/module/email.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">电子邮件模块</source>
      <description>模块 message-mail-spring-boot-starter 用途 工程中依赖了 message-mail-spring-boot-starter 模块，同时设定一定的配置参数，就可以开启电子邮件消息支持。发送纯文本或者基于 Thymeleaf 模版的 Html 电子邮件。 Dante Cloud 中提供的 用户电子邮件地址验证、登录常用位置...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>模块</h2>
<ul>
<li><code>message-mail-spring-boot-starter</code></li>
</ul>
<h2>用途</h2>
<p>工程中依赖了 <code>message-mail-spring-boot-starter</code> 模块，同时设定一定的配置参数，就可以开启电子邮件消息支持。发送纯文本或者基于 Thymeleaf 模版的 Html 电子邮件。</p>
<p>Dante Cloud 中提供的 <code>用户电子邮件地址验证</code>、<code>登录常用位置IP异常通知</code> 和 <code>电子邮件验证码</code> 等功能均需要依赖此模块</p>
<h2>准备</h2>
<p>想要实现发送电子邮件，首先要有一个支持 POP3 协议的电子邮件服务。这个根据自己实际情况选择，可以选择企业自建电子邮件服务，或者使用厂商提供的服务，比如：QQ 邮箱、163邮箱、阿里云等等。</p>
<p>根据自己使用的邮箱，生成发送邮件所需的 POP3 地址，访问用户名和密码等信息。</p>
<h2>用法</h2>
<h3>[1]添加依赖</h3>
<p>在工程中添加依赖：</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.stirrup&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>message-mail-spring-boot-starter&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]Spring Mail 配置</h3>
<p>Dante Cloud Email 发送基于 Spring Mail 实现。想要开启相关功能，除了添加 <code>message-mail-spring-boot-starter</code> 依赖以外，还要在系统中添加 Spring Mail 相关配置。下面以 QQ 邮箱作为示例：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  mail</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    # 指定邮件服务器地址</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">smtp.qq.com</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    # 登录账户</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    username</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">XXXXX@qq.com</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    # 登录密码</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    password</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">XXXXX</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    # 端口</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    port</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">465</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    # 使用的协议</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    protocol</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">smtps</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    # 其他的属性</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      "mail.smtp.connectiontimeout"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">5000</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      "mail.smtp.timeout"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">3000</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      "mail.smtp.writetimeout"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">5000</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      "mail.smtp.auth"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      "mail.smtp.starttls.enable"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      "mail.smtp.starttls.required"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">herodotus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    mail</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      service-account</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">xxx@xxx.com</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[3]自定义配置</h3>
<p>除了 Spring Mail 的配置以外，根据功能的不同，Dante Cloud 提供了自定义的邮件配置。不同的配置会影响不同功能的使用，具体配置参见：<a href="/properties/herodotus/authentication.html#%E7%94%B5%E5%AD%90%E9%82%AE%E4%BB%B6%E6%B6%88%E6%81%AF%E9%85%8D%E7%BD%AE" target="_blank">电子邮件消息配置</a></p>
<h3>[4]发送消息</h3>
<p>以上内容配置完成之后，系统会自动注入相关配置。Dante Cloud 对电子邮件消息的发送也做了简化，采用 Spring Boot 框架的事件(<code>Event</code>)机制，发送 <code>MailMessageEvent</code> 事件就可以实现电子邮件消息的发送。</p>
<p>以下几种方式实现消息发送：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 方式一</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">applicationContext</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">publishEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MailMessageEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MailMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> mailMessage))</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 方式二</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">applicationEventPublisher</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">publishEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MailMessageEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MailMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> mailMessage))</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// 方式三</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ServiceContextHolder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getInstance</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">publishEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MailMessageEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MailMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> mailMessage))</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>以上几种方式均不支持“跨服务”发送，如有需求，可查阅<a href="/develop-guide/coding/message.html" target="_blank">【统一消息发送】</a>。</p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>日志收集模块</title>
      <link>https://www.herodotus.cn/develop-guide/module/log.html</link>
      <guid>https://www.herodotus.cn/develop-guide/module/log.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">日志收集模块</source>
      <description>模块 logging-spring-boot-starter 用途 工程中依赖了 logging-spring-boot-starter 模块，同时设定一定的配置参数，就可以开启日志聚合收集支持。收集服务日志，统一存储进行日志分析。 Dante Cloud 中提供的 日志中心、日志聚合分析等功能均需要依赖此模块 准备 想要实现日志聚合分析，首先要实现日...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>模块</h2>
<ul>
<li><code>logging-spring-boot-starter</code></li>
</ul>
<h2>用途</h2>
<p>工程中依赖了 <code>logging-spring-boot-starter</code> 模块，同时设定一定的配置参数，就可以开启日志聚合收集支持。收集服务日志，统一存储进行日志分析。</p>
<p>Dante Cloud 中提供的 <code>日志中心</code>、<code>日志聚合分析</code>等功能均需要依赖此模块</p>
<h2>准备</h2>
<p>想要实现日志聚合分析，首先要实现日志信息的存储，需要搭建 ELK 或者 Loki + Minio（或其他 OSS） 等基础设施。</p>
<h2>用法</h2>
<h3>[1]添加依赖</h3>
<p>在工程中添加依赖：</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.stirrup&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>logging-spring-boot-starter&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]使用</h3>
<p>Dante Cloud 提供多种日志收集使用方式，具体查看: <a href="/user-guide/infrastructure/log-center.html" target="_blank">【聚合日志数据】</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>链路度量模块</title>
      <link>https://www.herodotus.cn/develop-guide/module/micrometer.html</link>
      <guid>https://www.herodotus.cn/develop-guide/module/micrometer.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">链路度量模块</source>
      <description>模块 micrometer-spring-boot-starter 用途 工程中依赖了 micrometer-spring-boot-starter 模块，就可以开启轻量级链路追踪和观测度量支持。 Dante Cloud 中提供的 轻量级链路追踪系统 功能需要依赖此模块 准备 使用 micrometer-spring-boot-starter 模块前，...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>模块</h2>
<ul>
<li><code>micrometer-spring-boot-starter</code></li>
</ul>
<h2>用途</h2>
<p>工程中依赖了 <code>micrometer-spring-boot-starter</code> 模块，就可以开启轻量级链路追踪和观测度量支持。</p>
<p>Dante Cloud 中提供的 <code>轻量级链路追踪系统</code> 功能需要依赖此模块</p>
<h2>准备</h2>
<p>使用 <code>micrometer-spring-boot-starter</code> 模块前，需要提前安装好 Zipkin 环境或者 Tempo 环境，具体可以参阅：<a href="/user-guide/infrastructure/tracing.html" target="_blank">【服务链路追踪】</a></p>
<h2>用法</h2>
<h3>[1]添加依赖</h3>
<p>在工程中添加依赖，建议在主工程的 <code>facility-spring-boot-starter</code> 中添加</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.stirrup&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>micrometer-spring-boot-starter&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]修改配置</h3>
<p>如果您想要修改上报 Zipkin 的数据的IP和端口，可以通过以下配置进行修改：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">management</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  zipkin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    tracing</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      endpoint</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">http://localhost:9411/api/v2/spans</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>对象存储模块</title>
      <link>https://www.herodotus.cn/develop-guide/module/oss.html</link>
      <guid>https://www.herodotus.cn/develop-guide/module/oss.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">对象存储模块</source>
      <description>[一]基础模块 oss-spring-boot-starter [1]用途 工程中依赖了 oss-spring-boot-starter 模块，同时设定一定的配置参数，就可以开启对象存储支持。 当前版本中的对象存储模块，不再采用原来 dialect 可切换方式，来支持不同厂商的 OSS。而改为完全使用 AWS SDK V2 版本进行重构，而且是仅使用 ...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]基础模块</h2>
<ul>
<li><code>oss-spring-boot-starter</code></li>
</ul>
<h3>[1]用途</h3>
<p>工程中依赖了 <code>oss-spring-boot-starter</code> 模块，同时设定一定的配置参数，就可以开启对象存储支持。</p>
<p>当前版本中的对象存储模块，不再采用原来 dialect 可切换方式，来支持不同厂商的 OSS。而改为完全使用 AWS SDK V2 版本进行重构，而且是仅使用 AWS SDK V2 来实现多厂商的兼容。</p>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>现目前绝大多数兼容 S3 的 OSS 厂商提供的 API，其实均是基于 AWS SDK V1 进行开发，除了兼容 S3 协议以外，有结合自己需求进行了扩展。虽然，不同厂商 OSS SDK 或 API 还是有所差异，但是整体风格、用法甚至类和方法的命名，还是与 S3 V1 保持一致。</p>
<p>但是，AWS SDK V2 的 API 与 V1 版本 API 出现了较大的差异，加之大多数 OSS 厂商 SDK 还是使用阻塞式方式，因此就使得很难使用同一种方式（主要是体力劳动），来同时适配不同厂商 OSS 和 AWS SDK V2。</p>
<p>个人观点认为：AWS SDK V2 API 是未来的标准化的参考，各大 OSS 厂商 SDK 要么维持现状，如果未来要大规模改版也会向着 AWS SDK V2 方向改。而且，AWS SDK V2 虽然 API 变化较大，但毕竟还是遵照的 S3 协议，基本操作还是可以兼容现有各厂商 OSS。</p>
<p>所以，综上考虑，未来对象存储仅使用 AWS SDK V2，而且能够满足大多数使用场景，就不再过多投入精力考虑其他厂商 OSS 的兼容问题。</p>
</div>
<h3>[2]准备</h3>
<p>搭建 Minio（或其他 OSS） 等基础设施。</p>
<h3>[3] 用法</h3>
<h4>添加依赖</h4>
<p>在工程中添加依赖：</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.stirrup&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>oss-spring-boot-starter&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>环境适配</h4>
<p><code>oss-spring-boot-starter</code> 模块中，包含了阻塞式和响应式两部分代码，会自动判断当前是阻塞式还是响应式环境，自动开启相关环境下的支持。</p>
<h2>[二]远程访问模块</h2>
<ul>
<li><code>rpc-client-oss-spring-boot-starter</code></li>
</ul>
<h3>[1]用途</h3>
<p><code>oss-spring-boot-starter</code> 模块仅适用于搭建对象存储服务，以接口的形式对各种&quot;客户端&quot;供文件上传和下载等功能的支撑。</p>
<p>在有些需求场景下，需要在服务间实现文件的远程传输和管理，这种情况下 <code>oss-spring-boot-starter</code> 就无法满足。当然，用户可以根据自己的实际需求，在 Dante Cloud 的基础上自己扩展或添加功能，但这毕竟还要花费时间和精力进行开发。</p>
<p>为了方便用户的使用，Dante Cloud 自 3.3.5.0 版本，新增了 <code>rpc-client-oss-spring-boot-starter</code> 对象存储远程调用模块。结合 3.3.4.0 后新增的服务本地文件管理，就可以实现服务间文件的远程上传于下载。</p>
<h3>[2] 用法</h3>
<p><code>rpc-client-oss-spring-boot-starter</code> 支持响应式环境和阻塞式环境，阻塞式环境使用 Openfeign，其它环境使用 GPRC。会根据当前服务依赖的组件自动切换配置。</p>
<p>需要注意的是，<code>rpc-client-oss-spring-boot-starter</code> 无法单独使用，需要配合以下组件来确定具体远程协议后，才能使用。</p>
<ul>
<li>如果添加了 <code>spring-cloud-starter-openfeign</code> 依赖，就会开启 Openfeign 方式远程调用。</li>
<li>如果添加了 <code>grpc-client-spring-boot-starter</code> 依赖，就会开启 GRPC 方式远程调用。</li>
</ul>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>需要注意的是，使用 <code>rpc-client-oss-spring-boot-starter</code> 时还要要考虑好远程服务的调用端和被调用端环境的匹配问题。</p>
<blockquote>
<p>注：调用端，即需要上传和下载文件支持的服务；被调用端，即对象存储服务，也就是当前 Dante Cloud 中的 <code>herodotus-cloud-oss-ability</code> 服务。</p>
</blockquote>
<p>因为，当前 Dante Cloud 的对象存储服务，既支持响应式环境（默认）又支持阻塞式环境，那么使用 <code>rpc-client-oss-spring-boot-starter</code> 的服务，就需要根据对象存储服务的环境情况选择与之匹配的远程调用方式。</p>
<ul>
<li>如果远程服务的调用端和被调用端都是阻塞式环境，即需要实现远程上传和下载支持的服务是 Servlet 的，同时对象存储服务也是 Servlet 的，那么就建议使用 Openfeign 方式。在调用端添加 <code>spring-cloud-starter-openfeign</code> 依赖。</li>
<li>如果远程服务的调用端和被调用端有一端或者两端都是响应式环境，那么就建议使用 GPRC 方式。在调用端添加 <code>grpc-client-spring-boot-starter</code> 依赖。</li>
</ul>
</div>
<h3>[3] 说明</h3>
<p>当前的远程文件上传和下载机制的设计，是以对象存储为中心，服务间的文件传输均是先上传至对象存储服务，再下载至服务本地。是无法做到脱离对象存储服务，实现任意服务间的上传和下载。</p>
<p>并不是技术上无法实现服务间点对点的文件传输，而是实现难度较大并且不便于管理意义不大。如果您真有类似的需求，那么考虑同一服务器服务间共享磁盘或者跨服务器使用第三方组件实现文件同步更实际一些。</p>
]]></content:encoded>
    </item>
    <item>
      <title>模块与组合</title>
      <link>https://www.herodotus.cn/develop-guide/module/</link>
      <guid>https://www.herodotus.cn/develop-guide/module/</guid>
      <source url="https://www.herodotus.cn/rss.xml">模块与组合</source>
      <description>Dante Cloud 早期版本中，系统代码虽然也是按照模块划分，但是还是采用全部代码在一个工程中的模式。这种模式下，即使划分了模块，各个模块的独立性还是比较差，耦合性比较高、难以独立使用。 自 2.7.X 版起，Dante Cloud 将核心的、共性的代码从工程中抽出，构建了独立的 Dante Engine 核心模块工程。各个模块尽可能的采用符合 S...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Dante Cloud 早期版本中，系统代码虽然也是按照模块划分，但是还是采用全部代码在一个工程中的模式。这种模式下，即使划分了模块，各个模块的独立性还是比较差，耦合性比较高、难以独立使用。</p>
<p>自 2.7.X 版起，Dante Cloud 将核心的、共性的代码从工程中抽出，构建了独立的 Dante Engine 核心模块工程。各个模块尽可能的采用符合 Spring Boot 标准的方式进行构建，极大地降低了模块的耦合性，也提升了模块的通用性。随着时间的推移以及 Dante Cloud 系统使用广泛性的提升，Dante Engine 工程的局限性逐渐显现。存在以下几点问题：</p>
<ul>
<li>模块的通用性并不像想象中的那么高，除了一些基础性用途的模块可以完全独立运行外，主要模块还是无法独立运行，</li>
<li>模块建的耦合性以及模块依赖的层次还是很高，依赖了某些模块之后就无法剔除这个模块。</li>
<li>虽然也大量使用的自动配置和 Starter，但是完全没有发挥 Spring Boot 提供的 &quot;可插拔&quot; 能力，所以系统代码和模块的灵活性就不高。</li>
</ul>
<p>因此，自 3.3.X 起，全新构建的 Dante Cloud 的响应式版本（商业版），严格依据 Spring Boot 规范，全新架构了核心模块代码，重点提升模块的独立性以及&quot;可插拔&quot; 能力，着力降低模块间的耦合性以及依赖深度。让系统核心模块更加独立，让整个系统使用也更加灵活，让系统代码更贴合 Spring Boot 风格。</p>
<p>正因为系统模块的使用更加灵活，所以有必要对模块的使用进行详细讲解和说明。本章节文档的主要内容，深入讲解各模块的使用方法，让用户在使用时可以快速了解各个模块的用法，不用花费大量时间查看源代码。</p>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>Dante Cloud 涉及的内容以及模块比较多，本章节的内容无法一次性完成，根据模块陆续补充。</p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>错误码体系</title>
      <link>https://www.herodotus.cn/develop-guide/design/exception.html</link>
      <guid>https://www.herodotus.cn/develop-guide/design/exception.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">错误码体系</source>
      <description>[一]前言 错误体系是任何编程语言和应用系统不可或缺的组成部分。有人对之退避三舍，有人对之甘之如饴。 在软件开发的复杂世界中，错误是不可避免的。无论是因为外部系统的变化、用户输入的错误，还是内部逻辑的缺陷，错误都会出现。为了有效管理这些错误，并向用户和开发者提供清晰、有用的反馈，设计一套合理的错误码和错误提示系统变得至关重要。 在软件项目的早期阶段就预...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]前言</h2>
<p>错误体系是任何编程语言和应用系统不可或缺的组成部分。有人对之退避三舍，有人对之甘之如饴。</p>
<p>在软件开发的复杂世界中，错误是不可避免的。无论是因为外部系统的变化、用户输入的错误，还是内部逻辑的缺陷，错误都会出现。为了有效管理这些错误，并向用户和开发者提供清晰、有用的反馈，设计一套合理的错误码和错误提示系统变得至关重要。</p>
<p>在软件项目的早期阶段就预测和规划所有可能的错误情况是一项挑战。设计过程需要在全面性和灵活性之间找到平衡点。此外，设计的错误码和提示不仅要对开发者有用，还要能够为最终用户提供清晰、易懂的信息。</p>
<h3>[1]设计最佳实践</h3>
<h4>系统化错误分类</h4>
<p>创建一个系统化的错误分类体系是确保错误码和提示设计既灵活又全面的基础。这可以帮助组织和规划错误码，并提高代码的可读性和可维护性。</p>
<h4>使用错误码模板</h4>
<p>错误码模板可以帮助生成一致和规范的错误码。例如，模板可以基于错误的类型、来源和严重程度来生成错误码。</p>
<h4>动态错误提示信息</h4>
<p>实现根据错误上下文动态生成错误提示信息的机制可以提高错误信息的实用性，帮助开发人员和用户更快地定位和解决问题。</p>
<h4>为未来的变化预留空间</h4>
<p>在设计错误码时，预留一定范围的代码用于未来可能出现的新错误，可以最大限度地减少因添加新错误类型而导致的重构需求。</p>
<h4>用户友好的错误提示</h4>
<p>错误提示应清晰、易懂，避免使用技术性或模糊的语言，并提供解决问题的建议或行动指导，以提升用户体验。</p>
<h3>[2]Dante Cloud 错误体系设计的初衷</h3>
<p>Dante Cloud 在开发之初，也和大多数应用系统一样，并没有太多的考虑错误体系的设计问题。随着，Dante Cloud 的开源以及用户的增多，问题就慢慢地暴露了出来。</p>
<p>抛开能力经验和技术水平不谈，仅是在问题沟通方面就出现了很多问题。大多数用户提问的习惯就是要么只打一句话、要么只截一张图，具体什么环境、参数设置、是否修改了代码、改了哪些地方、怎么操作能够重现问题均不会提供，在这种情况下想要确定问题，除非自己遇到过一模一样的问题，否则就只能全靠猜。即使提供了更详细的错误信息，由于错误体系不够成熟，导致根据错误信息很难有效区分具体的问题。</p>
<p>因此，这也促使 Dante Cloud 认证对自身的错误体系进行了深入的设计和改造。</p>
<h2>[二]Dante Cloud错误体系设计</h2>
<h3>[1]设计目标</h3>
<p>为了可以更便捷地定位问题，Dante Cloud 错误体系的设计设定了以下几个目标：</p>
<ol>
<li>设计一套方便定位区分问题类型的错误码体系。</li>
<li>错误码可以自动计算和自动生成</li>
<li>可以为前端提供更友好、更准确的错误信息。</li>
<li>可以兼容多种来源的 Exception 错误。</li>
<li>可以按照模块定义各自的错误信息，提升代码的内聚性。</li>
</ol>
<h3>[2]设计思考</h3>
<h4>(1) 方便定位区分问题类型的错误码</h4>
<p>很多应用系统以及软件产品都会根据自身的需求，设计一套遵照一定规则的<code>错误码</code>体系，通过错误码标识就可以很直观的看出一些常见的问题。错误码的设计到底采用什么规则设计，完全是由系统自身的需求而定，没有固定的标准和规范。</p>
<p>Dante Cloud 是一套微服务系统，核心内容就是 REST 接口，而 REST 接口与 HTTP 协议密不可分。HTTP 协议对于开发人员来说是再熟悉不过的知识点，特别是对于 HTTP 协议的状态码，大家都是耳熟能详的。仔细研究了 HTTP 协议的状态码，含义也非常清晰而且可以涵盖开发中大多数错误类型。</p>
<p>因此，Dante Cloud 错误码体系将 HTTP 状态码与错误码进行了融合。</p>
<p>例如：我们定义了一个<code>传递参数错误</code>的自定义错误 Exception，通常传递参数是属于代码执行的前置操作错误，这个错误就与 HTTP 状态码 <code>412</code> 的解析非常符合。那么，这个 Exception 的错误码就定义为：<code>412XX</code>。</p>
<p>错误码之所以定义为5位（前三位是 HTTP 状态码，剩余两位是自定义编码），是因为考虑到一个系统，同一个类型的错误如果太少，很难区分问题。如果太多就会干扰实际的开发使用。两位数意味着最大为99，应该足以满足一个系统对某一类型错误的定义。</p>
<h4>(2) 错误码自动计算</h4>
<p>因为错误码采用了数字形式，那么衍生的需求就是错误码可以自动计算和生成。如果错误码全靠手动计算，特别是在自定义错误分布在不同模块的情况下，就非常容易出错。</p>
<h4>(3) 提供更友好、更准确的错误信息</h4>
<p>我们可以通过定义丰富的自定义 Exception，来接解决错误不容易定位的问题，通过 Exception 名称就可以清晰错误的缘由以及大改的代码位置。但是，只有 Exception 错误名，仅能对后端人员定位问题提供方便，对于其它端甚至用户来说，是很难理解 Exception 错误名代表的含义的。</p>
<p>所以，需要实现一种方式，可以将 Exception 错误转换成用户或者其它端开发人员更容易理解的信息。</p>
<h4>(4) 兼容多种来源的 Exception</h4>
<p>将 Exception 错误转换成用户或者其它端开发人员更容易理解的信息，说起来容易，做起来却不简单。如果仅仅是自定义的 Exception，那么可以完全按照自己的思路实现即可。但是在实际开发中，我们的系统会依赖非常多的第三方组件，这些组件会定义和使用自己的 Exception，而这些 Exception 代码是很难修改的，即使修改了也不好与第三方组件融合。</p>
<p>目前，经常简单到的 Exception 类型，大概为以下三类：</p>
<ol>
<li>自定义的 Exception，可能是 RuntimeException 也可能是 Exception</li>
<li>第三方模块代码抛出的 Exception，可能是 RuntimeException 也可能是 Exception</li>
<li>第三方模块代码抛出的继承类型 Exception。即，抛出的是第三方模块自定义 Exception 的基类，实际 Exception 信息被包含在基类 Exception</li>
</ol>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>RuntimeException 和 Exception 在实际使用中还是有较大区别的</p>
<ul>
<li>RuntimeException：运行时错误。简单的说就是在操作或代码执行时抛出的错误，这种错误一般用于不会导致功能或系统无法运行或者崩溃的场景。最适合用于前后端传递错误信息或者根据不同类型错误做不同操作响应的场景。</li>
<li>Exception：通常是用于会导致功能或者系统无法运行的错误，对于这种错误会强制要求开发人员在代码层面做出应对。最直接的表现，就是代码的方法后面会标注 <code>throw XXXException</code>, 调用这个方法的代码必须要对这个错误做出处理，最常见的就是用 <code>try catch</code> 包裹住这个方法或者选择继续抛出。</li>
</ul>
<p>RuntimeException 和 Exception 最大的不同，就是前者是否抛出没有明确的说明，后者必须在代码方法中明确标识出会抛出。</p>
</div>
<h4>(5) 按照模块分别定义错误信息</h4>
<p>一想到错误信息，最简单的做法就是错误码、错误文字信息以及 Exception 放入到一个统一的类型进行定义。这种做法本身没有任何问题，但是如果涉及到多模块系统，就只能做到在一个模块中统一定义错误，其它模块都依赖这个模块，这就极大地增加了模块间的耦合度。这样做不是不可行，但是却不够优雅。</p>
<h2>[三]Dante Cloud错误体系</h2>
<h3>[1]明确错误码范围</h3>
<p>按照前文的设计思路，Dante Cloud 的错误码是与 HTTP 协议的状态码绑定，那么 HTTP 协议状态码的范围就是 Dante Cloud 可用的错误码的范围，如下所示：</p>
<ul>
<li><code>1**</code>：信息，服务器收到请求，需要请求者继续执行操作</li>
<li><code>2**</code>：成功，操作被成功接收并处理</li>
<li><code>3**</code>：重定向，需要进一步的操作以完成请求</li>
<li><code>4**</code>：客户端错误，请求包含语法错误或无法完成请求</li>
<li><code>5**</code>：服务器错误，服务器在处理请求的过程中发生了错误</li>
</ul>
<p>利用 HTTP 协议状态码本身的含义，再结合实际的应用场景以及错误信息定义，就可以描述大多数错误内容。</p>
<p>但是，HTTP 协议状态码毕竟范围有限，即使其含义表达的再准确，也很难涵盖所有的错误信息。为此，Dante Cloud 还额外增加了一个 <code>Custom</code> 错误类型，这种类型的自定义错误码，不局限于 HTTP 协议的状态可以随便定义，例如，可以是 600XX、701XX 等。</p>
<h3>[2]定义统一的错误反馈类型</h3>
<p>Dante Cloud 中，定义了一个 <code>Feedback</code> 类，作为错误体系的统一返回信息定义。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Feedback</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Serializable</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> IS_NOT_CUSTOMIZED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> status</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 实际错误码如果与 HttpStatus 错误码对应，即开头数字为 1~5；自定义错误码，开头数字为 6~9。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 为了方便区分错误码是与 HttpStatus 错误码对应的还是自定义的，增加了 custom 属性。如果 custom 为 0，即为与 HttpStatus 错误码对应；如果为 6~9 那么就代表是自定义错误码</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> custom</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> Feedback</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">int</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> status</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(message, status, IS_NOT_CUSTOMIZED);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> Feedback</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">int</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> status</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">int</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> custom</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        Assert</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">checkBetween</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(custom, IS_NOT_CUSTOMIZED, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">9</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">message</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> message;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">status</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> status;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">custom</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> custom;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> message;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getStatus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> status;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> boolean</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> isCustom</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> custom </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">!=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> IS_NOT_CUSTOMIZED;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getCustom</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> custom;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getSequence</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">isCustom</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> custom </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">*</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 10000</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">else</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> status </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">*</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 100</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getSequence</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">int</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> index</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getSequence</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">+</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> index;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> boolean</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> equals</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Object</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> o</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> ==</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> o) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (o </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">==</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> ||</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getClass</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">!=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> o</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getClass</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        Feedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> feedback</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (Feedback) o;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> Objects</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">equal</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(message, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">feedback</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> hashCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> Objects</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">hashCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(message);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>Feedback</code> 类中，主要包含 <code>message</code>、<code>status</code> 和 <code>custom</code> 三个属性。</p>
<ul>
<li><code>message</code>：就是更容易理解的错误信息</li>
<li><code>status</code>：HTTP 状态码，用于更好的区分错误状态</li>
<li><code>custom</code>: 用于标记是错误能否与HTTP 状态码关联，是否为自定义错误</li>
</ul>
<p><code>Feedback</code> 类中还增加了 <code>getSequence()</code> 方法，用于标记某一类型错误的初始值以及自动计算错误码。例如，<code>404</code> 类型的错误，初始值就是 <code>40400</code></p>
<h3>[3]常用错误类型定义</h3>
<p>为了方便实际代码的开发，减少配置过程产生不必要的错误，加之通用错误码与 HTTP 状态码绑定。Dante Cloud 以 <code>Feedback</code> 类为基础，扩展了常用错误类型反馈类。</p>
<ul>
<li>CustomizeFeedback</li>
<li>ForbiddenFeedback</li>
<li>InternalServerErrorFeedback</li>
<li>MethodNotAllowedFeedback</li>
<li>NoContentFeedback</li>
<li>NotAcceptableFeedback</li>
<li>NotFoundFeedback</li>
<li>NotImplementedFeedback</li>
<li>OkFeedback</li>
<li>PreconditionFailedFeedback</li>
<li>ServiceUnavailableFeedback</li>
<li>UnauthorizedFeedback</li>
<li>UnsupportedMediaTypeFeedback</li>
</ul>
<p>有了这些错误类型，无需在手动设置错误的 <code>status</code>，而且从字面意思就很容易判断错误类型，从而提升错误码定义的便捷性。部分错误代码定义如下：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 200</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    OkFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> OK </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> OkFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"成功"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 204</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    NoContentFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> NO_CONTENT </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> NoContentFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"无内容"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 401.** 未经授权 Unauthorized 请求要求用户的身份认证</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> UNAUTHORIZED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"未经授权"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCESS_DENIED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"您没有权限，拒绝访问"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCOUNT_DISABLED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"该账户已经被禁用"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCOUNT_ENDPOINT_LIMITED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"您已经使用其它终端登录,请先退出其它终端"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCOUNT_EXPIRED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"该账户已经过期"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ACCOUNT_LOCKED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"该账户已经被锁定"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> BAD_CREDENTIALS </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"用户名或密码错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> CREDENTIALS_EXPIRED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"该账户密码凭证已过期"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> INVALID_CLIENT </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"客户端身份验证失败或数据库未初始化"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> INVALID_TOKEN </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"提供的访问令牌已过期、吊销、格式错误或无效"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> INVALID_GRANT </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"提供的授权授予或刷新令牌无效、已过期或已撤销"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> UNAUTHORIZED_CLIENT </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"客户端无权使用此方法请求授权码或访问令牌"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> USERNAME_NOT_FOUND </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"用户名或密码错误"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> SESSION_EXPIRED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Session 已过期，请刷新页面后再使用"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    UnauthorizedFeedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> NOT_AUTHENTICATED </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UnauthorizedFeedback</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"请求的地址未通过身份认证"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    ······</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[4]错误码构建和配置</h3>
<p>定义好的统一的错误反馈对象，下一步就需要定义错误码的构建器 <code>ErrorCodeMapperBuilder</code>。之所以构建 <code>ErrorCodeMapperBuilder</code>，是因为新版的 Dante Cloud 的错误码体系实现，完全借鉴了 Spring Boot 中 <code>Jackson</code> 所使用的 <code>Customizer</code> 设计模式（想要了解详情，参见 <a href="/develop-guide/design/jackson.html#_1customizer%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%8E%9F%E7%90%86" target="_blank"><code>Customizer</code>设计模式原理</a>）</p>
<p><code>ErrorCodeMapperBuilder</code> 主要用途：</p>
<ol>
<li>接收自定义错误配置</li>
<li>根据不同的错误类型自动计算错误码</li>
<li>生成错误映射列表</li>
</ol>
<p>参照 Spring Boot 中 <code>Jackson</code> 所使用的 <code>Customizer</code> 模式，Dante Cloud 还定义了一个 <code>@FunctionalInterface</code> 类型的接口 <code>ErrorCodeMapperBuilderCustomizer</code>。配合<code>ErrorCodeMapperBuilder</code>配置，最终实现支持跨模块配置的自定义错误体系</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> StandardErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Ordered</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ErrorCodeMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        builder</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">unauthorized</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_DENIED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCOUNT_DISABLED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCOUNT_ENDPOINT_LIMITED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCOUNT_EXPIRED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCOUNT_LOCKED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">BAD_CREDENTIALS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CREDENTIALS_EXPIRED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_CLIENT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_TOKEN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_GRANT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNAUTHORIZED_CLIENT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">USERNAME_NOT_FOUND</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SESSION_EXPIRED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">NOT_AUTHENTICATED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">forbidden</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INSUFFICIENT_SCOPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SQL_INJECTION_REQUEST</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">methodNotAllowed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HTTP_REQUEST_METHOD_NOT_SUPPORTED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">notAcceptable</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNSUPPORTED_GRANT_TYPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNSUPPORTED_RESPONSE_TYPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UNSUPPORTED_TOKEN_TYPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">USERNAME_ALREADY_EXISTS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">FEIGN_DECODER_IO_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CAPTCHA_CATEGORY_IS_INCORRECT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CAPTCHA_HANDLER_NOT_EXIST</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CAPTCHA_HAS_EXPIRED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CAPTCHA_IS_EMPTY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CAPTCHA_MISMATCH</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">CAPTCHA_PARAMETER_ILLEGAL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">preconditionFailed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_REDIRECT_URI</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_REQUEST</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_SCOPE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">METHOD_ARGUMENT_NOT_VALID</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_IDENTITY_VERIFICATION_FAILED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">unsupportedMediaType</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HTTP_MEDIA_TYPE_NOT_ACCEPTABLE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">internalServerError</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SERVER_ERROR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HTTP_MESSAGE_NOT_READABLE_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ILLEGAL_ARGUMENT_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">IO_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">NULL_POINTER_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">TYPE_MISMATCH_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">BORROW_OBJECT_FROM_POOL_ERROR_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">OPENAPI_INVOKING_FAILED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">notImplemented</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">PROPERTY_VALUE_IS_NOT_SET_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">URL_FORMAT_INCORRECT_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ILLEGAL_SYMMETRIC_KEY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">DISCOVERED_UNRECORDED_ERROR_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">serviceUnavailable</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">COOKIE_THEFT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_COOKIE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">PROVIDER_NOT_FOUND</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">TEMPORARILY_UNAVAILABLE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">SEARCH_IP_LOCATION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">TRANSACTION_ROLLBACK</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">BAD_SQL_GRAMMAR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">DATA_INTEGRITY_VIOLATION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                        ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">PIPELINE_INVALID_COMMANDS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getOrder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> ErrorCodeMapperBuilderOrdered</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">STANDARD</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">AutoConfiguration</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ErrorCodeAutoConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Logger</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> log </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> LoggerFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getLogger</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodeAutoConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">PostConstruct</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> postConstruct</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">info</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Starter [Error Code] Configure."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> standardErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        StandardErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> customizer</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> StandardErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">debug</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Strategy [Standard ErrorCodeMapper Builder Customizer] Configure."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> customizer;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ErrorCodeMapperBuilder</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> errorCodeMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">List</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">customizers</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        ErrorCodeMapperBuilder</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ErrorCodeMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder, customizers);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">debug</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Bean [Error Code Mapper Builder] Configure."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> builder;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ErrorCodeMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">List</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">customizers</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ErrorCodeMapperBuilderCustomizer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> customizer</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> :</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> customizers) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            customizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ErrorCodeMapper</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> errorCodeMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ErrorCodeMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        ErrorCodeMapper</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> mapper</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">debug</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Bean [Error Code Mapper] Configure."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> mapper;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[四]错误体系的统一</h2>
<p>前面章节讲解了 Dante Cloud 自定义错误体系的设计与实现，但是也仅仅是实现自定义错误体系以及相关的配置，还不足以让这个错误体系运转起来。而且这也只是实现了自定义错误部分，还无法与第三方组件的 Exception 以及第三方组件抛出的继承类型 Exception 融合。</p>
<h3>[1]Spring Boot 中的错误捕获</h3>
<p>想要将自定义错误与第三方组件的 Exception 融合在一起，首先要解决的问题就是如何捕获第三方组件抛出的Exception。</p>
<p>Spring Boot 框架为我们提供了这个捕获的机制，就是利用 <code>@ExceptionHandler</code> 注解来实现。同时，因为微服务系统核心还是面向 REST 接口，可以配合使用 Spring Boot 提供的注解 <code>@RestControllerAdvice</code> 来捕获更多的错误。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">RestControllerAdvice</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ReactiveRestControllerAdvice</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> resolveException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Exception</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> ex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ServerWebExchange</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> serverWebExchange</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        ServerHttpRequest</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> request</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> serverWebExchange</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getRequest</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        ServerHttpResponse</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> response</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> serverWebExchange</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getResponse</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        Result</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">result</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> SecurityGlobalExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">resolveException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(ex, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">request</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getURI</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        response</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setStatusCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HttpStatusCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">valueOf</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getStatus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()));</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> result;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> resolveSecurityException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Exception</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> ex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ServerWebExchange</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> serverWebExchange</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        ServerHttpRequest</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> request</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> serverWebExchange</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getRequest</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        ServerHttpResponse</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> response</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> serverWebExchange</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getResponse</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        Result</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">result</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> SecurityGlobalExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">resolveSecurityException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(ex, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">request</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getURI</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        response</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setStatusCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HttpStatusCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">valueOf</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getStatus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()));</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> result;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">ExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">({</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MethodArgumentNotValidException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">})</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> validationMethodArgumentException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MethodArgumentNotValidException</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> ex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ServerWebExchange</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> serverWebExchange</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> validationBindException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(ex, serverWebExchange);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">ExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">({</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">BindException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">})</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> validationBindException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">BindException</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> ex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ServerWebExchange</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> serverWebExchange</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        ServerHttpRequest</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> request</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> serverWebExchange</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getRequest</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        ServerHttpResponse</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> response</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> serverWebExchange</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getResponse</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        Result</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">result</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> SecurityGlobalExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">resolveException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(ex, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">request</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getURI</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        BindingResult</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> bindingResult</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> ex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getBindingResult</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        FieldError</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> fieldError</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> bindingResult</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getFieldError</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        //返回第一个错误的信息</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ObjectUtils</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">isNotEmpty</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(fieldError)) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">validation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">fieldError</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDefaultMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(), </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">fieldError</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(), </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">fieldError</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getField</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        response</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setStatusCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HttpStatusCode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">valueOf</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getStatus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()));</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> result;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    ······</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]第三方组件错误的定义</h3>
<p>捕获了第三方组件的错误后，就需要考虑如何将这些错误信息转化为更友好的错误信息。</p>
<p>通过 <code>@ExceptionHandler</code> 捕获的 Exception，只是纯纯的 Exception 类，可以通过这个 Exception 拿到相关的信息，包括 Exception 的类信息。既然，在前文中我们已经定义了 <code>Feedback</code> 信息反馈类，那么想办法将 Exception 与 <code>Feedback</code> 进行关联，就可以解决第三方组件错误信息的转化。</p>
<p>在 Dante Cloud 中，定义了一个 <code>GlobalExceptionHandler</code> 类。在其中，定义了一个 Exception 与 <code>Feedback</code> 映射的字典。利用 Exception 的类名，查找对应的 <code>Feedback</code>信息来实现错误信息的转化。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> GlobalExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Logger</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> log </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> LoggerFactory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getLogger</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">GlobalExceptionHandler</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Map</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Feedback</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> EXCEPTION_DICTIONARY </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HashMap</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;></span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"AccessDeniedException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_DENIED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"InsufficientAuthenticationException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ACCESS_DENIED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"HttpRequestMethodNotSupportedException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HTTP_REQUEST_METHOD_NOT_SUPPORTED</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"HttpMediaTypeNotAcceptableException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HTTP_MEDIA_TYPE_NOT_ACCEPTABLE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"IllegalArgumentException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ILLEGAL_ARGUMENT_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"NullPointerException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">NULL_POINTER_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"IOException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">IO_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"HttpMessageNotReadableException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">HTTP_MESSAGE_NOT_READABLE_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"TypeMismatchException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">TYPE_MISMATCH_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"MissingServletRequestParameterException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"ProviderNotFoundException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">PROVIDER_NOT_FOUND</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"CookieTheftException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">COOKIE_THEFT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"InvalidCookieException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">INVALID_COOKIE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"BadSqlGrammarException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">BAD_SQL_GRAMMAR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"DataIntegrityViolationException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">DATA_INTEGRITY_VIOLATION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"TransactionRollbackException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">TRANSACTION_ROLLBACK</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"BindException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">METHOD_ARGUMENT_NOT_VALID</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"MethodArgumentNotValidException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">METHOD_ARGUMENT_NOT_VALID</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">put</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"RedisPipelineException"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ErrorCodes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">PIPELINE_INVALID_COMMANDS</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> resolveException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Exception</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> ex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">trace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Global Exception Handler, Path : [{}], Exception："</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, path, ex);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (ex </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">instanceof</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> HerodotusException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> exception) {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">            Result</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">result</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> exception</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getResult</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(path);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">error</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Global Exception Handler, Error is : {}"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, result);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> result;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">else</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">            Result</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">result</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> Result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">failure</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">            String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> exceptionName</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> ex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getClass</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getSimpleName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">StringUtils</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">isNotEmpty</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(exceptionName) </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">&#x26;&#x26;</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">containsKey</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(exceptionName)) {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">                Feedback</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> feedback</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> EXCEPTION_DICTIONARY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">get</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(exceptionName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                result </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> Result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">failure</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(feedback, exceptionName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            } </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">else</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">                log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">warn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Global Exception Handler,  Can not find the exception name [{}] in dictionary, please do optimize "</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, exceptionName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(path);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">stackTrace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getStackTrace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">detail</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ex</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">error</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Global Exception Handler, Error is : {}"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, result);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> result;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>将 <code>@ExceptionHandler</code> 捕获的 Exception 传递给 <code>GlobalExceptionHandler</code> 类，就实现了整体错误体系的统一。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>第三方组件到底会抛出哪些 Exception，以及会在什么场景下抛出哪个 Exception，是很难做到穷举的。所以， <code>GlobalExceptionHandler</code> 中的字典，仅是记录了已经遇到过的 Exception。对于新的、未曾遇到过的错误，在代码中会以日志的形式输出告警，发现这种类型的日志在手动补充进来</p>
</div>
<h3>[3]特殊组件错误的定义</h3>
<p><code>GlobalExceptionHandler</code> 中定义的错误，只是 Spring Boot 基础组件中会遇到的 Exception，对于像 OAuth2 和 Spring Security 这类特殊应用的组件，又会抛出不同的错误类型，特别是还会存在集成关系错误。</p>
<p>所以，Dante Cloud 在 <code>GlobalExceptionHandler</code> 之上，又定义了一层 <code>SecurityGlobalExceptionHandler</code>，专门用于处理 OAuth2 和 Spring Security 相关的错误。</p>
<p>之所以，没有将 <code>GlobalExceptionHandler</code> 和 <code>SecurityGlobalExceptionHandler</code> 合并在一个类中，主要考虑的是 <code>GlobalExceptionHandler</code> 和 <code>SecurityGlobalExceptionHandler</code> 涉及的底层依赖不同，划分成两个类型，可以更方便的划分模块，减少不必要的依赖。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Jackson 配置与扩展</title>
      <link>https://www.herodotus.cn/develop-guide/design/jackson.html</link>
      <guid>https://www.herodotus.cn/develop-guide/design/jackson.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">Jackson 配置与扩展</source>
      <description>[一]前言 JSON 可以说是现今软件开发，特别是 Web 开发中不可或缺的组成部分。相信搞过开发的一定非常熟悉，估计也会觉得一个 JSON 没有讲解的必要。 如果你的工作只会涉及常规的 JSON 使用，只要能够接收和发送参数、实现 JSON 和对象的互转就可以满足日常工作的需要，那么 JSON 确实没有讲解的必要。 如果你想进一步提升自己的技术水平，...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]前言</h2>
<p>JSON 可以说是现今软件开发，特别是 Web 开发中不可或缺的组成部分。相信搞过开发的一定非常熟悉，估计也会觉得一个 JSON 没有讲解的必要。</p>
<ul>
<li>如果你的工作只会涉及常规的 JSON 使用，只要能够接收和发送参数、实现 JSON 和对象的互转就可以满足日常工作的需要，那么 JSON 确实没有讲解的必要。</li>
<li>如果你想进一步提升自己的技术水平，了解更多的设计思想，那么 JSON 是非常重要的一个学习切入点。特别是 Spring Boot 生态中 Jackson 的使用以及设计实现。</li>
</ul>
<h3>[1]为什么会说 Jackson 在 Spring Boot 生态中很重要？</h3>
<ol>
<li><code>Jackson</code> 是 Spring Boot 生态中核心的 JSON 处理组件，贯穿于 Spring Boot 生态包括 Spring Cloud 的各个组件中。</li>
<li>正因为日常可见，往往就越不重视。而实际开发中，初次使用 Spring Boot 生态组件，遇到的很多问题往往就出自 <code>Jackson</code>。</li>
<li>Spring Boot 生态中 <code>Jackson</code> 的使用和设计，采用了 Spring Boot 生态中非常常用的一种我称之为 <code>Customizer</code> 设计模式。这种设计模式不仅仅用于 <code>Jackson</code>，在涉及的很多组件中都有体现，掌握了这个知识点，对于 Spring Boot 生态中很多设计实现就会非常容易理解了</li>
</ol>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Dante Cloud 自定义错误码体系，在后期的版本中，也是借鉴了 Spring Boot <code>Jackson</code> 所使用的这种设计模式。从而解决了之前版本中，所有的错误码必须写死、必须统一在某一个模块中定义、不灵活不好扩展、无法跨模块定义等问题。</p>
</div>
<h3>[2]Dante Cloud 中的 JSON</h3>
<p>在 Dante Cloud 系统中，主要依赖的 JSON 处理组件有：<code>Jackson</code>、<code>Fastjson</code>、<code>Gson</code> 以及 Hutool 中封装的 JSON 处理工具。</p>
<h4>(1)Jackson</h4>
<p>Dante Cloud 中主要使用的、也是默认使用 JSON 处理组件就是 <code>Jackson</code>。之所以是用 <code>Jackson</code> 主要由以下几方面考虑：</p>
<ol>
<li>一方面是为了与 Spring Boot 生态保持一致</li>
</ol>
<blockquote>
<p>使用纯整的 Spring Boot 生态是 Dante Cloud 的目标和特色之一。</p>
</blockquote>
<ol start="2">
<li>另一方面也是由于 Fastjson 频频爆出安全漏洞问题，为了减少系统安全漏洞、让用户不再受安全漏洞的影响，做大量无用的补漏和升级工作。</li>
</ol>
<blockquote>
<p>其实，个人认为 Fastjson 经常爆出的安全漏洞，并不是说 Fastjson 本身不好，反而是因为 Fastjson 太过方便所导致，这会在后面内容中详细解释。</p>
</blockquote>
<h4>(2)Fastjson</h4>
<p>可能会有人有疑惑，既然你主要使用的是 <code>Jackson</code>，什么系统中还要依赖 <code>Fastjson</code>？</p>
<p>依赖 <code>Fastjson</code> 并不代表就会使用这个组件，之所以这样做的原因是：</p>
<ol>
<li>我个人主张使用 <code>Jackson</code>，并不代表所有人都喜欢使用 <code>Jackson</code>。</li>
<li>我个人在意 <code>Fastjson</code> 的安全性问题，不代表所有用户都在意。</li>
<li>Java 生态圈中，还是有很多第三方组件使用了 <code>Fastjson</code>，只要使用了这些组件就会间接的依赖 <code>Fastjson</code>，而这些组件并不是所有都会像 Dante Cloud 一样随时更新依赖组件的版本，那么就很可能间接依赖了有安全漏洞的 <code>Fastjson</code> 版本。通过在 Dante Cloud 中指定依赖 <code>Fastjson</code> 的版本，就可以统一所有依赖中 <code>Fastjson</code> 的版本，如果发现漏洞版本可以及时通过更新版本修复问题。</li>
</ol>
<h4>(3)Gson</h4>
<p>不同的组件即使用途是相似的，但是由于设计和实现的思路不同，也会又各自的特色和特点存在，这也为在不同场景下选择更适合自己的机制提供了条件。</p>
<p>目前 Dante Cloud 仅在处理 SQL 注入的代码中使用了 <code>Gson</code>。并不是说 <code>Gson</code> 在 SQL 注入方面有什么特别的支持，而是在实际应用中发现，Gson 在 JSON 的遍历方面特别是对结构复杂的 JSON 遍历，API 的使用更加简洁方便。</p>
<h4>(4)Hutool JSON 工具</h4>
<p>因为 Dante Cloud 默认是依赖了 Hutool-all 这个包，所以自然就包含了 hutool JSON 工具，是否使用全凭用户自己选择</p>
<h2>[二]Spring Boot 中的 Jackson</h2>
<h3>[1]基本用法</h3>
<p><code>ObjectMapper</code> 是 <code>Jackson</code> 最核心的、最底层的类，当我们想要使用 <code>Jackson</code> 处理 JSON 时，只要初始化一个 <code>ObjectMapper</code> 对象，例如：<code>ObjectMapper objectMapper = new ObjectMapper()</code>，就可以编写各种各样的 JSON 处理逻辑了。</p>
<p>在实际的开发中，只是通过 <code>new ObjectMapper()</code> 对象，是不足以满足使用需求的。比如，在对象转换成 JSON 时，我们不希望结果中包含有空值的属性。那么就需要对 <code>ObjectMapper</code> 对象进行一些参数的设置，当然 <code>ObjectMapper</code> 也提供了丰富的自定义参数，方便用户定义自己的满足自己需求的 <code>ObjectMapper</code> 对象。</p>
<h3>[2]存在的问题</h3>
<p>但这也带来的新的问题，就是每次在使用 <code>Jackson</code> 的时候都需要 <code>new ObjectMapper()</code> 对象，然后再把对应的参数设置一遍，就非常不方便。常见的解决方法就是会定义一个单例模式的 Utils，这样就不用每次重新创建 <code>ObjectMapper</code> 对象，还可以统一 <code>ObjectMapper</code> 中的参数配置。虽然这种方法在一定程度上解决了问题，但其实还是不够灵活，还存在几方面的问题：</p>
<ol>
<li>一旦需要修改参数就需要修改代码；</li>
<li>这种方式是全局的方式，一旦设置就很难做差异化处理。</li>
<li>Utils 的方式仅能统一自己的代码，第三方组件中如果也用了 <code>Jackson</code> ，就很难统一还是需要个性化处理</li>
</ol>
<p>为了解决这个问题，在旧版本的 Spring Boot 中，默认的会帮我们创建一个 Bean 形式的 <code>ObjectMapper</code> 对象，同时利用 Spring Boot 的 Properties 机制，实现可以通过配置文件来修改 <code>ObjectMapper</code> 中的参数。</p>
<p>在 <code>application.properties</code> 中，修改配置是修改默认 <code>ObjectMapper</code> 最便捷的方式。</p>
<p>下面是 <code>Jackson</code> 配置的常规结构</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">spring.jackson.&#x3C;category_name>.&#x3C;feature_name>=true,false</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>以关闭 <code>Jackson</code> <code>SerializationFeature.WRITE_DATES_AS_TIMESTAMPS</code> 配置举例，Spring Boot 中对应的配置如下：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">spring.jackson.serialization.write-dates-as-timestamps=false</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>除了上述功能类别之外，我们还可以配置属性包含：</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">spring.jackson.default-property-inclusion=always, non_null, non_absent, non_default, non_empty</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>这种方式进一步提升了 <code>ObjectMapper</code> 配置的灵活性，通过 Bean 注入的方式还可以实现 <code>ObjectMapper</code> 的统一，但是还是不足以完美解决使用 <code>ObjectMapper</code> 存在的问题：</p>
<ol>
<li>这种方法的缺点是，我们无法自定义高级选项，如 <code>LocalDateTime</code> 的自定义日期格式，解决这个问题最终还是要回归到在代码中进行设置</li>
<li>Spring Boot 中允许定义多个 <code>ObjectMapper</code> Bean，不同的组件会根据自己的需求定义自己的 <code>ObjectMapper</code> Bean，这样就又变成无法全局统一 <code>ObjectMapper</code> 对象。</li>
</ol>
<h3>[3]使用@Primary注解</h3>
<p>在代码开发中，很多问题和设计都会无法做到“完美”的，更多的时候必须要“平衡”和“取舍”。</p>
<p>在使用了 Spring Boot 的项目中，最常见的方式就是自己重新定义 <code>ObjectMapper</code> Bean，在这个 Bean 中进行自己的参数设置，再利用 <code>@Primary</code> 注解标注这个 Bean，示例代码如下所示</p>
<blockquote>
<p>使用了<code>@Primary</code> 注解，所有注入 <code>ObjectMapper</code> Bean 的代码，包括第三方组件都只会注入 <code>@Primary</code> 注解标注的 <code>ObjectMapper</code> Bean 。</p>
</blockquote>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Primary</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ObjectMapper</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> objectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">() {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    JavaTimeModule</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> module </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> JavaTimeModule</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">addSerializer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(LOCAL_DATETIME_SERIALIZER);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ObjectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setSerializationInclusion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">JsonInclude</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Include</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">NON_NULL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        .</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">registerModule</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(module);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上面这种方式，本质就是一种“取舍”：“取”的是全局性配置，“舍”的是个性化配置</p>
<h2>[三]Spring Boot 中的 <code>Customizer</code></h2>
<h3>[1]<code>Customizer</code>设计模式原理</h3>
<p>在现今的 Spring Boot 中（只要不是太老的版本），Spring Boot 采用了一种我称之为 <code>Customizer</code> 设计模式来支持 <code>Jackson</code> 的配置。</p>
<p><code>Customizer</code> 设计模式的实现是利用了 Spring Boot 中一项隐藏的技能：<strong>同一个接口的不同实现类的 Bean，可以被注入到以该接口为了类型的集合中</strong></p>
<p>说得很绕口，下面举例解释一下：</p>
<p>假设，我们先定义一个接口（interface） <code>A</code></p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> A</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  ······</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>A</code> 这个接口，有两个实现类，分别为 <code>B</code> 和 <code>C</code></p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> B</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> A</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  ······</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> C</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> A</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  ······</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>那么，我们就可以通过注入的方式，动态获得基类为 <code>A</code> 的所有Bean。在下面例子中，我们首先定义 <code>B</code> 和 <code>C</code> 两个 Bean</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Configuration</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">proxyBeanMethods</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BeanConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> A</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> classB</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">      B</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> b</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> B</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">      return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> b;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> A</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> classB</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">      C</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> c</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> C</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">      return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> c;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>然后，我们就可以通过下面两种写法获取到 <code>B</code> 和 <code>C</code> 的集合</p>
<ul>
<li>写法一</li>
</ul>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Service</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CollectionService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Autowired</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> List</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">A</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> alist </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ArrayList</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>写法二</li>
</ul>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Configuration</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">proxyBeanMethods</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> CollectionConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> D</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> classD</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">List</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">A</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">alist</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">      return</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> D;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>上面两个例子中，<code>alist</code> 集合中就会自动存入已经配置好的 <code>B</code> 和 <code>C</code> 两个 Bean。</p>
<blockquote>
<p>注意：前提一定是要 <code>B</code> 和 <code>C</code> 两个 Bean在，<code>List&lt;A&gt; alist</code> 这段代码之前就已经注入和存在了才行</p>
</blockquote>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>这种同一类型基类的 Bean 会自动注入集合的方式，在 Spring Boot 中被大量使用。除了前面说的 <code>Customizer</code> 模式，还可以延伸出 <code>策略模式</code>。</p>
<p>在 Dante Cloud 中大量使用了这种<code>策略模式</code>，比如 Access 模块中的策略。Dante Cloud 也借鉴 <code>Customizer</code> 模式，重构了自身的自定义错误码体系</p>
</div>
<h3>[2] <code>Jackson2ObjectMapperBuilderCustomizer</code></h3>
<p>那么 Spring Boot 是如何利用 <code>Customizer</code> 设计模式来扩展 <code>Jackson</code> 的呢？</p>
<p>Spring Boot 对 <code>Jackson</code> 的基础扩展，是 <code>spring-boot-autoconfigure</code> 包的 <code>JacksonAutoConfiguration</code> 类中完成。下面来看看<code>JacksonAutoConfiguration</code> 类中具体的实现代码</p>
<h4>第一步：首先进行 <code>StandardJackson2ObjectMapperBuilderCustomizer</code> Bean 的注入</h4>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Configuration</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">proxyBeanMethods</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">ConditionalOnClass</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Jackson2ObjectMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">EnableConfigurationProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">JacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Jackson2ObjectMapperBuilderCustomizerConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    StandardJackson2ObjectMapperBuilderCustomizer</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> standardJacksonObjectMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    JacksonProperties</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ObjectProvider</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> StandardJackson2ObjectMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(jacksonProperties, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">stream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toList</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> StandardJackson2ObjectMapperBuilderCustomizer</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Jackson2ObjectMapperBuilderCustomizer</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Ordered</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> JacksonProperties</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Collection</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Module</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        StandardJackson2ObjectMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">JacksonProperties</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,  </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Collection</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> jacksonProperties;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">modules</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> modules;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> int</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getOrder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Jackson2ObjectMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDefaultPropertyInclusion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">!=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">serializationInclusion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDefaultPropertyInclusion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            }</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getTimeZone</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">!=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">timeZone</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getTimeZone</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            }</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureFeatures</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder, FEATURE_DEFAULTS);</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureVisibility</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getVisibility</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureFeatures</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getDeserialization</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureFeatures</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getSerialization</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureFeatures</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureFeatures</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getParser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureFeatures</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">jacksonProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getGenerator</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureDateFormat</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder);</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configurePropertyNamingStrategy</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder);</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureModules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder);</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureLocale</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder);</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureDefaultLeniency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder);</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            configureConstructorDetector</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">        ······</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在这个过程中，会将 <code>application.properties</code> 中相关的配置，通过 <code>Jackson2ObjectMapperBuilderCustomizer</code> 方式设置进来。</p>
<p>这里可以注意到，<code>StandardJackson2ObjectMapperBuilderCustomizer</code> 不仅实现了 <code>Jackson2ObjectMapperBuilderCustomizer</code> 接口，还实现了 <code>Ordered</code> 接口。通过 <code>Ordered</code> 接口，来控制不同 Customizer 的配置顺序。</p>
<p><code>StandardJackson2ObjectMapperBuilderCustomizer</code> 的 Order 被设置为 “0”，意味着其它 Customizer 的 Order 值，如果比 0 小，就在 <code>StandardJackson2ObjectMapperBuilderCustomizer</code> 之前配置；如果比 0 大，则在 <code>StandardJackson2ObjectMapperBuilderCustomizer</code> 之后配置。</p>
<h4>第二歩：进行 <code>Jackson2ObjectMapperBuilder</code> Bean 的注入</h4>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Configuration</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">proxyBeanMethods</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">ConditionalOnClass</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Jackson2ObjectMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> JacksonObjectMapperBuilderConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Scope</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"prototype"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">ConditionalOnMissingBean</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    Jackson2ObjectMapperBuilder</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> jacksonObjectMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ApplicationContext</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> applicationContext</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,  </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">List</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Jackson2ObjectMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">customizers</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        Jackson2ObjectMapperBuilder</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> Jackson2ObjectMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">applicationContext</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(applicationContext);</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder, customizers);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> builder;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Jackson2ObjectMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,  </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">List</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Jackson2ObjectMapperBuilderCustomizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">customizers</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Jackson2ObjectMapperBuilderCustomizer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> customizer</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> :</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> customizers) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">          customizer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">customize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(builder);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在这个过程中，会拿到所有的 <code>Jackson2ObjectMapperBuilderCustomizer</code> 类型的Bean，将其对应的配置，设置到 <code>Jackson2ObjectMapperBuilder</code> 中。</p>
<blockquote>
<p>注意：这里如上一部分所说，会根据不同 Customizer 的 Order 顺序进行处理，这就意味着会存在相同配置的覆盖。</p>
</blockquote>
<h4>第三步：进行 <code>ObjectMapper</code> Bean 的注入</h4>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Configuration</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">proxyBeanMethods</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">ConditionalOnClass</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Jackson2ObjectMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> JacksonObjectMapperConfiguration</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Primary</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">ConditionalOnMissingBean</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    ObjectMapper</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> jacksonObjectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Jackson2ObjectMapperBuilder</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">createXmlMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里通过已经设置好各项配置的 <code>Jackson2ObjectMapperBuilder</code>，来创建 <code>ObjectMapper</code>。这里使用了 <code>@Primary</code>，那么所有通过注入方式获取到的 <code>ObjectMapper</code> 对象将会是同一个对象。</p>
<h4>第四步：进入自定义的MappingJackson2HttpMessageConverter的注入</h4>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Bean</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MappingJackson2HttpMessageConverter</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> mappingJackson2HttpMessageConverter</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ObjectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> objectMapper) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    log</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">trace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"[Herodotus] |- Bean [Jackson2 Http Message Converter] Auto Configure."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    return</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MappingJackson2HttpMessageConverter</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(objectMapper)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>总结</h4>
<ol>
<li>以上所说的第一步、第二步等完全是根据跟踪代码、过程分析而得来的经验之谈，并不是实际存在这样的步骤过程。</li>
<li>经过分析，以上四步所涉及到 <code>ObjectMapper</code> 是同一个对象。在其它代码中，使用 <code>@Autowaire</code> 注入的 <code>ObjectMapper</code> 也是同一个对象。</li>
<li>如果在某个代码中，使用下面方式创建新的 <code>ObjectMapper</code> , 那么这个对象与上面所说的对象，是不同的对象，即所有的配置对这个新创建的对象，不生效。</li>
</ol>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ObjectMapper</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> objectMapper </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ObjectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h2>[四]为什么 Fastjson 总有安全漏洞？</h2>
<p>不可否认，任何一个组件出现安全漏洞，和这个组件代码的设计及实现逻辑肯定有密不可分的关系。</p>
<p>对于 JSON 处理组件来说，不管用哪个组件都需要支持<code>反序列化</code>，即：JSON 可以转换成对象，<strong>这就是 JSON 组件产生安全漏洞的根源。</strong></p>
<p>究其原理其实和我们所熟知的 SQL 注入是一个道理。SQL 注入就是将违规内容作为参数传递给了 JDBC，最终导致拼装出一个会产生异常操作结果的 SQL 语句。JSON 也是一样，在实际开发中 JSON 就是一个中间传输介质，单纯的 JSON 是无法执行任何操作的，我们需要把 JSON 反序列化为对象，即把 JSON 中的值 <code>set</code> 到对象指定属性中，其实就相当于 SQL 注入中的传参，从而会导致产生安全隐患。</p>
<p>由于不同的 JSON 实现机制和用法的不同，表现出的安全问题也会有差异。</p>
<h3>[1]Fastjson</h3>
<p>相信大家之所以选择 <code>Fastjson</code>，除了早期宣传的 <code>Fastjson</code> 有多快以外，我想还有一个关键点就是 <code>Fastjson</code> 用着非常方便。</p>
<p><code>Fastjson</code> 的序列化自然不用说，反序列化也非常方便，除非特别复杂的类型转换，比如多层泛型嵌套外，基本上用一个方法就可以搞定反序列化。</p>
<p>这就意味着，所有的安全问题都需要有 <code>Fastjson</code> 来帮你处理和考虑，一旦有遗漏或者考虑不周的地方，就容易出现安全漏洞。</p>
<h3>[2]Jackson</h3>
<p>通过以上的分析可以得出结论，<code>Jackson</code> 也支持 JSON 的反序列化，肯定也会出现安全漏洞，出现漏洞大概率也是出现在反序列化的过程中。</p>
<p>但是，<code>Jackson</code> 或者说使用 <code>Jackson</code> 的组件，采用了另外的一种降低安全风险的办法，就是<strong>尽量由用户自己来降低安全风险</strong></p>
<ul>
<li>在使用方面，除了 <code>Jackson</code> 所谓“慢”以外，规则要求也相对的多一些，特别对于复杂类型的反序列化，通常需要用户自己写大量的代码来适配，否则很难反序列化成功（想必这也是很多开发人员不愿意用 <code>Jackson</code> 的原因之一）。</li>
<li>另外，<code>Jackson</code> 提供了强大的扩展能力，这给使用 <code>Jackson</code> 的第三方组件提供了更多的想象空间，间接的分散出去大量的安全问题，由第三方组件自己设计提升安全。</li>
</ul>
<p>这也就是为什么使用新版本 Spring Security， 要求对 Spring Security 中核心类的扩展类，必须手动编写 <code>Mixin</code> 代码，同时需要将这些代码手动加入<code>白名单</code>，否则在不仅不允许运行，运行的时候就会出现 <code>XXX is not in the allowlist.If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin.</code> 错误提示。详细原因会在后续讲解</p>
<h2>[五]Jackson 中的 Mixin 和 Module</h2>
<h3>[1]Mixin</h3>
<p><code>Mixin</code> 对于前端开发者可不陌生，Vue、React 等知名前端框架都使用了 <code>Mixin</code>。而对于后端开发，尤其是Java后端开发来说 <code>Mixin</code> 却是一个很陌生的概念。</p>
<p><code>Mixin</code> 的意思是混入，是 <code>Jackson</code> 中一个非常重要的概念和机制。为什么需要混入？比如我们引用了一个Jar包，其中的某个类在某个场景需要反序列化，但是这个类没有提供默认构造。那么该怎么办呢？把原来的项目拉下来，重写一下？下下策! 你可以使用 <code>Jackson</code> 提供的 <code>Mixin</code> 特性来解决这个问题。</p>
<p><code>Jackson</code> 中 <code>Mixin</code>（混入） 主要的用途是：在目标对象无法实现的序列化或反序列化时，通过配置一个混入对象，在序列化或反序列化的时候把这些个性化配置混入到目标对象中，从而达到序列化或反序列化目标对象的目的。<strong>混入不改变目标对象本身的任何特性，混入对象和目标对象是映射的关系</strong>。下面我们来看一个混入的例子：</p>
<h4>（1）新建一个特殊实例</h4>
<p>假设，我们有一个 <code>User</code> 类，为了演示需要，我们极端一些，这个User没有无参构造，也没有属性的 getter方法。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> User</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Integer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> age</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> User</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Integer</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> age</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> name;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">age</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> age;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> toString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "User{"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">                "name='"</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> name </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">+</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\'</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">                ", age="</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> age </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">+</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">                '}'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>(2) 编写Mixin类</h4>
<p>想对这个极端的 <code>User</code> 进行序列化和反序列化。按以前的玩法我们会在在 <code>User</code> 类上加上 <code>@JsonAutoDetect</code> 注解就可以实现序列化了；加上 <code>@JsonDeserialize</code> 注解并指定反序列化类就可以反序列化了。不过现在我们不需要对 <code>User</code> 进行任何更改，只需要编写一个 <code>Mixin</code> 类把上述两个注解配置好就可以了。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">JsonAutoDetect</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">fieldVisibility</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> JsonAutoDetect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Visibility</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">ANY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> getterVisibility</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> JsonAutoDetect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Visibility</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">NONE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">        isGetterVisibility</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> JsonAutoDetect</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">Visibility</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">NONE</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">JsonIgnoreProperties</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">ignoreUnknown</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">JsonDeserialize</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">using</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> UserMixin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UserDeserializer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> abstract</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> UserMixin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 反序列化类</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     **/</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> UserDeserializer</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> JsonDeserializer</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> User</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> deserialize</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">JsonParser</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> p</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">DeserializationContext</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> ctxt</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> throws</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> IOException</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">            ObjectMapper</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> mapper</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (ObjectMapper) </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">p</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getCodec</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">            JsonNode</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> jsonNode</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> mapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">readTree</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(p);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">            String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> name</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readJsonNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(jsonNode, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"name"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">asText</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">            String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> age</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readJsonNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(jsonNode, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"age"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">).</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">asText</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">            Integer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ageVal</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> Objects</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">isNull</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(age)</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">?</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> null</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">:</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> Integer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">valueOf</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(age);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> User</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(name,ageVal);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> JsonNode</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readJsonNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">JsonNode</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> jsonNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> field</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> jsonNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">has</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(field) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">?</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> jsonNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">get</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(field) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">:</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> MissingNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getInstance</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>（3）Mixin 映射目标类</h4>
<p>编写完 <code>Mixin</code> 类后，我们通过<code>ObjectMapper</code>中的 <code>addMixIn</code> 方法把<code>UserMixin</code>和<code>User</code>映射起来。并编写一个序列化和反序列化的例子。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ObjectMapper</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> objectMapper </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ObjectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">objectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">addMixIn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UserMixin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> test </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> User</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"test"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 12</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> json </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> objectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">writeValueAsString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(test);</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">//{"name":"test","age":12}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">System</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">out</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">println</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"json = "</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> json);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> jsonStr </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "{</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">name</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">:</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">test</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">,</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">age</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\"</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">:12}"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> user </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> objectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">readValue</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(jsonStr, </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// User{name='test', age=12}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">System</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">out</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">println</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"user = "</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> user);</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样我们在不对目标类进行任何改变的情况下实现了个性化的JSON序列化和反序列化。</p>
<h3>[2]Module</h3>
<p><code>Jackson</code> 还提供了模块化功能，可以将个性化配置进行模块化统一管理，而且可以按需引用，甚至可插拔。它同样能够管理一组 <code>Mixin</code>。声明一个 Jackson Module 非常简单，继承 <code>SimpleModule</code> 覆写它的一些方法即可。针对 <code>Mixin</code> 我们可以这样写：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> UserModule</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> SimpleModule</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">   public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UserModule</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">       super</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UserModule</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">   }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">   @</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">   public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setupModule</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">SetupContext</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> context</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        context</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setMixInAnnotations</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">UserMixin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">   }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Module 同样可以注册到 <code>ObjectMapper</code> 中，同样也能实现我们想要的效果：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">ObjectMapper</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> objectMapper </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ObjectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">objectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">registerModule</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> UserModule</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>Module的功能更加强大。平常我们会使用以下几个 Module:</p>
<ul>
<li><strong>jackson-module-parameter-names</strong> ：此模块能够访问构造函数和方法参数的名称</li>
<li><strong>jackson-datatype-jdk8</strong>：除了Java8的时间API外其它新特性的的支持</li>
<li><strong>jackson-datatype-jsr310</strong> ：用以支持Java8新增的JSR310时间API</li>
</ul>
<p>另外 Spring Security 也提供了 Module 支持 <code>SecurityJackson2Modules</code>，它包含了下面的一些模块：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> List</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> securityJackson2ModuleClasses </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> Arrays</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">asList</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        "org.springframework.security.jackson2.CoreJackson2Module"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        "org.springframework.security.cas.jackson2.CasJackson2Module"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        "org.springframework.security.web.jackson2.WebJackson2Module"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        "org.springframework.security.web.server.jackson2.WebServerJackson2Module"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> webServletJackson2ModuleClass </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "org.springframework.security.web.jackson2.WebServletJackson2Module"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> oauth2ClientJackson2ModuleClass </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> javaTimeJackson2ModuleClass </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> ldapJackson2ModuleClass </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "org.springframework.security.ldap.jackson2.LdapJackson2Module"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> saml2Jackson2ModuleClass </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "org.springframework.security.saml2.jackson2.Saml2Jackson2Module"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[3]Module 的应用逻辑</h3>
<p>经过长时间的调试代码发现，<code>Jackson</code> 在处理序列化的过程中，会利用所有的 <code>Module</code> 对同一个事物进行序列化或反序列化操作，就类似于 Spring Security Filter，像“链”一样用所有的 <code>Module</code> 处理一遍。</p>
<p>例如 <code>Jackson</code> 默认配置了 A、B、C 三个 Module，请求发送一个 DTO 到接口，Jackson 会用A、B、C 每一个 Module 对这个DTO 处理一遍。添加一个新的 <code>Jackson2ObjectMapperBuilderCustomizer</code>，在其中新增 D 和 E 两个 Module，那么这时就会按照顺序，用 A、B、C、D、E 把这个 DTO 处理一遍。</p>
<h2>[六]<code>Spring Authorization Server</code> 中的 Jackson 的特殊处理</h2>
<h3>[1]Spring Security 中的 Jackson安全</h3>
<p>在 Sring Security 中就通过注解混入来解决反序列化时安全配置问题。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Override</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> JavaType</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> typeFromId</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">DatabindContext</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> context</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> id) throws IOException {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    DeserializationConfig</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> config </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> (DeserializationConfig) </span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">context</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getConfig</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    JavaType</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> result </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">delegate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">typeFromId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(context, id);</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    String</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> className </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getRawClass</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">().</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    if</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> (</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">isInAllowlist</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(className)) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    }</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    boolean</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> isExplicitMixin </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">findMixInClassFor</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getRawClass</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">())</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> !=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    if</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> (isExplicitMixin) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    }</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    JacksonAnnotation</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> jacksonAnnotation </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> AnnotationUtils</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">findAnnotation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getRawClass</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(),</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">            JacksonAnnotation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    if</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> (jacksonAnnotation </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">!=</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">) {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> result</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    }</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    throw</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> IllegalArgumentException</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"The class with "</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> id </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">+</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> " and name of "</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> className</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">            +</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> " is not in the allowlist. "</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">            +</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. "</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">            +</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "If the serialization is only done by a trusted source, you can also enable default typing. "</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">            +</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "See https://github.com/spring-projects/spring-security/issues/4370 for details"</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>从上面的代码可以看出 反序列化时只允许满足三种条件的类型执行：Spring Security 的白名单内、被混入、被JacksonAnnotation注解。</p>
</blockquote>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Spring Security 为什么要求对核心类的序列化使用混入呢？</p>
<p>因为前文在讲解 Jackson Mixin 时提过，Mixin 主要用于在不修改“外部”代码的情况下，对“外部”的代码进行序列化操作。</p>
<p>在使用 Spring Security 时，通常都需要对其核心类进行扩展，才能满足我们的实际应用需求。站在 Spring Security 角度，我们扩展的这些类对于 Spring Security 来说就是“外部”类，想要 Spring Security 帮你完成序列化就需要你提供 <code>Mixin</code>。</p>
<p>当然，最核心的还是前文提到的从安全性角度考虑，由用户自己来保证反序列化的安全性。</p>
</div>
<h3>[2]Spring Authorization Server 中的 Jackson</h3>
<p>想必大家在初次使用 <code>Spring Authorization Server</code> 时，就会遇到代码无法运行，要求你增加白名单的问题。</p>
<p>之所以会出现这个问题，原因就是可以把 <code>Spring Authorization Server</code> 理解为在 Spring Security 基础上“套了一层壳”。对 <code>Spring Authorization Server</code> 的大多数配置，本质上就是对底层 Spring Security 的配置，使用的还是 Spring Security 的核心内容。因此，在对一些核心类反序列化时，还是会用到的 Spring Security 的反序列化安全机制，所以就必须要编写对应的序列化 <code>Mixin</code>。</p>
<p>在 <code>Spring Authorization Server</code> 中，核心内容都是存储在数据库中，而且有些字段是以 JSON 的形式进行存储，这一块内容就是需要自己编写 <code>Mixin</code> 的部分。</p>
<p>Dante Cloud 是用 JPA 来实现的 <code>Spring Authorization Server</code> 数据存储。在这里就编写了大量的 <code>Jackson</code> <code>Mixin</code> 代码。</p>
<figure><img src="/assets/image/design/sas-jackson-mixin.png" alt="sas-mixin" tabindex="0" loading="lazy"><figcaption>sas-mixin</figcaption></figure>
<p>在<code>Spring Authorization Server</code> JPA 实现类中设置自定义的 <code>Mixin</code></p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> OAuth2JacksonProcessor</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">() {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">    objectMapper </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> ObjectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">()</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    ClassLoader</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> classLoader </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> OAuth2JacksonProcessor</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getClassLoader</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    List</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Module</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> securityModules </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> SecurityJackson2Modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getModules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(classLoader);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    objectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">registerModules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(securityModules);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    objectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">registerModules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> OAuth2AuthorizationServerJackson2Module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    objectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">registerModules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> HerodotusJackson2Module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    objectMapper</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">registerModules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> OAuth2TokenJackson2Module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[七]Dante Cloud 中的 Jackson 的配置</h2>
<h3>[1]配置方式</h3>
<p>在 Dante Cloud 早期版本中，<code>Jackson</code> 的配置是采用前面章节描述的方式，即定义 <code>ObjectMapper</code> Bean，并标注为 <code>@Primary</code>。</p>
<p>这种方式确实可以做到“全局” 设置 <code>ObjectMapper</code>，也是最简单的方式。但是采用这种方式，存在几个问题</p>
<ol>
<li><code>JacksonAutoConfiguration</code> 中，很多的 Bean 的配置将不再进行注入，包括：<code>Jackson2ObjectMapperBuilder</code> 。在这种模式下 <code>Jackson2ObjectMapperBuilderCustomizer</code> 也不再生效</li>
<li>同时，在 <code>application.properties</code> 中配置 <code>Jackson</code> 相关内容也不再生效。所有的配置只能通过修改代码，在 <code>ObjectMapper</code> 中配置。</li>
<li>这种配置方式在大多数情况下，只要不纠结细节，在使用上是没有任何问题的。但是，在需要有特殊处理的情况下，就会出现互相干扰</li>
</ol>
<p>在 Dante Cloud 新的版本中，修改为使用 <code>Jackson2ObjectMapperBuilderCustomizer</code> 方式进行 <code>ObjectMapper</code> 的配置。即兼顾了全局统一设置<code>ObjectMapper</code>，又同时支持配置文件配置，以及 Jackson配置的自定义。</p>
<p>最主要的优点是，你可以定义多个 <code>Jackson2ObjectMapperBuilderCustomizer</code> 接口的实现类来扩展 <code>Jackson</code>，而且这些类可以分散在不同的代码模块中，这就实现了针对不同的模块可以有不同的 <code>Jackson</code> 定义。</p>
<h3>[2]Jackson2Utils</h3>
<p>虽然上面所述的方式，解决了 <code>Jackson</code> 配置中的大量问题，但是在使用中还是不太方便，因为需要你在所有用到 <code>Jackson</code> 的代码中，都注入 <code>ObjectMapper</code> 对象。</p>
<p>为了解决这个问题，Dante Cloud 在上述内容的基础之上，又封装了一个 <code>Jackson2Utils</code> 类。</p>
<p><code>Jackson2Utils</code> 不仅封装了 <code>Jackson</code> 常用方法。而且为了解决 <code>ObjectMapper</code> 的统一，<code>Jackson2Utils</code> 类被定义为了 Spring Boot 的 <code>@Component</code>，需要注入 <code>ObjectMapper</code> 。这样在需要使用 Jackson 的地方只需要调用 <code>Jackson2Utils</code> 静态方法即可。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p><code>Jackson2Utils</code>类也是“静态类注入Bean”的典型示例</p>
</div>
<h3>[3]注意事项</h3>
<p>所有的 <code>Jackson2ObjectMapperBuilderCustomizer</code>，其基本原理就是通过该接口向 <code>Jackson2ObjectMapperBuilder</code> 中设置属性。</p>
<p>这里存在一个问题，通过查看源代码可以发现，所有的 <code>moduleToInstall</code>、<code>modules</code> 方法，都会重新设置 <code>module</code> 属性的值，而不是向其中增加新的值。这就意味着如果有两个 <code>Jackson2ObjectMapperBuilderCustomizer</code>，每一个里面都用到了 <code>moduleToInstall</code> 或 <code>modules</code> 方法，后面设置的 <code>modules</code> 将会覆盖前面设置的 <code>modules</code>，将会导致前面设置的 <code>modules</code> 失效。</p>
<p>为了解决这个问题，Dante Cloud 采用的二次设置的方式，先拿到已有的 Module，再增加新的 Module，然后再将所有的 Module 设置回去。</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">modulesToInstall</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    modules </span><span style="--shiki-light:#C18401;--shiki-dark:#C678DD">-></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        List</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Module</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">install</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ArrayList</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;>(modules);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        install</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> EncapsulationClassJackson2Module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        install</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> Jdk8Module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        install</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> JavaTimeModule</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">());</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        builder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">modulesToInstall</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">toArray</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(install));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[4]<code>Spring Authorization Server</code>中的特殊处理</h3>
<p>Dante Cloud 对 <code>Spring Authorization Server</code> 中 <code>Jackson</code> 相关的处理，采用的是重新 <code>new ObjectMapper()</code> 的方式处理相关数据的序列化，并没有使用前文所说的<code>Jackson2ObjectMapperBuilderCustomizer</code> 方式。</p>
<p>最初，也考虑到使用 <code>new ObjectMapper()</code> 方式来处理 <code>Spring Authorization Server</code> 中 <code>Jackson</code> 的配置，不够优雅，没有与现有的 Dante Cloud <code>Jackson</code> <code>Customizer</code> 模式形成统一体系。</p>
<p>也尝试过将 <code>Spring Authorization Server</code> 相关的 <code>Jackson</code> 配置代码，也定义为一个 <code>Jackson2ObjectMapperBuilderCustomizer</code>。但是在实践的过程中发现，这种方式会反而会导致 <code>Spring Authorization Server</code> 运行就会出现序列化问题</p>
<p>经过调试代码发现，<code>Jackson</code> <code>Module</code> 的“链”式机制（参见前文<a href="/develop-guide/design/jackson.html#_2module" target="_blank">【Jackson 中的 Module】</a>）。而 Spring Security <code>Jackson</code> 相关的 <code>Module</code> 与 Spring Boot 中默认配置的 <code>Jackson</code> <code>Module</code> 有冲突，会让反序列化产生不同的结果，导致原本正常的反序列化失败。</p>
<p>因此，在 Dante Cloud <code>Spring Authorization Server</code> JPA 的实现模块中，单独采用常规 <code>new ObjectMapper()</code> 的方式处理序列化和反序列化的问题，这就与系统全局配置的 <code>ObjectMapper</code> 形成了隔离，互补干扰互不影响。</p>
<div class="hint-container info">
<p class="hint-container-title">鼓励一下？！</p>
<p>各位看官老爷，都已经看到这里了，给 Dante Cloud 一个 Star 鼓励一下，要不 Dante Cloud 也得学其它项目强制点 Star 才能看文档了 🫤</p>
<ul>
<li><a href="https://gitee.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">Gitee</a></li>
<li><a href="https://github.com/dromara/dante-cloud" target="_blank" rel="noopener noreferrer">Github</a></li>
</ul>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/design/sas-jackson-mixin.png" type="image/png"/>
    </item>
    <item>
      <title>多环境配置</title>
      <link>https://www.herodotus.cn/develop-guide/design/profile.html</link>
      <guid>https://www.herodotus.cn/develop-guide/design/profile.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">多环境配置</source>
      <description>[一]什么是多环境配置？ 在实际项目开发过程中，我们往往需要区分开发，测试，联调,预发布，生产等不同的应用环境。这些应用环境用途不同，对应环境的配置项、稳定性、数据质量、保障性、可接触人群等要求也不同，比如 Swagger 一般上在生产时是关闭的；不同环境数据库地址,端口号等都是不尽相同的。要是没有多环境的自由切换，部署起来是很繁琐也容易出错的。 使用...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]什么是多环境配置？</h2>
<p>在实际项目开发过程中，我们往往需要区分开发，测试，联调,预发布，生产等不同的应用环境。这些应用环境用途不同，对应环境的配置项、稳定性、数据质量、保障性、可接触人群等要求也不同，比如 Swagger 一般上在生产时是关闭的；不同环境数据库地址,端口号等都是不尽相同的。要是没有多环境的自由切换，部署起来是很繁琐也容易出错的。</p>
<p>使用多环境配置，可以针对不同的应用环境，提前配置好对应的环境配置信息。在使用时，仅需要修改具体的环境名称，就可以把对应环境配置信息,系统参数等相关内容全部切换。不仅使用便捷，还极大地降低了手工修改参数的出错率。</p>
<h2>[二]不同套件中多环境</h2>
<h3>[1]Maven 多环境配置</h3>
<p>使用<code>Maven</code>可以通过在<code>pom.xml</code>中增加<code>&lt;profiles&gt;</code>配置进行多环境的配置。参见以下示例：</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profiles</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>development&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activeByDefault</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>true&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activeByDefault</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            ...</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>produtction&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            ...</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>testing&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            ...</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profiles</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过下面配置就可以指定当前默认的环境是哪个</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activeByDefault</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>true&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activeByDefault</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>在开发和使用过程中，必须要配置一个默认的环境。配置完成之后，在 IDE 中也会提供可操作性界面进行操作。下图即为 IDEA 的界面示例：</p>
<figure><img src="/assets/image/design/profiles.png" alt="profiles" tabindex="0" loading="lazy"><figcaption>profiles</figcaption></figure>
<h3>[1]Spring Boot 多环境配置</h3>
<h4>Spring Boot 环境设置机制</h4>
<p><code>spring.profiles.active</code> 属性可以为我们指定当前设置的环境，以此来选择我们的配置文件。例如我们有配置文件</p>
<ul>
<li>application.yml</li>
<li>application-dev.yml</li>
<li>application-test.yml</li>
<li>application-prod.yml</li>
</ul>
<p>当执行 <code>java -jar xxx.jar --spring.profiles.actvie=test</code> 此时，系统将启用 <code>application.yml</code> 和 <code>application-test.yml</code> 配置文件。</p>
<p>当执行 <code>java -jar xxx.jar --spring.profiles.actvie=prod</code> 此时，系统将启用 <code>application.yml</code> 和 <code>application-prod.yml</code> 配置文件。</p>
<p>正是这种配置参数可以决定我们使用哪种配置文件，如果我们把不同环境的配置写在对应的配置文件中，我们就可以实现多环境机制。</p>
<h4>配置多环境</h4>
<p>正如上一点所述，我们配置不同的配置文件</p>
<ul>
<li>application.yml</li>
<li>application-dev.yml（开发环境）</li>
<li>application-test.yml（测试环境）</li>
<li>application-uat.yml（预发布环境）</li>
<li>application-prod.yml（生产环境）</li>
</ul>
<h4>指定环境</h4>
<ul>
<li>在 cmd 命令中指定</li>
</ul>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">java</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -jar</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> xxx.jar</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --spring.profiles.actvie=dev</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ul>
<li>在 <code>application.yml</code> 中指定</li>
</ul>
<div class="language-yml line-numbers-mode" data-highlighter="shiki" data-ext="yml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  profiles</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    active</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">dev</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>在 IDEA 编辑器中指定</li>
</ul>
<p>在运行按钮（绿色三角形按钮）旁边选择 <code>Edit Configurations...</code>，在弹出的对话框中 <code>Active profiles</code> 输入 <code>dev</code> 或其他即可。</p>
<div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>这种方法只有在本地调试的时候才生效。</p>
</div>
<h4>单一文件写法</h4>
<div class="language-yml line-numbers-mode" data-highlighter="shiki" data-ext="yml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  application</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  @</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">artifactId@</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  profiles</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    active</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">develpment</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">...</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/design/profiles.png" type="image/png"/>
    </item>
    <item>
      <title>后端系统运行</title>
      <link>https://www.herodotus.cn/get-started/install/backend.html</link>
      <guid>https://www.herodotus.cn/get-started/install/backend.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">后端系统运行</source>
      <description>[一]创建数据库 [1]创建系统数据库 进入PostgreSQL 的SQL Shell(psql) (在 Windows 的菜单中可以找到)，使用超级管理员账号和密码登录进入数据库，执行以下脚本： 提示 postgresql 建库脚本，放置在工程代码的目录 ${project_home}/configuration/scripts/postgresql...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]创建数据库</h2>
<h3>[1]创建系统数据库</h3>
<p>进入<code>PostgreSQL</code> 的<code>SQL Shell(psql)</code> (在 Windows 的菜单中可以找到)，使用超级管理员账号和密码登录进入数据库，执行以下脚本：</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>postgresql 建库脚本，放置在工程代码的目录 <code>${project_home}/configuration/scripts/postgresql.md</code> 文件中，可以直接使用</p>
</div>
<h4>（1）为微服务版本建库</h4>

<h4>（2）为单体版本建库</h4>

<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<ol>
<li>以上脚本要分步执行（即：一条一条地执行），不要全部复制，一把全部执行。一定要注意每个语句结尾要以“;”结束。</li>
<li>这里不管是用户名，用户密码还是数据库名均使用系统默认是为了方便，实际请根据自己实际情况修改。</li>
<li>这里没有进行分库（即：所有的服务使用同一个数据库），如不满足需求，请在整体部署成功可以正常运行后，再结合自己的实际情况修改</li>
<li>建表操作由系统负责自动完成，不需要手动操作，具体请看后面的操作步骤。</li>
</ol>
</div>
<h3>[2]创建 Nacos 数据库</h3>
<h4>（1）使用 MySQL 数据库</h4>
<p>MySQL 是 Nacos 官方默认的外置数据库。相信大家对 MySQL 建库都很熟悉，就不再赘述。</p>
<p>建库脚本可以从 Nacos 官方 Github 下载，或者在 Dante Cloud 工程的 <code>${project_home}/configuration/scripts/nacos/sqls</code> 目录下也可以找到。</p>
<h4>（2）使用 PostgreSQL 数据库</h4>
<p>如果您打算使用 Dante Cloud 自主封装的 Docker 镜像 <code>herodotus/docker-server</code>，那么就可以使用 PostgreSQL 作为 Nacos 外置数据库。这样 Nacos 就可以和系统使用同一个数据库，无需在额外安装 MySQL。</p>
<p>创建 Nacos 数据库操作，与上一步相同，执行脚本如下：</p>
<div class="language-sql line-numbers-mode" data-highlighter="shiki" data-ext="sql" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-sql"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">// 以下要分步，一条一条地执行，不要全部复制，一把全部执行。一定要注意每个语句结尾要以“;”结束。</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">CREATE</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> USER</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> nacos</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> WITH</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> PASSWORD</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'nacos'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">CREATE</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> DATABASE</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> nacos</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> OWNER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> nacos;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">GRANT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ALL PRIVILEGES </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">ON</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> DATABASE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> nacos </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">TO</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> nacos;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><blockquote>
<p>这里不管是用户名，用户密码还是数据库名均使用 nacos 是为了方便，请根据自己实际情况修改。</p>
</blockquote>
<p>数据库创建完成之后，在 Dante Cloud 工程的 <code>${project_home}/configuration/scripts/nacos/sqls</code> 目录下找到 <code>nacos-postgresql_3.2.1.sql</code> 脚本，用这个脚本建表和初始化 Nacos 数据。</p>
<h2>[二]环境配置</h2>
<h3>[1]套件运行</h3>
<p>在正式开始之前，要确保 Nacos、Kafka 相关套件可以正确运行。为了方便，这里使用的是 Docker Compose 方式来部署运行 Nacos、Kafka。如果您采用的是其他的部署安装方式，可以跳过本章节。</p>
<h4>(1) 启动 Docker Desktop for Windows</h4>
<p>在本地启动 Docker 环境。本地 Docker 环境的安装配置，参见<a href="/get-started/prepare/docker.html" target="_blank">本地 Docker 安装配置</a></p>
<h4>(2) 执行 Docker Compose</h4>
<p>在 Dante Cloud 工程的 <code>${project_home}/configuration/docker/docker-compose/windows</code> 目录下找到 <code>env.base.yml</code> 脚本。</p>
<p>打开 CMD，执行以下命令运行 Docker Compose 脚本。</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">docker-compose</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -f</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF"> ${</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">project_home</span><span style="--shiki-light:#E45649;--shiki-dark:#ABB2BF">}</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/configurations/docker/docker-compose/windows/herodotus/env.base.yml</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> up</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -d</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><blockquote>
<p>脚本中的内容可结合自己的实际进行修改，修改后再执行脚本。</p>
</blockquote>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<ol>
<li><code>env.base.yml</code> 脚本中，进包含了 Nacos 和 Kafka，由于 ELK 等其他套件非常占用资源而且本地开发通常也不需要。</li>
<li>如果需要安装全套资源，请只用 <code>env.full.base.yml</code></li>
<li>新的版本中，为了减少环境依赖，改用纯 Kafka 镜像，无须再运行 Zookeeper。如果需要使用 Debezium，建议使用 Debezium 官方 Kafka 镜像，这种方式下还是需要使用 Zookeeper，详情参见 Debezium 官网。</li>
</ol>
</div>
<div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>自 Dante Cloud v4.0.5.1 版本起，开始使用全新的 Nacos 3.2.0 版本。该版本已经将关键的 plugin，例如：Postgresql、Oracle 等数据存储插件合并至 Nacos 主工程中，并打包至 Docker 的镜像中，通过修改配置即可更换数据库，无需像从前一样，更换数据库还得自己打包插件。</p>
<p>如果您打算使用 Dante Cloud v4.0.5.1 以及 Nacos 3.2.0，需要找到工程代码的目录 <code>${project_home}/configuration/scripts/nacos/3.2.0/config</code>，两其中的两个文件 <code>application.properties</code> 和 <code>nacos-logback.xml</code> 到前面所说的 Docker Compose 文件中对应的挂在目录下</p>
</div>
<h3>[2]导入 Nacos 配置</h3>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>在进行以下操作之前，建议对 Nacos 中 Namespace，Group 等基础概念和具体操作先行做一定的了解。<a href="https://nacos.io/zh-cn/docs/concepts.html" target="_blank" rel="noopener noreferrer">【官方文档】</a></p>
</div>
<h4>方式一：手动创建 Nacos 配置信息</h4>
<p>创建配置之前，请提前规划配置放置的命名空间（Namespace）。默认是放到 Nacos <code>public</code>命名空间中，如果不符合您的需求，可以在新建命名空间之后，再进行配置的创建。</p>
<p>工程的 <code>${project_home}/configurations/backup/yamls</code> 目录下，放置着所有需要导入到 <code>Nacos</code> 中的默认配置。开始其它工作之前，需要先在 Nacos 中创建所有配置</p>
<ol>
<li>访问 <code>Nacos</code> 后台管理，<a href="http://localhost:8848/nacos" target="_blank" rel="noopener noreferrer">默认本机地址</a>， 使用默认账号<code>nacos/nacos</code>登录。</li>
<li>在 <code>Nacos</code> 后台中，在 <code>配置管理 --&gt; 配置列表</code> 功能中，点击<strong>添加</strong>按钮，手动添加所有配置文件，操作如下图所示：</li>
</ol>
<figure><img src="/assets/image/install/config-create.png" alt="创建Nacos配置" tabindex="0" loading="lazy"><figcaption>创建Nacos配置</figcaption></figure>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p><code>${project_home}/configurations/backup/yamls</code> 目录下，还有一级目录。这级目录的名称，是配置文件对应 <code>Nacos</code> 中的 <code>Group</code> 名称。所以在 <code>Nacos</code> 中新建配置时，一定要注意 <code>Group</code> 的创建和匹配。切勿全部新建到 <code>DEFAULT_GROUP</code> 组下，这样会导致无法正常运行。</p>
</div>
<h4>方式二：批量导入 Nacos 配置</h4>
<p>手动方式创建 Nacos 配置，比较繁琐和容易出错。为了使用提升使用的便捷性，在工程 <code>${project_home}/configurations/backup/alibaba</code> 目录下提供可以直接导入的配置包。可以方便的完成导入配置，创建<code>Group</code>等操作。</p>
<ol>
<li>访问 <code>Nacos</code> 后台管理，<a href="http://localhost:8848/nacos" target="_blank" rel="noopener noreferrer">默认本机地址</a>， 使用默认账号<code>nacos/nacos</code>登录。</li>
<li>在 <code>Nacos</code> 后台中，在 <code>配置管理 --&gt; 配置列表</code> 功能中，点击<strong>导入配置</strong>按钮，选择配置导入包即可，完成配置文件导入和 Group 的创建，操作如下图所示：</li>
</ol>
<figure><img src="/assets/image/install/config-import.png" alt="导入Nacos配置" tabindex="0" loading="lazy"><figcaption>导入Nacos配置</figcaption></figure>
<h3>[3]修改 Nacos 配置</h3>
<p>上一步所导入的配置参数均是使用默认的、本地化的配置，需要根据自己搭建的基础设施环境，对相关的配置进行修改。</p>
<ol>
<li>访问 <code>Nacos</code> 后台管理，<a href="http://localhost:8848/nacos" target="_blank" rel="noopener noreferrer">默认本机地址</a>， 使用默认账号<code>nacos/nacos</code>登录。</li>
<li>找到对应的配置文件 <code>herodotus-cloud-environment.yaml</code> 进行修改。常用的设置在下面配置文件中即可修改完成，如果不满足您实际需求，可再到其它的配置文件中详细修改。</li>
</ol>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>开源版和企业版配置文件名称不同，如下所示。请根据你所使用的版本对应修改，后续内容均是如此，就不再赘述解释说明</p>
</div>

<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>为了方便使用，<strong>Dante Cloud</strong> 所有的配置采用<strong>“共享式，统一化多环境配置模式”</strong>设计，大多数配置在对应 <code>多环境</code> 的 <code>herodotus-cloud-environment.yaml</code> 配置中就可以完成修改。例如，您当前使用的是 <code>development</code> 环境，那么在 Nacos 后台管理中，找到 Group 是 <code>development</code> 的 <code>herodotus-cloud-environment.yaml</code> 配置修改。</p>
<p><strong>Dante Cloud</strong> 多环境配置设计及使用，参阅：<a href="/develop-guide/design/profile.html" target="_blank">【架构与设计】-&gt;【多环境配置】</a></p>
</div>
<h3>[4]检出代码创建工程</h3>
<p><code>IDEA</code> 菜单 <code>File -&gt; New -&gt; Project From Version Control...</code>， 在弹出框中输入本工程 Git 地址，然后设置放置目录，点击 <strong>OK</strong> 即可</p>
<div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>不建议直接从 Gitee 或 Github 上，以 <em>下载 ZIP</em> 的方式获取代码。因为代码中集成了 git 信息打包工具，依赖于工程中的 <code>.git</code> 目录，用于查看 git 版本信息，相关信息也可在 <code>Spring Boot Admin</code> 中查看。直接以 ZIP 方式下载代码，可能会因找不到 <code>.git</code> 目录而导致编译出错。</p>
</div>
<h3>[5]修改 pom.xml 配置</h3>
<p>在工程根目录下，找到 <code>pom.xml</code>， 修改对应 <code>profile</code> 中参数。由于使用的多环境，所以在该文件中，需要找到应环境的配置，修改该环境下的对应的配置信息。主要结构如下所示：</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!-- 开发 --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>development&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--默认激活配置--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activeByDefault</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>true&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activeByDefault</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">activation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--当前环境--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>development&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">database</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>postgresql&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">database</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--基础设施：tencent、alibaba、spring--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">facility</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>alibaba&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">facility</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--代码构建控制--></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--跳过构建源代码包--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.build.source.package</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>false&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.build.source.package</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--不copy代码包到docker构建目录--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.copy.docker.resource</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>false&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.copy.docker.resource</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--不执行git commit 构建--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.build.git.commit.info</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>false&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.build.git.commit.info</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--Spring Cloud Alibaba配置中心命名空间，用于支持多环境.这里必须使用ID，不能使用名称，默认为空--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">alibaba.namespace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>2e4f5cf6-2ac8-4fe0-99be-b0562659c120&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">alibaba.namespace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--Spring Cloud Alibaba配置中心地址--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">alibaba.config.server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>192.168.101.10:8848&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">alibaba.config.server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--Spring Cloud Alibaba服务发现地址--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">alibaba.discovery.server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>192.168.101.10:8848&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">alibaba.discovery.server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">alibaba.sentinel.server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>192.168.101.10:8858&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">alibaba.sentinel.server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">tencent.polaris.namespace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>default&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">tencent.polaris.namespace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--Spring Cloud Tencent 配置 【(8091)grpc/tcp，默认注册中心端口;(8090)http/tcp，默认注册中心端口】--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">tencent.polaris.server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>192.168.101.10:8091&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">tencent.polaris.server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--Spring Cloud Tencent Config 配置 【(8093)grpc/tcp，默认配置中心端口】--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">tencent.polaris.config-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>192.168.101.10:8093&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">tencent.polaris.config-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--Spring Cloud Tencent 本地缓存目录--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">tencent.polaris.local.dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>./configurations/configs/tencent/backup/config&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">tencent.polaris.local.dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>必须要修改的参数涉及以下几项：</p>
<ul>
<li><strong>alibaba.namespace</strong>：Nacos 命名空间 ID。如果要使用的是默认的命名空间，该参数不设置任何值。</li>
<li><strong>alibaba.config.server-addr</strong>：配置中心地址，即 Nacos 服务端地址</li>
<li><strong>alibaba.discovery.server-addr</strong>：服务发现地址，即 Nacos 服务端地址</li>
<li><strong>alibaba.sentinel.server-addr</strong>：Sentinel Dashborad 地址</li>
</ul>
<h3>[6]代码编译</h3>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>对工程根目录下 <code>pom.xml</code> 中的参数进行修改，一定要重新编译代码，这样修改后的参数才会生效</p>
</div>
<h4>（1）编译方式一</h4>
<p>用命令行进入到工程代码所在根据目录，通过执行下面命令进行编译</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">mvn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">或</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">mvn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> package</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>在第一次编译的时候，因为在本地缓存缺少相关的包，所以在执行 <code>mvn clean</code> 操作时会提示错误。因此，在第一次编译时只使用 <code>mvn package</code> 或 <code>mvn install</code>。第一次编译完成之后，就可以使用 <code>mvn clean package</code> 或 <code>mvn clean install</code> 命令进行编译。IDE 界面操作也是同理。</p>
</div>
<h4>（2）编译方式二</h4>
<p>在 IDE 工程中，利用 IDE 提供的界面操作进行编译。</p>
<p>以<code>IDEA</code>为例，在右侧<code>Maven Panel</code>中，选择<code>dante-cloud(root)</code>节点，点击 Lifecycle 节点下的<code>install</code> 或者 <code>package</code> 进行编译。参考下图：</p>
<figure><img src="/assets/image/install/lifecycle.png" alt="输入图片说明" tabindex="0" loading="lazy"><figcaption>输入图片说明</figcaption></figure>
<p>详细参数说明，参阅：<a href="/develop-guide/design/profile.html" target="_blank">【架构与设计】-&gt;【多环境配置】</a></p>
<h2>[三]创建数据表</h2>
<div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>正式环境一定要提前做好数据备份</p>
</div>
<h3>[1]修改配置</h3>
<ol>
<li>进入 Nacos 管理界面。找到您所使用的对应 <code>多环境</code> 下的 <code>herodotus-cloud-environment.yaml</code> 配置文件</li>
</ol>
<p>例如，您当前使用的是 <code>development</code> 环境，那么在 Nacos 后台管理中，找到 <code>Group</code> 是 <code>development</code> 的 <code>herodotus-cloud-environment.yaml</code> 配置。多环境配置，参阅：【架构与设计】-&gt; <a href="/develop-guide/design/profile.html" target="_blank">多环境配置</a></p>
<ol>
<li>设置 <code>herodotus-cloud-environment.yaml</code>配置文件中属性 <code>herodotus.switch.database.ddl-auto</code> 的值为 <code>create</code> 或 <code>update</code></li>
</ol>
<ul>
<li>如果是<code>MySQL</code>数据库的初次部署，那么最好将 <code>herodotus.switch.database.ddl-auto</code> 的值设置为 <code>update</code>，否则就会出错。</li>
<li><code>herodotus.switch.database.ddl-auto</code> 的值设置为 <code>update</code>, 再次运行服务，由于 <code>Hibernate</code> 不同方言实现逻辑的不同，在有些数据库下运行，例如：<code>PostgreSQL</code>, 对于某些兼容性问题，只是输出告警；在某些数据库下运行，例如 MySQL，对于某些兼容性问题，会直接抛出 Exception。所以出现这种情况，只要服务继续在运行就不是系统 BUG；如果出错直接导致服务运行停止，那么请报 <code>ISSUE</code>。</li>
</ul>
<ol start="3">
<li>点击<code>发布</code>按钮，发布配置，让配置生效</li>
</ol>
<h3>[2]启动服务建表</h3>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>如果在 IDEA 中首次启动服务出现如下图所示的提示，要首先对服务运行的配置进行修改，参见：<a href="/get-started/prepare/ide.html#_4-command-line-is-too-long%E9%97%AE%E9%A2%98%E4%BF%AE%E5%A4%8D" target="_blank">【Command line is too long问题修复】</a></p>
<figure><img src="/assets/image/prepare/idea-toolong-01.png" alt="idea-toolong-01" tabindex="0" loading="lazy"><figcaption>idea-toolong-01</figcaption></figure>
</div>
<h4>1. 运行 <code>UpmsApplication</code> 服务</h4>
<p><code>UpmsApplication</code> 服务第一次正常运行之后，所有核心业务表都已经创建，<code>UpmsApplication</code> 服务中所有接口都已经存入 <code>sys_interface</code> 表中并转换为权限数据存入<code>sys_attribute</code>表中。</p>
<blockquote>
<p>如果看到有数据库表创建，同时<code>sys_authority</code>表中有数据，证明数据库表创建成功。</p>
</blockquote>
<h4>2. 运行 <code>UaaApplication</code> 服务</h4>
<p><code>UaaApplication</code> 服务第一次正常运行之后，所有 OAuth2 认证相关表都已经创建。<code>UaaApplication</code> 服务中所有接口都已经存入 <code>sys_interface</code> 表中并转换为权限数据存入<code>sys_attribute</code>表中。</p>
<blockquote>
<p>如果看到有以<code>oauth2_</code>开头的数据表创建，证明数据库表创建成功。</p>
</blockquote>
<h4>3. 运行 <code>OssAbilityApplication</code> 服务</h4>
<p><code>OssAbilityApplication</code> 服务第一次正常运行之后，<code>OssAbilityApplication</code> 服务中所有接口都已经存入 <code>sys_interface</code> 表中并转换为权限数据存入<code>sys_attribute</code>表中。</p>
<h4>4. 运行 <code>ManageApplication</code> 服务（企业版）</h4>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>开源版本没有 <code>ManageAbilityApplication</code> 可以跳过</p>
</div>
<p><code>ManageAbilityApplication</code> 服务第一次正常运行之后，所有系统管理支持等相关表都已经创建。</p>
<blockquote>
<p>如果看到有以<code>mgt_</code>开头的数据表创建，证明数据库表创建成功。</p>
</blockquote>
<h4>5. 运行 <code>MessageApplication</code> 服务</h4>
<p><code>MessageApplication</code> 服务第一次正常运行之后，所有站内消息等相关表都已经创建。<code>MessageApplication</code> 服务中所有接口都已经存入 <code>sys_interface</code> 表中并转换为权限数据存入<code>sys_attribute</code>表中。</p>
<blockquote>
<p>如果看到有以<code>msg_</code>开头的数据表创建，证明数据库表创建成功。</p>
</blockquote>
<div class="hint-container info">
<p class="hint-container-title">为什么表结构的创建不提供脚本？</p>
<p>主要出于以下考虑：</p>
<ul>
<li>一方面系统默认使用 Spring Data JPA 已经支持了自动建表，数据表的创建和更新都很方便，仅有的问题对于 MySQL 方言支持的一般，会有错误提示，但是不影响整体初始化逻辑。</li>
<li>另一方面微服务架构相比单体应用来说庞大得多，需要关注的内容也扩展了很多倍。提供数据库表结构的脚本，就意味随时要去关注这个 SQL 脚本的更新问题，一旦有遗漏就很容出错，会浪费很多时间和精力，得不偿失。</li>
</ul>
</div>
<h3>[3]服务启动顺序说明</h3>
<h4>1.核心服务</h4>
<ol>
<li>UpmsApplication （用户中心服务 必需）</li>
<li>UaaApplication （统一认证服务 必需）</li>
<li>MessageApplication （平台消息服务 必需）</li>
<li>OssAbilityApplication （对象存储服务 必需）</li>
<li>ManageAbilityApplication （系统管理服务 必需）</li>
<li>GatewayApplication （服务网关服务 必需）</li>
</ol>
<blockquote>
<p>因 UpmsApplication 服务中包含权限数据汇总和分发机制，所以 UpmsApplication 服务要最先启动。UaaApplication、MessageApplication、MessageApplication 和 OssAbilityApplication 服务启动顺序没有硬性要求。GatewayApplication 启动顺序没有硬性要求，建议可以放在最后启动</p>
</blockquote>
<h4>2.其它服务</h4>
<ol>
<li>MonitorApplication （监控中心服务 可选）</li>
<li>BpmnAbilityApplication （分布式工作流 可选）</li>
</ol>
<blockquote>
<p>建议 MonitorApplication 服务在所有服务启动之后，再行启动。</p>
</blockquote>
<h2>[四]数据库初始化</h2>
<p>系统提供自动初始化和手动初始化数据库两种方式。自动化初始化数据库的机制完全由 Spring Data JPA 提供，使用习惯之后非常便捷。但是项目开源至今，发现很多人还是喜欢手动导数据，也提供了数据库初始化脚本。也正因为如此，也修改了本部分说明，主要说明手动导入数据，自动导入数据仅做说明供对此感兴趣的朋友参考。</p>
<h3>[1]方式一：手动初始化</h3>
<ol>
<li>设置 <code>herodotus-cloud-environment.yaml</code> 配置文件中属性 <code>herodotus.switch.database.ddl-auto</code> 的值为 <code>update</code> 或 <code>none</code>。</li>
<li>在工程目录下，可以找到对应数据库数据初始化脚本。可以用你喜欢的方式导入数据库。</li>
</ol>

<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>要按照前面步骤，保证 <code>UpmsApplication</code>、<code>UaaApplication</code>、<code>MessageApplication</code>、<code>OssAbilityApplication</code> 、<code>ManageAbilityApplication</code> 几个服务均已经正常运行过、服务中所有接口都已经转换为权限数据存入<code>sys_attribute</code>表中以后，在执行数据库初始化脚本。</p>
<p>因为，数据库初始化脚本中，最后一条 SQL 语句，是根据 <code>sys_attribute</code> 表中存储的数据，动态生成权限数据。如果过早执行数据库初始化脚本，会导致权限数据创建不完整，前端出现“没有权限”提示问题。</p>
</div>
<h3>[2]方式二：自动初始化</h3>
<ol>
<li>停止<code>ManageAbilityApplication</code>服务</li>
<li>进入 Nacos 管理界面。找到您所使用的对应 <code>多环境</code> 下的 <code>herodotus-cloud-environment.yaml</code> 配置文件。</li>
<li>设置 <code>herodotus-cloud-environment.yaml</code> 配置文件中属性 <code>herodotus.switch.database.ddl-auto</code> 的值为 <code>update</code> 或 <code>none</code></li>
<li>设置 <code>herodotus-cloud-environment.yaml</code> 配置文件中属性 <code>herodotus.switch.database.init-mode</code> 的值为 <code>always</code></li>
<li>点击<code>发布</code>按钮，发布配置，让配置生效。</li>
<li>如果默认使用的不是 <code>PostgreSQL</code> 数据库，需要修改<code>${project_home}/platform/herodotus-cloud-manage-ability/resources/bootstrap.yml</code> 中 <code>spring.sql.init.data-locations</code> 配置，放开您所使用数据库对应的 SQL 脚本。注释掉其它 SQL 脚本</li>
<li>再次运行 <code>OssAbilityApManageAbilityApplicationplication</code> 服务。</li>
</ol>
<p><code>ManageAbilityApplication</code> 服务再一次正常运行之后，如果<code>sys_user</code>,<code>sys_role</code>,<code>sys_role_permission</code>等表中都都已经有数据了，证明数据初始化成功。</p>
<div class="hint-container info">
<p class="hint-container-title">自动初始化基本原理</p>
<p>Dante Cloud 数据库自动初始化，采用的是 <code>Spring Boot</code> 提供的数据库 SQL 脚本的执行机制。将已生成好的数据库 SQL 脚本，放入到代码工程的<code>resources</code>目录下，按照指定格式命名，通过修改配置就可以实现 SQL 脚本的自动执行。</p>
<p>具体 SQL 文件名的格式为：<code>schema-${platform}.sql</code> 和 <code>data-${platform}.sql</code>。<code>platform</code> 是用来指定不同的数据库类型，以此来实现不同数据库脚本的切换。</p>
<ul>
<li><code>schema-${platform}.sql</code>：主要放置数据库定义语言(<code>DDL</code>)SQL 脚本，例如：数据库表,视图,存储过程等。</li>
<li><code>data-${platform}.sql</code>：主要放置数据操纵语言(<code>DML</code>)SQL 脚本，例如：最经常用到的 SELECT,UPDATE,INSERT,DELETE。</li>
</ul>
<blockquote>
<p>由此可见，<code>schema-${platform}.sql</code> 是先于 <code>data-${platform}.sql</code> 执行的。</p>
</blockquote>
<p>主要涉及需要修改的配置参数如下表所示：</p>
<p>| 配置                             | 说明                                                                             |<br>
|</p>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/install/config-create.png" type="image/png"/>
    </item>
    <item>
      <title>常见使用问题</title>
      <link>https://www.herodotus.cn/get-started/install/faq.html</link>
      <guid>https://www.herodotus.cn/get-started/install/faq.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">常见使用问题</source>
      <description>[1] Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 该问题通常在使用 JPA 或 Hibernate 的 saveOrUpdate 或类似的方法时出现。 简单解释这个问题的原因就是：saveOrUpdate 或类...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[1] Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1</h2>
<p>该问题通常在使用 JPA 或 Hibernate 的 <code>saveOrUpdate</code> 或类似的方法时出现。</p>
<p>简单解释这个问题的原因就是：<code>saveOrUpdate</code> 或类似的方法要求实体的 ID 为 null 时才执行 <code>SAVE</code> 操作 (对应数据库的 Insert)，如果 ID 不为 null，就默认数据库中已经存在该调数据，在这种情况下就会执行 <code>UPDATE</code> 操作 (对应数据库的 Update)。正常通过 <code>saveOrUpdate</code> 保存实体数据的时候是新增，但如果此时实体的 ID 不为 null，<code>saveOrUpdate</code> 保存实体数据时就会改为 <code>UPDATE</code> 操作，但是数据表中并不存在以 ID 值作为主键的数据，那么更新操作失败，所以出现异常。</p>
<p>Dante Cloud 出现此问题，原理与上面所述的&quot;问题原理&quot;基本类似。</p>
<p>主要原因是服务在启动时，会扫描接口数据作为权限，汇总保存至 sys_authority 数据表中，由于增加的缓存的支持，所以在保存同时会向缓存中存储缓存数据。很多情况出现该问题，都是因为第一次部署不熟悉，要么发现操作步骤出错，要么发现配置错误等，在第一次表建好之后，清理了已有的表重新启动服务打算重新建。因为在第一次启动服务时，就会进行一次权限数据的汇总，缓存中就已经存在了数据。将数据表清理之后，再次启动服务，服务会首先读取缓存，发现缓存中有数据就认为是 <code>UPDATE</code> 操作，而此时数据库中并没有对应数据，所以导致跑错。</p>
<p>发现服务运行出现 <code>Batch update returned unexpected ...</code> 错误，首先将所有服务停止，清理 Redis 中已经存储的缓存数据，再次启动服务即可。</p>
<h2>[2] Failed to bind properties under 'spring.datasource.password' to java.lang.String</h2>
<p>所有基础设施搭建完成、代码编译完成之后，再运行服务时，有时会遇到服务运行抛出<code>Failed to bind properties under 'spring.datasource.password' to java.lang.String</code>错误，导致无法运行的情况。出现这种问题的原因有很多，例举几种常见情况：</p>
<p>因为服务相关的所有配置，绝大部分都是通过读取 Nacos 获得。如果 Nacos 无法正常访问，当然就会导致服务无法运行。因为很多 Spring Boot 或 Spring Cloud 组件的运行依赖于正确的配置信息。</p>
<p>Nacos 无法正常访问的情况就会有很多种，比如：</p>
<ol>
<li>Nacos 运行异常：连 Nacos 后台管理页面也无法打开，这种情况就需要检查 Nacos 的安装是否正确</li>
<li>网络不通：很多情况下会选择服务与 Nacos 不再同一台机器中，网络不通就会导致无法访问。</li>
<li>配置错误：工程根目录下的 pom.xml 中，需要对 Nacos 的访问地址进行配置，配置错误当然也无法访问。（需要注意的是 pom 文件中 Nacos 配置完或者修改后，要重新编译代码才能正常访问，具体原因参见<a href="/develop-guide/design/profile.html" target="_blank">多环境配置章节</a>）</li>
<li>Nacos Namespace 使用不正确：工程根目录下的 pom.xml 中，除了需要配置 Nacos 地址外，还有一项配置<code>&lt;config.namespace&gt;</code>。如果在 Nacos 中，将本系统的配置导入到了某个命名空间中，那么就要在此处配置所使用命名空间的 ID；如果没有使用任何命名空间，那么该配置就留空。</li>
</ol>
<h2>[3]Redis 密码包含特殊字符无法连接</h2>
<p>既然称之为特殊字符，大多都是特殊用途的符号。比如说 “@”，在很多连接协议中都是作为分隔符存在。在很多中间件或数据库之类的软件中，对于密码中包含的特殊字符也会有不同的要求。</p>
<p>Redis 连接密码除了极特别特殊字符外(具体是什么还是自己尝试吧)，也是允许包含一些特殊字符的。</p>
<p>Dante Cloud 用户也曾遇到 Redis 密码中包含特殊字符，导致服务无法正常连接 Redis 而导致服务启动出错的问题。一下就是出错密码的示例：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">mg3y9^3Wkp*g</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x26;</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ZBc.qM2</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>正常情况下，在 Spring Boot 环境的配置文件中，指定 Redis 的密码配置，Spring Data Redis 也会进行一定的转义，保证密码可用。本系统无法使用该密码，并非是 Spring Boot 的转义没有生效，而是因为系统中还集成了 JetCache。JetCache 在使用 Lettuce 进行 Redis 连接时，采用的是 <code>url</code> 协议进行 Redis 连接。</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">url</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">redis://password@ip:port</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>这就导致包含特殊字符的 Redis 密码无法正常连接 Redis。</p>
<p>而 JetCache 作者也表示，不打算支持特殊字符的转义。</p>
<figure><img src="/assets/image/install/redis-password.png" alt="Redis password" tabindex="0" loading="lazy"><figcaption>Redis password</figcaption></figure>
<p>也尝试过对密码进行转义，但目前没有成功。如果你有什么好的解决办法，欢迎提 ISSUE 或 PR。</p>
<h2>[4]能不能不用 Kafka(或消息队列)</h2>
<h3>为什么要用消息队列</h3>
<p>消息队列是多系统集成的多种方式中，比较好的一种方式。既规避了 ETL 集成方式时效性差的问题，又规避了纯页面或者纯接口集成方式出发机制不理想的问题，而且将多系统集成的耦合性降到了最低。</p>
<p>微服务平台看似是一个完整的系统，本质还是多个系统（服务）间的整合及配合。因此，消息队列是必不可少的一个组成部分。</p>
<p>目前，就作者已知的或已了解过的开源微服务框架，还没有发现那个系统不用消息队列的。（不一定都是用 Kafka，但都是用了消息队列）</p>
<h3>确实不想用怎么办</h3>
<p>因为本系统没有使用最基础的 Kafka，而是集成了 <code>spring-cloud-bus</code>。而 <code>spring-cloud-bus</code> 又依赖于 <code>spring-cloud-stream</code>、<code>spring-integration</code>和 <code>spring-kafka</code> 等多种组件。这就导致无法简单的通过修改配置文件来彻底关闭 <code>Kafka</code>。</p>
<div class="hint-container info">
<p class="hint-container-title">友情提示</p>
<p><code>spring-integration</code> 是个神器的存在，只要你代码的上下文中包含 <code>spring-cloud-stream</code> 或 <code>spring-kafka</code> 等依赖，连 <code>Swagger</code> 都会主动去连接消息队列。</p>
</div>
<p>所以想要彻底不使用 Kafka (消息队列)，只能通过去除依赖包的方法。</p>
<p>具体操作的步骤是：</p>
<ol>
<li>在 <code>herodotus-cloud-message</code> 包中，删除 <code>spring-cloud-starter-bus-kafka</code> 依赖。</li>
<li>把 <code>herodotus-cloud-message</code> 包中涉及到 Kafka 相关引用的代码删除。</li>
<li>将 <code>herodotus-cloud-bpmn-ability</code> 包中，<code>@KafkaListener</code> 注解以及代码引用删除</li>
<li>重新编译代码运行。</li>
</ol>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>消息队列是本系统的核心组件，因目前关联的内容还不算特别多。非要剔除消息队列，可能导致部分功能的无法使用或者出现后续版本不兼容的情况。</p>
<p>所以请三思而后行！切记！</p>
</div>
<h2>[5]为什么没有看到 <code>Seata</code></h2>
<p>文档中，有提到本系统集成了 <code>Seata</code>，问什么在代码中，连 <code>Seata</code> 的安装信息都没有看到。</p>
<p>这个问题的主要原因是：</p>
<ol>
<li>当前开源版本基础的服务本身比较少，没有什么具体的、合适的场景必须用到 <code>Seata</code>。即使加上就是多装一个东西，最多些点 Demo 代码。</li>
<li>在这种情况下，强制加上 <code>Seata</code> 只会增加本系统搭建资源成本、时间成本以及复杂程度。特别是学习使用本系统的朋友，各种能力程度和技能水平的都有，有些朋友搭建基本内容都有困难，多一项内容就更多了一份难度。</li>
<li>微服务框架本身涉及的内容就非常多，个人观点非必要、非必需的内容没有必要增加太多。确实需要时，集成一个 <code>Seata</code> 也非常简单，真正能用起来的东西才是有价值的。就像做单体项目时，又有多少人真真正正的使用过“事务”？</li>
</ol>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>这里引用一篇个人认为比较好的介绍分布式事务文章的结论供参考：</p>
<blockquote>
<p>上边简单介绍了 2PC、3PC、TCC、MQ、Seata 这五种分布式事务解决方案，还详细的实践了 Seata 中间件。但不管我们选哪一种方案，在项目中应用都要谨慎再谨慎，除特定的数据强一致性场景外，能不用尽量就不要用，因为无论它们性能如何优越，一旦项目套上分布式事务，整体效率会几倍的下降，在高并发情况下弊端尤为明显。</p>
</blockquote>
<p><a href="https://blog.csdn.net/qq_35067322/article/details/110914143" target="_blank" rel="noopener noreferrer">原文地址</a></p>
</div>
<h2>[6]登录系统提示“您没有权限拒绝访问”</h2>
<p>前端工程正常启动后，弹出错误提示“您没有权限，拒绝访问”，如下图所示：</p>
<figure><img src="/assets/image/install/access-denied.png" alt="您没有权限拒绝访问" tabindex="0" loading="lazy"><figcaption>您没有权限拒绝访问</figcaption></figure>
<p>出现该问题，请首先检查是否严格按照文档描述的内容和步骤进行的后端部署，特别是有没有执行 <a href="/get-started/install/backend.html#%E4%BA%94%E9%87%8D%E7%BD%AE%E7%BC%93%E5%AD%98" target="_blank">【重置缓存】</a> 这一步。</p>
<p>具体问题的原理，和<code>重置缓存</code>章节描述的基础原理一致。初次搭建环境，需要初始化数据库，这里就包括用户、角色、权限以及相关的关联关系。不管是以手动方式还是自动方式进行的数据库初始化，本质都是直接执行 sql 脚本。而使用 <code>Spring Authorization Server</code> 和 <code>Spring Security</code> 做微服务，标准合理的方式是在各个服务中进行接口的鉴权。各服务的鉴权数据是以缓存的形式存储在各个服务中。因为使用了多级缓存，在数据库中执行 sql 脚本，是无法触发缓存数据生成的，这就导致即前后端成功运行之后，登录前端会出现没有权限的提示，因为缓存中没有权限数据。</p>
<blockquote>
<p>另外，目前单体版和微服务版采用的是同一套前端，如果 <code>VITE_PROJECT</code> 配置错误，也会导致出现权限问题。注意使用单体版时，前端 <code>VITE_PROJECT</code> 参数要改成 非 <code>dante</code> 和 <code>herodotus</code> 以外的名称</p>
</blockquote>
<ol>
<li>停止所有服务</li>
<li>清空 Redis 缓存数据</li>
<li>再次运行所有服务</li>
</ol>
<blockquote>
<p>单体版也是如此</p>
</blockquote>
<h2>[7]客户端身份验证失败</h2>
<p>前端工程正常启动后，弹出错误提示“客户端身份验证失败”，如下图所示：</p>
<figure><img src="/assets/image/install/login-error.jpg" alt="客户端身份验证失败" tabindex="0" loading="lazy"><figcaption>客户端身份验证失败</figcaption></figure>
<p>具体问题的原理，与下面问题的原理一致。想要了解原理，可以看下面问题中的&quot;系统设计逻辑&quot;，不关心可以跳过。</p>
<div class="hint-container caution">
<p class="hint-container-title">问题原因</p>
<p>这个问题原因就是：没有<code>初始化数据库</code>。</p>
<blockquote>
<p>第一次搭建本系统，还是建议耐心、细致的看文档，相关的内容都有说。</p>
</blockquote>
</div>
<h2>[8]登录框无法输入用户名和密码</h2>
<p>前端工程正常启动后，出现登录框无法输入用户名和密码的情况。通过浏览器控制台查看，控制台中会出现 <code>412</code> 错误，如下所示：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">POST</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> http://localhost:8847/herodotus-cloud-uaa/open/identity/session</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 412</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (Precondition </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Failed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h3>系统设计逻辑</h3>
<ol>
<li>为了提高系统的安全性，本系统混合 SM2(非对称) 和 SM4(对称加密) 算法，基于自定义注解和数字信封技术，设计接口数据前后端加密传输逻辑。实现 Sm4 KEY 动态生成、前后端数据加密传输、一人一钥的安全机制。</li>
</ol>
<blockquote>
<p>2.7.X 版本之前，默认使用的是 RSA + AES 算法实现，2.7.X 版本之后改为使用 SM2 + SM4，仍旧支持 RSA + AES 可以通过修改配置进行变更</p>
</blockquote>
<ol>
<li>由于 Vue 的 Session 存在变化的问题，导致使用 Vue Session 无法确认唯一性，因此系统中增加了自定义 Session 机制。本系统的安全机制，就是基于自定义 Session 机制实现。每次使用系统时，根据自定义 Session，后端动态生成秘钥实现数据的加密传输。</li>
<li>因为，自定义 Session 的创建和秘钥的传输，使用的是<code>开放</code>接口，即无须认证既可以使用的接口。所以为了进一步提升安全性，<code>open/identity/session</code>接口，增加了对客户端的认证逻辑，如果是非法客户端或者客户端未验证通过，<code>open/identity/session</code>的接口是无法返回正确信息，输入用户名和密码也无法正确解密，所以输入框被禁用。</li>
</ol>
<h3>出现问题的原因</h3>
<p><code>open/identity/session</code> 接口要验证客户端的合法性，使用的是 <code>clientId</code> 和 <code>clientSecret</code> 信息，在系统后端进行验证。客户端信息验证失败，通常是以下几方面原因：</p>
<ul>
<li>后端 IP 地址错误，前端无法正常连接。</li>
<li>数据库中有数据，<code>clientId</code> 和 <code>clientSecret</code> 信息不匹配。</li>
<li>数据库未正确初始化数据，<code>oauth2_registered_client</code> 表中没有数据，所以 <code>clientId</code> 和 <code>clientSecret</code> 信息始终是不匹配的。</li>
</ul>
<h3>如何解决</h3>
<ol>
<li>检查前端工程中，<code>.env.development</code> 或 <code>.env.production</code> 文件中配置的后端 IP 地址是否正确。</li>
<li>初次搭建本项目的过程中，在工程尚未正确运行时，除了在前端修改后端访问 IP 外，不要擅自修改其它已经配置好的参数。</li>
<li>检查数据库是否正确进行了数据初始化操作（检查数据表里是否有数据，特别是 <code>oauth_client_details</code>）。</li>
</ol>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>大多数情况，要么是没有耐心和认真的看文档，不按照文档操作；要么是在系统还没有运行成功，搭建的过程中，就按照自己的想法和理解进行修改和参数调整，导致运行失败。</p>
</div>
<h3>为什么控制台中看到返回的是<code>412</code>错误</h3>
<p><code>412</code> 错误是源于本项目中的自定义错误体系。为了更快捷更准确的定位错误，本系统自定义错误体系与 Http 协议中的状态进行了有机结合，并尽可能借鉴 Http 状态的含义。</p>
<p>本系统自定义错误体系借鉴了 Http <code>412</code> (Precondition Failed) 状态的含义： <strong>未满足前提条件</strong>。看到这一类错误，通常代表是因为前序的条件或者参数为准备充足，导致无法正确运行。</p>
<h2>[9]初次 install 或者 package 工程很慢</h2>
<h3>Maven 使用的基本逻辑</h3>
<p>在 Maven 的术语中，仓库是一个位置（place）。</p>
<p>Maven 仓库是项目中依赖的第三方包库，这个库所在的位置叫做仓库。</p>
<p>Maven 仓库有三种类型：</p>
<ul>
<li>本地（local）</li>
<li>中央（central）</li>
<li>远程（remote）</li>
</ul>
<p>Maven 的本地仓库，在安装 Maven 后并不会创建，它是在第一次执行 maven 命令的时候才被创建。</p>
<p>运行 Maven 的时候，Maven 所需要的任何构件都是直接从本地仓库获取的。如果本地仓库没有，它会首先尝试从远程仓库下载构件至本地仓库，然后再使用本地仓库的构件。</p>
<h3>为什么初次 Install 会很慢？</h3>
<ol>
<li>可能本工程中所使用的包并不是你常用的包，本工程的包尽可能用最新版本的代码，所以第一次使用会下载很多的包。根据自身网络的不同，依赖包的下载速度也会不同，编译所需要的时间也会不同。</li>
<li>可能目前你所使用的，还是默认的 Maven 源，默认的 Maven 源是国外源，通常访问都会很慢，甚至有访问响应超时的情况。根据自身网络的不同，依赖包的下载速度也会不同，编译所需要的时间也会不同。</li>
<li>为了方便调试单体版以及支持 <code>Spring Boot Admin</code> 查看 git 信息和 Docker 打包，在编译过程中工程默认是开启了 <code>源代码打包</code>、<code>Git信息打包</code>以及<code>资源包拷贝</code>功能的，因为有更多的操作，势必会增加编译打包的时间。</li>
</ol>
<h3>提升编译速度的办法</h3>
<ol>
<li><strong>提现下载好依赖包</strong></li>
</ol>
<p>初次使用本项目，在执行 <code>Install</code> 或 <code>Package</code> 命令之前，打开 Idea Maven 面板，点击工具栏的刷新按钮，手动触发依赖包的下载，等所有包下载完成之后再在执行 <code>Install</code> 或 <code>Package</code> 命令进行编译。刷新方式如下图所示：</p>
<figure><img src="/assets/image/install/refresh-maven.png" alt="刷新Maven" tabindex="0" loading="lazy"><figcaption>刷新Maven</figcaption></figure>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>可能会出现，虽然依赖包都下载完成了，但是 Idea Maven 面板中，仍旧存在一些依赖包“飘红”的现象。这是 Idea 没有实时读取完整依赖包的问题，只要能正常编译通过，就不会有任何问题。下次再打开 Idea 通常就不会有标红的内容。</p>
</div>
<ol start="2">
<li><strong>更改为使用腾讯 Maven 源。</strong></li>
</ol>
<p>如果你熟悉怎么改请略过，如果不熟悉，在线文档中：【环境准备】-&gt; <a href="/get-started/prepare/backend.html#_6%E4%BF%AE%E6%94%B9-maven-%E9%85%8D%E7%BD%AE" target="_blank">后端环境搭建配置</a></p>
<ol start="3">
<li><strong>关闭编译辅助工具</strong></li>
</ol>
<p>可以在工程根目录中的 pom.xml 中，将对应 profiles 里面的 &lt;skip.build.source.package&gt;、&lt;skip.copy.docker.resource&gt;、&lt;<a href="http://skip.build.git.commit.info" target="_blank" rel="noopener noreferrer">skip.build.git.commit.info</a>&gt; 三个属性值改为 true。这样可以减少打包内容，提升打包速度。</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    ······</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        ······</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--跳过构建源代码包--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.build.source.package</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>false&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.build.source.package</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--不copy代码包到docker构建目录--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.copy.docker.resource</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>false&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.copy.docker.resource</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        &#x3C;!--不执行git commit 构建--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.build.git.commit.info</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>false&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">skip.build.git.commit.info</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        ······</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">properties</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="4">
<li><strong>修改为多线程编译</strong></li>
</ol>
<p>在 IDEA 中，默认是使用单线程进行 Maven 编译的。可以将其修改为多线程编译方式，可以提高一定的编译速度。</p>
<p>在 Idea 中，找点击 <code>Settings... -&gt; Build,Execution,Deployment -&gt; Build Tools -&gt; Maven</code>。在 Maven 配置面板中。将 Thread Count 的值设置为 1C。这样可以进行多线程打包，提升打包速度，如下图所示：</p>
<figure><img src="/assets/image/install/maven-thread-count.png" alt="架构图" tabindex="0" loading="lazy"><figcaption>架构图</figcaption></figure>
<h2>[10]found character '@' that cannot start any token. (Do not use @ for indentation)</h2>
<p>启动服务时，出现以下错误</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">16:01:05.726</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [main] ERROR org.springframework.boot.SpringApplication - Application run failed</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">org.yaml.snakeyaml.scanner.ScannerException:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> while</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> scanning</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> for</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> the</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> next</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> token</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">found</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> character</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> '@'</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> that</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> cannot</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> start</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> any</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> token.</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (Do </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">not</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> use</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> @</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> for</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> indentation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> in </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'reader'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, line 7, column 13:</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        active:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> @profile.name@</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">                ^</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.scanner.ScannerImpl.fetchMoreTokens</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ScannerImpl.java:439</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.scanner.ScannerImpl.checkToken</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ScannerImpl.java:248</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.parser.ParserImpl</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$ParseBlockMappingValue</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.produce</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ParserImpl.java:633</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.parser.ParserImpl.peekEvent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ParserImpl.java:165</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.comments.CommentEventsCollector</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">$1</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.peek</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">CommentEventsCollector.java:59</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.comments.CommentEventsCollector</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">$1</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.peek</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">CommentEventsCollector.java:45</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.comments.CommentEventsCollector.collectEvents</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">CommentEventsCollector.java:140</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.comments.CommentEventsCollector.collectEvents</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">CommentEventsCollector.java:119</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeScalarNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:221</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:191</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeKeyNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:309</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeMappingChildren</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:300</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeMappingNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:288</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:195</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeValueNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:313</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeMappingChildren</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:304</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeMappingNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:288</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:195</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeValueNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:313</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeMappingChildren</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:304</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeMappingNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:288</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.composeNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:195</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.composer.Composer.getNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Composer.java:115</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.constructor.BaseConstructor.getData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">BaseConstructor.java:135</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.springframework.boot.env.OriginTrackedYamlLoader</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$OriginTrackingConstructor</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.getData</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">OriginTrackedYamlLoader.java:99</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    at</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> org.yaml.snakeyaml.Yaml</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">$1</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.next</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">Yaml.java:512</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>该问题主要是由于 <code>bootstrap.yml</code> 中使用了 <code>@ @</code> 变量，而在 <code>target</code> 目录中生成的 <code>bootstrap.yml</code>中的 <code>@ @</code> 变量没有被正确替换。</p>
<h3>解决办法</h3>
<h4>办法一</h4>
<p>使用 <code>mvn install</code> 或 <code>mvn package</code> 命令重新编译工程。</p>
<h4>办法二</h4>
<p>在 IDEA 中，打开<code>Maven</code>面板，点击刷新按钮。如下图所示：</p>
<figure><img src="/assets/image/install/refresh-maven.png" alt="刷新Maven" tabindex="0" loading="lazy"><figcaption>刷新Maven</figcaption></figure>
<h2>[11]服务启动后，无法连接 Redis 抛错</h2>
<h3>背景</h3>
<p>Dante Cloud 基础服务在启动时必须要连接 Redis，连接成功后才能正常运行。</p>
<p>目前涉及到 Redis 的内容，主要包括两部分：</p>
<ol>
<li>常规的数据缓存特别像 Session、验证码一类的临时缓存，这一类缓存根据场景以及实现要求的不同，有些会直接使用 Redis；</li>
<li>数据共享缓存，像权限数据、CRUD 数据等缓存，这一类缓存除了要实现数据缓存的目的之外，还要解决缓存数据在多服务、多实例环境下数据共享同步等问题。因此采用的是 JetCache 多级缓存组件。</li>
</ol>
<p>因本项目更倾向于贴近 Spring 生态，尽量使用 Spring 生态主打或推荐的相关组件。所以不管是直接访问 Redis 还是集成 JetCache，底层数据访问客户端组件均使用的是 lettuce。也正因为如此，Spring 生态相关组件自身的不足也会体现在 Dante Cloud 中。</p>
<h3>问题原因</h3>
<p>服务启动后无法连接 Redis，除了网络问题导致的无法连接以外，最主要的诱因就是：<strong>Redis 密码中包含特殊字符</strong>。</p>
<p>包含特殊字符的密码导致 Redis 无法连接，原因大概出自两方面:</p>
<ul>
<li>一方面是 Spring Boot 基本规范导致。Spring Boot Yaml 对特殊字符是有处理要求的，Yaml 配置信息中，如果包含以下特殊字符必须要进行转义</li>
</ul>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">:</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">,</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {,</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> },</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">],</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ,,</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> &#x26;</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">,</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> *</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">,</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> #, ?, |, -, &#x3C;, >, =, !, %, @, `</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ul>
<li>另一方面是基础组件自身处理机制导致。有些组件是采用 uri 的方式进行 Redis 连接配置，而有些组件使用 ip + 端口等方式处理。所以就可能存在，同样的密码，使用 Jedis、Redisson 等组件是正常的，换到 Dante Cloud 所使用的 lettuce 就不正常；甚至可能使用 lettuce 是正常的，换到 JetCache 又会有问题。</li>
</ul>
<h3>解决方法</h3>
<h4>方法一</h4>
<p>这个方法最直接最简单，就是修改 Redis 密码，去掉密码中的某个或者全部特殊字符。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>很多人都期望通过增加 Redis 密码复杂度来提升 Redis 的安全性。不可否认这确实能提升一定的安全性，但是毕竟 Redis 的密码安全机制太过简单，所以个人建议不要太过于依赖于此，网络层面或者物理层面保证 Redis 的相对隔离才能靠谱。(换句话说 Redis 密码复杂度更像是心理安慰，不能从本质上提升多大的安全性，所以就不要太纠结密码中要不要包含特殊字符的问题)</p>
</div>
<h4>方法二</h4>
<p>对密码中的特殊字符进行转义，以保证可以正确被读取和使用。</p>
<ul>
<li>在 Spring Boot Yaml 方面，如果存在特殊字符就需要对密码字符串进行转义，例如下例中密码包含了特殊字符 “#”，这种写法是会出现运行错误问题的</li>
</ul>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  #data source connection</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  datasource</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    url</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">jdbc:mysql://localhost:3306/vaquarkhan</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    username</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">rootadmin</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    password</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">root#</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>需要对特殊字符进行转义：</p>
<p>可以用引号包裹字符串进行转义</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"root#"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>或者用反斜杠</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">root\#</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>完整的例子</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  #data source connection</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  datasource</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    url</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">jdbc:mysql://localhost:3306/vaquarkhan</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    username</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">rootadmin</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    password</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"root#"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>对于像 JetCache 一样使用 uri 进行 Redis 连接配置的，那么特殊字符的转义，就要参考 url.encode 的方式，例如：</li>
</ul>
<p>| 符号 |  -  | 转义结果 | 符号 |  -  | 转义结果 |<br>
| :--: | :-: | :</p>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/install/redis-password.png" type="image/png"/>
    </item>
    <item>
      <title>前端系统运行</title>
      <link>https://www.herodotus.cn/get-started/install/frontend.html</link>
      <guid>https://www.herodotus.cn/get-started/install/frontend.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">前端系统运行</source>
      <description>重要 自 Dante Cloud 4.0.X 版本起，使用 Vuetify 组件及其 Material Design 3 蓝图风格和 Vue 3 组合式 API，全新构建的新版本的前端工程。 之所以使用 Vuetify 全新构建前端工程的主要原因： 一是因为，原版工程使用 Quasar 组件库，目前 Quasar 更新频率变低，以防出现停止维护的情况做...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>自 Dante Cloud 4.0.X 版本起，使用 Vuetify 组件及其 Material Design 3 蓝图风格和 Vue 3 组合式 API，全新构建的新版本的前端工程。</p>
<p>之所以使用 Vuetify 全新构建前端工程的主要原因：</p>
<ul>
<li>一是因为，原版工程使用 Quasar 组件库，目前 Quasar 更新频率变低，以防出现停止维护的情况做的防御措施。</li>
<li>二是因为，Vuetify 的 Material Design 3 蓝图风格，与目前流行的前端界面风格相契合，可以更容易的做出漂亮的界面。同时，Vuetify 社区活跃，目前看短期内不会出现停止维护的情况。</li>
</ul>
<p>自 Dante Cloud v4.0.5.1 版本起，为了提升维护的效率，将企业版前端与开源版前端合并，新版本前端（Vuetify 版）也一并开源。</p>
</div>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>需要注意的是，当前版前端界面为企业版和开源版共享，由于开源版后端部分功能不包含（例如：Passkey），那么前端可能会出现操作异常问题。</p>
</div>
<h2>[一]开发模式</h2>
<h3>[1]检出代码</h3>
<p>在合适位置上，使用 Git 检出前端工程代码。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>推荐使用新版：<code>herodotus-cloud-ui-vuetify</code>。当然，也可以选择原版 <code>dante-cloud-ui</code>。</p>
<p>需要注意的是，Dante Cloud 4.0.X 的原版前端与新版前端采用共享模块方式，使用原版需要自己配置私库，参见：<a href="#_5-%E5%85%B1%E4%BA%AB%E6%A8%A1%E5%9D%97">共享模块</a> 。如果只是使用新版前端则不需要配置私库</p>
</div>
<h3>[2]修改配置</h3>
<p>在前端工程的 packages/ui 目录下，找到 <code>.env.development</code> 文件。修改其中的参数</p>
<p>参数说明：</p>
<ul>
<li><strong>VITE_BASE_PATH</strong>：基础路径，默认为根目录 <code>/</code> 可以根据你的部署需求修改。主要用于有路径映射需求的场景。</li>
<li><strong>VITE_PROJECT</strong>：工程名称。通过该参数以实现单体版和微服务版使用同一套操作界面。<code>dante</code>指定是微服务版，其它任意字符串或者空值指定是单体版</li>
<li><strong>VITE_APPLICATION_NAME</strong>：界面显示的系统名称</li>
<li><strong>VITE_API_URL</strong>：后端服务网关访问地址</li>
<li><strong>VITE_WS_URL</strong>：后端服务 WebSocket 访问地址</li>
<li><strong>VITE_OAUTH_CLIENT_ID</strong>：OAuth2 密码模式 Client Id。</li>
<li><strong>VITE_OAUTH_CLIENT_SECRET</strong>：OAuth2 密码模式 Client 密码</li>
<li><strong>VITE_OAUTH2_REDIRECT_URI</strong>：授权码模式登录前端回调地址</li>
<li><strong>VITE_AUTO_REFRESH_TOKEN</strong>：开启自动刷新 Token 机制</li>
<li><strong>VITE_USE_CRYPTO</strong>：是否开启前后端数据加密</li>
<li><strong>VITE_USE_WEBSOCKET</strong>：是否开启 WebSocket</li>
<li><strong>VITE_USE_OIDC</strong>：是否使用 OpenID Connect（OIDC） 协议</li>
<li><strong>VITE_SECRET_KEY</strong>：前端关键信息 SM4 加密秘钥</li>
<li><strong>VITE_MULTI_TENANCY_ID</strong>：当前应用租户 ID，需要后台进行租户配置，空值为不启用租户模式。</li>
<li><strong>VITE_USE_DISABLE_DEVTOOL</strong>：是否开启调试防护工具，开启后浏览器 F12、邮件等操作将被屏蔽。开发环境可以关闭，生产环境建议开启</li>
</ul>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p><code>VITE_SECRET_KEY</code> 秘钥，并不是本系统前后端数据加密传输使用的秘钥，只是对前端临时存储的关键信息加密的秘钥。</p>
<p>本系统前后端数据加密传输，是基于自主设计的自定义数据传输 Session，实现国密 SM4 加密秘钥的动态生成，加密传输，一人一钥机制。每次使用系统都会动态重新生成，采用 SM2 前后端加密传输，临时存储至前端。缓存时间与 <code>OAuth 2 Token</code> 设置时间相同，正常退出系统后，会清除前端临时存储数据。详情参见：<a href="/user-guide/security/security-extend.html#%E4%BA%8C-%E6%95%B0%E5%AD%97%E4%BF%A1%E5%B0%81" target="_blank">【数字信封】</a></p>
<blockquote>
<p>并没有绝对安全的加密措施，前后端数据加密传输也仅仅是相对提升安全性。毕竟不管如何处理，前端都要存储一定的关键信息，而且前端是最容易破解的。正式环境使用还是要配合 <code>Https</code> 以及其它安全防护措施。</p>
</blockquote>
</div>
<h3>[3]编译运行</h3>
<p>前面配置配置完成之后，在运行之前，需要进行依赖下载，以及模块编译等操作。</p>
<h4>(1)首次检出</h4>
<p>如果你是首次检出前端工程，使用以下命令下载依赖，并运行工程</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 进入目录</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">cd</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> herodotus-cloud-ui-vuetify</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 安装依赖</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 使用</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> `</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">.env.development</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> 中的配置值，使用下面命令</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dev</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h4>(2)非首次检出（使用 Git 更新代码方式）</h4>
<p>当你采用更新代码的方式，那么更新下来的可能不仅仅是新的代码，还包含依赖的模块的更新。所以需要采用以下命令：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 更新代码（如果已经采用其它方式更新了代码，可以忽略该操作）</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pull</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 1.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 更新主工程依赖模块</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> update</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --latest</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 2.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 更新</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> monorepo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 模块依赖模块</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -r</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> update</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --latest</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 3.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 重新编译模块</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> shared:build</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 4.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 再次运行。使用</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> `</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">.env.development</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">`</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> 中的配置值，使用下面命令</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> dev</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[4]运行验证</h3>
<blockquote>
<p>通过前端界面进行操作，是对的整个系统最好的验证方式之一。</p>
</blockquote>
<p>打开浏览器，输入：<a href="http://localhost:3000" target="_blank" rel="noopener noreferrer">http://localhost:3000</a>。若能正确展示登录页面，并能成功登录，菜单及页面展示正常，则表明环境搭建成功</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>系统登录默认的账户和密码是：<strong>system/123456</strong>。测试账号的账户和密码是：<strong>test/123456</strong></p>
</div>
<h3>[5]共享模块</h3>
<h4>(1)共享逻辑</h4>
<p>基于 Vuetify 的新版除了使用的组件库以及界面风格不同外，很多代码都与原有基于 Quasar 的旧版前端工程一致。重复的维护相同的代码，这与 Dante Cloud 的编程哲学不符。因此，结合 <code>monorepo</code> 的模块化特点，提取共性代码为模块，将模块代码编译后其私库实现共享。</p>
<p>Dante Cloud 前端采用 <code>monorepo</code> 结构，除了主工程（前端工程 <code>packages</code> 目录下的代码）以外，很多核心内容都被抽取为 <code>模块</code> 放置在 <code>shared</code> 目录下。这些 <code>模块</code> 可以编译后发布至 npm 或者私库中方便共享（这和 Java Maven 多模块工程，以及将模块发布至 Maven 仓库是一个道理）。</p>
<p>当前，Dante Cloud 前端以 <code>herodotus-cloud-ui-vuetify</code> 作为 <code>主</code> 工程，共享的三个模块 <code>@herodotus/core</code> <code>@herodotus/api</code> 和 <code>@herodotus/framework</code> 代码均在该工程中。需要首先在该工程中编译共享模块，然后将其发布至私库之后，旧版前端工程才能使用。<code>dante-cloud-ui</code> 作为 <code>辅</code> 工程，在使用时，需要先从私库中下载，之后才能编译其它代码。</p>
<h4>(2)搭建私库</h4>
<p>搭建 npm 私库的方式有很多中，你可以按照自己的方式搭建。作者这里推荐使用阿里云云效（Codeup）搭建，免费方便。</p>
<div class="hint-container info">
<p class="hint-container-title">说明</p>
<p>如果你不想搭建私库，可以考虑直接将相关模块发布至 npm 仓库中。</p>
</div>
<p>注册完阿里云云效（Codeup）后，已经默认帮你开通的 npm 仓库。找到【制品仓库】功能，找到 <code>npm-registry</code> 就是默认的 npm 仓库。</p>
<p>在 <code>npm-registry</code> 页面中，点击【仓库指南】，其中提供了明确的操作方法，如下所示</p>
<figure><img src="/assets/image/install/npm-registry.png" alt="npm-registry" tabindex="0" loading="lazy"><figcaption>npm-registry</figcaption></figure>
<p>私库搭建完成时，可以直接将本地的 npm 镜像替换为私库的镜像。该镜像中除了包含你自己上传的模块外，还包含 npm 中的所有内容，和正常使用 npm 仓库没有任何区别。</p>
<p>使用下面命令，直接替换默认的 <code>registry</code></p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> registry</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "https://packages.aliyun.com/XXX/npm/npm-registry/"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>由于作者本人本地也使用自己的私库开发，所以旧版前端和新版前端，<code>shared</code> 目录下所有模块的 <code>package.json</code> 文件中都会自动添加 <code>publishConfig</code> 配置。在开始后面操作之前，需要将 <code>publishConfig</code> 全部删除或者替换为你自己的私库才行。</p>
</div>
<h4>(3)操作方法</h4>
<p>先编译 <code>herodotus-cloud-ui-vuetify</code> 工程模块代码，将其发布至私库。按照以下命令操作即可：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 进入目录</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">cd</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> herodotus-cloud-ui-vuetify</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 安装依赖</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">如果已经安装了可以跳过</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 1.更新主工程依赖模块</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> update</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --latest</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 2.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 更新</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> monorepo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 模块依赖模块</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -r</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> update</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --latest</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 3.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 重新编译模块</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> shared:build</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 4.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 标记发布信息。使用下面命令会要求选择模块，然后设定具体的版本号。根据界面提示选定即可</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> changeset</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 5.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 让版本号信息生效</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> changeset</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> version</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 6.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 发布模块</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> changeset</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> publish</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> //</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 该种方式需要将</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 默认的</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> registry</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 修改为你自己的私库地址</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">or</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> changeset</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> publish</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --registry=https://packages.aliyun.com/XXX/npm/npm-registry/</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> //</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 该种方式是手动指定仓库</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>使用 <code>pnpm changeset</code> 命令，就是在帮助你设置模块的版本号。当前 Dante Cloud 各个模块中已经有了之前发布的版本号，你可以继续沿用这个版本号，也可以将所有版本号统一修改为你需要的起始版本号（例如：0.0.0），然后再用 <code>pnpm changeset</code> 命令设置新的版本号</p>
<blockquote>
<p>使用 changeset 你可以完全按照自己的版本号策略操作。例如：可以让所有模块的版本号一致，也可以独立设置各个模块的版本号。</p>
</blockquote>
</div>
<p>再进入 <code>dante-cloud-ui</code> 更新共享模块，之后就可以正常使用前端。使用下面命令进行更新：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 1.更新主工程依赖模块</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> update</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --latest</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 2.</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 更新</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> monorepo</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 模块依赖模块</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -r</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> update</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --latest</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[二]生产模式(手动部署)</h2>
<h3>[1]编译代码</h3>
<ol>
<li>根据实际使用情况，修改 <code>.env.production</code> 中的配置值</li>
<li>使用下面命令，编译工程代码</li>
</ol>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 编译组件库。升级过依赖版本或者修改过模块代码，需要重新编译组件</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> shared:build</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 编译代码</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> prod</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]安装 Nginx</h3>
<p>安装 Nginx 用于部署编译后的前端代码。需要修改 Nginx 配置，参考配置如下：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">worker_processes</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">  1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">error_log</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  /var/log/nginx/error.log</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> warn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pid</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        /var/run/nginx.pid</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">events</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    worker_connections</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">  1024</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">http</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    include</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">       /etc/nginx/mime.types</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    default_type</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  application/octet-stream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    log_format</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  main</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    '$remote_addr - $remote_user [$time_local] "$request" '</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">                        '$status $body_bytes_sent "$http_referer" '</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">                        '"$http_user_agent" "$http_x_forwarded_for"'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    access_log</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  /var/log/nginx/access.log</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  main</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    sendfile</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">        on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    keepalive_timeout</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">  65</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    #gzip  on;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    map</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $http_upgrade</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $connection_upgrade</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        default</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> upgrade</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        ''</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">      close</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    upstream</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> herodotus.cn</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        server</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 192.168.101.10:8847</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">    server</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        listen</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">       8000</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        server_name</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  localhost</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        location</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            root</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">   /etc/nginx/html/ui</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            index</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  index.html</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> index.htm</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            # 解决刷新页面变成404问题的代码</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            try_files</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $uri</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $uri</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> @router</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            # 开启gzip</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            gzip</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            # 启用gzip压缩的最小文件，小于设置值的文件将不会压缩</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            gzip_min_length</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 1k</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            #gzip 压缩级别，1-9，数字越大压缩的越好，也越占用CPU时间</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            gzip_comp_level</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 6</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            # 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            gzip_types</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> text/plain</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> application/javascript</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> application/x-javascript</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> text/css</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> application/xml</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> text/javascript</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> image/jpeg</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> image/gif</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> image/png</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> image/avif</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> image/webp</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> image/apng</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> image/svg+xml</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> image/x-icon</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> application/vnd.ms-fontobject</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> font/ttf</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> font/opentype</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> font/x-woff</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> font/woff2</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> font/eot</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            # 是否在http header中添加Vary: Accept-Encoding，建议开启</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            gzip_vary</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            # 禁用IE 6 gzip</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            gzip_disable</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "MSIE [1-6]\."</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            # 设置压缩所需要的缓冲区大小</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            gzip_buffers</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 32</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 16k</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            # 设置gzip压缩针对的HTTP协议版本</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            gzip_http_version</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 1.1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            gzip_static</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            gzip_proxied</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> expired</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> no-cache</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> no-store</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> private</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> auth</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        location</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> @router</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            rewrite</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ^.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">*</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">$ </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/index.html</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> last</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        location</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /api/</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_pass</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  http://herodotus.cn/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  Host</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  X-Real-IP</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $remote_addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  X-Forwarded-For</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $proxy_add_x_forwarded_for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        location</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /socket/</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_pass</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  http://herodotus.cn/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  Host</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  X-Real-IP</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $remote_addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  X-Forwarded-For</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $proxy_add_x_forwarded_for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_http_version</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">  1.1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  Upgrade</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $http_upgrade</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  Connection</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $connection_upgrade</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        location</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /reactive/</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_pass</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  http://herodotus.cn/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  Host</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  X-Real-IP</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $remote_addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  X-Forwarded-For</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $proxy_add_x_forwarded_for</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_http_version</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">  1.1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  Upgrade</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $http_upgrade</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            proxy_set_header</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  Connection</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  $connection_upgrade</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        error_page</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">   500</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 502</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 503</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 504</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  /50x.html</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        location</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> =</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> /50x.html</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">            root</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">   /usr/share/nginx/html</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[3]拷贝代码</h3>
<p>将在第一步编译好的代码，放置在 Nginx 对应目录中。例如：上一步 Nginx 配置的目录为 <code>/etc/nginx/html/ui</code>，将所有编译好的代码放置在该目录即可。</p>
<h2>[三]生产模式(容器部署)</h2>
<p>为了方便使用和部署，Dante Cloud 对企业版前端做了专门的改造。打包完成之后，可以直接部署至 Nginx 下，并以 Docker 的方式运行。一些应用相关的必要参数，例如：应用名称等，支持以 Docker 环境变量的方式，在 Docker 外部系统通过环境变量进行修改。</p>
<h3>[1]编译代码</h3>
<p>按照以下命令，编译工程代码，并生成可部署至生产换的的代码。</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 编译组件库。</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> shared:build</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 编译代码</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> prod</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]打包Docker镜像</h3>
<p>手动部署前端代码，需要自己搭建 Nginx 以及拷贝代码，操作略显繁琐。Dante Cloud 提供了直接将前端打包为可运行的 Docker 镜像。通过修改 Docker 环境变量可以动态修改内部参数。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>这里假设，你本地就有 Docker 环境，如果你的 Docker 环境是在其它服务器中，需要拷贝 <code>build</code> 目录下的文件到服务器中，然后注意修改命令以及调整目录</p>
</div>
<h4>1. Dockerfile</h4>
<p>使用命令行工具，进入到 <code>build</code> 目录下，执行以下命令，利用 Dockerfile 进行打包。</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">docker</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> buildx</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> build</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --platform</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> linux/amd64,linux/arm64</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">  -t</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> quay.io/herodotus-cloud/herodotus-cloud-preview:latest</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> .</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<ol>
<li>注意前面命令中，结尾的 <code>.</code></li>
<li>修改 <code>-t</code> 后面的名称为你想要的名称</li>
</ol>
</div>
<h4>2. Docker Compose</h4>
<p>使用下面的 Docker Compose 脚本，可以将前面已经编译完成的生产环境代码以及 Nginx 打包为可以直接运行的 Docker 镜像。</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">services</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  ui</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    image</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus/herodotus-cloud-ui:3.3.0.0</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    container_name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-ui</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    environment</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      HERODOTUS_SERVER_PROJECT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      HERODOTUS_SERVER_ADDR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">192.168.101.10:8847</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      HERODOTUS_PROJECT_NAME</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Dante\ Cloud</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      HERODOTUS_OAUTH2_CLIENT_ID</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">14a9cf797931430896ad13a6b1855611</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      HERODOTUS_OAUTH2_CLIENT_SECRET</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">a05fe1fc50ed42a4990c6c6fc4bec398</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      HERODOTUS_OAUTH2_REDIRECT_URI</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">http://192.168.101.10:4000/authorization-code</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    ports</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"8888:8000"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>当然，也可以根据您的喜欢，直接使用 Dockerfile 进行镜像的构建。相关的内容均放置在前端工程 <code>${project_home}/build</code> 目录下</p>
<blockquote>
<p>使用 <code>pnpm prod</code> 编译生成的可部署代码，会直接生成至 <code>${project_home}/build/dist</code> 下。</p>
</blockquote>
</div>
<p>参数说明：</p>
<h4>1. HERODOTUS_SERVER_PROJECT(可选)</h4>
<p>工程类型。Dante Cloud 前端工程和后端工程使用的是同一套前端，通过修改这个参数来适配不同的后端。</p>
<ul>
<li>微服务版：将该值设置为 <code>herodotus</code> (企业版)， <code>dante</code> (开源版)</li>
<li>单体版：将该值设置为 <code>athena</code></li>
</ul>
<p>默认值为： <code>herodotus</code>。如果后端是微服务版，无需修改该参数。</p>
<h4>2. HERODOTUS_SERVER_ADDR(可选)</h4>
<p>后端服务的访问地址</p>
<ul>
<li>微服务版：即网关服务的访问地址，IP + 端口，例如：192.168.101.10:8847</li>
</ul>
<h4>3. HERODOTUS_PROJECT_NAME(可选)</h4>
<p>系统名称。可通过该参数修改系统名称，当前默认名称为 <code>Herodotus Cloud</code></p>
<div class="hint-container note">
<p class="hint-container-title">注</p>
<p>当前还不支持系统名称中包含空格。如果包含空格将不会生效，还是显示默认系统名称</p>
</div>
<h4>4. HERODOTUS_OAUTH2_CLIENT_ID(可选)</h4>
<p>前端系统的 Client Id，默认值：<code>14a9cf797931430896ad13a6b1855611</code></p>
<h4>5. HERODOTUS_OAUTH2_CLIENT_SECRET(可选)</h4>
<p>前端系统的 Client Secret，默认值：<code>a05fe1fc50ed42a4990c6c6fc4bec398</code></p>
<h4>6. HERODOTUS_OAUTH2_REDIRECT_URI(可选)</h4>
<p>前端使用 OAuth2 授权码授权模式进行登录，需要指定具体的 <code>redirect_uri</code>。当前默认值为：<code>http://192.168.101.10:3000/authorization-code</code></p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>请根据实际部署情况修改这个值。该值需要与数据表 <code>oauth2_registered_client</code> 中对应 Client 的配置对应，否则将会出现参数错误无法登录。</p>
<p>因为配置了缓存，所以不建议直接手动在数据库中修改这个值，修改了也不会马上生效。建议通过系统界面进行修改。</p>
</div>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>以上参数都是可选参数，仅在你在 Docker 已经打包完成，需要动态修改参数时使用。如果运行 Docker 时不设置这些环境变量，代码默认使用 <code>.env.production</code> 中的配置。</p>
</div>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/install/npm-registry.png" type="image/png"/>
    </item>
    <item>
      <title>单体版本运行</title>
      <link>https://www.herodotus.cn/get-started/install/monomer.html</link>
      <guid>https://www.herodotus.cn/get-started/install/monomer.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">单体版本运行</source>
      <description>[一]简介 Dante Cloud 除了提供一套完整的微服务架构以外，还支持以单体架构运行。这里的微服务架构和单体架构并不是分离的两套代码，也不是分离的两个项目。而是完全融合的一整套代码，使用时可以根据需要选择是以微服务模式或者单体模式运行。这是 Dante Cloud 微服务最大的特色之一：“一套代码、两种架构”。 重要 自 v3.3.4.2 版本起...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]简介</h2>
<p>Dante Cloud 除了提供一套完整的微服务架构以外，还支持以单体架构运行。这里的微服务架构和单体架构并不是分离的两套代码，也不是分离的两个项目。而是完全融合的一整套代码，使用时可以根据需要选择是以微服务模式或者单体模式运行。这是 Dante Cloud 微服务最大的特色之一：<strong>“一套代码、两种架构”</strong>。</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>自 v3.3.4.2 版本起，为了减少误解，单体版不再采用独立的代码仓库，相关模块和配置统一合并至微服务版本工程中。在同一工程中，就可以选择启动单体版本或者微服务版。解决原有模式下，需要单独编译微服务版，再开启另一个工程中启动单体版。提升开发和使用的便捷性。</p>
</div>
<h3>[1]背景</h3>
<p>之所以会衍生出 Dante Cloud 单体版（Athena），主要是出于以下两方面考虑：</p>
<ul>
<li>一方面，不管是单独搭建单体后台管理脚手架，还是构建基于 Spring Cloud 的分布式微服务架构，只要是前后端分离并且是基于 <code>Spring Authorization Server</code> 和 <code>Spring Security</code> 等 Spring 生态组件构建的系统，使用方式以及大部分代码都是相同的，完全可以通用，没有必要编写两套增加维护工作。</li>
<li>另一方面，基于 <code>Spring Boot</code> 和 <code>Spring Cloud</code> 的微服务架构，已经成为应用建设的主流方案。但不可否认的是，搭建一套微服务架构所需的基础设施越来越多，也越来越复杂，所需的配套资源也越来越庞大。仅仅是在开发电脑上搭建一套运行开发调试环境，其复杂度和所需的资源也不容小觑。而很多应用特别是小型应用，在早期开发中或者用户量不大的前期，很多情况下一套单体的，前后端分离的后台就足以满足。完全没有必要上一整套微服务，额外增加复杂度。</li>
</ul>
<p>Dante Cloud 单体版具体以下特点：</p>
<ol>
<li>不需要搭建 Nacos，ELK，Sentinel，Skywalking 等诸多基础设施，只要一个数据库和Redis就可以独立运行，具备微服务架构除分布式以外的所有功能。</li>
<li>代码开发调试以及系统运行速度，相较于微服务版本有几倍的提升。</li>
<li>只要自己开发的业务代码规范，子模块划分合理，可以快速无缝的从单体架构迁移到微服务架构。</li>
</ol>
<p>这些特点助于在项目早期快速建设项目、方便开发人员在本地进行开发以及新技术研究。在项目后期随着用户规模增大以及并发需求提升时，快速迁移至微服务模式。</p>
<h3>[2]结构</h3>
<p>在项目主工程中，模块 <code>herodotus-monomer-application</code> 就是 Dante Cloud 单体版应用。在 IDEA 中，运行名为 <code>AthenaApplication</code> 的 <code>服务</code> 就可以启动单体版本。</p>
<p>本质上就是搭建了一个标准的 Spring Boot 工程，依赖了 <code>authentication-spring-boot-starter</code> 模块就形成了 Dante Cloud 单体版本的主体结构和主要功能。<code>authentication-spring-boot-starter</code> 模块也是 Dante Cloud 微服务版本中，UAA 服务的核心依赖组件。</p>
<p>通过调整和增减所依赖的核心库中的组件，就可以在单体版本中增加或者删减相关的功能。例如：系统中的对象存储支持，就是通过依赖平台提供的 <code>oss-spring-boot-starter</code> 模块的实现的。</p>
<p>由此可以看出，Dante Cloud 单体版（Athena）是与 Dante Cloud 微服务系统完全融合在一起的，是通过代码的高度抽象和通用化，加上高度策略化的定制自然而然形成的。<strong>这也从另外的角度证明了 Dante Cloud 代码的质量和水平</strong></p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>Dante Cloud 单体版（Athena），仍旧是前后端分离的架构。这里为什么强调 <code>前后端分离</code>？因为，前后端不分离的架构（也就是我们常说的前端页面和后端代码混合在一起的传统单体架构），核心功能均是围绕 Session 进行开发和设计的。这与基于 Token 的、前后端分离架构，在功能设计和开发思维方式方面有较大差异。切记 <strong>生搬硬套</strong></p>
</div>
<h2>[二]后端运行</h2>
<p>单体版本后端需要的基础设施组件非常简单，仅需要数据库和Redis即可。这里就略过具体的安装和部署说明，需要了解相关的内容，可以查阅 <a href="/get-started/prepare/backend.html" target="_blank">【基础组件安装配置】</a> 章节</p>
<h3>[1]创建数据</h3>
<p>这里以 PostgreSQL 为例，在 PostgreSQL 自带的命令行工具中，执行以下命令：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 创建用户</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 密码是</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">CREATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> USER</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> WITH</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> PASSWORD</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 'athena'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 创建数据库</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena,</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 并将用户</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 分配给该数据</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">CREATE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> DATABASE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> OWNER</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 将数据库</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 的所有权限分配给</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">GRANT</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ALL</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> PRIVILEGES</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ON</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> DATABASE</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> TO</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> athena</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]编译代码</h3>
<p>检出工程代码，进行编译。这里与微服务版本相关操作相同，需要了解相关的内容，可以查阅 <a href="/get-started/install/backend.html#_4%E6%A3%80%E5%87%BA%E4%BB%A3%E7%A0%81%E5%88%9B%E5%BB%BA%E5%B7%A5%E7%A8%8B" target="_blank">【检出代码创建工程章节】</a> 章节</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>自 3.3.5.0 版本起，为了提升使用的便捷性，单体版代码已经合并至 Herodotus Cloud 工程中。无需再像原来一样，需要检出两个工程，要先编译 Herodotus Cloud 工程代码，才能使用单体版本。</p>
</div>
<h3>[3]创建数据表</h3>
<p>代码检出后，在工程中找到 <code>herodotus-monomer-application</code>, 修改工程下 <code>appliation.yml</code> 中相关的配置信息。主要修改数据库连接和Redis连接即可。</p>
<p>然后，启动 <code>AthenaApplication</code> 应用。<code>AthenaApplication</code> 正常启动之后，会自动创建数据表以及自动完成基础信息的采集。</p>
<blockquote>
<p>如果看到有数据库表创建，同时<code>sys_authority</code>、<code>sys_interface</code>、<code>sys_attribute</code> 等表中有数据，证明数据库表创建成功。</p>
</blockquote>
<h3>[4]数据库初始化</h3>
<p>在工程中 <code>herodotus-monomer-application</code> 模块下，找到数据库初始化脚本，初始化数据。这里原理与微服务版本相同，如果想要使用自动初始化操作，可以查阅 <a href="/get-started/install/backend.html#%E5%9B%9B%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%9D%E5%A7%8B%E5%8C%96" target="_blank">【数据库初始化】</a> 章节</p>
<h3>[5]重置缓存</h3>
<p>原理和设计与微服务版本完全想通，可以查阅 <a href="/get-started/install/backend.html#%E4%BA%94%E9%87%8D%E7%BD%AE%E7%BC%93%E5%AD%98" target="_blank">【重置缓存】</a> 章节</p>
<h3>[6]启动服务</h3>
<p>以上步骤操作完成之后，再次启动 <code>AthenaApplication</code> 应用即可以使用。整个安装部署工作就完成了。</p>
<h2>[三]前端运行</h2>
<h3>[1]部署编译</h3>
<p>Dante Cloud Athena（单体版）前端与 Dante Cloud（微服务版）前端，使用的是同一套代码。所以，部署方式和运行方式，与微服务版本完全相同，具体可以查阅 <a href="/get-started/install/frontend.html" target="_blank">前端系统运行</a> 章节。</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>这里唯一需要注意的是，前端工程有一个参数 <code>VITE_PROJECT</code> 需要进行特别的修改。</p>
<p><code>VITE_PROJECT</code> 的值，如果是 <code>herodotus</code> 则表示是微服务版，其它任意字符串或者空值指定是单体版</p>
</div>
<h3>[2]运行验证</h3>
<p>Dante Cloud Athena（单体版）前端的运行验证方式也与 Dante Cloud（微服务版）前端完全相同，具体可以查阅 <a href="/get-started/install/frontend.html" target="_blank">前端系统运行</a> 章节。</p>
]]></content:encoded>
    </item>
    <item>
      <title>本地后端环境配置</title>
      <link>https://www.herodotus.cn/get-started/prepare/backend.html</link>
      <guid>https://www.herodotus.cn/get-started/prepare/backend.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">本地后端环境配置</source>
      <description>[一]JDK 安装及配置 [1]下载 JDK 下载 Liberica OpenJDK (JDK) 下载地址。 重要 这里我们推荐使用 Liberica OpenJDK，不建议使用 Oracle JDK。 一方面，Oracle JDK 涉及版权问题，而且也因使用 Oracle JDK 出现过很多不可预知问题 另一方面 Liberica OpenJDK，是...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]JDK 安装及配置</h2>
<h3>[1]下载 JDK</h3>
<p>下载 <code>Liberica OpenJDK</code> (JDK) <a href="https://bell-sw.com/pages/downloads/#downloads" target="_blank" rel="noopener noreferrer">下载地址</a>。</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>这里我们推荐使用 <code>Liberica OpenJDK</code>，不建议使用 Oracle JDK。</p>
<ul>
<li>一方面，Oracle JDK 涉及版权问题，而且也因使用 Oracle JDK 出现过很多不可预知问题</li>
<li>另一方面 <code>Liberica OpenJDK</code>，是 Spring Boot 官方推荐使用的 OpenJDK，和 Spring Boot 更搭配，而且还不涉及版权问题。另外，经过作者自己尝试使用 Liberica OpenJDK 做镜像遇到的问题都会更少，比如说字体等问题</li>
</ul>
</div>
<figure><img src="/assets/image/prepare/spring-boot-liberica.png" alt="spring-boot-liberica" tabindex="0" loading="lazy"><figcaption>spring-boot-liberica</figcaption></figure>
<h3>[2]安装 JDK</h3>
<p>点击安装下载后的<code>JDK</code>安装文件，全部点击下一步即可。</p>
<p>建议全部点击下一步安装，不要修改安装目录等内容，全部默认安装。一方面，在没有搞清楚 JDK 和 JRE 区别时，不建议修改安装目录，这经常会导致装错或者漏装；另一方面，个人觉得装在默认目录挺好，方便定位查找，重装系统也一并清除了。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Liberica JDK 会自动设置环境变量，如果您已经设置过环境变量，安装完成之后建议手动调整一下。</p>
</div>
<h3>[3]配置 JDK 环境变量（Windows）</h3>
<p>右键点击 Windows 左下角的 Windows 图标，选择 <code>系统—&gt;高级系统设置—&gt;环境变量</code>。</p>
<ol>
<li>在 <code>系统变量</code> 栏中，点击<code>新建</code>，弹出 <code>编辑系统变量</code> 对话框。
<ul>
<li>在 <code>变量名(N)</code> 中，输入变量名称 <code>JAVA_HOME</code></li>
<li>在 <code>变量值(V)</code> 中，输入 JDK 所在目录 <code>C:\Program Files\BellSoft\LibericaJDK-25</code></li>
</ul>
</li>
</ol>
<p>点击 <code>编辑系统变量</code> 对话框中的【确定】按钮，保存配置。</p>
<ol start="2">
<li>还是在 <code>系统变量</code> 栏中，找到 <code>Path</code> 变量，点击<code>编辑</code>， 弹出 <code>编辑环境变量</code> 对话框</li>
</ol>
<p>在 <code>编辑环境变量</code> 对话框中，添加 <code>%JAVA_HOME%\bin</code></p>
<blockquote>
<p><code>%JAVA_HOME%</code> 的意思，即使引用上一步中，定义的变量 <code>JAVA_HOME</code> 的 变量值。</p>
</blockquote>
<p>点击 <code>编辑环境变量</code> 对话框中的【确定】按钮，保存配置。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>至此，JDK 环境变量已经配置完成。以上这些配置已经可以完全满足 JAVA 开发的正常需要，除非特别的应用需求，比如说本地运行自定义的,需要独立运行的纯 Java 程序外，不再需要像网上说的，还要配置什么 <code>.</code> , <code>classpath</code>, <code>tools.jar</code> 之类的东西。</p>
</div>
<h3>[4]验证 JDK 安装</h3>
<p>新打开一个命令提示符（CMD），输入以下命令：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">java</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --version</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>如果可以正确显示版本信息，说明安装成功，如下图所示。</p>
<figure><img src="/assets/image/prepare/jdk-validation.png" alt="jdk-validation" tabindex="0" loading="lazy"><figcaption>jdk-validation</figcaption></figure>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>CMD 自身有 Session 机制，每次打开是以打开时环境变量配置作为依据运行。环境变量配置完成之后，如果还是使用之前的 CMD 窗口，新的环境变量配置将不会生效。所以一定要关掉当前 CMD 窗口，重新开一个 CMD 窗口进行安装验证。</p>
</div>
<h2>[二]Maven 安装及配置</h2>
<h3>[1]下载 Maven</h3>
<p>下载 <code>Apache Maven</code> <a href="http://maven.apache.org/download.cgi" target="_blank" rel="noopener noreferrer">下载地址</a>。</p>
<h3>[2]解压缩 Maven</h3>
<p>将下载好的 <code>apache-maven-XXXX-bin.zip</code>, 解压到你想要放置的任何位置。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Maven 是 Java 开发常用的工具，建议最好找一个相对固定的位置放置，一方面，不需要频繁修改和更换版本；另一方面，主要是好找，方便反复使用。</p>
</div>
<h3>[3]配置 Maven 环境变量（Windows）</h3>
<p>右键点击 Windows 左下角的 Windows 图标，选择 <code>系统—&gt;高级系统设置—&gt;环境变量</code>。</p>
<ol>
<li>在 <code>系统变量</code> 栏中，点击<code>新建</code>，弹出 <code>编辑系统变量</code> 对话框。
<ul>
<li>在 <code>变量名(N)</code> 中，输入变量名称 <code>MAVEN_HOME</code></li>
<li>在 <code>变量值(V)</code> 中，输入 Maven 所在目录，这里以 <code>D:\Environment\apache-maven-3.9.4</code> 为例，请根据实际情况进行修改。</li>
</ul>
</li>
</ol>
<p>点击 <code>编辑系统变量</code> 对话框中的【确定】按钮，保存配置。</p>
<ol start="2">
<li>还是在 <code>系统变量</code> 栏中，找到 <code>Path</code> 变量，点击<code>编辑</code>， 弹出 <code>编辑环境变量</code> 对话框</li>
</ol>
<p>在 <code>编辑环境变量</code> 对话框中，添加 <code>%MAVEN_HOME%\bin</code></p>
<blockquote>
<p><code>%MAVEN_HOME%</code> 的意思，即使引用上一步中，定义的变量 <code>MAVEN_HOME</code> 的 变量值。</p>
</blockquote>
<p>点击 <code>编辑环境变量</code> 对话框中的【确定】按钮，保存配置。</p>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>IDEA 从 2025.2 版本开始，修改了对 Maven 执行脚本的调用方式，导致在 IDEA 中编译代码会出现乱码。</p>
<p>如果你使用了 2025.2 及以上版本，需要额外增加一个环境变量 <code>MAVEN_OPTS</code>，具体设置如下：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">MAVEN_OPTS</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">-Dsun.stdout.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">encoding</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">UTF-8</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> -Dstderr.encoding</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">=UTF-8</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -Dconsole.encoding=UTF-8</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -Dfile.encoding=UTF-8</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div></div>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>IDEA 2025.3.X 已经内置了对 Maven 控制台的编码设置，已经不需要设置前面的环境变量。</p>
</div>
<h3>[4]验证 Maven 安装</h3>
<p>新打开一个命令提示符（CMD），输入以下命令：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">mvn</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -v</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>如果可以正确显示版本信息，说明安装成功，如下图所示。</p>
<figure><img src="/assets/image/prepare/maven-validation.png" alt="maven-validation" tabindex="0" loading="lazy"><figcaption>maven-validation</figcaption></figure>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>CMD 自身有 Session 机制，每次打开是以打开时环境变量配置作为依据运行。环境变量配置完成之后，如果还是使用之前的 CMD 窗口，新的环境变量配置将不会生效。所以一定要关掉当前 CMD 窗口，重新开一个 CMD 窗口进行安装验证。</p>
</div>
<h3>[5]创建本地仓库目录</h3>
<p>建议找一个相对固定的位置，创建任意名称的文件夹，作为本地仓库存储 Maven 下载的依赖。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>使用 Maven 时，Maven 会从中央库，将 <code>pom.xml</code> 中依赖的组件，下载至本地供开发和编译使用。默认情况下，Maven 会将下载的依赖组件，放置到系统盘中的 <code>.m2</code> 目录下。这不仅会慢慢撑大系统盘空间的使用，如果重装系统，相关的组件又需要重新下载。</p>
<p>指定一个文件夹，作为本地 Maven 存储仓库，可以减少依赖组件的反复下载，起到重复使用的作用。</p>
</div>
<p>这里以 <code>maven-repository</code> 为例，放在与 <code>apache-maven-3.9.6</code> 平级的目录下。只是示例，请根据实际情况修改。</p>
<h3>[6]修改 Maven 配置</h3>
<p>在 Maven 安装目录下，找到 <code>settings.xml</code> 文件，具体路径为 <code>${maven_home}/conf/settings.xml</code>。</p>
<p>用任意编辑器打开 <code>settings.xml</code> 文件中，找到被注释掉的 <code>&lt;localRepository&gt;</code> 节点，在其下方添加以下配置：</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">localRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>D:/Environment/maven-repository&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">localRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container info">
<p class="hint-container-title">相关信息</p>
<p>这里的路径，就是上一步中创建的本地 Maven 存储仓库文件夹所在的路径。</p>
<blockquote>
<p>注意：路径的格式，斜杠的方向。不同的操作系统，会略有不同。</p>
</blockquote>
</div>
<p>在 <code>settings.xml</code> 文件中，找到 <code>&lt;mirrors&gt;</code> 节点，在其内部添加以下配置：</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">mirror</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>tencent&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">mirrorOf</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>*&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">mirrorOf</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>腾讯云公共仓库&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">name</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">url</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>http://mirrors.cloud.tencent.com/nexus/repository/maven-public/&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">url</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">mirror</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container info">
<p class="hint-container-title">相关信息</p>
<p>Maven 默认是从中央仓库进行依赖的下载，具体地址为 <code>https://repo.maven.apache.org/maven2</code>。因为涉及到外部网络，下载速度比较慢。通过上面的配置，将 Maven 仓库修改为腾讯云公共仓库，依赖下载速度会快很多。</p>
</div>
<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>建议使用腾讯云公共仓库。腾讯云公共仓库对比阿里云公共仓库，速度会略慢一些，但是时效性高了非常多，新发布至中央仓库的 jar，腾讯很快就会同步到，而阿里云的同步周期间隔非常不确定。</p>
</div>
<h2>[三]Git(Windows)</h2>
<h3>[1]Git 下载</h3>
<p>下载 <code>Git</code> <a href="https://git-scm.com/download/win" target="_blank" rel="noopener noreferrer">下载地址</a>。</p>
<h3>[2]Git 安装</h3>
<p>全部使用默认设置，一路 <code>Next</code> 安装即可。</p>
<h2>[四]Redis(Windows)</h2>
<h3>[1]Redis 下载</h3>
<p>下载 <code>Redis</code>, <a href="https://github.com/redis-windows/redis-windows" target="_blank" rel="noopener noreferrer">下载地址</a></p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Redis 官方未提供 Windows 环境可直接安装的安装包。Redis v3、v5 可以找到开源社区提供的 Windows 安装文件。Redis v7 目前尚未找到可直接在 Windows 下安装的包。</p>
<p>前文下载地址中，是开源社区提供的、需要自己手动配置的 Redis。虽然没有安装包方便，但总比自己手动编译强。</p>
<blockquote>
<p>当然也可以选择 Docker 方式安装。</p>
</blockquote>
</div>
<h3>[2]Redis 安装</h3>
<ol>
<li>将下载的 Redis 压缩包，解压到电脑中任意位置即可。推荐使用 <code>Redis-8.6.2-Windows-x64-msys2-with-Service</code>，具体的版本可以根据自己的实际需求。</li>
<li>以 <strong>管理员模式</strong> 打开 PowerShell</li>
<li>执行以下命令，将 Redis 注册为服务并开机启动</li>
</ol>

<h3>[3]Redis 配置</h3>
<p>为了方便本地的开发和使用，需要对 Redis 的配置进行一定的修改。</p>
<blockquote>
<p>这里说明的仅是对本地建议性的基础修改。实际想要怎么修改和配置按照你的实际情况来就可以。</p>
</blockquote>
<p>进入到解压的 Redis 目录，找到 <code>redis.conf</code> 文件。</p>
<ol>
<li>修改默认端口号</li>
</ol>
<p>从安全角度考虑，即使本地开发也不建议使用默认端口号，所以建议将 Redis 访问端口号修改为一个不易被识别的端口</p>
<p>打开 <code>redis.conf</code> 文件，找到 <code>138</code> 行进行修改</p>
<ol start="2">
<li>开放 Redis IP 访问</li>
</ol>
<p>正常情况下，本地开发使用 <code>127.0.0.1</code> 就可以访问已安装的 Redis 服务; 因为 Docker 是使用内部的独立网络，Docker 内部容器想要访问外部的数据库，只能通过 ip 地址才能访问。所以需要开放数据库的 IP 访问，否则会出现 Docker 容器发无法连接数据库的情况。</p>
<blockquote>
<p>如果您不需要通过 Docker 可以访问外部 Redis，可以跳过。</p>
</blockquote>
<p>打开 <code>redis.conf</code> 文件</p>
<ul>
<li>找到 <code>87</code> 行，将 <code>bind 127.0.0.1 -::1</code> 注释掉。</li>
<li>找到 <code>111</code> 行，将 <code>protected-mode</code> 值修改为 <code>no</code>。</li>
</ul>
<h2>[五]PostgreSQL(Windows)</h2>
<h3>[1]PostgreSQL 下载</h3>
<p>下载 <code>PostgreSQL</code> <a href="https://www.postgresql.org/download/" target="_blank" rel="noopener noreferrer">下载地址</a>。</p>
<h3>[2]PostgreSQL 安装</h3>
<p>全部使用默认设置，一路 <code>Next</code> 安装即可。从安全角度讲，即使本地开发也不建议使用默认端口号，所以建议在安装过程中，将 PostgreSQL 默认的访问端口修改为其它未被使用的端口</p>
<h3>[3]开放 PostgreSQL 数据库 IP 访问</h3>
<p>正常情况下，本地开发使用 <code>localhost</code> 就可以访问已安装的 PostgreSQL 服务; 因为 Docker 是使用内部的独立网络，Docker 内部容器想要访问外部的数据库，只能通过 ip 地址才能访问。所以需要开放数据库的 IP 访问，否则会出现 Docker 容器发无法连接数据库的情况。</p>
<blockquote>
<p>如果您不需要通过 Docker 可以访问外部 PostgreSQL，可以跳过这部分内容。</p>
</blockquote>
<ol>
<li>找到 PostgreSQL 配置文件目录 <code>${PostgreSQL_HOME}/16/data</code></li>
<li>打开 <code>pg_hba.conf</code> 配置文件</li>
<li>在配置文件末尾，增加如下配置</li>
</ol>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">host</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    all</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">             all</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">             192.168.0.0/16</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">          trust`</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>上面的配置，允许访问 PostgreSQL 服务器的客户端 IP 地址, 其中：192.168.0.0/16 表示允许 192.168.0.1-192.168.255.255 网段访问。这仅是配置参考，安全性不高。请结合自己实际的安全需求进行修改</p>
</div>
<h2>[六]MySQL(Windows)</h2>
<blockquote>
<p>如果您决定使用 PostgreSQL 数据库并且 Nacos 也使用 Dante Cloud 封装的、使用 PostgreSQL 作为持久化数据库的 Docker 镜像 <code>herodotus/docker-server</code>，可以跳过这部分内容。</p>
</blockquote>
<h3>[1]MySQL 下载</h3>
<p>下载 <code>MySQL</code> 社区版 <a href="https://dev.mysql.com/downloads/installer/" target="_blank" rel="noopener noreferrer">下载地址</a>。</p>
<h3>[2]MySQL 安装</h3>
<p>全部使用默认设置，一路 <code>Next</code> 安装即可。从安全角度讲，即使本地开发也不建议使用默认端口号，所以建议在安装过程中，将 MySQL 默认的访问端口修改为其它未被使用的端口</p>
<h3>[3]开放 MySQL 数据库 IP 访问</h3>
<p>正常情况下，本地开发使用<code>localhost</code>就可以访问已安装的 mysql 服务; 因为 Docker 是使用内部的独立网络，Docker 内部容器想要访问外部的数据库，只能通过 ip 地址才能访问。所以需要开放数据库的 IP 访问，否则会出现 Docker 容器发无法连接数据库的情况。</p>
<p>使用命令行工具，登录进入 MySQL，输入以下脚本查看数据库的可访问情况：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">mysql</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">select</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> host,user</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> from</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> mysql.user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>查询结果示例：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">| </span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">host</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      | </span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">             |</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">|</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/prepare/spring-boot-liberica.png" type="image/png"/>
    </item>
    <item>
      <title>本地 Docker 安装</title>
      <link>https://www.herodotus.cn/get-started/prepare/docker.html</link>
      <guid>https://www.herodotus.cn/get-started/prepare/docker.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">本地 Docker 安装</source>
      <description>[一]Docker 用途 之所以将 Docker 作为环境准备的一个章节，并不代表在本地开发也必须要使用 Docker。但因为微服务涉及的组件比较多，即使是系统运行最小化需求，部署的工作量也不小。使用 Docker 进行一部分组件安装和部署，可以节省大量的工作和时间。 [二]环境准备 [1]开启Hyper-V 在 Windows 11 25h2 及以上...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]Docker 用途</h2>
<p>之所以将 Docker 作为环境准备的一个章节，并不代表在本地开发也必须要使用 Docker。但因为微服务涉及的组件比较多，即使是系统运行最小化需求，部署的工作量也不小。使用 Docker 进行一部分组件安装和部署，可以节省大量的工作和时间。</p>
<h2>[二]环境准备</h2>
<h3>[1]开启Hyper-V</h3>
<p>在 Windows 11 25h2 及以上版本中，可以按照下面方式通过界面操作开启：</p>
<ul>
<li>右键单击 Windows 按钮并选择“系统”。（左下角 Windows 图标或者 Win + I 打开设置）</li>
<li>“系统”设置右侧列表中，找到并选择“可选功能”（Win 11 25h2 中，在 AI 选项下方）。</li>
<li>在“可选功能”面板中，选择“更多 Windows 功能”</li>
<li>在弹出的“打开或关闭 Windows 功能”对话框中，选择“Hyper-V”，然后单击“确定”。</li>
</ul>
<div class="hint-container info">
<p class="hint-container-title">相关信息</p>
<p>Windows 不同的版本中，系统设置的差异以及变化会比较大，也可以直接找到“控制面板”来开启</p>
</div>
<blockquote>
<p>安装完成后，系统会提示你重启计算机。</p>
</blockquote>
<h3>[2]适用于 Linux 的 Windows 子系统安装</h3>
<ul>
<li>打开 <a href="https://aka.ms/wslstore" target="_blank" rel="noopener noreferrer">Microsoft Store</a>，并选择你偏好的 Linux 分发版 (如果上述连接打开有错，请直接打开 Microsoft Store 搜索)。</li>
<li>在分发版的页面中，选择自己想要安装的Linux版本（我使用的是 Debian），点击“获取”进行安装。等待下载和安全完成</li>
</ul>
<p>首次启动新安装的 Linux分发版时，将打开一个控制台窗口，系统会要求你等待一分钟或两分钟，以便文件解压缩并存储到电脑上。未来的所有启动时间应不到一秒。</p>
<p>然后，需要为新的 Linux 分发版创建用户帐户和密码。</p>
<h3>[3]安装 WSL 2</h3>
<p>WSL2 是适用于 Linux 的 Windows 子系统，可让开发人员按原样运行 GNU/Linux 环境，包括大多数命令行工具、实用工具和应用程序，且不会产生传统虚拟机或双启动设置开销。</p>
<p>在新版本 Window 11 中，WSL 不在使用内置方式，改为使用外部安装。</p>
<p>可以通过以下命令进行安装:</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">wsl</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --install</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> //</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 如果没有安装</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> wsl，输入任何</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> wsl</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 命令都会提示安装</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">wsl</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --update</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> //</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 如果已经安装了，可以使用该命令进行升级</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>目前 WSL 是托管在 Github 当中，采用开源的方式维护。使用 <code>wsl --install</code> 和 <code>wsl --update</code> 命令进行 WSL 更新时，是从 Github 中下载更新。由于众所周知的原因，使用命令根据你自身的网络不同，可能会非常慢。如果出现这种情况，建议直接到 Github 上手动下载。<a href="https://github.com/microsoft/WSL/releases" target="_blank" rel="noopener noreferrer">【下载地址】</a></p>
</div>
<h3>[4]设置WSL默认版本</h3>
<p>在 Powershell 中运行以下命令，以将 WSL 2 设置为默认版本：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">wsl</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --set-default-version</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> 2</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h3>[5]设置WSL运行环境参数</h3>
<h4>(1)手动方式</h4>
<p>在当前系统的用户下，新建 <code>.wlsconfig</code>文件，在其中配置以下代码。</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[wsl2]</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">memory</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">16GB</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">processors</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">1</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">swap</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">5GB</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">swapFile</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">D:</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\\</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">local-cached</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\\</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">wsl2</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\\</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">swap.vhdx</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">localhostForwarding</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">true</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>配置项说明：</p>
<p>| 参数                   | 值类型    | 默认值                                               | 说明                                                                                                                                                |<br>
|</p>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/prepare/wsl2-settings.png" type="image/png"/>
    </item>
    <item>
      <title>本地运行部署概述</title>
      <link>https://www.herodotus.cn/get-started/prepare/environment.html</link>
      <guid>https://www.herodotus.cn/get-started/prepare/environment.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">本地运行部署概述</source>
      <description>重要 微服务系统不同于单体系统。搭建一套微服务系统所需要的基础设施以及涉及的组件非常多，即使是仅搭建本地开发环境，所需安装和运行的组件也远超单体系统。 所以，请对自身的硬件设施以及将要部署环境的资源有充份的评估和规划。如果您只是想看看界面、随便点两下建议直接部署 Dante Cloud 单体版。 注意 本章所有内容之所以均为本地说明，是因为不同用户的部...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<div class="hint-container important">
<p class="hint-container-title">重要</p>
<p>微服务系统不同于单体系统。搭建一套微服务系统所需要的基础设施以及涉及的组件非常多，即使是仅搭建本地开发环境，所需安装和运行的组件也远超单体系统。</p>
<p>所以，请对自身的硬件设施以及将要部署环境的资源有充份的评估和规划。如果您只是想看看界面、随便点两下建议直接部署 Dante Cloud 单体版。</p>
</div>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>本章所有内容之所以均为本地说明，是因为不同用户的部署拓扑结构会千差万别，文档无法涵盖所有内容。但是“万变不离其宗”，正式环境或者其它环境的部署逻辑以及所需组件与本地部署基本一致，可以参考本章内容进行部署。</p>
</div>
<h2>[一]后端工程本地开发运行系统所需环境</h2>
<h3>[1]环境必要组件</h3>
<p>| 序号 | 设施       | 推荐版本  | 说明                              |<br>
| :--: |</p>
]]></content:encoded>
    </item>
    <item>
      <title>本地前端环境配置</title>
      <link>https://www.herodotus.cn/get-started/prepare/frontend.html</link>
      <guid>https://www.herodotus.cn/get-started/prepare/frontend.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">本地前端环境配置</source>
      <description>[一]Nodejs 安装 下载 Nodejs，下载地址，按照提示安装即可，记得选择将 Nodejs 增加到 PATH 一项，如果界面上没有，应该就是默认帮你搞定了，可以忽略。 安装完成后，通过命令，验证是否安装成： 如果正常显示版本号，即安装成功。 [二]Nodejs 配置 Nodejs 安装完成之后，默认会将 Node.exe 的路径，添加到系统的环...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]Nodejs 安装</h2>
<p>下载 Nodejs，<a href="https://nodejs.org/zh-cn/" target="_blank" rel="noopener noreferrer">下载地址</a>，按照提示安装即可，记得选择将 Nodejs 增加到 PATH 一项，如果界面上没有，应该就是默认帮你搞定了，可以忽略。</p>
<p>安装完成后，通过命令，验证是否安装成：</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">node</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -v</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">npm</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -v</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>如果正常显示版本号，即安装成功。</p>
<h2>[二]Nodejs 配置</h2>
<p>Nodejs 安装完成之后，默认会将 Node.exe 的路径，添加到系统的环境变量中，如果没有请手动添加。</p>
<p>Nodejs 默认的缓存目录和全局安装目录都在系统盘，会占用大量的系统盘空间，所以建议修改，如果不想修改，请跳过此部分内容。</p>
<h3>[1]配置 prefix 和 cache 目录</h3>
<p><code>prefix</code> 就是 Nodejs 来全局安装的位置，<code>cache</code> 就是 Nodejs 依赖下载缓存的目录</p>
<ul>
<li>改变 npm prefix 全局安装位置</li>
</ul>
<p>假设你所指定的根目录地址为：<code>C:\Program Files\nodejs</code></p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">npm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> prefix</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> “C:</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\P</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">rogram</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> Files</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">odejs</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">ode_global”</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><ul>
<li>改变 npm cache 缓存位置</li>
</ul>
<p>假设你所指定的根目录地址为：<code>C:\Program Files\nodejs</code></p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">npm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> cache</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">  “C:</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\P</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">rogram</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> Files</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">odejs</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">ode_cache</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h3>[2]配置环境变量</h3>
<p>Nodejs 在安装过程中，会自动配置 Path 环境变量。但这个环境变量只是针对 <code>Node</code> 和 <code>Npm</code> 有效。对于一些全局安装的应用，例如 @vue/cli，如果不配置环境变量，它的相关命令是无法正常执行的。</p>
<p>右键点击 Windows 左下角的 Windows 图标，选择 <code>系统—&gt;高级系统设置—&gt;环境变量</code>。</p>
<ol>
<li>在 <code>系统变量</code> 栏中，点击<code>新建</code>，弹出 <code>编辑系统变量</code> 对话框。
<ul>
<li>在 <code>变量名(N)</code> 中，输入变量名称 <code>NODE_PATH</code></li>
<li>在 <code>变量值(V)</code> 中，输入上一步中指定的 <code>prefix</code> 加上 <code>node_modules</code> 文件夹，如下所示，请根据实际情况修改</li>
</ul>
</li>
</ol>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">C:\Program</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> Files</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">odejs</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">ode_global</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">\n</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">ode_modules</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>点击 <code>编辑系统变量</code> 对话框中的【确定】按钮，保存配置。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>这个值就是上一步中，通过 <strong>“npm config set prefix”</strong> 设置的值。可以，用下面命令查看</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">npm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> get</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> prefix</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><blockquote>
<p>切记，一定要加上 <code>\node_modules</code>。因为，有些全局应用是安装在 <code>C:\Program Files\nodejs\node_global\node_modules</code> 目录下</p>
</blockquote>
</div>
<ol start="2">
<li>在 <code>用户变量</code> 栏中， 找到 <code>Path</code> 变量，点击<code>编辑</code>， 弹出 <code>编辑环境变量</code> 对话框</li>
</ol>
<p>在 <code>编辑环境变量</code> 对话框中，添加 <code>C:\Program Files\nodejs\node_global\</code></p>
<blockquote>
<p>这个值就是上一步中，通过 <strong>“npm config set prefix”</strong> 设置的值。</p>
</blockquote>
<p>至此，Node 相关的环境变量配置完成。</p>
<h3>[3]配置阿里源</h3>
<p>将 Npm 的下载源，修改为阿里源，可以提升依赖包下载速度，执行一下命令设置。</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">npm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> registry</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> https://registry.npmmirror.com/</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> （新的镜像仓库地址）</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h2>[三] Pnpm 安装和配置</h2>
<h3>[1]Pnpm 简介</h3>
<p>一个现代的包管理工具，名字叫做 <code>pnpm</code>，英文里面的意思叫做 <code>performant npm</code> ，意味“高性能的 npm”，官网地址可以参考 <a href="https://pnpm.io/" target="_blank" rel="noopener noreferrer">PNPM</a>。</p>
<p>pnpm 相比较于 yarn/npm 这两个常用的包管理工具在性能上也有了极大的提升，根据目前官方提供的 benchmark 数据可以看出在一些综合场景下比 npm/yarn 快了大概两倍：</p>
<h3>[2]Pnpm 安装</h3>
<p>Nodejs 安装完成之后，执行以下命令进行 Pnpm 的安装和验证</p>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 全局安装pnpm</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">npm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> install</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -g</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pnpm</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 查看</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 版本，能够输出版本号说明安装成功</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --version</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[3]Pnpm 配置</h3>
<ol>
<li>在自己喜欢的地方新建三个目录，<code>pnpm</code>、<code>pnpm\pnpm-global</code>、<code>pnpm\pnpm-cache</code></li>
<li>执行以下命令配置 pnpm 相关目录，请结合自己实际修改以下命令中涉及的目录路径</li>
</ol>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pnpm全局仓库路径</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">类似</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> .git</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 仓库</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> store-dir</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "D:\local-cached\npm\pnpm\.pnpm-store"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pnpm创建pnpm-state.json文件的目录</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> state-dir</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "D:\local-cached\npm\pnpm"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pnpm全局bin路径</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 指代的是</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> bin</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 这个文件夹所在的目录，即</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> bin</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 的父目录。而不是</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> bin</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 本身。</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 这里使用了</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> nodejs</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 全局目录，主要为了解决</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 全局安装不生效以及与</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> npm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 全局安装重复的问题</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> global-bin-dir</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "D:\local-cached\npm\nodejs\node_global"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> pnpm全局缓存路径</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> cache-dir</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "D:\local-cached\npm\pnpm\pnpm-cache"</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[4]Pnpm 源配置</h3>
<div class="language-shell line-numbers-mode" data-highlighter="shiki" data-ext="shell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-shell"><span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 查看源（如果前面配置</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> npm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> registry，这里会显示已经配置的源）</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> get</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> registry</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">//</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> 也可以手动重新设置淘宝源</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">pnpm</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> config</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> set</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> registry</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> https://registry.npmmirror.com/</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>本地开发工具配置</title>
      <link>https://www.herodotus.cn/get-started/prepare/ide.html</link>
      <guid>https://www.herodotus.cn/get-started/prepare/ide.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">本地开发工具配置</source>
      <description>[一]IDEA [1]下载 IDEA 下载 IDEA 下载地址。 全部使用默认设置，一路 Next 安装即可 [2]在 IDEA 中修改 Maven 配置 打开 IDEA，点击 File -&amp;gt; Settings... -&amp;gt; Maven，打开 Maven 配置面板，如下图所示： maven-config-01maven-config-01 在...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]IDEA</h2>
<h3>[1]下载 IDEA</h3>
<p>下载 <code>IDEA</code> <a href="https://www.jetbrains.com/idea/" target="_blank" rel="noopener noreferrer">下载地址</a>。</p>
<p>全部使用默认设置，一路 <code>Next</code> 安装即可</p>
<h3>[2]在 IDEA 中修改 Maven 配置</h3>
<p>打开 IDEA，点击 <code>File -&gt; Settings... -&gt; Maven</code>，打开 Maven 配置面板，如下图所示：</p>
<figure><img src="/assets/image/prepare/maven-config-01.png" alt="maven-config-01" tabindex="0" loading="lazy"><figcaption>maven-config-01</figcaption></figure>
<p>在 Maven 配置面板中，修改 Maven 的配置。</p>
<ol>
<li>设置 <code>Maven home path</code></li>
</ol>
<p>在 Maven 配置面板中，设置 <code>Maven home path</code> 的值，这个值就是环境变量 <code>MAVEN_HOME</code> 中配置的值。如下图所示：</p>
<figure><img src="/assets/image/prepare/maven-config-02.png" alt="maven-config-02" tabindex="0" loading="lazy"><figcaption>maven-config-02</figcaption></figure>
<ol start="2">
<li>设置 <code>User setting files</code></li>
</ol>
<p>在 Maven 配置面板中，首先勾选 <code>User setting files</code> 设置后面的 <code>Override</code>，勾选之后 <code>User setting files</code> 的选择框就会变为激活的状态。</p>
<p>点击 <code>User setting files</code> 设置中的 <code>文件夹</code> 按钮，选择 <code>${maven_home}/conf/settings.xml</code> 文件，具体如下图所示：</p>
<figure><img src="/assets/image/prepare/maven-config-03.png" alt="maven-config-03" tabindex="0" loading="lazy"><figcaption>maven-config-03</figcaption></figure>
<p>如果 <code>${maven_home}/conf/settings.xml</code> 文件已经正确配置，并且在 <code>User setting files</code> 设置中也选择正确的情况下，<code>Local repository</code> 配置就会自动显示出自定义本地仓库的路径，这个路径就是刚刚，在 <code>${maven_home}/conf/settings.xml</code> 中设置的 <code>&lt;localRepository&gt;</code> 的值，这就说明配置成功。如上图所示。</p>
<h3>[3]在 IDEA 中修改 Maven 的全局配置</h3>
<p>IDEA 中很多设置否分为两种，一种设置只是针对当前工程，这种设置只是针对当前工程有效，打开新的工程或者重新检出当前工程就需要重新配置；一种设置是针对新工程，配置之后所有用 IDEA 打开工程都会生效，就不需要重新设置。</p>
<p>上一步中进行 Maven 设置，就是前一种设置，只对当前工程有效，打开新的工程或者重新检出当前工程就需要重新设置 Maven。</p>
<p>可以通过下面的方式，进行 Maven 的 “全局” 配置。</p>
<p>点击 <code>File -&gt; New Projects Setup -&gt; Settings For New Prjects...</code>，会跳出全局设置面板，如下图所示：</p>
<figure><img src="/assets/image/prepare/maven-config-04.png" alt="maven-config-04" tabindex="0" loading="lazy"><figcaption>maven-config-04</figcaption></figure>
<p>在这个面板中，找到 Maven 配置，重复进行上一步的操作即可。</p>
<h3>[4]Command line is too long问题修复</h3>
<p>不管是微服务还是单体，因为运行依赖的组件非常多，首次在 IDEA 中启动服务出现 <code>Command line is too long.Shorten the command line and rerun.</code> 提示，如下图所示：</p>
<figure><img src="/assets/image/prepare/idea-toolong-01.png" alt="idea-toolong-01" tabindex="0" loading="lazy"><figcaption>idea-toolong-01</figcaption></figure>
<p>解决的办法如下：</p>
<ol>
<li>在 IDEA 右上角，找到 <code>Edit Configurations...</code>，点击后弹出 <code>Run/Debug Configurations</code> 配置窗口，如下图所示：</li>
</ol>
<figure><img src="/assets/image/prepare/idea-toolong-02.png" alt="idea-toolong-02" tabindex="0" loading="lazy"><figcaption>idea-toolong-02</figcaption></figure>
<ol start="2">
<li>在 <code>Run/Debug Configurations</code> 界面中，选择具体的服务，然后点击右侧的 <code>Modify Options</code>，如下图所示：</li>
</ol>
<figure><img src="/assets/image/prepare/idea-toolong-03.png" alt="idea-toolong-03" tabindex="0" loading="lazy"><figcaption>idea-toolong-03</figcaption></figure>
<ol start="3">
<li>在弹出的选项列表中，勾选 <code>Shorten the command line</code> 选项，如下图所示：</li>
</ol>
<figure><img src="/assets/image/prepare/idea-toolong-04.png" alt="idea-toolong-04" tabindex="0" loading="lazy"><figcaption>idea-toolong-04</figcaption></figure>
<ol start="4">
<li>勾选 <code>Shorten the command line</code> 选项完成之后，就会在 <code>Run/Debug Configurations</code> 配置界面中，多出一个 <code>Shorten command line</code> 选项，在这个选项中，选择 <code>JAR manifest</code>。如下图所示：</li>
</ol>
<figure><img src="/assets/image/prepare/idea-toolong-05.png" alt="idea-toolong-05" tabindex="0" loading="lazy"><figcaption>idea-toolong-05</figcaption></figure>
<ol start="5">
<li>然后点击 <code>Apply</code> 和 <code>OK</code> 完成所有操作。</li>
</ol>
<figure><img src="/assets/image/prepare/idea-toolong-06.png" alt="idea-toolong-06" tabindex="0" loading="lazy"><figcaption>idea-toolong-06</figcaption></figure>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>所有的服务都需要配置该项内容，所以可以在代码检出之后，统一进行配置</p>
</div>
<h2>[二]Vscode</h2>
<h3>[1]下载 Vscode</h3>
<p>下载 <code>Vscode</code> <a href="https://www.jetbrains.com/idea/" target="_blank" rel="noopener noreferrer">下载地址</a>。</p>
<p>全部使用默认设置，一路 <code>Next</code> 安装即可</p>
<h3>[2]设置 Vscode</h3>
<p>因为 Vscode 设置非常灵活，加之插件也非常丰富，所以每个人 Vscode 的设置都可能不同。为了方便大家使用，提供 Dante Cloud Vscode 环境的设置供参考。</p>
<p>使用 <code>ctrl + shift + p</code> 快捷键调出搜索框，输入 <code>setting</code>，在搜索结果中找到 <code>Open User Settings (JSON)</code>， 打开用户设置 <code>settings.json</code> 文件。</p>
<p>将下面配置，贴入 <code>settings.json</code> 文件中</p>
<div class="language-json line-numbers-mode" data-highlighter="shiki" data-ext="json" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-json"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // ===========以下工作区显示的配置================</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // 开启 vscode 文件路径导航</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "breadcrumbs.enabled"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // 自动保存 关闭</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "files.autoSave"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"afterDelay"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "workbench.tree.indent"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">20</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "workbench.editor.labelFormat"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"medium"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // ===========以下编辑区显示的配置================</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.minimap.enabled"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // 控制折行方式。可以选择： - “off” （禁用折行）， - “on” （视区折行），</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  //  “wordWrapColumn”（在“editor.wordWrapColumn”处折行）或 - “bounded”（在视区与“editor.wordWrapColumn”两者的较小者处折行）。</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.wordWrap"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"wordWrapColumn"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.fontSize"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">16</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // 150 列后换行</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.wordWrapColumn"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">150</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // ===========以下4个是控制保存时自动格式化的，并且以4格缩进================</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.tabCompletion"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"on"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // tab 大小为2个空格</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.tabSize"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">2</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.autoClosingBrackets"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"always"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.autoClosingQuotes"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"always"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.formatOnSave"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.codeActionsOnSave"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "source.fixAll.eslint"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"explicit"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // 设置 eslint 保存时自动修复</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.quickSuggestions"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "strings"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.defaultFormatter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"esbenp.prettier-vscode"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "editor.fontLigatures"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // ===========以下是eslint配置的============================</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "eslint.enable"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "eslint.options"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "extensions"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">".js"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">".vue"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">".ts"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">".tsx"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "eslint.validate"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "vue"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "html"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "javascript"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "json"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "typescript"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "vue-html"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  ],</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  // ===========以下是vue配置的============================</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "javascript.format.enable"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "javascript.format.insertSpaceBeforeFunctionParenthesis"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "typescript.format.enable"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "typescript.preferences.importModuleSpecifier"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"relative"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "typescript.updateImportsOnFileMove.enabled"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"always"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "css.validate"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "less.validate"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "scss.validate"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "[scss]"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "editor.defaultFormatter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"esbenp.prettier-vscode"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "[jsonc]"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "editor.defaultFormatter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"esbenp.prettier-vscode"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "[javascript]"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "editor.defaultFormatter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"esbenp.prettier-vscode"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "[html]"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "editor.defaultFormatter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"esbenp.prettier-vscode"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "[typescript]"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "editor.defaultFormatter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"esbenp.prettier-vscode"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "[json]"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "editor.defaultFormatter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"esbenp.prettier-vscode"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "[vue]"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "editor.defaultFormatter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"esbenp.prettier-vscode"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "[css]"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "editor.defaultFormatter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"esbenp.prettier-vscode"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "[markdown]"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "editor.defaultFormatter"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"esbenp.prettier-vscode"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "workbench.iconTheme"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"vscode-icons"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "markdownlint.config"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "MD029"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    "MD033"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">false</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  },</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "redhat.telemetry.enabled"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "workbench.editor.enablePreview"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "vsicons.dontShowNewVersionMessage"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "vite.autoStart"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "settingsSync.ignoredExtensions"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [],</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "sync.autoUpload"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "sync.autoDownload"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#0184BC;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  "workbench.colorTheme"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"Default Dark Modern"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[3]安装插件</h3>
<p>推荐安装的插件：</p>
<ul>
<li>Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code</li>
<li>Code Spell Checker</li>
<li>EditorConfig for VS Code</li>
<li>ESLint</li>
<li>HTML CSS Support</li>
<li>Image preview</li>
<li>Import Cost</li>
<li>indent-rainbow</li>
<li>JavaScript (ES6) code snippets</li>
<li>Markdown All in One</li>
<li>Path Intellisense</li>
<li>Prettier - Code formatter</li>
<li>Vite</li>
<li>vscode-icons</li>
<li>Vue - Official</li>
<li>Vue 3 Snippets</li>
<li>Vue Peek</li>
<li>Vue VSCode Snippets</li>
</ul>
]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/prepare/maven-config-01.png" type="image/png"/>
    </item>
    <item>
      <title>认证开启关闭</title>
      <link>https://www.herodotus.cn/develop-guide/coding/authentication.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/authentication.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">认证开启关闭</source>
      <description>概述 认证管理，是微服务系统的核心内容。通常通过前端提供的管理界面进行管理和配置即可，大多数场景仅提供必要的管理功能。而且认证管理功能，通常由专门的认证服务（例如：UAA） 提供支持，其它 OAuth2 的资源服务器是无法对认证进行管理的。但是对于某些场景，比如：物联网客户端的接入，一方面需要实现动态的接入与授权；另一方面需要可以实现认证的动态开启和关...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>认证管理，是微服务系统的核心内容。通常通过前端提供的管理界面进行管理和配置即可，大多数场景仅提供必要的管理功能。而且认证管理功能，通常由专门的认证服务（例如：UAA） 提供支持，其它 OAuth2 的资源服务器是无法对认证进行管理的。但是对于某些场景，比如：物联网客户端的接入，一方面需要实现动态的接入与授权；另一方面需要可以实现认证的动态开启和关闭管理。</p>
<p>Dante Cloud 核心认证管理使用的是 <code>Spring Authorization Server</code> 组件。该组件并未提供动态开启或关闭某个客户端认证功能的支持。</p>
<p>为了解决这个问题，自 3.3.5.0 版本起，Dante Cloud 新增的远程的（非UAA服务）认证开启和关闭支持。不仅支持动态开启和管理某个客户端的认证，还支持在 UAA 服务以外远程调用。</p>
<h2>[一]开启支持</h2>
<p>要启用动态认证开启和关闭，需要手动在您需要启用动态认证开启和关闭的资源服中开启。</p>
<p>开启方式，在当前服务中的 <code>Application</code> 类中，添加注解 <code>@EnableAuthorizationEnhance</code> 即可。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>动态认证开启和关闭功能，并没有像其它功能一样，默认注入到系统中，而是需要手动开启。主要原因是认证是系统核心功能，随意操作可能会导致不可预知的问题，而且会涉及的动态认证功能的业务需求并不会太多，所以目前设计为手动开启方式。</p>
</div>
<h2>[二]引用操作Bean</h2>
<p>需要在你的代码中，注入 <code>AuthenticationManager</code> Bean，然后通过调用其中相应的方法就可以进行认证动态开启和关闭操作。</p>
<p>例如，新建一个 <code>MyAuthenticationService</code>，在这个 Service 代码中注入<code>AuthenticationManager</code> Bean，如下例所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Service</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MyAuthenticationService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AuthenticationManager</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> authenticationManager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MyFileService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">AuthenticationManager</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> authenticationManager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">authenticationManager</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> authenticationManager;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>AuthenticationManager</code> 支持的操作如下所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> AuthenticationManager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 关闭认证</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> id</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> oauth2_registered_client 表中的ID</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> disable</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 开启认证</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> authentication</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 手动创建 oauth2_registered_client 数据的必要信息 {@link Authentication}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> enable</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Authentication</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> authentication</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
    <item>
      <title>新建子模块</title>
      <link>https://www.herodotus.cn/develop-guide/coding/create-new-module.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/create-new-module.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">新建子模块</source>
      <description>[一]新建子模块概述 新建子模块是指在“多模块”工程中新增新的模块。 对于 Dante Cloud 来说，模块一般有两种类型： 常规模块：即为了隔离代码，提升代码的通用性，会将一部分代码迁移汇总到一个单独的模块，通过依赖这个模块实现代码的共享。这种模块就是这里所说的常规模块。 服务模块：即基于 Spring Boot 的服务模块，虽然称之为一个模块，但...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>[一]新建子模块概述</h2>
<p>新建子模块是指在“多模块”工程中新增新的模块。</p>
<p>对于 Dante Cloud 来说，模块一般有两种类型：</p>
<ol>
<li>常规模块：即为了隔离代码，提升代码的通用性，会将一部分代码迁移汇总到一个单独的模块，通过依赖这个模块实现代码的共享。这种模块就是这里所说的常规模块。</li>
<li>服务模块：即基于 Spring Boot 的服务模块，虽然称之为一个模块，但实际上这个模块其实是一个可以独立运行的应用。之所以采用模块的方式，是为了放置在同一个代码工程中，方便区分、调试、管理。</li>
</ol>
<p>对于新建模块的方式，本文中主要包含两种方式：</p>
<ol>
<li>工程内：指的是在现有 Dante Cloud 工程中新建模块。</li>
<li>工程外：指的是可以在 Dante Cloud 工程之外，其它的工程中新建模块。</li>
</ol>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>以下内容是基于 Maven 多模块工程创建子模块的案例说明</p>
</div>
<h2>[二]在现有工程内，新建常规模块</h2>
<h3>[1]手工创建常规模块</h3>
<ol>
<li>在合适的位置，新建模块目录，例如：<code>dante-test</code></li>
<li>在<code>dante-test</code>业务模块下，新建<code>pom.xml</code>文件,以及<code>src\main\java</code>和<code>src\main\resources</code>目录</li>
<li>修改<code>dante-test</code>业务模块下的<code>pom.xml</code>文件，添加 Maven 基础配置</li>
</ol>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;?</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">xml</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"1.0"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> encoding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"UTF-8"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">?></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">project</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> xmlns</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://maven.apache.org/POM/4.0.0"</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         xmlns:xsi</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://www.w3.org/2001/XMLSchema-instance"</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         xsi:schemaLocation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modelVersion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>4.0.0&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modelVersion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    // 根据子模块所在位置，指定其上级目录pom信息作为parent</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">parent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-cloud&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.dante&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>X.X.X&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">parent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>X.X.X&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">> // 此处指定版本号，便于versions-maven-plugin统一修改</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">packaging</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>jar&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">packaging</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante 微服务测试模块&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependencies</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>commons-beanutils&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>commons-beanutils&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        ...</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependencies</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">project</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="4">
<li>找到<code>dante-test</code>目录的上级目录中的 pom.xml 文件，在其中添加<code>&lt;modules&gt;</code>标签，以保持模块的联动依赖或编译</li>
</ol>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="5">
<li>在工程<code>dependencies</code>目录下的 pom.xml 中，找到<code>dependencyManagement</code>下的</li>
</ol>
<p><code>&lt;!-- Dante Cloud --&gt;</code> 部分，添加模块声明</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.dante&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>${project.version}&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样在其它模块中，就可以便捷的引用该模块，而且可以实现通过<code>dependencies</code>中的参数，统一控制模块版本。</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.dante&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]使用IDEA常规模块</h3>
<ol>
<li>在 Idea 中，点击菜单 <code>File -&gt; New -&gt; Module...</code>，在弹出的对话框中选择，选择<code>Maven</code>，然后点击<code>Next</code></li>
</ol>
<figure><img src="/assets/image/infrastructure/create-module-01.png" alt="新建模块" tabindex="0" loading="lazy"><figcaption>新建模块</figcaption></figure>
<p>2,在新的<code>New Module</code>窗口中，输入想要新建的模块名称，点击<code>Finish</code>按钮完成模块新建操作。</p>
<figure><img src="/assets/image/infrastructure/create-module-02.png" alt="新建模块" tabindex="0" loading="lazy"><figcaption>新建模块</figcaption></figure>
<div class="hint-container caution">
<p class="hint-container-title">警告</p>
<p>输入完名称之后，一定要重新选择<code>Parent</code>内容，这决定了新建模块所在的位置和层次结构。</p>
</div>
<p>以上操作完成之后，会创建与“手工创建”章节结果相同。</p>
<p>在工程<code>dependencies</code>目录下的 pom.xml 中，找到<code>dependencyManagement</code>下的</p>
<p><code>&lt;!-- Dante Cloud --&gt;</code> 部分，添加模块声明</p>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.dante&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>${project.version}&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这样在其它模块中，就可以便捷的引用该模块，而且可以实现通过<code>dependencies</code>中的参数，统一控制模块版本。</p>
<h2>[三]在现有工程内，新建微服务模块</h2>
<h3>[1]手工新建服务模块</h3>
<ol>
<li>在合适的位置，新建模块目录，例如：<code>dante-test-ability</code></li>
<li>在<code>dante-test-ability</code>业务模块下，新建<code>pom.xml</code>文件,以及<code>src\main\java</code>和<code>src\main\resources</code>目录</li>
<li>修改<code>dante-test-abilit</code>y 业务模块下的<code>pom.xml</code>文件，添加 Maven 基础配置</li>
</ol>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;?</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">xml</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"1.0"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> encoding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"UTF-8"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">?></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">project</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> xmlns</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://maven.apache.org/POM/4.0.0"</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         xmlns:xsi</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://www.w3.org/2001/XMLSchema-instance"</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         xsi:schemaLocation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">parent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-cloud&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.dante&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>X.X.X&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">parent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modelVersion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>4.0.0&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modelVersion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>herodotus-cloud-bpmn-ability&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>X.X.X&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test-ability测试服务&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependencies</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.cloud&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>authorization-servlet-spring-boot-starter&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        ......</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependencies</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugins</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            &#x3C;!-- spring boot 默认插件 --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>org.springframework.boot&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>spring-boot-maven-plugin&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            &#x3C;!-- 拷贝插件 --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>org.apache.maven.plugins&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>maven-antrun-plugin&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            &#x3C;!-- Git信息记录插件 --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>io.github.git-commit-id&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>git-commit-id-maven-plugin&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugins</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">project</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p><code>authorization-servlet-spring-boot-starter</code> 是所有&quot;阻塞式&quot;业务微服务启动运行,接入平台必须依赖的核心包。</p>
</div>
<ol start="4">
<li>找到<code>dante-test-ability</code>目录的上级目录中的 pom.xml 文件，在其中添加<code>&lt;modules&gt;</code>标签，以保持模块的联动依赖或编译</li>
</ol>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test-ability&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="5">
<li>在<code>src\main\resources</code>目录下添加<code>bootstrap.yml</code>文件</li>
</ol>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    activate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      on-profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">alibaba</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  cloud</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    nacos</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        namespace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_NAMESPACE:@alibaba.namespace@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_CONFIG_SERVER_ADDR:@alibaba.config.server-addr@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        file-extension</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        shared-configs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-environment.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${PROFILE:@profile@}</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-platform.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-database-${DATABASE:@database@}.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-database.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-redis.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-cache.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-rest.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-kafka.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-social.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${spring.application.name}.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">service</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      discovery</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        namespace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_NAMESPACE:@alibaba.namespace@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_DISCOVERY_SERVER_ADDR:@alibaba.discovery.server-addr@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    sentinel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      transport</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        port</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">8719</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        dashboard</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_SENTINEL_SERVER_ADDR:@alibaba.sentinel.server-addr@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      eager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> #服务注启动，直接注册到dashboard</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="6">
<li>在<code>cn.herodotus.dante.test</code>.ability 包下面添加启动类</li>
</ol>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">package</span><span style="--shiki-light:#383A42;--shiki-dark:#C678DD"> cn.herodotus.dante.test.ability</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> org.springframework.boot.SpringApplication</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> org.springframework.boot.autoconfigure.SpringBootApplication</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">SpringBootApplication</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> TestApplication</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> main</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[] </span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic">args</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        SpringApplication</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">run</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">TestApplication</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, args);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>[2]使用IDEA新建服务模块</h3>
<ol>
<li>在 Idea 中，点击菜单 <code>File -&gt; New -&gt; Module...</code>，在弹出的对话框中选择，选择<code>Spring Initializr</code>，添加相关信息，然后点击<code>Next</code></li>
</ol>
<figure><img src="/assets/image/infrastructure/create-module-03.png" alt="使用IDEA新建服务模块" tabindex="0" loading="lazy"><figcaption>使用IDEA新建服务模块</figcaption></figure>
<p>2,在新的<code>New Module</code>窗口中，选择想要添加的微服务相关依赖模块，点击<code>Finish</code>按钮完成模块新建操作。</p>
<figure><img src="/assets/image/infrastructure/create-module-04.png" alt="使用IDEA新建服务模块" tabindex="0" loading="lazy"><figcaption>使用IDEA新建服务模块</figcaption></figure>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>这里与前面【手工新建服务模块】章节描述的操作类似，都是通过点击菜单 <code>File -&gt; New -&gt; Module...</code>进行子模块的创建。因为是选择<code>Spring Initializr</code>，虽然操作路径一致，但是完全是按照创建一个新的<code>Spring Boot</code>进行操作，包括创建包的位置,<code>Group</code>,<code>Artifact</code>等信息都需要手工填写，也不会自动添加<code>&lt;modules&gt;</code>等关联信息。模块生成之后，还需要手动修改<code>pom.xml</code>。</p>
<blockquote>
<p>使用该种方式的好处，是微服务工程所需要的代码结构,Application 服务启动类,<code>Edit Configuraitons...</code>服务运行的默认配置都会帮助自动建好</p>
</blockquote>
</div>
<ol start="3">
<li>修改<code>dante-test-ability</code>业务模块下的<code>pom.xml</code>文件，添加 Maven 基础配置，修改 parent 依赖关系</li>
</ol>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;?</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">xml</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"1.0"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> encoding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"UTF-8"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">?></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">project</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> xmlns</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://maven.apache.org/POM/4.0.0"</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         xmlns:xsi</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://www.w3.org/2001/XMLSchema-instance"</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         xsi:schemaLocation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">parent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-cloud&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.dante&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>X.X.X&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">parent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modelVersion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>4.0.0&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modelVersion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>herodotus-cloud-bpmn-ability&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>X.X.X&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test-ability测试服务&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependencies</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.cloud&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>authorization-servlet-spring-boot-starter&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        ......</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependencies</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugins</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            &#x3C;!-- spring boot 默认插件 --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>org.springframework.boot&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>spring-boot-maven-plugin&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            &#x3C;!-- 拷贝插件 --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>org.apache.maven.plugins&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>maven-antrun-plugin&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            &#x3C;!-- Git信息记录插件 --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>io.github.git-commit-id&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>git-commit-id-maven-plugin&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugins</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">project</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p><code>authorization-servlet-spring-boot-starter</code>是所有业务微服务启动运行,接入平台必须依赖的核心包</p>
</div>
<ol start="4">
<li>找到<code>dante-test-ability</code>目录的上级目录中的<code>pom.xml</code>文件，在其中添加<code>&lt;modules&gt;</code>标签，以保持模块的联动依赖或编译</li>
</ol>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test-ability&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">module</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modules</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="5">
<li>在<code>src\main\resources</code>目录下，新建<code>bootstrap.yml</code>文件并删除自动生成的<code>application.properties</code>文件，或者将自动生成的<code>application.properties</code>文件重命名为<code>bootstrap.yml</code></li>
</ol>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    activate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      on-profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">alibaba</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  cloud</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    nacos</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        namespace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_NAMESPACE:@alibaba.namespace@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_CONFIG_SERVER_ADDR:@alibaba.config.server-addr@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        file-extension</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        shared-configs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-environment.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${PROFILE:@profile@}</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-platform.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-database-${DATABASE:@database@}.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-database.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-redis.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-cache.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-rest.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-kafka.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-social.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${spring.application.name}.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">service</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      discovery</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        namespace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_NAMESPACE:@alibaba.namespace@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_DISCOVERY_SERVER_ADDR:@alibaba.discovery.server-addr@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    sentinel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      transport</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        port</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">8719</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        dashboard</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_SENTINEL_SERVER_ADDR:@alibaba.sentinel.server-addr@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      eager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> #服务注启动，直接注册到dashboard</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[四]在现有工程外，新建微服务独立模块工程</h2>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>现有工程外（<code>herodotus-cloud</code>工程外部）建立微服务工程，需要依赖于<code>herodotus-cloud</code>代码，因此需要使用<code>mvn install</code> 命令正确编译过<code>herodotus-cloud</code>工程，确保 Maven 本地缓存（<code>Maven Repository</code>）或者独立部署的 Maven 仓库中存在已经编译好的<code>herodotus-cloud</code>工程代码。</p>
</div>
<ol>
<li>在<code>Idea</code>中，点击菜单 <code>File -&gt; New -&gt; Module...</code>，在弹出的对话框中选择，选择<code>Spring Initializr</code>，添加相关信息，然后点击<code>Next</code></li>
</ol>
<figure><img src="/assets/image/infrastructure/create-module-03.png" alt="新建微服务独立模块工程" tabindex="0" loading="lazy"><figcaption>新建微服务独立模块工程</figcaption></figure>
<ol start="2">
<li>在新的<code>New Module</code>窗口中，选择想要添加的微服务相关依赖模块，点击<code>Finish</code>按钮完成模块新建操作。</li>
</ol>
<figure><img src="/assets/image/infrastructure/create-module-04.png" alt="新建微服务独立模块工程" tabindex="0" loading="lazy"><figcaption>新建微服务独立模块工程</figcaption></figure>
<ol start="3">
<li>修改新建业务模块下的<code>pom.xml</code>文件中，添加 Maven 基础配置</li>
</ol>
<div class="language-xml line-numbers-mode" data-highlighter="shiki" data-ext="xml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-xml"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;?</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">xml</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"1.0"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> encoding</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"UTF-8"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">?></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">project</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> xmlns</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://maven.apache.org/POM/4.0.0"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> xmlns:xsi</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://www.w3.org/2001/XMLSchema-instance"</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">         xsi:schemaLocation</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modelVersion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>4.0.0&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">modelVersion</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">parent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.dante&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dependencies&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>X.X.X&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">parent</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test-ability&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>X.X.X&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">packaging</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>jar&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">packaging</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>dante-test-ability测试服务&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">description</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependencies</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>cn.herodotus.cloud&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>authorization-servlet-spring-boot-starter&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependency</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        ......</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">dependencies</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugins</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            &#x3C;!-- spring boot 默认插件 --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>org.springframework.boot&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>spring-boot-maven-plugin&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            &#x3C;!-- 拷贝插件 --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>org.apache.maven.plugins&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>maven-antrun-plugin&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">            &#x3C;!-- Git信息记录插件 --></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>io.github.git-commit-id&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">groupId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">                &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>git-commit-id-maven-plugin&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">artifactId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">            &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugin</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">plugins</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">project</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ol start="4">
<li>在<code>src\main\resources</code>目录下，新建<code>bootstrap.yml</code>文件并删除自动生成的<code>application.properties</code>文件，或者将自动生成的<code>application.properties</code>文件重命名为<code>bootstrap.yml</code></li>
</ol>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">spring</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    activate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      on-profile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">alibaba</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  cloud</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    nacos</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      config</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        namespace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_NAMESPACE:@alibaba.namespace@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_CONFIG_SERVER_ADDR:@alibaba.config.server-addr@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        file-extension</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        shared-configs</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-environment.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${PROFILE:@profile@}</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-platform.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-database-${DATABASE:@database@}.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-database.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-redis.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-cache.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-rest.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-kafka.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">herodotus-cloud-social.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">common</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">          - </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">data-id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${spring.application.name}.yaml</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            group</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">service</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      discovery</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        namespace</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_NAMESPACE:@alibaba.namespace@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        server-addr</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_DISCOVERY_SERVER_ADDR:@alibaba.discovery.server-addr@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    sentinel</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      transport</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        port</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">8719</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        dashboard</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${ALIBABA_SENTINEL_SERVER_ADDR:@alibaba.sentinel.server-addr@}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      eager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">true</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> #服务注启动，直接注册到dashboard</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
      <enclosure url="https://www.herodotus.cn/assets/image/infrastructure/create-module-01.png" type="image/png"/>
    </item>
    <item>
      <title>服务本地文件</title>
      <link>https://www.herodotus.cn/develop-guide/coding/file.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/file.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">服务本地文件</source>
      <description>概述 上传和下载文件，基本上算是一个应用系统的标配。在微服务架构下，为了方便集中管理文件的上传和下载，大多都会选择 OSS 统一对文件进行管理。 但是对于硬件资源紧张或者仅是需要简单的文件上传和下载功能的用户来说，单独搭建 OSS 又是较大资源的投入和资源的浪费。另外，即使搭建了 OSS，大多需求也只会向前端提供上传下载接口，服务内部涉及文件管理，也只...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>上传和下载文件，基本上算是一个应用系统的标配。在微服务架构下，为了方便集中管理文件的上传和下载，大多都会选择 OSS 统一对文件进行管理。</p>
<p>但是对于硬件资源紧张或者仅是需要简单的文件上传和下载功能的用户来说，单独搭建 OSS 又是较大资源的投入和资源的浪费。另外，即使搭建了 OSS，大多需求也只会向前端提供上传下载接口，服务内部涉及文件管理，也只能通过远程调用的方式解决，这无形中就增加了代码的复杂度以及出错的记录。</p>
<p>在一些应用场景下，难免会存在个别服务自身需要管理部分文件，同时又不想随时采用远程上传至其它服务的需求。为了解决这个问题，Dante Cloud 新增了基于服务本地的文件上传和下载支持。可以理解为单个服务就像单体应用一样在应用本地磁盘管理部分文件。这样即可以满足规模较小的或者服务特有的文件管理需求，又不需要集成复杂的远程文件上传下载调用。</p>
<p>特别是 Dante Cloud 单体版本，如果有了本地文件管理，就不需要单独再搭建 OSS。</p>
<h2>[一]添加配置</h2>
<p>在你需要使用简单文件管理的服务中，添加配置，来指定存放文件的磁盘目录</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">herodotus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  service</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    file</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      destination</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">D:/file-store</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[二]引入操作Bean</h2>
<p>需要在你的代码中，注入 <code>FileTemplate</code> Bean，然后通过调用其中相应的方法就可以进行文件管理操作。</p>
<p>例如，新建一个 <code>MyFileService</code>，在这个 Service 代码中注入<code>FileTemplate</code> Bean，如下例所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Service</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MyFileService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> FileTemplate</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> fileTemplate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MyFileService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">FileTemplate</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileTemplate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">fileTemplate</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> fileTemplate;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>Dante Cloud 已经默认将 <code>FileTemplate</code> 注入到了所有的服务中，在自己的代码中直接注入就可以使用。如果当前的操作不满足您的需求，<code>FileTemplate</code> 是一个接口定义，可以按您自己的需求进行扩展。</p>
</div>
<p>接口 <code>FileTemplate</code> 支持的操作如下所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> FileTemplate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 根据文件夹和文件名获取完整的文件路径</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件夹</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  文件名</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件 {@link File}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    Path</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 根据文件名获取完整的文件路径</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件名</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件 {@link File}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Path</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, fileName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 获取系统存储 Json Schema 目录</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> Json Schema 目录</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getJsonSchemaDirectory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 获取系统存储证书目录</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 证书目录</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getCertificateDirectory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 删除文件</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件路径</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> delete</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Path</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 字符串写入文件</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    文件路径</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> content</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 内容</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件路径 {@link Path}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    Path</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> writeString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Path</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> content</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 写入文件</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件坐在文件夹</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  文件名。可以是包含相对路径的文件名。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> content</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   写入的内容</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件 {@link File}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Path</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> writeString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> content</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        Path</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(directory, fileName);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> writeString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(path, content);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 写入文件</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件名。可以是包含相对路径的文件名。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> content</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  写入的内容</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件 {@link File}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Path</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> writeString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> content</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> writeString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, fileName, content);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 写入文件流</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件路径</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 流 {@link BufferedOutputStream}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    BufferedOutputStream</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> writeOutputStream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Path</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 写入文件 需要手动关闭流</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件坐在文件夹</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  文件名。可以是包含相对路径的文件名。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 流 {@link BufferedOutputStream}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BufferedOutputStream</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> writeOutputStream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        Path</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(directory, fileName);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> writeOutputStream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(path);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 写入文件 需要手动关闭流</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件名。可以是包含相对路径的文件名。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 流 {@link BufferedOutputStream}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BufferedOutputStream</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> writeOutputStream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> writeOutputStream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, fileName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 读取文件</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件路径</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件内容 {@link String}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Path</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 读取文件</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件坐在文件夹</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  文件名。可以是包含相对路径的文件名。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件内容 {@link String}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        Path</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(directory, fileName);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(path);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 读取文件</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件名。可以是包含相对路径的文件名。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件内容 {@link String}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> String</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readString</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, fileName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 读取文件。需要手动关闭流</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件路径</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 流 {@link BufferedInputStream}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">    BufferedInputStream</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readInputStream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Path</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 读取文件。需要手动关闭流</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件坐在文件夹</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  文件名。可以是包含相对路径的文件名。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 流 {@link BufferedInputStream}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BufferedInputStream</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readInputStream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">        Path</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(directory, fileName);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readInputStream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(path);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 读取文件。需要手动关闭流</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 文件名。可以是包含相对路径的文件名。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 流 {@link BufferedInputStream}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BufferedInputStream</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readInputStream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> readInputStream</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, fileName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 文件上传</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> destinationUrl</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 上传请求地址</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">           文件路径</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 是否成功</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    boolean</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> upload</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> destinationUrl</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Path</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 文件下载</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> destinationUrl</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 上传请求地址</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">           文件路径</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 是否成功</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    boolean</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> download</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> destinationUrl</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Path</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> path</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[三]注意事项</h2>
<ol>
<li>如果您的服务中包含文件操作代码，那么当前服务在部署为多实例的情况下，一定要考虑本地文件的共享问题。</li>
<li>如果您的服务中包含文件操作代码，那么服务在以 Docker 方式运行时，建议将服务本地文件存储目录，挂载映射至主机磁盘中</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>统一消息发送</title>
      <link>https://www.herodotus.cn/develop-guide/coding/message.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/message.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">统一消息发送</source>
      <description>概述 各种组件和模块可以便捷的集成是 Spring Boot 框架的一大亮点和特色。微服务系统由于其架构的复杂性以及不同场景的需求，往往会集成多种不同的“消息系统”，例如：消息队列、WebSocket、Email 等，以满足服务与服务间、系统与用户间的消息交互需求。 不同的消息系统，使用的组件、实现方式以及调用方式均不相同，这无形之中就增加了代码的复杂...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>各种组件和模块可以便捷的集成是 Spring Boot 框架的一大亮点和特色。微服务系统由于其架构的复杂性以及不同场景的需求，往往会集成多种不同的“消息系统”，例如：消息队列、WebSocket、Email 等，以满足服务与服务间、系统与用户间的消息交互需求。</p>
<p>不同的消息系统，使用的组件、实现方式以及调用方式均不相同，这无形之中就增加了代码的复杂度。在开发过程中，如果任由开发人员在服务中随意添加相关代码，无序的使用和依赖，不仅会让代码以及服务越来越臃肿，而且会让代码逻辑越来越混乱，越来越难以维护。</p>
<p>为了解决这个问题，Dante Cloud 实现了一种统一的消息发送机制。所有的“消息系统”的配置和支持均由 <code>Message</code> 服务负责，提供统一的消息发送接口通过传递不同的参数，就可以在任意的服务中通过代码的形式发送任意类型的消息。</p>
<div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>这里所说的统一消息发送，指的是在后端服务代码层面，使用统一的方法在任意的微服务中发送消息。不包含前端与后端的调用和交互，对于前端来说，有提供有专门的 Rest API 供使用，而且并不是所有的消息系统都需要与前端存在调用关系。</p>
</div>
<h2>[一]支持内容</h2>
<p>消息统一发送，是指在 <code>Message</code> 服务以外的服务中，以代码的形式，通过统一的接口将不同的类型消息统一发送至 <code>Message</code> 服务中，再由 <code>Message</code> 服务将不同类型的消息转发至不同的消息系统中。</p>
<p>所以，在使用时要区分 <code>Message</code> 服务以及非 <code>Message</code> 服务。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p><code>Gateway</code> 是特殊的服务，不建议在 <code>Gateway</code> 服务中直接发送消息。这样做不仅让 <code>Gateway</code> 的职责混乱，还会降低系统整体的安全性。</p>
</div>
<p>目前，以下“消息系统”支持使用统一发送接口进行消息发送：</p>
<ul>
<li><code>Mail</code>：发送 Email 通知</li>
<li><code>Mail Notification</code>：也是发送 Email 通知，主要用于系统内置的通知消息，包含：登录地址异常通知，Email 验证码和电子邮件确认（不支持 HTML 格式邮件）</li>
<li><code>Mqtt</code>：向 Mqtt 中某个 Topic 发送消息。主要用于物联网应用支持</li>
<li><code>RSocket 广播消息</code>：发送基于 RSocket 的 WebSocket 广播通知（仅使用于 <code>Message</code> 服务为 Reactive 模式）</li>
<li><code>RSocket 用户消息</code>：向指定用户发送基于 RSocket 的 WebSocket 用户消息，支持跨示例发送（仅使用于 <code>Message</code> 服务为 Reactive 模式）</li>
<li><code>WebSocket 广播消息</code>：发送基于 Stomp 的 WebSocket 广播通知（仅使用于 <code>Message</code> 服务为 Servlet 模式）</li>
<li><code>WebSocket 用户消息</code>：向指定用户发送基于 Stomp 的 WebSocket 用户消息，支持跨示例发送（仅使用于 <code>Message</code> 服务为 Servlet 模式）</li>
<li><code>Stream 消息</code>：即消息队列消息（本系统主要使用 Spring Cloud Stream 和 Spring Cloud Bus 实现消息队列需求）</li>
</ul>
<h2>[二]非Message服务使用</h2>
<p>想要在非<code>Message</code>服务的代码中使用统一发送消息，仅需要在你的代码中，注入 <code>MessageSendingEventManager</code> Bean，然后通过调用其中相应的方法就可以发送消息。</p>
<p>例如，新建一个 <code>MyMessageService</code>，在这个 Service 代码中注入<code>MessageSendingEventManager</code> Bean，如下例所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Service</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MyMessageService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MessageSendingEventManager</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> messageSendingEventManager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MyMessageService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MessageSendingEventManager</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> messageSendingEventManager</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">messageSendingEventManager</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> messageSendingEventManager;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>通过调用 <code>MessageSendingEventManager</code> 不同的方法，就可以实现不同类型消息系统的消息发送。具体的接口定义如下：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MessageSendingEventManager</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> extends</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> ApplicationStrategyEventManager</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Message</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#C18401;--shiki-dark:#C678DD">?</span><span style="--shiki-light:#C18401;--shiki-dark:#ABB2BF">>></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 发送电子邮件消息</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 注意：这个方法目前不支持 HTML 模版解析</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> mailMessage</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> {@link MailMessage}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> mail</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MailMessage</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> mailMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        postProcess</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;>(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MessageCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MAIL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, mailMessage));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 发送内置电子邮件通知。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 注意：这个方法仅支持系统内置 HTML 模版电子邮件。具体解析方式参见：&#x3C;code>message-mail-spring-boot-starter&#x3C;/code></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> mailNotification</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> {@link MailNotification}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> mailNotification</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MailNotification</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> mailNotification</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        postProcess</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;>(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MessageCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MAIL_NOTIFICATION</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, mailNotification));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 向 Mqtt Broker 发送消息</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> mqttMessage</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> {@link MqttMessage}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> mqtt</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">MqttMessage</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> mqttMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        postProcess</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;>(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MessageCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MQTT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, mqttMessage));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 发送 RSocket 广播消息</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 当 Message 服务为 Reactive 模式时，可使用该方法发送基于 RSocket 的广播消息。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 注意：因为 RSocket 的特性使然，只有当有客户端已经连接了 RSocket Server（例如：使用前端登录某个或者某些用户，RSocket 服务内存中已经有了用户信息）才能接收到消息。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> broadcastMessage</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> {@link BroadcastMessage}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> rsocketBroadcast</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">BroadcastMessage</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> broadcastMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        postProcess</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;>(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MessageCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">RSOCKET_BROADCAST</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, broadcastMessage));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 向某个在线用户发送基于 RSocket 的消息</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 当 Message 服务为 Reactive 模式时，可使用该方法发送基于 RSocket 的广播消息。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 注意：因为 RSocket 的特性使然，只有当有客户端已经连接了 RSocket Server（例如：使用前端登录某个或者某些用户，RSocket 服务内存中已经有了用户信息）才能接收到消息。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> userMessage</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> {@link UserMessage}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> rsocketToUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">UserMessage</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> userMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        postProcess</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;>(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MessageCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">RSOCKET_USER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, userMessage));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 发送 Websocket 广播消息</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 当 Message 服务为 Servlet 模式时，可使用该方法发送基于 Websocket 的广播消息。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 注意：因为 Websocket 的特性使然，只有当有客户端已经连接了 Websocket Server（例如：使用前端登录某个或者某些用户，Websocket 服务内存中已经有了用户信息）才能接收到消息。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> broadcastMessage</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> {@link BroadcastMessage}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> websocketBroadcast</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">BroadcastMessage</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> broadcastMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        postProcess</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;>(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MessageCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">WEBSOCKET_BROADCAST</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, broadcastMessage));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 向某个在线用户发送基于 Websocket 的消息</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 当 Message 服务为 Servlet 模式时，可使用该方法发送基于 Websocket 的广播消息。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 注意：因为 Websocket 的特性使然，只有当有客户端已经连接了 Websocket Server（例如：使用前端登录某个或者某些用户，Websocket 服务内存中已经有了用户信息）才能接收到消息。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> userMessage</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> {@link UserMessage}</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> void</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> websocketToUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">UserMessage</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> userMessage</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">        postProcess</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Message</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;>(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">MessageCategory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">WEBSOCKET_USER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, userMessage));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>[三]Message服务使用</h2>
<p>前端想要集成消息支持，直接调用 <code>Message</code> 服务的 Rest API 即可；后端服务想要发送消息，使用统一的发送接口即可。</p>
<p>如果想要在 <code>Message</code> 服务中发送消息，本质上就是在扩展 <code>Message</code> 服务的代码，所以直接修改和使用现有代码即可。当然，本项目中也考虑到了使用的便捷性，如果你的业务需求并不复杂，仅是需要在 <code>Message</code> 服务中发送消息，使用以下事件就可以发送消息。</p>
<p>具体的事件如下：</p>
<ul>
<li><code>MailMessageSendingEvent</code>：发送 Email 消息，不支持 HTML 模版邮件</li>
<li><code>MailNotificationSendingEvent</code>：发送内置的邮件通知，包含登录地址异常通知，Email 验证码和电子邮件确认，支持 HTML 模版邮件</li>
<li><code>MqttMessageSendingEvent</code>：向指定的 Topic 发送 Mqtt 消息</li>
<li><code>RSocketBroadcastMessageSendingEvent</code>：发送基于 RSocket 的 WebSocket 广播消息</li>
<li><code>RSocketFireAndForgetMessageReceivingEvent</code>：向指定用户发送基于 RSocket 的 WebSocket 用户消息</li>
<li><code>WebSocketBroadcastMessageSendingEvent</code>：发送基于 Stomp 的 WebSocket 广播消息</li>
<li><code>WebSocketUserMessageSendingEvent</code>：向指定用户发送基于 Stomp 的 WebSocket 用户消息</li>
<li><code>StreamMessageSendingEvent</code>：发送 MQ 消息</li>
</ul>
<h2>[四]注意事项</h2>
<ol>
<li>系统代码已经在所有的非 <code>Message</code> 服务中，配置了 <code>MessageSendingEventManager</code> Bean，所以在自己的代码中直接注入使用即可。</li>
<li>虽然支持各种消息的统一发送，但是相关的消息系统还需要在 <code>Message</code> 服务中进行配置。如果没有配置相关的组件，消息发送不会出错，但是不会有具体效果。</li>
<li>因 RSocket 和 WebSocket 自身特性所限，不管是发送广播还是用户消息，前提是在 <code>Message</code> 服务中已经有了相关用户信息，即有用户通过前端登录了系统，否则看不到具体消息效果。</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>文件内部传输</title>
      <link>https://www.herodotus.cn/develop-guide/coding/oss.html</link>
      <guid>https://www.herodotus.cn/develop-guide/coding/oss.html</guid>
      <source url="https://www.herodotus.cn/rss.xml">文件内部传输</source>
      <description>概述 Dante Cloud 默认使用 OSS 作为文件存储介质，并提供了 OSS 服务支持文件的操作和管理。但是，当前模式下，仅支持前端以接口调用的方式来实现文件管理，如果存在服务间传输文件的需求，将无法满足。 为了解决这个问题，同时便于用户使用，在最新版本的 Dante Cloud 中，新增了服务间远程文件传输支持，填补了微服务文件管理的空缺。 [...</description>
      <pubDate>Wed, 18 Dec 2024 16:37:00 GMT</pubDate>
      <content:encoded><![CDATA[<h2>概述</h2>
<p>Dante Cloud 默认使用 OSS 作为文件存储介质，并提供了 OSS 服务支持文件的操作和管理。但是，当前模式下，仅支持前端以接口调用的方式来实现文件管理，如果存在服务间传输文件的需求，将无法满足。</p>
<p>为了解决这个问题，同时便于用户使用，在最新版本的 Dante Cloud 中，新增了服务间远程文件传输支持，填补了微服务文件管理的空缺。</p>
<h2>[一]引入依赖</h2>
<p>自 Dante Cloud 3.3.5.0 版本起，新增了 <code>rpc-client-oss-spring-boot-starter</code> 模块。需要在使用服务间文件传输的服务（非 OSS 服务）中引入该模块后，才能调用文件间传输接口。</p>
<p><code>rpc-client-oss-spring-boot-starter</code> 模块支持 Openfeign 和 GRPC 两种远程传输模式，会根据当前服务的环境自动判断使用哪种方式。</p>
<ul>
<li>如果当前服务为 Reactive 环境，那么就会自动切换为使用 GPRC 进行传输</li>
<li>如果当前服务为 Servlet 环境，那么就会自动切换为使用 Openfeign 进行传输</li>
</ul>
<p>另外，远程传输还需要服务本地文件管理的支持。利用服务本地文件管理的支持，不仅可以实现服务向 OSS 服务远程上传和下载文件，还可以实现以 OSS 服务为中转，非 OSS 服务间传递文件。</p>
<div class="hint-container warning">
<p class="hint-container-title">注意</p>
<p>目前，不支持脱离 OSS 服务，实现其它服务间点对点的文件传输。一方面，这种实现比较困难，完全类似于下载软件的需求实现较为复杂，另一方面，这种需求非常小众，应用场景非常有限，特别是在服务于服务之间。</p>
<p>如果确实需要这种需求，建议使用外部的文件同步或者文件共享机制实现。</p>
</div>
<h2>[二]添加配置</h2>
<p>因为需要服务本地文件管理的支持，所以需要添加服务本地文件管理的支持。服务本地文件管理详情参见：<a href="/develop-guide/coding/file.html" target="_blank">【服务本地文件管理】</a></p>
<h2>[三]引入操作Bean</h2>
<p>需要在你的代码中，注入 <code>FileTransformer</code> Bean，然后通过调用其中相应的方法就可以进行文件管理操作。</p>
<p>例如，新建一个 <code>MyFileService</code>，在这个 Service 代码中注入<code>FileTransformer</code> Bean，如下例所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">@</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">Service</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> MyFileService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> FileTransformer</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> fileTransformer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> MyFileService</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">FileTransformer</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileTransformer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">fileTransformer</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> FileTransformer;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container tip">
<p class="hint-container-title">提示</p>
<p>依赖了 <code>rpc-client-oss-spring-boot-starter</code> 模块后，会自动将 <code>FileTransformer</code> 注入，在自己的代码中直接注入就可以使用。如果当前的操作不满足您的需求，<code>FileTransformer</code> 是一个接口定义，可以按您自己的需求进行扩展。</p>
</div>
<p><code>FileTransformer</code> 支持的操作如下所示：</p>
<div class="language-java line-numbers-mode" data-highlighter="shiki" data-ext="java" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-java"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> FileTransformer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 文件上传。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 将服务本地文件上传至对象存储服务中</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> bucketName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 存储桶名称</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  本地文件目录</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   文件名</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> true 上传成功；false 上传失败。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    boolean</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> upload</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> bucketName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 文件上传。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 将服务本地文件上传至对象存储服务中</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> bucketName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 存储桶名称</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   文件名</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> true 上传成功；false 上传失败。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> boolean</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> upload</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> bucketName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> upload</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(bucketName, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, fileName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 文件下载。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 将对象存储服务中文件下载至服务本地</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> bucketName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 存储桶名称</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  本地文件目录</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   文件名</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> true 上传成功；false 上传失败。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    boolean</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> download</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> bucketName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> directory</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    /**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 文件下载。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * &#x3C;p></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * 将对象存储服务中文件下载至服务本地</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     *</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> bucketName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> 存储桶名称</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@param</span><span style="--shiki-light:#383A42;--shiki-light-font-style:italic;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">   文件名</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     * </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@return</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> true 上传成功；false 上传失败。</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">     */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    default</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> boolean</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> download</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> bucketName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">String</span><span style="--shiki-light:#383A42;--shiki-light-font-style:inherit;--shiki-dark:#E06C75;--shiki-dark-font-style:italic"> fileName</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> download</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(bucketName, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, fileName);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></content:encoded>
    </item>
  </channel>
</rss>