<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/atom.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-07-26T10:12:58+00:00</updated><id>/atom.xml</id><title type="html">Fas Note</title><subtitle>个人笔记</subtitle><author><name>2han9wen71an</name></author><entry><title type="html">Polarion-Docker-开发环境搭建</title><link href="/Toss-notes/polarion-docker-dev.html" rel="alternate" type="text/html" title="Polarion-Docker-开发环境搭建" /><published>2025-06-23T05:49:45+00:00</published><updated>2025-06-23T05:49:45+00:00</updated><id>/Toss-notes/Polarion-Docker-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA</id><content type="html" xml:base="/Toss-notes/polarion-docker-dev.html"><![CDATA[<h1 id="目录">目录</h1>
<ul>
  <li><a href="#前言">前言</a></li>
  <li><a href="#前置要求">前置要求</a></li>
  <li><a href="#系统要求">系统要求</a></li>
  <li><a href="#polarion-dockerfile">Polarion Dockerfile</a></li>
  <li><a href="#polarion-启动脚本">Polarion 启动脚本</a></li>
  <li><a href="#开发环境设置">开发环境设置</a></li>
  <li><a href="#使用说明">使用说明</a></li>
  <li><a href="#配置参数详解">配置参数详解</a></li>
  <li>
    <p><a href="#故障排除">故障排除</a></p>
  </li>
  <li><a href="#版本兼容性">版本兼容性</a></li>
</ul>

<h1 id="前言">前言</h1>

<p>在之前的文章《<a href="/Toss-notes/polarion-arm-fix.html">Polarion-Arm-环境修复</a>》中，我们解决了 Polarion 在 ARM 环境下的兼容性问题。本文将进一步介绍如何使用 Docker 搭建完整的 Polarion 开发环境。</p>

<p>作为 Polarion 开发者，经常需要在不同版本之间切换进行测试和开发。由于 Polarion 不支持多版本共存，传统的安装方式会带来很多不便。本文介绍的 Docker 方案可以：</p>

<ul>
  <li>✅ 支持多个 Polarion 版本并存</li>
  <li>✅ 快速切换开发环境</li>
  <li>✅ 支持 x86_64 和 ARM64 多架构</li>
  <li>✅ 隔离环境，避免版本冲突</li>
  <li>✅ 便于团队协作和环境复制</li>
</ul>

<h1 id="前置要求">前置要求</h1>

<p>在开始之前，请确保您的系统已安装以下工具：</p>

<h2 id="必需软件">必需软件</h2>
<ul>
  <li><strong>Docker Desktop</strong> 20.10.0+ 或 <strong>Docker Engine</strong> 20.10.0+</li>
  <li><strong>Git</strong> 2.0+</li>
  <li><strong>Bash</strong> 4.0+ (macOS/Linux) 或 <strong>PowerShell</strong> 5.0+ (Windows)</li>
</ul>

<h2 id="安装验证">安装验证</h2>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 验证 Docker 安装</span>
docker <span class="nt">--version</span>
docker-compose <span class="nt">--version</span>

<span class="c"># 验证 Git 安装</span>
git <span class="nt">--version</span>
</code></pre></div></div>

<h2 id="权限要求">权限要求</h2>
<ul>
  <li>Docker 运行权限（Linux 用户需要加入 docker 组）</li>
  <li>文件系统读写权限</li>
  <li>网络访问权限（用于下载依赖）</li>
</ul>

<h1 id="系统要求">系统要求</h1>

<h2 id="硬件要求">硬件要求</h2>
<ul>
  <li><strong>CPU</strong>: 4 核心以上（推荐 8 核心）</li>
  <li><strong>内存</strong>: 8GB 以上（推荐 16GB）</li>
  <li><strong>磁盘空间</strong>: 20GB 以上可用空间</li>
  <li><strong>网络</strong>: 稳定的互联网连接</li>
</ul>

<h2 id="支持的操作系统">支持的操作系统</h2>
<ul>
  <li><strong>Linux</strong>: Ubuntu 18.04+, CentOS 7+, RHEL 7+</li>
  <li><strong>macOS</strong>: 10.15+ (支持 Intel 和 Apple Silicon)</li>
  <li><strong>Windows</strong>: Windows 10/11 with WSL2</li>
</ul>

<h2 id="架构支持">架构支持</h2>
<ul>
  <li><strong>x86_64</strong> (AMD64)</li>
  <li><strong>ARM64</strong> (Apple Silicon, ARM 服务器)</li>
</ul>

<blockquote>
  <p><strong>注意</strong>: ARM64 环境可能需要额外的兼容性修复，详见《<a href="/Toss-notes/polarion-arm-fix.html">Polarion-Arm-环境修复</a>》。</p>
</blockquote>

<h2 id="polarion-dockerfile">Polarion Dockerfile</h2>
<p>以下是完整的 Dockerfile，支持多架构构建和动态版本选择：</p>

<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 多架构支持的 Ubuntu 基础镜像</span>
<span class="k">FROM</span><span class="s"> ubuntu:22.04</span>

<span class="c"># 设置构建参数，支持动态版本选择</span>
<span class="k">ARG</span><span class="s"> POLARION_VERSION=""</span>
<span class="k">ARG</span><span class="s"> TARGETARCH</span>
<span class="k">ARG</span><span class="s"> TARGETOS</span>

<span class="c"># 环境变量设置</span>
<span class="k">ENV</span><span class="s"> DEBIAN_FRONTEND=noninteractive</span>
<span class="k">ENV</span><span class="s"> RUNLEVEL=1</span>
<span class="k">ENV</span><span class="s"> LANG=en_US.UTF-8</span>
<span class="k">ENV</span><span class="s"> LC_ALL=en_US.UTF-8</span>
<span class="k">ENV</span><span class="s"> TZ=Asia/Shanghai</span>

<span class="c"># 安装基础软件包和时区配置</span>
<span class="k">RUN </span>apt-get <span class="nt">-y</span> update <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get <span class="nt">-y</span> <span class="nb">install sudo </span>unzip expect curl wget mc nano iputils-ping net-tools iproute2 gnupg software-properties-common locales file tzdata <span class="o">&amp;&amp;</span> <span class="se">\
</span>    locale-gen en_US.UTF-8 <span class="o">&amp;&amp;</span> <span class="se">\
</span>    update-locale <span class="nv">LANG</span><span class="o">=</span>en_US.UTF-8 <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">ln</span> <span class="nt">-sf</span> /usr/share/zoneinfo/Asia/Shanghai /etc/localtime <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">echo</span> <span class="s2">"Asia/Shanghai"</span> <span class="o">&gt;</span> /etc/timezone <span class="o">&amp;&amp;</span> <span class="se">\
</span>    dpkg-reconfigure <span class="nt">-f</span> noninteractive tzdata <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get clean <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">rm</span> <span class="nt">-rf</span> /var/lib/apt/lists/<span class="k">*</span>

<span class="c"># 设置工作目录</span>
<span class="k">WORKDIR</span><span class="s"> /polarion_root</span>

<span class="c"># 复制所有 ZIP 文件和脚本</span>
<span class="k">COPY</span><span class="s"> *.zip ./</span>
<span class="k">COPY</span><span class="s"> pl_starter.sh ./</span>
<span class="k">COPY</span><span class="s"> pl_installer.sh ./</span>
<span class="k">COPY</span><span class="s"> auto_installer.exp ./</span>

<span class="c"># 动态检测和解压 Polarion 安装包</span>
<span class="k">RUN </span><span class="nb">echo</span> <span class="s2">"检测 Polarion 安装包..."</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="c"># 查找 Polarion ZIP 文件</span>
    if [ -n "$POLARION_VERSION" ]; then \
        echo "查找指定版本: $POLARION_VERSION"; \
        POLARION_ZIP=$(find /polarion_root -name "*${POLARION_VERSION}*linux*.zip" | head -1); \
    fi &amp;&amp; \
    # 如果未找到指定版本或未指定版本，查找所有可用版本
    if [ -z "$POLARION_ZIP" ]; then \
        POLARION_ZIP=$(find /polarion_root -name "*Polarion*linux*.zip" -o -name "*polarion*linux*.zip" | sort -V | tail -1); \
    fi &amp;&amp; \
    # 检查是否找到安装包
    if [ -z "$POLARION_ZIP" ]; then \
        echo "错误: 未找到任何 Polarion ZIP 安装包"; \
        exit 1; \
    fi &amp;&amp; \
    echo "使用安装包: $(basename $POLARION_ZIP)" &amp;&amp; \
    # 解压安装包
    echo "解压安装包..." &amp;&amp; \
    unzip -q "$POLARION_ZIP" &amp;&amp; \
    # 查找解压后的目录
    POLARION_DIR=$(find /polarion_root -maxdepth 1 -type d -name "*Polarion*" | head -1) &amp;&amp; \
    if [ -z "$POLARION_DIR" ]; then \
        echo "错误: 解压后未找到 Polarion 目录"; \
        exit 1; \
    fi &amp;&amp; \
    echo "Polarion 目录: $POLARION_DIR" &amp;&amp; \
    # 标准化目录名
    if [ "$POLARION_DIR" != "/polarion_root/Polarion" ]; then \
        mv "$POLARION_DIR" /polarion_root/Polarion; \
    fi &amp;&amp; \
    # 复制安装脚本到正确位置
    cp /polarion_root/pl_installer.sh /polarion_root/Polarion/ &amp;&amp; \
    cp /polarion_root/auto_installer.exp /polarion_root/Polarion/ &amp;&amp; \
    # 设置执行权限
    chmod +x /polarion_root/pl_starter.sh &amp;&amp; \
    chmod +x /polarion_root/Polarion/pl_installer.sh &amp;&amp; \
    chmod +x /polarion_root/Polarion/auto_installer.exp &amp;&amp; \
    echo "版本检测和准备完成"

# 根据架构安装 OpenJDK 11
<span class="k">RUN </span><span class="nb">echo</span> <span class="s2">"当前架构: </span><span class="nv">$TARGETARCH</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$TARGETARCH</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"amd64"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> <span class="se">\
</span>        <span class="nv">JDK_ARCH</span><span class="o">=</span><span class="s2">"x64"</span><span class="p">;</span> <span class="se">\
</span>        <span class="nv">JDK_URL</span><span class="o">=</span><span class="s2">"https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.23%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.23_9.tar.gz"</span><span class="p">;</span> <span class="se">\
</span>    <span class="k">elif</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$TARGETARCH</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"arm64"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span> <span class="se">\
</span>        <span class="nv">JDK_ARCH</span><span class="o">=</span><span class="s2">"aarch64"</span><span class="p">;</span> <span class="se">\
</span>        <span class="nv">JDK_URL</span><span class="o">=</span><span class="s2">"https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.23%2B9/OpenJDK11U-jdk_aarch64_linux_hotspot_11.0.23_9.tar.gz"</span><span class="p">;</span> <span class="se">\
</span>    <span class="k">else</span> <span class="se">\
</span>        <span class="nb">echo</span> <span class="s2">"不支持的架构: </span><span class="nv">$TARGETARCH</span><span class="s2">"</span><span class="p">;</span> <span class="se">\
</span>        <span class="nb">exit </span>1<span class="p">;</span> <span class="se">\
</span>    <span class="k">fi</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">echo</span> <span class="s2">"下载 JDK for </span><span class="nv">$JDK_ARCH</span><span class="s2">..."</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    wget <span class="nt">--no-check-certificate</span> <span class="s2">"</span><span class="nv">$JDK_URL</span><span class="s2">"</span> <span class="nt">-O</span> openjdk.tar.gz <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">mkdir</span> <span class="nt">-p</span> /usr/lib/jvm <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">tar</span> <span class="nt">-zxf</span> openjdk.tar.gz <span class="nt">-C</span> /usr/lib/jvm <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">rm </span>openjdk.tar.gz

<span class="c"># 配置 Java 环境</span>
<span class="k">RUN </span><span class="nv">JDK_DIR</span><span class="o">=</span><span class="si">$(</span>find /usr/lib/jvm <span class="nt">-maxdepth</span> 1 <span class="nt">-type</span> d <span class="nt">-name</span> <span class="s2">"jdk-*"</span> | <span class="nb">head</span> <span class="nt">-1</span><span class="si">)</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">echo</span> <span class="s2">"JDK 目录: </span><span class="nv">$JDK_DIR</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    update-alternatives <span class="nt">--install</span> /usr/bin/java java <span class="nv">$JDK_DIR</span>/bin/java 100 <span class="o">&amp;&amp;</span> <span class="se">\
</span>    update-alternatives <span class="nt">--install</span> /usr/bin/jar jar <span class="nv">$JDK_DIR</span>/bin/jar 100 <span class="o">&amp;&amp;</span> <span class="se">\
</span>    update-alternatives <span class="nt">--install</span> /usr/bin/javac javac <span class="nv">$JDK_DIR</span>/bin/javac 100 <span class="o">&amp;&amp;</span> <span class="se">\
</span>    update-alternatives <span class="nt">--set</span> jar <span class="nv">$JDK_DIR</span>/bin/jar <span class="o">&amp;&amp;</span> <span class="se">\
</span>    update-alternatives <span class="nt">--set</span> javac <span class="nv">$JDK_DIR</span>/bin/javac <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">echo</span> <span class="s2">"JAVA_HOME=</span><span class="se">\"</span><span class="nv">$JDK_DIR</span><span class="se">\"</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> /etc/environment <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">echo</span> <span class="s2">"JDK_HOME=</span><span class="se">\"</span><span class="nv">$JDK_DIR</span><span class="se">\"</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> /etc/environment

<span class="c"># 设置 Java 环境变量</span>
<span class="k">ENV</span><span class="s"> JAVA_HOME=/usr/lib/jvm/jdk-11.0.23+9</span>
<span class="k">ENV</span><span class="s"> JDK_HOME=/usr/lib/jvm/jdk-11.0.23+9</span>

<span class="c"># 动态设置 JAVA_HOME</span>
<span class="k">RUN </span><span class="nv">JDK_DIR</span><span class="o">=</span><span class="si">$(</span>find /usr/lib/jvm <span class="nt">-maxdepth</span> 1 <span class="nt">-type</span> d <span class="nt">-name</span> <span class="s2">"jdk-*"</span> | <span class="nb">head</span> <span class="nt">-1</span><span class="si">)</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">echo</span> <span class="s2">"export JAVA_HOME=</span><span class="nv">$JDK_DIR</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> /etc/bash.bashrc <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">echo</span> <span class="s2">"export JDK_HOME=</span><span class="nv">$JDK_DIR</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> /etc/bash.bashrc <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">echo</span> <span class="s2">"JAVA_HOME and JDK_HOME 已设置为: </span><span class="nv">$JDK_DIR</span><span class="s2">"</span>

<span class="c"># 切换到 Polarion 目录进行安装</span>
<span class="k">WORKDIR</span><span class="s"> /polarion_root/Polarion</span>

<span class="c"># 配置系统服务</span>
<span class="k">RUN </span><span class="nb">printf</span> <span class="s1">'#!/bin/sh\nexit 0'</span> <span class="o">&gt;</span> /usr/sbin/policy-rc.d <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">chmod</span> +x /usr/sbin/policy-rc.d <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">sed</span> <span class="nt">-i</span> <span class="s2">"s/^exit 101</span><span class="nv">$/</span><span class="s2">exit 0/"</span> /usr/sbin/policy-rc.d

<span class="c"># 执行 Polarion 安装</span>
<span class="k">RUN </span>./pl_installer.sh

<span class="c"># 返回根目录</span>
<span class="k">WORKDIR</span><span class="s"> /polarion_root</span>

<span class="c"># 设置 PostgreSQL 路径</span>
<span class="k">ENV</span><span class="s"> PATH="/usr/lib/postgresql/14/bin:${PATH}"</span>

<span class="c"># 设置入口点</span>
<span class="k">ENTRYPOINT</span><span class="s"> ["./pl_starter.sh"]</span>
</code></pre></div></div>

<h3 id="dockerfile-功能说明">Dockerfile 功能说明</h3>

<p>这个 Dockerfile 实现了以下核心功能：</p>

<ol>
  <li><strong>多架构支持</strong>: 自动检测 x86_64 和 ARM64 架构，下载对应的 JDK 版本</li>
  <li><strong>动态版本选择</strong>: 通过 <code class="language-plaintext highlighter-rouge">POLARION_VERSION</code> 参数指定要安装的 Polarion 版本</li>
  <li><strong>自动化安装</strong>: 使用 expect 脚本实现无人值守安装</li>
  <li><strong>环境优化</strong>: 预配置时区、语言环境和必要的系统工具</li>
</ol>

<h3 id="相关脚本说明">相关脚本说明</h3>

<ul>
  <li><strong>pl_installer.sh</strong>: Polarion 安装脚本，处理安装过程中的交互</li>
  <li><strong>auto_installer.exp</strong>: Expect 自动化脚本，用于无人值守安装</li>
  <li><strong>pl_starter.sh</strong>: 容器启动脚本，负责启动相关服务</li>
</ul>

<h2 id="polarion-启动脚本">Polarion 启动脚本</h2>

<p>以下是容器启动时执行的 <code class="language-plaintext highlighter-rouge">pl_starter.sh</code> 脚本：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="c"># 设置时区为中国上海</span>
<span class="nb">export </span><span class="nv">TZ</span><span class="o">=</span>Asia/Shanghai
<span class="nb">ln</span> <span class="nt">-sf</span> /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
<span class="nb">echo</span> <span class="s2">"Asia/Shanghai"</span> <span class="o">&gt;</span> /etc/timezone

<span class="c"># PostgreSQL 端口配置</span>
<span class="nv">POSTGRES_PORT</span><span class="o">=</span>5434

<span class="c"># 启动 PostgreSQL 服务</span>
<span class="nb">sudo</span> <span class="nt">-u</span> postgres /usr/lib/postgresql/14/bin/pg_ctl <span class="nt">-D</span> /opt/polarion/data/postgres-data <span class="nt">-l</span> /opt/polarion/data/postgres-data/log.out <span class="nt">-o</span> <span class="s2">"-p </span><span class="nv">$POSTGRES_PORT</span><span class="s2">"</span> start

<span class="c"># 启动 Apache 服务</span>
service apache2 start

<span class="c"># Polarion 配置文件路径</span>
<span class="nv">FILE</span><span class="o">=</span><span class="s2">"/opt/polarion/etc/polarion.properties"</span>

<span class="c"># 其他配置参数</span>
<span class="nv">OTHER_PARAMS</span><span class="o">=(</span>
    <span class="s2">"com.siemens.polarion.rest.enabled=true"</span>
    <span class="s2">"com.siemens.polarion.rest.swaggerUi.enabled=true"</span>
    <span class="s2">"com.siemens.polarion.rest.cors.allowedOrigins=*"</span>
    <span class="s2">"com.polarion.platform.internalPG=polarion:polarion@localhost:</span><span class="nv">$POSTGRES_PORT</span><span class="s2">"</span>
<span class="o">)</span>

<span class="c"># 处理允许的主机列表</span>
<span class="k">if</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$ALLOWED_HOSTS</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">TomcatServiceRequestSafeListedHosts</span><span class="o">=</span><span class="s2">"TomcatService.request.safeListedHosts=</span><span class="nv">$ALLOWED_HOSTS</span><span class="s2">"</span>
<span class="k">elif</span> <span class="o">[[</span> <span class="s2">"$#"</span> <span class="nt">-gt</span> 0 <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">TomcatServiceRequestSafeListedHostsValues</span><span class="o">=</span><span class="si">$(</span><span class="nb">printf</span> <span class="s2">"%s,"</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span><span class="si">)</span>
    <span class="nv">TomcatServiceRequestSafeListedHosts</span><span class="o">=</span><span class="s2">"TomcatService.request.safeListedHosts=</span><span class="k">${</span><span class="nv">TomcatServiceRequestSafeListedHostsValues</span><span class="p">%,</span><span class="k">}</span><span class="s2">"</span>
<span class="k">else
    </span><span class="nb">echo</span> <span class="s2">"No values provided for TomcatService.request.safeListedHosts. Exiting"</span>
    <span class="nb">exit </span>1
<span class="k">fi</span>

<span class="c"># 合并所有配置参数</span>
<span class="nv">PARAMS</span><span class="o">=(</span>
    <span class="s2">"</span><span class="nv">$TomcatServiceRequestSafeListedHosts</span><span class="s2">"</span>
    <span class="s2">"</span><span class="k">${</span><span class="nv">OTHER_PARAMS</span><span class="p">[@]</span><span class="k">}</span><span class="s2">"</span>
<span class="o">)</span>

<span class="c"># 移除配置文件末尾标记</span>
<span class="nb">sed</span> <span class="nt">-i</span> <span class="s1">'/^# End property file$/d'</span> <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span>

<span class="c"># 添加或更新配置参数的函数</span>
add_or_update_param<span class="o">()</span> <span class="o">{</span>
    <span class="nb">local </span><span class="nv">param</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
    <span class="nb">local </span><span class="nv">param_name</span><span class="o">=</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$param</span><span class="s2">"</span> | <span class="nb">cut</span> <span class="nt">-d</span> <span class="s1">'='</span> <span class="nt">-f</span> 1<span class="si">)</span>

    <span class="k">if </span><span class="nb">grep</span> <span class="nt">-q</span> <span class="s2">"^</span><span class="nv">$param_name</span><span class="s2">="</span> <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">sed</span> <span class="nt">-i</span> <span class="s2">"/^</span><span class="nv">$param_name</span><span class="s2">=/c</span><span class="se">\\</span><span class="nv">$param</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span>
    <span class="k">else
        </span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$param</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span>
    <span class="k">fi</span>
<span class="o">}</span>

<span class="c"># 应用所有配置参数</span>
<span class="k">for </span>param <span class="k">in</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PARAMS</span><span class="p">[@]</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="k">do
    </span>add_or_update_param <span class="s2">"</span><span class="nv">$param</span><span class="s2">"</span>
<span class="k">done
</span><span class="nb">echo</span> <span class="s2">"# End property file"</span> <span class="o">&gt;&gt;</span> <span class="s2">"</span><span class="nv">$FILE</span><span class="s2">"</span>

<span class="nb">echo</span> <span class="s2">"Polarion Properties Updated Successfully."</span>

<span class="c"># 默认不自动启动 Polarion 服务，需要手动启动</span>
<span class="c"># service polarion start</span>

<span class="c"># 保持容器运行</span>
<span class="nb">wait
tail</span> <span class="nt">-f</span> /dev/null
</code></pre></div></div>

<h3 id="启动脚本功能说明">启动脚本功能说明</h3>

<p>这个脚本负责容器启动时的初始化工作：</p>

<ol>
  <li><strong>时区设置</strong>: 配置为中国上海时区</li>
  <li><strong>服务启动</strong>: 启动 PostgreSQL 和 Apache 服务</li>
  <li><strong>配置更新</strong>: 动态更新 Polarion 配置文件</li>
  <li><strong>安全配置</strong>: 设置允许访问的主机列表</li>
  <li><strong>REST API</strong>: 启用 REST API 和 Swagger UI</li>
  <li><strong>CORS 支持</strong>: 配置跨域访问支持</li>
</ol>

<blockquote>
  <p><strong>注意</strong>: 脚本默认不自动启动 Polarion 服务，需要手动启动以便进行开发调试。</p>
</blockquote>

<h2 id="开发环境设置">开发环境设置</h2>

<p>使用 Docker 构建镜像后，Polarion 文件都在容器内部。为了便于开发，我们需要解决以下问题：</p>

<ol>
  <li><strong>文件同步问题</strong>: 每次修改代码都需要复制文件到容器中</li>
  <li><strong>服务管理问题</strong>: 需要进入容器才能重启 Polarion 服务</li>
  <li><strong>权限问题</strong>: 容器内外的文件权限不一致</li>
</ol>

<h3 id="解决方案">解决方案</h3>

<p>我们使用 Docker volumes 功能将 Polarion 目录挂载到宿主机，并提供便捷的管理脚本。</p>

<p><strong>仓库地址</strong>: <a href="https://github.com/2han9wen71an/Polarion_Docker_Development_Environment">Polarion_Docker_Development_Environment</a></p>

<h3 id="快速开始">快速开始</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 克隆开发环境仓库</span>
git clone https://github.com/2han9wen71an/Polarion_Docker_Development_Environment.git
<span class="nb">cd </span>Polarion_Docker_Development_Environment

<span class="c"># 设置执行权限</span>
<span class="nb">chmod</span> +x setup_polarion_dev_env.sh

<span class="c"># 运行一键配置脚本</span>
./setup_polarion_dev_env.sh
</code></pre></div></div>

<h3 id="脚本功能特性">脚本功能特性</h3>

<p>这个一键配置脚本提供以下功能：</p>

<ul>
  <li>✅ <strong>完整环境配置</strong>: 自动配置 Docker 开发环境</li>
  <li>✅ <strong>卷挂载管理</strong>: 自动处理文件挂载和权限问题</li>
  <li>✅ <strong>PostgreSQL 优化</strong>: 针对数据库的特殊权限要求进行优化</li>
  <li>✅ <strong>全局别名系统</strong>: 在任何目录都可以使用简短命令</li>
  <li>✅ <strong>Shell 兼容</strong>: 自动检测 shell 类型并配置别名</li>
  <li>✅ <strong>工作流指导</strong>: 提供完整的开发工作流程说明</li>
</ul>

<h2 id="使用说明">使用说明</h2>

<h3 id="快速开始-1">快速开始</h3>

<h4 id="1-运行配置脚本">1. 运行配置脚本</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 克隆开发环境仓库</span>
git clone https://github.com/2han9wen71an/Polarion_Docker_Development_Environment.git
<span class="nb">cd </span>Polarion_Docker_Development_Environment

<span class="c"># 运行一键配置脚本</span>
./setup_polarion_dev_env.sh
</code></pre></div></div>

<h4 id="2-重新加载-shell-配置">2. 重新加载 Shell 配置</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 根据你的 shell 类型选择</span>
<span class="nb">source</span> ~/.zshrc    <span class="c"># 对于 zsh</span>
<span class="nb">source</span> ~/.bashrc   <span class="c"># 对于 bash</span>
</code></pre></div></div>

<h4 id="3-启动服务推荐使用全局别名">3. 启动服务（推荐使用全局别名）</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 检查服务状态（可在任何目录执行）</span>
polarion-status

<span class="c"># 启动 PostgreSQL 数据库</span>
postgresql-start

<span class="c"># 启动 Polarion 应用</span>
polarion-start
</code></pre></div></div>

<h4 id="4-访问-polarion">4. 访问 Polarion</h4>
<p>打开浏览器访问：http://localhost:8080/polarion</p>

<h3 id="脚本功能选项">脚本功能选项</h3>

<h4 id="完整配置">完整配置</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./setup_polarion_dev_env.sh
</code></pre></div></div>
<p>执行完整的开发环境配置流程：</p>
<ul>
  <li>检查 Docker 和镜像</li>
  <li>清理现有容器</li>
  <li>配置挂载目录</li>
  <li>初始化 Polarion 数据</li>
  <li>修复所有权限问题</li>
  <li>创建开发容器</li>
</ul>

<h4 id="仅修复权限">仅修复权限</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./setup_polarion_dev_env.sh fix-permissions
</code></pre></div></div>
<p>仅修复现有环境的权限问题，适用于：</p>
<ul>
  <li>权限配置出错时</li>
  <li>升级系统后权限变化</li>
  <li>手动修改文件后需要重置权限</li>
</ul>

<h4 id="仅创建全局别名">仅创建全局别名</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./setup_polarion_dev_env.sh create-aliases
</code></pre></div></div>
<p>仅创建全局 shell 别名，适用于：</p>
<ul>
  <li>别名丢失或损坏时</li>
  <li>需要重新配置全局命令时</li>
  <li>更换 shell 或配置文件时</li>
</ul>

<h4 id="查看帮助">查看帮助</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./setup_polarion_dev_env.sh <span class="nb">help</span>
</code></pre></div></div>

<h3 id="全局别名系统">全局别名系统</h3>

<p>配置完成后，会自动创建全局 shell 别名，让你可以在<strong>任何目录</strong>直接使用简短命令控制容器内的服务：</p>

<h4 id="全局别名列表">全局别名列表</h4>
<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">polarion-status</code></strong> - 检查所有服务状态</li>
  <li><strong><code class="language-plaintext highlighter-rouge">polarion-start</code></strong> - 启动 Polarion 服务</li>
  <li><strong><code class="language-plaintext highlighter-rouge">polarion-stop</code></strong> - 停止 Polarion 服务</li>
  <li><strong><code class="language-plaintext highlighter-rouge">polarion-restart</code></strong> - 重启 Polarion 服务</li>
  <li><strong><code class="language-plaintext highlighter-rouge">postgresql-start</code></strong> - 启动 PostgreSQL 服务</li>
  <li><strong><code class="language-plaintext highlighter-rouge">postgresql-stop</code></strong> - 停止 PostgreSQL 服务</li>
  <li><strong><code class="language-plaintext highlighter-rouge">polarion-shell</code></strong> - 进入容器</li>
  <li><strong><code class="language-plaintext highlighter-rouge">polarion-exec</code></strong> - 执行容器内命令</li>
  <li><strong><code class="language-plaintext highlighter-rouge">polarion-logs</code></strong> - 智能日志查看器（支持多种日志类型）</li>
</ul>

<h4 id="全局别名使用示例">全局别名使用示例</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 检查服务状态（可在任何目录执行）</span>
polarion-status

<span class="c"># 启动/停止 PostgreSQL</span>
postgresql-start
postgresql-stop

<span class="c"># 启动/停止/重启 Polarion</span>
polarion-start
polarion-stop
polarion-restart

<span class="c"># 进入容器</span>
polarion-shell

<span class="c"># 执行容器内命令</span>
polarion-exec ps aux
polarion-exec <span class="nb">tail</span> <span class="nt">-f</span> /opt/polarion/data/logs/main/polarion.log

<span class="c"># 查看日志（智能日志查看器）</span>
polarion-logs          <span class="c"># 交互模式，选择日志类型</span>
polarion-logs main     <span class="c"># 查看主日志</span>
polarion-logs error    <span class="c"># 查看错误日志</span>
polarion-logs startup  <span class="c"># 查看启动日志</span>
polarion-logs list     <span class="c"># 列出所有日志文件</span>
</code></pre></div></div>

<h3 id="开发工作流程">开发工作流程</h3>

<h4 id="推荐工作流程使用全局别名">推荐工作流程（使用全局别名）</h4>

<ol>
  <li><strong>启动开发环境</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./setup_polarion_dev_env.sh
</code></pre></div>    </div>
  </li>
  <li><strong>重新加载 shell 配置</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source</span> ~/.zshrc  <span class="c"># 或 source ~/.bashrc</span>
</code></pre></div>    </div>
  </li>
  <li><strong>检查服务状态（可在任何目录执行）</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>polarion-status
</code></pre></div>    </div>
  </li>
  <li><strong>启动必要服务</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postgresql-start
polarion-start
</code></pre></div>    </div>
  </li>
  <li><strong>开发和调试</strong>
    <ul>
      <li>在宿主机 <code class="language-plaintext highlighter-rouge">/opt/polarion</code> 目录中修改文件</li>
      <li>修改会立即反映到容器中</li>
      <li>根据需要重启相应服务：<code class="language-plaintext highlighter-rouge">polarion-restart</code></li>
    </ul>
  </li>
  <li><strong>停止服务</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>polarion-stop
postgresql-stop
</code></pre></div>    </div>
  </li>
</ol>

<h4 id="传统工作流程进入容器">传统工作流程（进入容器）</h4>

<ol>
  <li><strong>启动开发环境</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./setup_polarion_dev_env.sh
</code></pre></div>    </div>
  </li>
  <li><strong>进入容器</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>polarion-shell
</code></pre></div>    </div>
  </li>
  <li><strong>启动必要服务</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>service postgresql start
<span class="nb">sudo </span>service apache2 start
<span class="nb">sudo </span>service polarion start
</code></pre></div>    </div>
  </li>
  <li><strong>开发和调试</strong>
    <ul>
      <li>在宿主机 <code class="language-plaintext highlighter-rouge">/opt/polarion</code> 目录中修改文件</li>
      <li>修改会立即反映到容器中</li>
      <li>根据需要重启相应服务</li>
    </ul>
  </li>
  <li><strong>停止服务</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>service polarion stop
<span class="nb">sudo </span>service apache2 stop
<span class="nb">sudo </span>service postgresql stop
</code></pre></div>    </div>
  </li>
</ol>

<h3 id="目录结构">目录结构</h3>

<h4 id="宿主机目录">宿主机目录</h4>
<ul>
  <li><strong>配置目录</strong>: <code class="language-plaintext highlighter-rouge">/opt/polarion/etc/</code></li>
  <li><strong>数据目录</strong>: <code class="language-plaintext highlighter-rouge">/opt/polarion/data/</code></li>
  <li><strong>日志目录</strong>: <code class="language-plaintext highlighter-rouge">/opt/polarion/data/logs/</code></li>
  <li><strong>插件目录</strong>: <code class="language-plaintext highlighter-rouge">/opt/polarion/polarion/plugins/</code></li>
  <li><strong>PostgreSQL 数据</strong>: <code class="language-plaintext highlighter-rouge">/opt/polarion/data/postgres-data/</code></li>
</ul>

<h4 id="容器内目录">容器内目录</h4>
<p>所有目录都挂载到容器内的 <code class="language-plaintext highlighter-rouge">/opt/polarion/</code> 对应位置。</p>

<h2 id="配置参数详解">配置参数详解</h2>

<h3 id="环境变量">环境变量</h3>

<table>
  <thead>
    <tr>
      <th>变量名</th>
      <th>描述</th>
      <th>默认值</th>
      <th>示例</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ALLOWED_HOSTS</code></td>
      <td>允许访问的主机列表</td>
      <td>无</td>
      <td><code class="language-plaintext highlighter-rouge">localhost,192.168.1.100</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">POSTGRES_PORT</code></td>
      <td>PostgreSQL 端口</td>
      <td><code class="language-plaintext highlighter-rouge">5434</code></td>
      <td><code class="language-plaintext highlighter-rouge">5432</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TZ</code></td>
      <td>时区设置</td>
      <td><code class="language-plaintext highlighter-rouge">Asia/Shanghai</code></td>
      <td><code class="language-plaintext highlighter-rouge">UTC</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">POLARION_VERSION</code></td>
      <td>构建时指定版本</td>
      <td>自动检测</td>
      <td><code class="language-plaintext highlighter-rouge">21R2</code></td>
    </tr>
  </tbody>
</table>

<h3 id="端口映射">端口映射</h3>

<table>
  <thead>
    <tr>
      <th>容器端口</th>
      <th>服务</th>
      <th>描述</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">8080</code></td>
      <td>Polarion Web</td>
      <td>主要的 Web 界面端口</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">5434</code></td>
      <td>PostgreSQL</td>
      <td>数据库服务端口</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">80</code></td>
      <td>Apache</td>
      <td>HTTP 服务端口</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">443</code></td>
      <td>Apache SSL</td>
      <td>HTTPS 服务端口（如果配置）</td>
    </tr>
  </tbody>
</table>

<h3 id="权限说明">权限说明</h3>

<h4 id="postgresql-特殊权限">PostgreSQL 特殊权限</h4>
<ul>
  <li>PostgreSQL 数据目录权限：<strong>750</strong> (必须)</li>
  <li>PostgreSQL 文件权限：<strong>640</strong></li>
  <li>这是 PostgreSQL 的安全要求，不能设置为 777</li>
</ul>

<h4 id="其他目录权限">其他目录权限</h4>
<ul>
  <li>配置目录：<strong>777</strong> (开发需要)</li>
  <li>工作空间目录：<strong>777</strong> (开发需要)</li>
  <li>日志目录：<strong>777</strong> (开发需要)</li>
</ul>

<h2 id="故障排除">故障排除</h2>

<h3 id="常见问题及解决方案">常见问题及解决方案</h3>

<h4 id="1-容器启动后无法访问-polarion">1. 容器启动后无法访问 Polarion</h4>

<p><strong>问题</strong>: 容器启动后无法访问 Polarion</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 检查服务是否已启动</span>
docker <span class="nb">exec</span> <span class="nt">-it</span> polarion22r1 bash
<span class="nb">sudo </span>service postgresql status
<span class="nb">sudo </span>service apache2 status
<span class="nb">sudo </span>service polarion status
</code></pre></div></div>

<p><strong>解决方案</strong>:</p>
<ul>
  <li>确保所有服务都已启动</li>
  <li>使用全局别名检查状态：<code class="language-plaintext highlighter-rouge">polarion-status</code></li>
</ul>

<h4 id="2-postgresql-启动失败">2. PostgreSQL 启动失败</h4>

<p><strong>问题</strong>: PostgreSQL 服务启动失败</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 通常是权限问题，运行权限修复</span>
./setup_polarion_dev_env.sh fix-permissions
</code></pre></div></div>

<p><strong>解决方案</strong>:</p>
<ul>
  <li>检查 PostgreSQL 数据目录权限是否为 750</li>
  <li>确保 postgres 用户拥有数据目录</li>
</ul>

<h4 id="3-修改配置文件后不生效">3. 修改配置文件后不生效</h4>

<p><strong>问题</strong>: 修改配置文件后不生效</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 重启相应的服务</span>
docker <span class="nb">exec</span> <span class="nt">-it</span> polarion22r1 <span class="nb">sudo </span>service polarion restart
<span class="c"># 或使用全局别名</span>
polarion-restart
</code></pre></div></div>

<h4 id="4-arm64-架构兼容性问题">4. ARM64 架构兼容性问题</h4>

<p><strong>问题</strong>: 在 Apple Silicon 或 ARM 服务器上运行出错</p>

<p><strong>解决方案</strong>: 参考《<a href="/Toss-notes/polarion-arm-fix.html">Polarion-Arm-环境修复</a>》文章中的详细修复步骤：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 应用 ARM64 兼容性修复</span>
git clone https://github.com/2han9wen71an/Polarion_Arm64_Compatibility.git
<span class="nb">cd </span>Polarion_Arm64_Compatibility
./main.sh all /opt/polarion
</code></pre></div></div>

<h4 id="5-如何查看日志">5. 如何查看日志</h4>

<p><strong>问题</strong>: 需要查看各种日志进行调试</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 容器日志</span>
docker logs polarion22r1

<span class="c"># Polarion 应用日志</span>
docker <span class="nb">exec</span> <span class="nt">-it</span> polarion22r1 <span class="nb">tail</span> <span class="nt">-f</span> /opt/polarion/data/logs/main/polarion.log
<span class="c"># 或使用全局别名</span>
polarion-logs main

<span class="c"># PostgreSQL 日志</span>
docker <span class="nb">exec</span> <span class="nt">-it</span> polarion22r1 <span class="nb">tail</span> <span class="nt">-f</span> /opt/polarion/data/postgres-data/log.out

<span class="c"># 智能日志查看器（推荐）</span>
polarion-logs  <span class="c"># 交互模式选择日志类型</span>
</code></pre></div></div>

<h3 id="开发提示">开发提示</h3>

<ol>
  <li><strong>文件修改</strong>: 直接在宿主机 <code class="language-plaintext highlighter-rouge">/opt/polarion</code> 目录中修改文件</li>
  <li><strong>配置更改</strong>: 修改配置后记得重启相应服务</li>
  <li><strong>插件开发</strong>: 插件文件放在 <code class="language-plaintext highlighter-rouge">/opt/polarion/polarion/plugins/</code> 目录</li>
  <li><strong>数据备份</strong>: 重要数据建议定期备份 <code class="language-plaintext highlighter-rouge">/opt/polarion/data/</code> 目录</li>
  <li><strong>资源管理</strong>: 开发完成后停止服务以释放系统资源</li>
</ol>

<h3 id="故障排除流程">故障排除流程</h3>

<p>如果遇到问题，按以下顺序排查：</p>

<ol>
  <li><strong>检查 Docker 状态</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker ps
docker logs polarion22r1
</code></pre></div>    </div>
  </li>
  <li><strong>检查权限</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./setup_polarion_dev_env.sh fix-permissions
</code></pre></div>    </div>
  </li>
  <li><strong>重新配置环境</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./setup_polarion_dev_env.sh
</code></pre></div>    </div>
  </li>
  <li><strong>查看详细日志</strong>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker <span class="nb">exec</span> <span class="nt">-it</span> polarion22r1 bash
<span class="nb">tail</span> <span class="nt">-f</span> /opt/polarion/data/logs/main/polarion.log
</code></pre></div>    </div>
  </li>
</ol>

<h2 id="版本兼容性">版本兼容性</h2>

<h3 id="支持的-polarion-版本">支持的 Polarion 版本</h3>

<table>
  <thead>
    <tr>
      <th>Polarion 版本</th>
      <th>状态</th>
      <th>备注</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>21R2</td>
      <td>✅ 完全支持</td>
      <td>推荐版本，经过充分测试</td>
    </tr>
    <tr>
      <td>22R1</td>
      <td>✅ 完全支持</td>
      <td>支持最新功能</td>
    </tr>
    <tr>
      <td>22R2</td>
      <td>✅ 完全支持</td>
      <td>最新稳定版本</td>
    </tr>
    <tr>
      <td>20R2</td>
      <td>⚠️ 部分支持</td>
      <td>需要额外配置</td>
    </tr>
    <tr>
      <td>更早版本</td>
      <td>❌ 不支持</td>
      <td>建议升级到支持版本</td>
    </tr>
  </tbody>
</table>

<h3 id="架构兼容性">架构兼容性</h3>

<table>
  <thead>
    <tr>
      <th>架构</th>
      <th>状态</th>
      <th>备注</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>x86_64 (AMD64)</td>
      <td>✅ 完全支持</td>
      <td>原生支持，性能最佳</td>
    </tr>
    <tr>
      <td>ARM64 (Apple Silicon)</td>
      <td>✅ 支持</td>
      <td>需要兼容性修复，参考 ARM 修复文章</td>
    </tr>
    <tr>
      <td>ARM64 (服务器)</td>
      <td>✅ 支持</td>
      <td>需要兼容性修复</td>
    </tr>
  </tbody>
</table>

<h3 id="操作系统兼容性">操作系统兼容性</h3>

<table>
  <thead>
    <tr>
      <th>操作系统</th>
      <th>Docker 版本要求</th>
      <th>状态</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Ubuntu 20.04+</td>
      <td>20.10+</td>
      <td>✅ 推荐</td>
    </tr>
    <tr>
      <td>CentOS 8+</td>
      <td>20.10+</td>
      <td>✅ 支持</td>
    </tr>
    <tr>
      <td>macOS 11+</td>
      <td>Docker Desktop 4.0+</td>
      <td>✅ 支持</td>
    </tr>
    <tr>
      <td>Windows 10/11</td>
      <td>Docker Desktop 4.0+ with WSL2</td>
      <td>✅ 支持</td>
    </tr>
  </tbody>
</table>

<h3 id="升级注意事项">升级注意事项</h3>

<ol>
  <li><strong>数据备份</strong>: 升级前务必备份 PostgreSQL 数据和 Polarion 配置</li>
  <li><strong>版本兼容</strong>: 检查插件和自定义代码的版本兼容性</li>
  <li><strong>测试环境</strong>: 先在测试环境验证升级流程</li>
  <li><strong>回滚计划</strong>: 准备回滚方案以防升级失败</li>
</ol>

<hr />

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

<p>本文介绍了使用 Docker 搭建 Polarion 开发环境的完整方案，包括：</p>

<ul>
  <li>🐳 <strong>多架构 Docker 镜像构建</strong></li>
  <li>🔧 <strong>自动化安装和配置脚本</strong></li>
  <li>📁 <strong>开发环境卷挂载方案</strong></li>
  <li>🛠️ <strong>常见问题故障排除</strong></li>
  <li>⚡ <strong>性能优化建议</strong></li>
  <li>🔄 <strong>版本兼容性说明</strong></li>
</ul>

<p>结合《<a href="/Toss-notes/polarion-arm-fix.html">Polarion-Arm-环境修复</a>》文章中的 ARM 兼容性修复方案，您可以在各种环境下快速搭建稳定的 Polarion 开发环境。</p>

<p>如果您在使用过程中遇到问题，欢迎参考故障排除章节或查看相关仓库的 Issues。</p>]]></content><author><name>2han9wen71an</name></author><category term="代码笔记" /><category term="折腾笔记" /><category term="生活随笔" /><category term="polarion" /><category term="docker" /><category term="dev" /><category term="多版本" /><category term="开发环境" /><summary type="html"><![CDATA[使用 Docker 部署 Polarion 不同版本进行开发，支持多架构和版本切换]]></summary></entry><entry><title type="html">Polarion-Arm-环境修复</title><link href="/Toss-notes/polarion-arm-fix.html" rel="alternate" type="text/html" title="Polarion-Arm-环境修复" /><published>2025-06-21T03:20:39+00:00</published><updated>2025-06-21T03:20:39+00:00</updated><id>/Toss-notes/Polarion-Arm-%E7%8E%AF%E5%A2%83%E4%BF%AE%E5%A4%8D</id><content type="html" xml:base="/Toss-notes/polarion-arm-fix.html"><![CDATA[<h1 id="前言">前言</h1>

<p>最近换了一台 Mac Mini 用来办公，主要工作是 Polarion 的开发，需要在本地安装开发环境。</p>

<h2 id="背景">背景</h2>
<p>一开始使用 Parallels Desktop 虚拟机安装 Windows 进行开发，但是 ARM64 转译性能比较低，每次启动就要 50 秒以上。</p>

<p>考虑到 Polarion ALM 本质上是一个 Java 应用，可以运行在 Linux 上，于是尝试在 macOS 上直接安装开发环境。虽然 Polarion 官方文档只提供了 Windows 和 Linux 的安装包，没有提供 macOS 的安装包，但由于 macOS 和 Linux 都是类 Unix 系统，理论上应该可以兼容运行。</p>

<h2 id="解决思路">解决思路</h2>
<p>最终采用 Docker 容器的方式安装 Polarion，然后将目录数据挂载到宿主机进行开发。这种方案将启动速度优化到 10 秒以内，效率提升了 5 倍。</p>

<h2 id="目录">目录</h2>
<ul>
  <li><a href="#nodejs-兼容性问题">Node.js 兼容性问题</a></li>
  <li><a href="#jna-问题修复">JNA 问题修复</a></li>
  <li><a href="#一键脚本">一键脚本</a>
    <h1 id="问题描述与解决方案">问题描述与解决方案</h1>
  </li>
</ul>

<h2 id="nodejs-兼容性问题">Node.js 兼容性问题</h2>

<h3 id="问题现象">问题现象</h3>
<p>在 ARM64 环境下运行 Polarion 时，遇到了 Node.js 兼容性问题：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2025-06-20 12:57:56,179 <span class="o">[</span>Thread-118] ERROR class com.polarion.alm.server.util.ChartExporterStartup - Chart renderer says: /opt/polarion/polarion/plugins/com.polarion.alm.ui_3.22.1/node/bin/node: cannot execute binary file
2025-06-20 12:57:56,179 <span class="o">[</span>Thread-119] ERROR class com.polarion.alm.server.util.ChartExporterStartup - Chart renderer has terminated <span class="k">for </span>some reason. Exit code <span class="o">=</span> 126
</code></pre></div></div>

<h3 id="问题分析">问题分析</h3>
<p>这是因为 Polarion 内置的 Node.js 是为 x86_64 架构编译的，无法在 ARM64 环境下运行。</p>

<h3 id="解决方案">解决方案</h3>

<h4 id="步骤1替换-nodejs-二进制文件">步骤1：替换 Node.js 二进制文件</h4>
<ol>
  <li>在 Polarion 安装目录下找到 <code class="language-plaintext highlighter-rouge">plugins/com.polarion.alm.ui_3.22.1/node/</code> 文件夹</li>
  <li>备份原有的 Node.js 文件</li>
  <li>下载 ARM64 架构的 Node.js 版本进行替换</li>
</ol>

<p><strong>版本选择说明：</strong></p>
<ul>
  <li>Polarion 21R2 版本内置的是 Node.js 10.15.0</li>
  <li>Node.js 16.0.0+ 开始正式支持 Apple Silicon (ARM64)</li>
  <li>推荐使用 Node.js 18.x 版本以获得更好的兼容性</li>
</ul>

<h4 id="步骤2处理依赖文件缺失问题">步骤2：处理依赖文件缺失问题</h4>
<p>替换 Node.js 后重启 Polarion，可能会遇到新的错误：</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR: Cannot find module <span class="s1">'/opt/polarion/polarion/plugins/com.polarion.alm.ui_3.22.1/node/bin/highcharts-convert-8.0.3.js'</span>
ERROR: Cannot find module <span class="s1">'/opt/polarion/polarion/plugins/com.polarion.alm.ui_3.22.1/node/bin/mj-formula-convert.js'</span>
</code></pre></div></div>

<p><strong>问题原因：</strong> 替换 Node.js 时丢失了 Polarion 需要的特定 JavaScript 文件。</p>

<p><strong>解决方法：</strong></p>
<ol>
  <li>从备份目录恢复丢失的 JavaScript 文件：
    <ul>
      <li><code class="language-plaintext highlighter-rouge">highcharts-convert-8.0.3.js</code></li>
      <li><code class="language-plaintext highlighter-rouge">mj-formula-convert.js</code></li>
    </ul>
  </li>
  <li>恢复 <code class="language-plaintext highlighter-rouge">node_modules</code> 目录</li>
  <li>重启 Polarion 验证修复效果</li>
</ol>

<h2 id="jna-问题修复">JNA 问题修复</h2>

<h3 id="问题背景">问题背景</h3>
<p>Polarion 的 PureVariant 插件使用了 JNA (Java Native Access) 进行本地调用，但引入的 JNA 版本较低，不支持 ARM64 架构。</p>

<p>根据 <a href="https://github.com/java-native-access/jna">JNA 官方仓库</a> 的信息，ARM64 支持最低需要 JNA 5.7.0 版本。</p>

<h3 id="错误现象">错误现象</h3>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Caused</span> <span class="nl">by:</span> <span class="n">com</span><span class="o">.</span><span class="na">google</span><span class="o">.</span><span class="na">inject</span><span class="o">.</span><span class="na">ConfigurationException</span><span class="o">:</span> <span class="nc">Guice</span> <span class="n">configuration</span> <span class="nl">errors:</span>
<span class="mi">1</span><span class="o">)</span> <span class="nc">No</span> <span class="n">implementation</span> <span class="k">for</span> <span class="n">com</span><span class="o">.</span><span class="na">polarion</span><span class="o">.</span><span class="na">alm</span><span class="o">.</span><span class="na">tracker</span><span class="o">.</span><span class="na">spi</span><span class="o">.</span><span class="na">variantmanagement</span><span class="o">.</span><span class="na">IVariantManagementProvider</span> <span class="n">was</span> <span class="n">bound</span><span class="o">.</span>
   <span class="k">while</span> <span class="n">locating</span> <span class="n">com</span><span class="o">.</span><span class="na">polarion</span><span class="o">.</span><span class="na">alm</span><span class="o">.</span><span class="na">tracker</span><span class="o">.</span><span class="na">spi</span><span class="o">.</span><span class="na">variantmanagement</span><span class="o">.</span><span class="na">IVariantManagementProvider</span>
</code></pre></div></div>

<p><strong>核心错误：</strong> PureVariant 插件的 JNA 依赖不支持 ARM64 架构。</p>

<h3 id="解决方案-1">解决方案</h3>

<h4 id="步骤1下载兼容的-jna-版本">步骤1：下载兼容的 JNA 版本</h4>
<ol>
  <li>从 <a href="https://mvnrepository.com/artifact/net.java.dev.jna/jna">Maven 中央仓库</a> 下载 JNA 5.7.0+ 版本</li>
  <li>确保下载的版本包含 ARM64 支持</li>
</ol>

<h4 id="步骤2替换-jna-库文件">步骤2：替换 JNA 库文件</h4>
<ol>
  <li>备份原有的 JNA jar 文件</li>
  <li>将新版本的 JNA jar 文件替换到 Polarion 的 lib 目录</li>
  <li>重启 Polarion 服务进行验证</li>
</ol>

<h1 id="一键修复脚本">一键修复脚本</h1>

<p>为了方便其他遇到相同问题的开发者，我编写了一键修复脚本。</p>

<h2 id="环境要求">环境要求</h2>
<ul>
  <li><strong>适用版本：</strong> Polarion ALM 21R2</li>
  <li><strong>适用架构：</strong> ARM64 (Apple Silicon)</li>
  <li><strong>操作系统：</strong> macOS</li>
</ul>

<h2 id="使用方法">使用方法</h2>

<h3 id="1-克隆仓库">1. 克隆仓库</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/2han9wen71an/Polarion_Arm64_Compatibility.git
<span class="nb">cd </span>Polarion_Arm64_Compatibility
</code></pre></div></div>

<h3 id="2-设置执行权限">2. 设置执行权限</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">chmod</span> +x main.sh
<span class="nb">chmod</span> +x node/fix_nodejs_arm64.sh
<span class="nb">chmod</span> +x jna/fix_jna_arm64.sh
</code></pre></div></div>

<h3 id="3-执行修复脚本">3. 执行修复脚本</h3>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 修复所有问题（Node.js + JNA）</span>
./main.sh all /opt/polarion

<span class="c"># 或者单独修复</span>
./main.sh node /opt/polarion    <span class="c"># 仅修复 Node.js 问题</span>
./main.sh jna /opt/polarion     <span class="c"># 仅修复 JNA 问题</span>
</code></pre></div></div>

<h2 id="注意事项">注意事项</h2>
<ul>
  <li>执行前请备份 Polarion 安装目录</li>
  <li>确保 Polarion 服务已停止</li>
  <li>脚本执行完成后需要重启 Polarion 服务</li>
</ul>

<p><strong>仓库地址：</strong> <a href="https://github.com/2han9wen71an/Polarion_Arm64_Compatibility">Polarion_Arm64_Compatibility</a></p>]]></content><author><name>2han9wen71an</name></author><category term="折腾笔记" /><category term="Polarion" /><category term="Arm" /><summary type="html"><![CDATA[修复 Polarion 在 Arm 环境下 JNA 和 Node 的兼容性问题]]></summary></entry><entry><title type="html">这是一篇私密文章</title><link href="/%E6%B5%8B%E8%AF%95/private-test.html" rel="alternate" type="text/html" title="这是一篇私密文章" /><published>2025-03-13T00:00:00+00:00</published><updated>2025-03-13T00:00:00+00:00</updated><id>/%E6%B5%8B%E8%AF%95/private-test</id><content type="html" xml:base="/%E6%B5%8B%E8%AF%95/private-test.html"><![CDATA[<p>这是一篇私密文章的内容。只有登录后才能看到这篇文章。</p>

<h2 id="私密内容">私密内容</h2>

<p>这里是一些私密的内容…</p>

<h2 id="访问控制">访问控制</h2>

<p>如果您能看到这篇文章，说明您已经成功登录了系统。</p>]]></content><author><name>2han9wen71an</name></author><category term="测试" /><category term="私密" /><summary type="html"><![CDATA[这是一篇私密文章的内容。只有登录后才能看到这篇文章。]]></summary></entry><entry><title type="html">Jekyll-Next主题添加备案号</title><link href="/Toss-notes/jekyll-next-beian.html" rel="alternate" type="text/html" title="Jekyll-Next主题添加备案号" /><published>2025-01-06T08:17:54+00:00</published><updated>2025-01-06T08:17:54+00:00</updated><id>/Toss-notes/Jekyll-Next%E4%B8%BB%E9%A2%98%E6%B7%BB%E5%8A%A0%E5%A4%87%E6%A1%88%E5%8F%B7</id><content type="html" xml:base="/Toss-notes/jekyll-next-beian.html"><![CDATA[<h1 id="前言">前言</h1>
<p>国内域名备案后需要在首页添加备案号，否则会被管局打电话提示被强制注销备案，所以需要添加备案号。</p>

<p>本博客使用Jekyll进行搭建，使用的主题是Jekyll-Next。但是这款移植主题已经很久没有更新了，其实Next主题6.x版本已经添加了备案号的配置项，但是我使用的是5.x版本的主题，所以需要自己手动添加。</p>
<h1 id="添加备案号">添加备案号</h1>
<h2 id="添加配置">添加配置</h2>
<p>在主题的配置文件<code class="language-plaintext highlighter-rouge"> _config.yml</code>中添加备案信息，需要自己手动添加。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>footer:
  beian: 
    enable: true #为true表示启用
    domains: #备案号的域名和备案号
      - url: xtboke.cn #域名
        strict: true #为true表示严格匹配域名
        icp: "湘ICP备18013761号-1" #备案号
      - url: localhost:4000
        icp: "湘ICP备18013761号-2"
</code></pre></div></div>
<p>我这里是在页脚添加的备案号
由于我使用的是本地预览，所以需要添加localhost:4000的备案号进行测试验证。
这里domains是一个数组，所以可以添加多个域名和备案号,方便你根据不同的域名添加不同的备案号。</p>

<h2 id="修改页脚界面">修改页脚界面</h2>
<p>本主题的页脚界面是在<code class="language-plaintext highlighter-rouge">/_includes/_partials/footer.html</code>文件，我们需要修改这个文件，添加备案号。
原始文件如下：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
&lt;div class="copyright" &gt;
  {% assign current = site.time | date: '%Y' %}
  {% assign since = site.since | downcase %}
  &amp;copy; {% if site.since and since != current %} {{ since }} - {% endif %}
  &lt;span itemprop="copyrightYear"&gt;{{ current }}&lt;/span&gt;
  &lt;span class="with-love"&gt;
    &lt;i class="fa fa-{{ site.authoricon }}"&gt;&lt;/i&gt;
  &lt;/span&gt;
  &lt;span class="author" itemprop="copyrightHolder"&gt;{{ site.author }}&lt;/span&gt;
&lt;/div&gt;

{% if site.copyright %}
&lt;div class="powered-by"&gt;
  {{ __.footer.powered | replace: '%s', '&lt;a class="theme-link" href="https://jekyllrb.com"&gt;Jekyll&lt;/a&gt;') }}
&lt;/div&gt;

&lt;div class="theme-info"&gt;
  {{ __.footer.theme }} -
  &lt;a class="theme-link" href="https://github.com/simpleyyt/jekyll-theme-next"&gt;
    NexT.{{ site.scheme }}
  &lt;/a&gt;
&lt;/div&gt;
{% endif %}

</code></pre></div></div>
<p>我们需要在最后添加备案号的代码，如下：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
{% if site.footer.beian.enable %}
&lt;div id="beian-info" 
     data-domains="{{ site.footer.beian.domains | jsonify | escape }}"
     data-current-host="{{ '' }}"
&gt;&lt;/div&gt;
&lt;script&gt;
  const beianDiv = document.getElementById('beian-info');
  const beianConfig = JSON.parse(beianDiv.dataset.domains);;

  // 获取当前浏览器 URL 的 host
  const currentHost = window.location.host;

  // 匹配备案信息
  function getBeianInfo(host, domains) {
    for (const domain of domains) {
      if (domain.strict) {
        // 严格模式：完全匹配
        if (host === domain.url) {
          return domain.icp;
        }
      } else {
        // 非严格模式：匹配一级域名或特定host
        const domainParts = domain.url.split('.');
        const currentParts = host.split('.');
        if (domainParts.length === 1) {
          // 如果配置的域名只有一个部分，如 "localhost"
          if (host === domain.url) {
            return domain.icp;
          }
        } else if (
          domainParts.length &gt;= 2 &amp;&amp;
          currentParts.length &gt;= 2 &amp;&amp;
          domainParts.slice(-2).join('.') === currentParts.slice(-2).join('.')
        ) {
          return domain.icp;
        }
      }
    }
    return null;
  }

  // 获取备案信息
  const beianInfo = getBeianInfo(currentHost, beianConfig);

  // 如果找到备案信息，插入到页面中
  if (beianInfo) {
    document.getElementById("beian-info").innerHTML = `
      &lt;a href="https://beian.miit.gov.cn/" target="_blank"&gt;${beianInfo}&lt;/a&gt;
    `;
  }
&lt;/script&gt;
{% endif %}

</code></pre></div></div>
<p>这样就能根据不同的域名展示不同的备案号了。</p>
<h1 id="题外">题外</h1>
<p>我是因为有多个域名，所以需要添加多个备案号，所以我使用了数组的方式来添加备案号。实际使用中，你也可以使用单个备案号的方式来添加，精简代码，这里给出一个示例：</p>
<ol>
  <li>在 _config.yml 中添加备案信息：
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>footer:
  beian: "京ICP备12345678号"
  beian_url: "https://beian.miit.gov.cn/"
</code></pre></div>    </div>
  </li>
  <li>在页脚文件中引用配置：</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
{% if site.footer.beian %}
&lt;div class="beian"&gt;
  &lt;a href="{{ site.footer.beian_url }}" target="_blank"&gt;{{ site.footer.beian }}&lt;/a&gt;
&lt;/div&gt;
{% endif %}

</code></pre></div></div>]]></content><author><name>2han9wen71an</name></author><category term="折腾笔记" /><category term="jekyll next" /><summary type="html"><![CDATA[前言 国内域名备案后需要在首页添加备案号，否则会被管局打电话提示被强制注销备案，所以需要添加备案号。]]></summary></entry><entry><title type="html">自建docker镜像源</title><link href="/Toss-notes/65E2082B.html" rel="alternate" type="text/html" title="自建docker镜像源" /><published>2025-01-03T09:18:20+00:00</published><updated>2025-01-03T09:18:20+00:00</updated><id>/Toss-notes/%E8%87%AA%E5%BB%BAdocker%E9%95%9C%E5%83%8F%E6%BA%90</id><content type="html" xml:base="/Toss-notes/65E2082B.html"><![CDATA[<p><strong>第①步：打开 <a href="https://www.cloudflare.com/">Cloudflare</a> 官网</strong></p>

<p><strong>第②步：在左侧列表中找到 Workers &amp; Pages</strong></p>

<p><img src="https://images.zvt.me/1736321296789.png" alt="Workers &amp; Pages" /></p>

<p><strong>第③步：找到 Create 按钮</strong></p>

<p><img src="https://images.zvt.me/1736322627927.png" alt="Create 按钮" /></p>

<p>选择 Workers 然后点击 Create Workers</p>

<p><img src="https://images.zvt.me/1736322358695.png" alt="选择 Workers" /></p>

<p><strong>第④步：输入前缀</strong></p>

<p>例如：<code class="language-plaintext highlighter-rouge">dockerproxy</code> （或其他容易记住的名称）</p>

<p><img src="https://images.zvt.me/1736322368676.png" alt="输入前缀" /></p>

<p>然后点击 Save 保存。</p>

<p><img src="https://images.zvt.me/1736322707050.png" alt="点击 Save" /></p>

<p>保存后会出现新页面，直接点击 Finish。</p>

<p>点击 ‘Edit code’ 开始编辑 <code class="language-plaintext highlighter-rouge">workers.js</code></p>

<p><img src="https://images.zvt.me/1736322725610.png" alt="点击 Edit code" /></p>

<p>删除自带的内容。</p>

<p><img src="https://images.zvt.me/1736322743824.png" alt="删除自带内容" /></p>

<p>粘贴以下代码：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// _worker.js

// Docker镜像仓库主机地址
let hub_host = 'registry-1.docker.io';
// Docker认证服务器地址
const auth_url = 'https://auth.docker.io';
// 自定义的工作服务器地址
let workers_url = 'https://你的自定义域名/';

let 屏蔽爬虫UA = ['netcraft'];

// 根据主机名选择对应的上游地址
function routeByHosts(host) {
	// 定义路由表
	const routes = {
		// 生产环境
		"quay": "quay.io",
		"gcr": "gcr.io",
		"k8s-gcr": "k8s.gcr.io",
		"k8s": "registry.k8s.io",
		"ghcr": "ghcr.io",
		"cloudsmith": "docker.cloudsmith.io",
		"nvcr": "nvcr.io",
		
		// 测试环境
		"test": "registry-1.docker.io",
	};

	if (host in routes) return [ routes[host], false ];
	else return [ hub_host, true ];
}

/** @type {RequestInit} */
const PREFLIGHT_INIT = {
	// 预检请求配置
	headers: new Headers({
		'access-control-allow-origin': '*', // 允许所有来源
		'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // 允许的HTTP方法
		'access-control-max-age': '1728000', // 预检请求的缓存时间
	}),
}

/**
 * 构造响应
 * @param {any} body 响应体
 * @param {number} status 响应状态码
 * @param {Object&lt;string, string&gt;} headers 响应头
 */
function makeRes(body, status = 200, headers = {}) {
	headers['access-control-allow-origin'] = '*' // 允许所有来源
	return new Response(body, { status, headers }) // 返回新构造的响应
}

/**
 * 构造新的URL对象
 * @param {string} urlStr URL字符串
 */
function newUrl(urlStr) {
	try {
		return new URL(urlStr) // 尝试构造新的URL对象
	} catch (err) {
		return null // 构造失败返回null
	}
}

function isUUID(uuid) {
	// 定义一个正则表达式来匹配 UUID 格式
	const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
	
	// 使用正则表达式测试 UUID 字符串
	return uuidRegex.test(uuid);
}

async function nginx() {
	const text = `
	&lt;!DOCTYPE html&gt;
	&lt;html&gt;
	&lt;head&gt;
	&lt;title&gt;Welcome to nginx!&lt;/title&gt;
	&lt;style&gt;
		body {
			width: 35em;
			margin: 0 auto;
			font-family: Tahoma, Verdana, Arial, sans-serif;
		}
	&lt;/style&gt;
	&lt;/head&gt;
	&lt;body&gt;
	&lt;h1&gt;Welcome to nginx!&lt;/h1&gt;
	&lt;p&gt;If you see this page, the nginx web server is successfully installed and
	working. Further configuration is required.&lt;/p&gt;
	
	&lt;p&gt;For online documentation and support please refer to
	&lt;a href="http://nginx.org/"&gt;nginx.org&lt;/a&gt;.&lt;br/&gt;
	Commercial support is available at
	&lt;a href="http://nginx.com/"&gt;nginx.com&lt;/a&gt;.&lt;/p&gt;
	
	&lt;p&gt;&lt;em&gt;Thank you for using nginx.&lt;/em&gt;&lt;/p&gt;
	&lt;/body&gt;
	&lt;/html&gt;
	`
	return text;
}

async function searchInterface() {
	const text = `
	&lt;!DOCTYPE html&gt;
	&lt;html&gt;
	&lt;head&gt;
		&lt;title&gt;Docker Hub Search&lt;/title&gt;
		&lt;style&gt;
		body {
			font-family: Arial, sans-serif;
			display: flex;
			flex-direction: column;
			align-items: center;
			justify-content: center;
			height: 100vh;
			margin: 0;
			background: linear-gradient(to right, rgb(28, 143, 237), rgb(29, 99, 237));
		}
		.logo {
			margin-bottom: 20px;
		}
		.search-container {
			display: flex;
			align-items: center;
		}
		#search-input {
			padding: 10px;
			font-size: 16px;
			border: 1px solid #ddd;
			border-radius: 4px;
			width: 300px;
			margin-right: 10px;
		}
		#search-button {
			padding: 10px;
			background-color: rgba(255, 255, 255, 0.2); /* 设置白色，透明度为10% */
			border: none;
			border-radius: 4px;
			cursor: pointer;
			width: 44px;
			height: 44px;
			display: flex;
			align-items: center;
			justify-content: center;
		}			
		#search-button svg {
			width: 24px;
			height: 24px;
		}
		&lt;/style&gt;
	&lt;/head&gt;
	&lt;body&gt;
		&lt;div class="logo"&gt;
		&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 18" fill="#ffffff" width="100" height="75"&gt;
			&lt;path d="M23.763 6.886c-.065-.053-.673-.512-1.954-.512-.32 0-.659.03-1.01.087-.248-1.703-1.651-2.533-1.716-2.57l-.345-.2-.227.328a4.596 4.596 0 0 0-.611 1.433c-.23.972-.09 1.884.403 2.666-.596.331-1.546.418-1.744.42H.752a.753.753 0 0 0-.75.749c-.007 1.456.233 2.864.692 4.07.545 1.43 1.355 2.483 2.409 3.13 1.181.725 3.104 1.14 5.276 1.14 1.016 0 2.03-.092 2.93-.266 1.417-.273 2.705-.742 3.826-1.391a10.497 10.497 0 0 0 2.61-2.14c1.252-1.42 1.998-3.005 2.553-4.408.075.003.148.005.221.005 1.371 0 2.215-.55 2.68-1.01.505-.5.685-.998.704-1.053L24 7.076l-.237-.19Z"&gt;&lt;/path&gt;
			&lt;path d="M2.216 8.075h2.119a.186.186 0 0 0 .185-.186V6a.186.186 0 0 0-.185-.186H2.216A.186.186 0 0 0 2.031 6v1.89c0 .103.083.186.185.186Zm2.92 0h2.118a.185.185 0 0 0 .185-.186V6a.185.185 0 0 0-.185-.186H5.136A.185.185 0 0 0 4.95 6v1.89c0 .103.083.186.186.186Zm2.964 0h2.118a.186.186 0 0 0 .185-.186V6a.186.186 0 0 0-.185-.186H8.1A.185.185 0 0 0 7.914 6v1.89c0 .103.083.186.186.186Zm2.928 0h2.119a.185.185 0 0 0 .185-.186V6a.185.185 0 0 0-.185-.186h-2.119a.186.186 0 0 0-.185.186v1.89c0 .103.083.186.185.186Zm-5.892-2.72h2.118a.185.185 0 0 0 .185-.186V3.28a.186.186 0 0 0-.185-.186H5.136a.186.186 0 0 0-.186.186v1.89c0 .103.083.186.186.186Zm2.964 0h2.118a.186.186 0 0 0 .185-.186V3.28a.186.186 0 0 0-.185-.186H8.1a.186.186 0 0 0-.186.186v1.89c0 .103.083.186.186.186Zm2.928 0h2.119a.185.185 0 0 0 .185-.186V3.28a.186.186 0 0 0-.185-.186h-2.119a.186.186 0 0 0-.185.186v1.89c0 .103.083.186.185.186Zm0-2.72h2.119a.186.186 0 0 0 .185-.186V.56a.185.185 0 0 0-.185-.186h-2.119a.186.186 0 0 0-.185.186v1.89c0 .103.083.186.185.186Zm2.955 5.44h2.118a.185.185 0 0 0 .186-.186V6a.185.185 0 0 0-.186-.186h-2.118a.185.185 0 0 0-.185.186v1.89c0 .103.083.186.185.186Z"&gt;&lt;/path&gt;
		&lt;/svg&gt;
		&lt;/div&gt;
		&lt;div class="search-container"&gt;
		&lt;input type="text" id="search-input" placeholder="Search Docker Hub"&gt;
		&lt;button id="search-button"&gt;
			&lt;svg focusable="false" aria-hidden="true" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"&gt;
			&lt;path d="M21 21L16.65 16.65M19 11C19 15.4183 15.4183 19 11 19C6.58172 19 3 15.4183 3 11C3 6.58172 6.58172 3 11 3C15.4183 3 19 6.58172 19 11Z" stroke="white" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"&gt;&lt;/path&gt;
			&lt;/svg&gt;
		&lt;/button&gt;
		&lt;/div&gt;
		&lt;script&gt;
		function performSearch() {
			const query = document.getElementById('search-input').value;
			if (query) {
			window.location.href = '/search?q=' + encodeURIComponent(query);
			}
		}
	
		document.getElementById('search-button').addEventListener('click', performSearch);
		document.getElementById('search-input').addEventListener('keypress', function(event) {
			if (event.key === 'Enter') {
			performSearch();
			}
		});
		&lt;/script&gt;
	&lt;/body&gt;
	&lt;/html&gt;
	`;
	return text;
}

export default {
	async fetch(request, env, ctx) {
		const getReqHeader = (key) =&gt; request.headers.get(key); // 获取请求头

		let url = new URL(request.url); // 解析请求URL
		const userAgentHeader = request.headers.get('User-Agent');
		const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null";
		if (env.UA) 屏蔽爬虫UA = 屏蔽爬虫UA.concat(await ADD(env.UA));
		workers_url = `https://${url.hostname}`;
		const pathname = url.pathname;

		// 获取请求参数中的 ns
		const ns = url.searchParams.get('ns'); 
		const hostname = url.searchParams.get('hubhost') || url.hostname;
		const hostTop = hostname.split('.')[0]; // 获取主机名的第一部分

		let checkHost; // 在这里定义 checkHost 变量
		// 如果存在 ns 参数，优先使用它来确定 hub_host
		if (ns) {
			if (ns === 'docker.io') {
				hub_host = 'registry-1.docker.io'; // 设置上游地址为 registry-1.docker.io
			} else {
				hub_host = ns; // 直接使用 ns 作为 hub_host
			}
		} else {
			checkHost = routeByHosts(hostTop);
			hub_host = checkHost[0]; // 获取上游地址
		}

		const fakePage = checkHost ? checkHost[1] : false; // 确保 fakePage 不为 undefined
		console.log(`域名头部: ${hostTop}\n反代地址: ${hub_host}\n伪装首页: ${fakePage}`);
		const isUuid = isUUID(pathname.split('/')[1].split('/')[0]);

		if (屏蔽爬虫UA.some(fxxk =&gt; userAgent.includes(fxxk)) &amp;&amp; 屏蔽爬虫UA.length &gt; 0) {
			// 首页改成一个nginx伪装页
			return new Response(await nginx(), {
				headers: {
					'Content-Type': 'text/html; charset=UTF-8',
				},
			});
		}

		const conditions = [
			isUuid,
			pathname.includes('/_'),
			pathname.includes('/r/'),
			pathname.includes('/v2/repositories'),
			pathname.includes('/v2/user'),
			pathname.includes('/v2/orgs'),
			pathname.includes('/v2/_catalog'),
			pathname.includes('/v2/categories'),
			pathname.includes('/v2/feature-flags'),
			pathname.includes('search'),
			pathname.includes('source'),
			pathname == '/',
			pathname == '/favicon.ico',
			pathname == '/auth/profile',
		];

		if (conditions.some(condition =&gt; condition) &amp;&amp; (fakePage === true || hostTop == 'docker')) {
			if (env.URL302) {
				return Response.redirect(env.URL302, 302);
			} else if (env.URL) {
				if (env.URL.toLowerCase() == 'nginx') {
					//首页改成一个nginx伪装页
					return new Response(await nginx(), {
						headers: {
							'Content-Type': 'text/html; charset=UTF-8',
						},
					});
				} else return fetch(new Request(env.URL, request));
			} else if (url.pathname == '/'){
				return new Response(await searchInterface(), {
					headers: {
					  'Content-Type': 'text/html; charset=UTF-8',
					},
				});
			}
			
			const newUrl = new URL("https://registry.hub.docker.com" + pathname + url.search);

			// 复制原始请求的标头
			const headers = new Headers(request.headers);

			// 确保 Host 头部被替换为 hub.docker.com
			headers.set('Host', 'registry.hub.docker.com');

			const newRequest = new Request(newUrl, {
					method: request.method,
					headers: headers,
					body: request.method !== 'GET' &amp;&amp; request.method !== 'HEAD' ? await request.blob() : null,
					redirect: 'follow'
			});

			return fetch(newRequest);
		}

		// 修改包含 %2F 和 %3A 的请求
		if (!/%2F/.test(url.search) &amp;&amp; /%3A/.test(url.toString())) {
			let modifiedUrl = url.toString().replace(/%3A(?=.*?&amp;)/, '%3Alibrary%2F');
			url = new URL(modifiedUrl);
			console.log(`handle_url: ${url}`);
		}

		// 处理token请求
		if (url.pathname.includes('/token')) {
			let token_parameter = {
				headers: {
					'Host': 'auth.docker.io',
					'User-Agent': getReqHeader("User-Agent"),
					'Accept': getReqHeader("Accept"),
					'Accept-Language': getReqHeader("Accept-Language"),
					'Accept-Encoding': getReqHeader("Accept-Encoding"),
					'Connection': 'keep-alive',
					'Cache-Control': 'max-age=0'
				}
			};
			let token_url = auth_url + url.pathname + url.search;
			return fetch(new Request(token_url, request), token_parameter);
		}

		// 修改 /v2/ 请求路径
		if ( hub_host == 'registry-1.docker.io' &amp;&amp; /^\/v2\/[^/]+\/[^/]+\/[^/]+$/.test(url.pathname) &amp;&amp; !/^\/v2\/library/.test(url.pathname)) {
			//url.pathname = url.pathname.replace(/\/v2\//, '/v2/library/');
			url.pathname = '/v2/library/' + url.pathname.split('/v2/')[1];
			console.log(`modified_url: ${url.pathname}`);
		}

		// 更改请求的主机名
		url.hostname = hub_host;

		// 构造请求参数
		let parameter = {
			headers: {
				'Host': hub_host,
				'User-Agent': getReqHeader("User-Agent"),
				'Accept': getReqHeader("Accept"),
				'Accept-Language': getReqHeader("Accept-Language"),
				'Accept-Encoding': getReqHeader("Accept-Encoding"),
				'Connection': 'keep-alive',
				'Cache-Control': 'max-age=0'
			},
			cacheTtl: 3600 // 缓存时间
		};

		// 添加Authorization头
		if (request.headers.has("Authorization")) {
			parameter.headers.Authorization = getReqHeader("Authorization");
		}

		// 发起请求并处理响应
		let original_response = await fetch(new Request(url, request), parameter);
		let original_response_clone = original_response.clone();
		let original_text = original_response_clone.body;
		let response_headers = original_response.headers;
		let new_response_headers = new Headers(response_headers);
		let status = original_response.status;

		// 修改 Www-Authenticate 头
		if (new_response_headers.get("Www-Authenticate")) {
			let auth = new_response_headers.get("Www-Authenticate");
			let re = new RegExp(auth_url, 'g');
			new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url));
		}

		// 处理重定向
		if (new_response_headers.get("Location")) {
			return httpHandler(request, new_response_headers.get("Location"));
		}

		// 返回修改后的响应
		let response = new Response(original_text, {
			status,
			headers: new_response_headers
		});
		return response;
	}
};

/**
 * 处理HTTP请求
 * @param {Request} req 请求对象
 * @param {string} pathname 请求路径
 */
function httpHandler(req, pathname) {
	const reqHdrRaw = req.headers;

	// 处理预检请求
	if (req.method === 'OPTIONS' &amp;&amp;
		reqHdrRaw.has('access-control-request-headers')
	) {
		return new Response(null, PREFLIGHT_INIT);
	}

	let rawLen = '';

	const reqHdrNew = new Headers(reqHdrRaw);

	const refer = reqHdrNew.get('referer');

	let urlStr = pathname;

	const urlObj = newUrl(urlStr);

	/** @type {RequestInit} */
	const reqInit = {
		method: req.method,
		headers: reqHdrNew,
		redirect: 'follow',
		body: req.body
	};
	return proxy(urlObj, reqInit, rawLen);
}

/**
 * 代理请求
 * @param {URL} urlObj URL对象
 * @param {RequestInit} reqInit 请求初始化对象
 * @param {string} rawLen 原始长度
 */
async function proxy(urlObj, reqInit, rawLen) {
	const res = await fetch(urlObj.href, reqInit);
	const resHdrOld = res.headers;
	const resHdrNew = new Headers(resHdrOld);

	// 验证长度
	if (rawLen) {
		const newLen = resHdrOld.get('content-length') || '';
		const badLen = (rawLen !== newLen);

		if (badLen) {
			return makeRes(res.body, 400, {
				'--error': `bad len: ${newLen}, except: ${rawLen}`,
				'access-control-expose-headers': '--error',
			});
		}
	}
	const status = res.status;
	resHdrNew.set('access-control-expose-headers', '*');
	resHdrNew.set('access-control-allow-origin', '*');
	resHdrNew.set('Cache-Control', 'max-age=1500');

	// 删除不必要的头
	resHdrNew.delete('content-security-policy');
	resHdrNew.delete('content-security-policy-report-only');
	resHdrNew.delete('clear-site-data');

	return new Response(res.body, {
		status,
		headers: resHdrNew
	});
}

async function ADD(envadd) {
	var addtext = envadd.replace(/[	 |"'\r\n]+/g, ',').replace(/,+/g, ',');	// 将空格、双引号、单引号和换行符替换为逗号
	if (addtext.charAt(0) == ',') addtext = addtext.slice(1);
	if (addtext.charAt(addtext.length - 1) == ',') addtext = addtext.slice(0, addtext.length - 1);
	const add = addtext.split(',');
	return add;
}
</code></pre></div></div>

<p><strong>修改自定义域名</strong></p>

<p>需要将 <code class="language-plaintext highlighter-rouge">workers_url</code> 变量的值修改为你自己的域名，该域名需要托管在 Cloudflare 上。</p>

<p><img src="https://images.zvt.me/1736322761680.png" alt="修改自定义域名" /></p>

<p>填写示例如下：</p>

<p><img src="https://images.zvt.me/1736322779396.png" alt="填写示例" /></p>

<p>然后点击右上角 Deploy 保存。</p>

<p>返回 Workers &amp; Pages 设置页面。</p>

<p><img src="https://images.zvt.me/1736324747468.png" alt="返回设置" /></p>

<p>添加自定义域名，例如：<code class="language-plaintext highlighter-rouge">docker.zvt.me</code></p>

<p><img src="https://images.zvt.me/1736322799933.png" alt="添加自定义域名" /></p>

<p>保存，至此部署完成。</p>

<p><strong>可选的环境变量配置 (伪装首页)</strong></p>

<p>变量说明：</p>

<table>
  <thead>
    <tr>
      <th>变量名</th>
      <th>示例</th>
      <th>必填</th>
      <th>备注</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>URL302</td>
      <td><code class="language-plaintext highlighter-rouge">https://www.example.com</code></td>
      <td>否</td>
      <td>主页 302 跳转</td>
    </tr>
    <tr>
      <td>URL</td>
      <td><code class="language-plaintext highlighter-rouge">https://www.baidu.com/</code></td>
      <td>否</td>
      <td>主页伪装 (设为 <code class="language-plaintext highlighter-rouge">nginx</code> 则伪装为 nginx 默认页面)</td>
    </tr>
    <tr>
      <td>UA</td>
      <td><code class="language-plaintext highlighter-rouge">netcraft</code></td>
      <td>否</td>
      <td>支持多元素，元素之间使用空格或换行作间隔。用于屏蔽爬虫UA。</td>
    </tr>
  </tbody>
</table>

<p>如果你想隐藏你的镜像站，可以设置伪装页面。</p>

<p>找到设置 -&gt; 环境变量。</p>

<p><img src="https://images.zvt.me/1736324800727.png" alt="找到环境变量" /></p>

<p><strong>示例 1：将首页重定向到其他网站</strong></p>

<p>添加环境变量 <code class="language-plaintext highlighter-rouge">URL302</code> (必须大写)，值填写目标域名，例如 <code class="language-plaintext highlighter-rouge">https://www.baidu.com</code></p>

<p><img src="https://images.zvt.me/1736324818805.png" alt="URL302 示例" /></p>

<p>保存后，访问首页会自动跳转到百度。</p>

<p><strong>示例 2：伪装成其他网页</strong></p>

<p>添加环境变量 <code class="language-plaintext highlighter-rouge">URL</code> (必须大写)，值填写目标网页地址，例如 <code class="language-plaintext highlighter-rouge">https://docker.zvt.me/</code></p>

<p><img src="https://images.zvt.me/1736324841691.png" alt="URL 示例" /></p>

<p>保存后，访问首页会显示 <code class="language-plaintext highlighter-rouge">https://docker.zvt.me/</code> 的页面。</p>

<p><strong>如何使用</strong></p>

<p>在官方镜像路径前面加上你的域名：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull 你的域名/stilleshan/frpc:latest
</code></pre></div></div>

<p>例如：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull docker.zvt.me/stilleshan/frpc:latest
</code></pre></div></div>

<p><strong>一键设置镜像加速</strong></p>

<p>修改文件 <code class="language-plaintext highlighter-rouge">/etc/docker/daemon.json</code>（如果不存在则创建）</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo mkdir</span> <span class="nt">-p</span> /etc/docker
<span class="nb">sudo tee</span> /etc/docker/daemon.json <span class="o">&lt;&lt;-</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
{
  "registry-mirrors": ["https://你的域名"]  # 请替换为您自己的Worker自定义域名
}
</span><span class="no">EOF
</span><span class="nb">sudo </span>systemctl daemon-reload
<span class="nb">sudo </span>systemctl restart docker
</code></pre></div></div>

<p><strong>注意：</strong> 请将 <code class="language-plaintext highlighter-rouge">https://你的域名</code> 替换为你自己的 Worker 自定义域名。</p>

<p>本文参考:<a href="https://linux.do/t/topic/114345">https://linux.do/t/topic/114345</a></p>]]></content><author><name>2han9wen71an</name></author><category term="代码笔记" /><category term="折腾笔记" /><category term="生活随笔" /><category term="docker" /><summary type="html"><![CDATA[第①步：打开 Cloudflare 官网]]></summary></entry><entry><title type="html">metersphere开发部署文档</title><link href="/CodeNotes/metersphere-dev.html" rel="alternate" type="text/html" title="metersphere开发部署文档" /><published>2024-12-24T09:05:18+00:00</published><updated>2024-12-24T09:05:18+00:00</updated><id>/CodeNotes/metersphere%E5%BC%80%E5%8F%91%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3</id><content type="html" xml:base="/CodeNotes/metersphere-dev.html"><![CDATA[<h1 id="开发环境搭建">开发环境搭建</h1>
<h2 id="中间件搭建">中间件搭建</h2>
<p>使用<code class="language-plaintext highlighter-rouge">docker-compose</code>启动，这里mysql配置文件需要从官网帮助文档下载并放入指定位置,主要是忽略大小写，这里提供一个示例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[mysqld]
datadir=/var/lib/mysql

default-storage-engine=INNODB
character_set_server=utf8mb4
lower_case_table_names=1
performance_schema=off
table_open_cache=128
transaction_isolation=READ-COMMITTED
max_connections=1000
max_connect_errors=6000
max_allowed_packet=64M
innodb_file_per_table=1
innodb_buffer_pool_size=512M
innodb_flush_method=O_DIRECT
innodb_lock_wait_timeout=1800

server-id=1
log-bin=mysql-bin
expire_logs_days = 2
binlog_format=mixed

character-set-client-handshake = FALSE
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
init_connect='SET default_collation_for_utf8mb4=utf8mb4_general_ci'

sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

skip-name-resolve

[mysql]
default-character-set=utf8mb4

[mysql.server]
default-character-set=utf8mb4
</code></pre></div></div>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.8"</span>

<span class="na">services</span><span class="pi">:</span>
  <span class="na">db</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">mysql:8.0.33</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">MYSQL_ROOT_PASSWORD=Password123@mysql</span>
      <span class="pi">-</span> <span class="s">MYSQL_DATABASE=metersphere_dev</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">3306:3306"</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">metersphere</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">/opt/metersphere/conf/mysql.conf:/etc/mysql/conf.d/my.cnf</span>
      <span class="pi">-</span> <span class="s">mysql-data:/var/lib/mysql</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>

  <span class="na">redis</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">redis:7.2.4-alpine</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">REDIS_PASSWORD=Password123@redis</span>
    <span class="na">command</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">redis-server"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">--requirepass"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">${REDIS_PASSWORD}"</span><span class="pi">]</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">metersphere</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">6379:6379"</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>

  <span class="na">kafka</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">apache/kafka:3.7.0</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">metersphere</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">9092:9092"</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>

  <span class="na">minio</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">minio/minio</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">MINIO_ACCESS_KEY=admin</span>
      <span class="pi">-</span> <span class="s">MINIO_SECRET_KEY=Password123@minio</span>
    <span class="na">networks</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">metersphere</span>
    <span class="na">command</span><span class="pi">:</span> <span class="s">server /data --address ":9000" --console-address ":9001"</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">9000:9000"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">9001:9001"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">files:/data</span>
    <span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>

<span class="na">networks</span><span class="pi">:</span>
  <span class="na">app_network</span><span class="pi">:</span>
    <span class="na">driver</span><span class="pi">:</span> <span class="s">bridge</span>
  <span class="na">metersphere</span><span class="pi">:</span> <span class="pi">{</span> <span class="pi">}</span>

<span class="na">volumes</span><span class="pi">:</span>
  <span class="na">mysql-data</span><span class="pi">:</span>
  <span class="na">files</span><span class="pi">:</span>

</code></pre></div></div>

<h1 id="打包部署">打包部署</h1>
<h2 id="打包脚本">打包脚本</h2>
<p>使用<code class="language-plaintext highlighter-rouge">build_docker.sh</code>可以按模块来打包并进行构建镜像，默认构建的镜像名为<code class="language-plaintext highlighter-rouge">模块名称:git分支名称</code>。
构建好镜像后需要手动进行推送<code class="language-plaintext highlighter-rouge">docker push 10.6.132.102:31443/metersphere/system-setting:v2.10</code></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="c"># 配置，根据实际情况修改</span>
<span class="nv">IMAGE_PREFIX</span><span class="o">=</span><span class="s1">'10.6.132.102:31443/metersphere'</span>
<span class="nv">WORKSPACE</span><span class="o">=</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>

<span class="c"># 获取当前 Git 分支名称</span>
<span class="nv">GIT_BRANCH</span><span class="o">=</span><span class="si">$(</span>git symbolic-ref <span class="nt">--short</span> HEAD<span class="si">)</span>

<span class="c"># 可用库（模块）</span>
<span class="nv">libraries</span><span class="o">=(</span><span class="s1">'framework/eureka'</span> <span class="s1">'framework/gateway'</span> <span class="s1">'api-test'</span> <span class="s1">'performance-test'</span> <span class="s1">'project-management'</span> <span class="s1">'report-stat'</span> <span class="s1">'system-setting'</span> <span class="s1">'test-track'</span> <span class="s1">'workstation'</span><span class="o">)</span>

<span class="c"># 特殊处理的模块</span>
<span class="nv">special_libraries</span><span class="o">=(</span><span class="s1">'framework/eureka'</span> <span class="s1">'framework/gateway'</span><span class="o">)</span>

<span class="c"># 打包 JAR 文件</span>
build_jar<span class="o">()</span> <span class="o">{</span>
    <span class="nb">local </span><span class="nv">module_name</span><span class="o">=</span><span class="nv">$1</span>
    <span class="nb">echo</span> <span class="s2">"Building JAR for module: </span><span class="nv">$module_name</span><span class="s2">"</span>
    <span class="nb">cd</span> <span class="nv">$module_name</span>

    <span class="c"># 清理并打包</span>
    mvn clean package <span class="nt">-DskipTests</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> <span class="nt">-ne</span> 0 <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"Error: Maven build failed for module: </span><span class="nv">$module_name</span><span class="s2">"</span>
        <span class="nb">exit </span>1
    <span class="k">fi

    if</span> <span class="o">[[</span> <span class="s2">" </span><span class="k">${</span><span class="nv">special_libraries</span><span class="p">[@]</span><span class="k">}</span><span class="s2"> "</span> <span class="o">=</span>~ <span class="s2">" </span><span class="k">${</span><span class="nv">module_name</span><span class="k">}</span><span class="s2"> "</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">mkdir</span> <span class="nt">-p</span> ./target/dependency <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="nb">cd</span> ./target/dependency<span class="p">;</span> jar <span class="nt">-xf</span> ../<span class="k">*</span>.jar<span class="o">)</span>
    <span class="k">else
        </span><span class="nb">mkdir</span> <span class="nt">-p</span> ./backend/target/dependency <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="nb">cd</span> ./backend/target/dependency<span class="p">;</span> jar <span class="nt">-xf</span> ../<span class="k">*</span>.jar<span class="o">)</span>
    <span class="k">fi

    if</span> <span class="o">[</span> <span class="nv">$?</span> <span class="nt">-ne</span> 0 <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"Error: Failed to extract JAR file in module: </span><span class="nv">$module_name</span><span class="s2">"</span>
        <span class="nb">exit </span>1
    <span class="k">fi</span>
<span class="o">}</span>

<span class="c"># 打包单个模块的 Docker 镜像</span>
build_module<span class="o">()</span> <span class="o">{</span>
    <span class="nb">local </span><span class="nv">module_name</span><span class="o">=</span><span class="nv">$1</span>
    <span class="nb">local </span><span class="nv">image_name</span><span class="o">=</span><span class="k">${</span><span class="nv">module_name</span><span class="p">#*/</span><span class="k">}</span>
    <span class="nb">echo</span> <span class="s2">"Building Docker image for module: </span><span class="nv">$module_name</span><span class="s2">"</span>
    docker build <span class="nt">-t</span> <span class="nv">$IMAGE_PREFIX</span>/<span class="nv">$image_name</span>:<span class="nv">$GIT_BRANCH</span> <span class="nb">.</span>

    <span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> <span class="nt">-ne</span> 0 <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"Error: Docker build failed for module: </span><span class="nv">$module_name</span><span class="s2">"</span>
        <span class="nb">exit </span>1
    <span class="k">fi</span>
<span class="o">}</span>

<span class="c"># 显示菜单并获取用户选择</span>
show_menu<span class="o">()</span> <span class="o">{</span>
    <span class="nb">echo</span> <span class="s2">"Available modules:"</span>
    <span class="k">for </span>i <span class="k">in</span> <span class="s2">"</span><span class="k">${</span><span class="p">!libraries[@]</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="k">do
        </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">$((</span>i+1<span class="k">))</span><span class="s2">. </span><span class="k">${</span><span class="nv">libraries</span><span class="p">[i]</span><span class="k">}</span><span class="s2">"</span>
    <span class="k">done
    </span><span class="nb">echo</span> <span class="s2">"Enter the numbers of the modules to build (comma-separated, e.g., 1,2,3):"</span>
    <span class="nb">read</span> <span class="nt">-r</span> selections
<span class="o">}</span>

<span class="c"># 主程序</span>
show_menu

<span class="nv">IFS</span><span class="o">=</span><span class="s1">','</span> <span class="nb">read</span> <span class="nt">-ra</span> selected_indices <span class="o">&lt;&lt;&lt;</span> <span class="s2">"</span><span class="nv">$selections</span><span class="s2">"</span>

<span class="k">for </span>index <span class="k">in</span> <span class="s2">"</span><span class="k">${</span><span class="nv">selected_indices</span><span class="p">[@]</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="k">do
    if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$index</span><span class="s2">"</span> <span class="o">=</span>~ ^[0-9]+<span class="nv">$ </span><span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$index</span><span class="s2">"</span> <span class="nt">-ge</span> 1 <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$index</span><span class="s2">"</span> <span class="nt">-le</span> <span class="s2">"</span><span class="k">${#</span><span class="nv">libraries</span><span class="p">[@]</span><span class="k">}</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nv">module</span><span class="o">=</span><span class="k">${</span><span class="nv">libraries</span><span class="p">[</span><span class="k">$((</span>index-1<span class="k">))</span><span class="p">]</span><span class="k">}</span>
        build_jar <span class="s2">"</span><span class="nv">$module</span><span class="s2">"</span>
        build_module <span class="s2">"</span><span class="nv">$module</span><span class="s2">"</span>
    <span class="k">else
        </span><span class="nb">echo</span> <span class="s2">"Invalid selection: </span><span class="nv">$index</span><span class="s2">"</span>
    <span class="k">fi
done</span>
</code></pre></div></div>
<h1 id="部署">部署</h1>
<h2 id="allinone方式">allinone方式</h2>
<p>进入服务器停止旧的容器</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>msctl stop system-setting
<span class="c"># 这里首次可能不是私服的镜像地址，需要按照实际情况修改</span>
docker rmi 10.6.132.102:31443/metersphere/system-setting:v2.10
</code></pre></div></div>
<p>删除旧的容器后重新加载</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>msctl reload
</code></pre></div></div>

<h2 id="微服务方式">微服务方式</h2>
<p>目前测试环境网络使用host模式启动，需修改<code class="language-plaintext highlighter-rouge">docker-compose.yml</code>文件来挂载到宿主机，否则无法访问。</p>

<p>删除<code class="language-plaintext highlighter-rouge">docker-compose-base.yml</code>中关于network的配置</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">#networks:</span>
      <span class="c1">#  ms-network:</span>
      <span class="c1">#    driver: bridge</span>
      <span class="c1">#    ipam:</span>
      <span class="c1">#      driver: default</span>
      <span class="c1">#      config:</span>
      <span class="c1">#        - subnet: ${MS_DOCKER_SUBNET}</span>
</code></pre></div></div>
<p>重启容器</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker stop test-track
docker <span class="nb">rm </span>test-track
docker rmi 10.6.132.102:31443/metersphere/test-track:v2.10
msctl up <span class="nt">-d</span> test-track
</code></pre></div></div>]]></content><author><name>2han9wen71an</name></author><category term="代码笔记" /><category term="metersphere" /><summary type="html"><![CDATA[开发环境搭建 中间件搭建 使用docker-compose启动，这里mysql配置文件需要从官网帮助文档下载并放入指定位置,主要是忽略大小写，这里提供一个示例 ``` [mysqld] datadir=/var/lib/mysql]]></summary></entry><entry><title type="html">Spring-Native踩坑并使用Docker部署</title><link href="/CodeNotes/1F12D412.html" rel="alternate" type="text/html" title="Spring-Native踩坑并使用Docker部署" /><published>2024-08-14T08:52:45+00:00</published><updated>2024-08-14T08:52:45+00:00</updated><id>/CodeNotes/Spring-Native%E6%89%93%E5%8C%85Docker%E9%83%A8%E7%BD%B2</id><content type="html" xml:base="/CodeNotes/1F12D412.html"><![CDATA[<h1 id="前言">前言</h1>
<p>最近有一个小项目，没有历史包裹，直接使用最新的spring native进行构建。
在打包的时候，遇到了很多坑，虽然打包成功，但是在运行时，还是报错，特此写一篇文章记录下来。</p>
<h1 id="首先是项目配置">首先是项目配置</h1>
<p>我这边使用的是最新的spring-boot版本3.3.2</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nt">&lt;parent&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>org.springframework.boot<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>spring-boot-starter-parent<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;version&gt;</span>3.3.2<span class="nt">&lt;/version&gt;</span>
        <span class="nt">&lt;relativePath/&gt;</span> <span class="c">&lt;!-- lookup parent from repository --&gt;</span>
    <span class="nt">&lt;/parent&gt;</span>
</code></pre></div></div>
<p>由于项目中使用了AWS的S3 SDK，所以需要添加依赖</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>com.amazonaws<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>aws-java-sdk-s3<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.12.767<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>
<h1 id="遇到的问题">遇到的问题</h1>
<h2 id="本地打包成二进制启动报错">本地打包成二进制启动报错</h2>
<h3 id="aws-sdk报错">AWS SDK报错</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    Caused by: java.lang.ExceptionInInitializerError: null
    at com.amazonaws.util.VersionInfoUtils.userAgent(VersionInfoUtils.java:142) ~[na:na]
    at com.amazonaws.util.VersionInfoUtils.initializeUserAgent(VersionInfoUtils.java:137) ~[na:na]
    at com.amazonaws.util.VersionInfoUtils.getUserAgent(VersionInfoUtils.java:100) ~[na:na]
    at com.amazonaws.internal.EC2ResourceFetcher.&lt;clinit&gt;(EC2ResourceFetcher.java:44) ~[awsnativedemo:na]
    at java.base@20.0.2/java.lang.Class.ensureInitialized(DynamicHub.java:579) ~[awsnativedemo:na]
    at com.amazonaws.auth.InstanceMetadataServiceCredentialsFetcher.&lt;init&gt;(InstanceMetadataServiceCredentialsFetcher.java:37) ~[na:na]
    at com.amazonaws.auth.InstanceProfileCredentialsProvider.&lt;init&gt;(InstanceProfileCredentialsProvider.java:111) ~[na:na]
    at com.amazonaws.auth.InstanceProfileCredentialsProvider.&lt;init&gt;(InstanceProfileCredentialsProvider.java:91) ~[na:na]
    at com.amazonaws.auth.InstanceProfileCredentialsProvider.&lt;init&gt;(InstanceProfileCredentialsProvider.java:75) ~[na:na]
    at com.amazonaws.auth.InstanceProfileCredentialsProvider.&lt;clinit&gt;(InstanceProfileCredentialsProvider.java:58) ~[na:na]
    at com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper.initializeProvider(EC2ContainerCredentialsProviderWrapper.java:64) ~[na:na]
    at com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper.&lt;init&gt;(EC2ContainerCredentialsProviderWrapper.java:53) ~[na:na]
    at com.amazonaws.auth.DefaultAWSCredentialsProviderChain.&lt;init&gt;(DefaultAWSCredentialsProviderChain.java:49) ~[awsnativedemo:na]
    at com.amazonaws.auth.DefaultAWSCredentialsProviderChain.&lt;clinit&gt;(DefaultAWSCredentialsProviderChain.java:43) ~[awsnativedemo:na]
    at java.base@20.0.2/java.lang.Class.ensureInitialized(DynamicHub.java:579) ~[awsnativedemo:na]
    at com.amazonaws.services.s3.AmazonS3ClientBuilder.standard(AmazonS3ClientBuilder.java:46) ~[awsnativedemo:na]
    at com.example.awsnativedemo.S3ClientConfig.s3Client(S3ClientConfig.kt:19) ~[awsnativedemo:na]
    at com.example.awsnativedemo.S3ClientConfig$$SpringCGLIB$$0.CGLIB$s3Client$0(&lt;generated&gt;) ~[awsnativedemo:na]
    at com.example.awsnativedemo.S3ClientConfig$$SpringCGLIB$$2.invoke(&lt;generated&gt;) ~[awsnativedemo:na]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[awsnativedemo:6.0.12]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[na:na]
    at com.example.awsnativedemo.S3ClientConfig$$SpringCGLIB$$0.s3Client(&lt;generated&gt;) ~[awsnativedemo:na]
    at com.example.awsnativedemo.S3ClientConfig__BeanDefinitions.lambda$getSClientInstanceSupplier$1(S3ClientConfig__BeanDefinitions.java:37) ~[na:na]
    at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63) ~[awsnativedemo:6.0.12]
    at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51) ~[awsnativedemo:6.0.12]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$withGenerator$0(BeanInstanceSupplier.java:167) ~[na:na]
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68) ~[awsnativedemo:6.0.12]
    at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[awsnativedemo:6.0.12]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:202) ~[na:na]
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[awsnativedemo:6.0.12]
    at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[awsnativedemo:6.0.12]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:214) ~[na:na]
    at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:202) ~[na:na]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[awsnativedemo:6.0.12]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[awsnativedemo:6.0.12]
            ... 34 common frames omitted
    Caused by: java.lang.IllegalArgumentException: null
    at com.amazonaws.internal.config.InternalConfig.loadfrom(InternalConfig.java:260) ~[na:na]
    at com.amazonaws.internal.config.InternalConfig.load(InternalConfig.java:274) ~[na:na]
    at com.amazonaws.internal.config.InternalConfig$Factory.&lt;clinit&gt;(InternalConfig.java:347) ~[na:na]
            ... 69 common frames omitted
</code></pre></div></div>
<p>参考AWS的issue<a href="https://github.com/aws/aws-sdk-java/issues/3035">InternalConfig throws exception in runtime with native application</a>，解决方法如下：</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="kn">package</span> <span class="nn">com.amazonaws.http.conn</span><span class="o">;</span>

    <span class="kn">import</span> <span class="nn">com.amazonaws.services.s3.internal.AWSS3V4Signer</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.aot.hint.ExecutableMode</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.aot.hint.RuntimeHints</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.aot.hint.RuntimeHintsRegistrar</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.core.io.ClassPathResource</span><span class="o">;</span>
    
    <span class="kn">import</span> <span class="nn">java.util.HashSet</span><span class="o">;</span>
    
    <span class="kd">public</span> <span class="kd">class</span> <span class="nc">ApplicationRuntimeHints</span> <span class="kd">implements</span> <span class="nc">RuntimeHintsRegistrar</span> <span class="o">{</span>
    
        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">registerHints</span><span class="o">(</span><span class="nc">RuntimeHints</span> <span class="n">hints</span><span class="o">,</span> <span class="nc">ClassLoader</span> <span class="n">classLoader</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">hints</span><span class="o">.</span><span class="na">resources</span><span class="o">().</span><span class="na">registerResource</span><span class="o">(</span><span class="k">new</span> <span class="nc">ClassPathResource</span><span class="o">(</span><span class="s">"com/amazonaws/internal/config/awssdk_config_default.json"</span><span class="o">));</span>
            <span class="n">hints</span><span class="o">.</span><span class="na">resources</span><span class="o">().</span><span class="na">registerResource</span><span class="o">(</span><span class="k">new</span> <span class="nc">ClassPathResource</span><span class="o">(</span><span class="s">"com/amazonaws/partitions/endpoints.json"</span><span class="o">));</span>
            <span class="n">hints</span><span class="o">.</span><span class="na">resources</span><span class="o">().</span><span class="na">registerResource</span><span class="o">(</span><span class="k">new</span> <span class="nc">ClassPathResource</span><span class="o">(</span><span class="s">"com/amazonaws/sdk/versionInfo.properties"</span><span class="o">));</span>
    
    
            <span class="n">hints</span><span class="o">.</span><span class="na">resources</span><span class="o">().</span><span class="na">registerResource</span><span class="o">(</span><span class="k">new</span> <span class="nc">ClassPathResource</span><span class="o">(</span><span class="s">"org/joda/time/tz/data/ZoneInfoMap"</span><span class="o">));</span>
    
            <span class="k">for</span> <span class="o">(</span><span class="kt">var</span> <span class="n">constructor</span> <span class="o">:</span> <span class="nc">HashSet</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getDeclaredConstructors</span><span class="o">())</span> <span class="o">{</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerConstructor</span><span class="o">(</span><span class="n">constructor</span><span class="o">,</span> <span class="nc">ExecutableMode</span><span class="o">.</span><span class="na">INVOKE</span><span class="o">);</span>
            <span class="o">}</span>
            <span class="k">for</span> <span class="o">(</span><span class="kt">var</span> <span class="n">constructor</span> <span class="o">:</span> <span class="nc">AWSS3V4Signer</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getDeclaredConstructors</span><span class="o">())</span> <span class="o">{</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerConstructor</span><span class="o">(</span><span class="n">constructor</span><span class="o">,</span> <span class="nc">ExecutableMode</span><span class="o">.</span><span class="na">INVOKE</span><span class="o">);</span>
            <span class="o">}</span>
    
            <span class="n">hints</span><span class="o">.</span><span class="na">proxies</span><span class="o">().</span><span class="na">registerJdkProxy</span><span class="o">(</span><span class="n">org</span><span class="o">.</span><span class="na">apache</span><span class="o">.</span><span class="na">http</span><span class="o">.</span><span class="na">conn</span><span class="o">.</span><span class="na">HttpClientConnectionManager</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                    <span class="n">org</span><span class="o">.</span><span class="na">apache</span><span class="o">.</span><span class="na">http</span><span class="o">.</span><span class="na">pool</span><span class="o">.</span><span class="na">ConnPoolControl</span><span class="o">.</span><span class="na">class</span><span class="o">,</span><span class="n">com</span><span class="o">.</span><span class="na">amazonaws</span><span class="o">.</span><span class="na">http</span><span class="o">.</span><span class="na">conn</span><span class="o">.</span><span class="na">Wrapped</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
    
    
            <span class="n">hints</span><span class="o">.</span><span class="na">proxies</span><span class="o">().</span><span class="na">registerJdkProxy</span><span class="o">(</span><span class="n">org</span><span class="o">.</span><span class="na">apache</span><span class="o">.</span><span class="na">http</span><span class="o">.</span><span class="na">conn</span><span class="o">.</span><span class="na">ConnectionRequest</span><span class="o">.</span><span class="na">class</span><span class="o">,</span><span class="n">com</span><span class="o">.</span><span class="na">amazonaws</span><span class="o">.</span><span class="na">http</span><span class="o">.</span><span class="na">conn</span><span class="o">.</span><span class="na">Wrapped</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>

</code></pre></div></div>
<p>这里要注意，<code class="language-plaintext highlighter-rouge">com.amazonaws.http.conn.Wrapped</code> 是私有接口，所以我这个类的包名和<code class="language-plaintext highlighter-rouge">com.amazonaws.http.conn.Wrapped</code>的包名要一致。</p>
<h3 id="mybatis-plus报错">Mybatis-plus报错</h3>
<p>解决办法参考<a href="https://github.com/baomidou/mybatis-plus/issues/5527">SpringBoot3支持问题</a></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.annotation.IEnum</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.MybatisParameterHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.conditions.AbstractWrapper</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.conditions.query.QueryWrapper</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.handlers.CompositeEnumTypeHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.toolkit.support.SFunction</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.extension.handlers.GsonTypeHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.commons.logging.LogFactory</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.annotations.DeleteProvider</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.annotations.InsertProvider</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.annotations.SelectProvider</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.annotations.UpdateProvider</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.cache.decorators.FifoCache</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.cache.decorators.LruCache</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.cache.decorators.SoftCache</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.cache.decorators.WeakCache</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.cache.impl.PerpetualCache</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.executor.Executor</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.executor.parameter.ParameterHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.executor.resultset.ResultSetHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.executor.statement.BaseStatementHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.executor.statement.RoutingStatementHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.executor.statement.StatementHandler</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.javassist.util.proxy.ProxyFactory</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.javassist.util.proxy.RuntimeSupport</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.logging.Log</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.logging.log4j2.Log4j2Impl</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.logging.nologging.NoLoggingImpl</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.logging.slf4j.Slf4jImpl</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.logging.stdout.StdOutImpl</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.mapping.BoundSql</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.reflection.TypeParameterResolver</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.scripting.defaults.RawLanguageDriver</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.scripting.xmltags.XMLLanguageDriver</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.apache.ibatis.session.SqlSessionFactory</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.mybatis.spring.SqlSessionFactoryBean</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.mybatis.spring.SqlSessionTemplate</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.mybatis.spring.mapper.MapperFactoryBean</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.mybatis.spring.mapper.MapperScannerConfigurer</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.aot.hint.MemberCategory</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.aot.hint.RuntimeHints</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.aot.hint.RuntimeHintsRegistrar</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.beans.PropertyValue</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.beans.factory.BeanFactory</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.beans.factory.BeanFactoryAware</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.beans.factory.config.*</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.beans.factory.support.RegisteredBean</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.beans.factory.support.RootBeanDefinition</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Bean</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.context.annotation.Configuration</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.context.annotation.ImportRuntimeHints</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.core.ResolvableType</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.util.ClassUtils</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">org.springframework.util.ReflectionUtils</span><span class="o">;</span>
    
    <span class="kn">import</span> <span class="nn">java.lang.annotation.Annotation</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.lang.reflect.Method</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.lang.reflect.ParameterizedType</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.lang.reflect.Type</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.util.ArrayList</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.util.HashMap</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.util.HashSet</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.util.Set</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.util.TreeSet</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.util.function.Function</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.util.stream.Collectors</span><span class="o">;</span>
    <span class="kn">import</span> <span class="nn">java.util.stream.Stream</span><span class="o">;</span>
    
    <span class="cm">/**
     * This configuration will move to mybatis-spring-native.
     */</span>
    <span class="nd">@Configuration</span><span class="o">(</span><span class="n">proxyBeanMethods</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
    <span class="nd">@ImportRuntimeHints</span><span class="o">(</span><span class="nc">MyBatisNativeConfiguration</span><span class="o">.</span><span class="na">MyBaitsRuntimeHintsRegistrar</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
    <span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyBatisNativeConfiguration</span> <span class="o">{</span>
    
        <span class="nd">@Bean</span>
        <span class="nc">MyBatisBeanFactoryInitializationAotProcessor</span> <span class="nf">myBatisBeanFactoryInitializationAotProcessor</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nf">MyBatisBeanFactoryInitializationAotProcessor</span><span class="o">();</span>
        <span class="o">}</span>
    
        <span class="nd">@Bean</span>
        <span class="kd">static</span> <span class="nc">MyBatisMapperFactoryBeanPostProcessor</span> <span class="nf">myBatisMapperFactoryBeanPostProcessor</span><span class="o">()</span> <span class="o">{</span>
            <span class="k">return</span> <span class="k">new</span> <span class="nf">MyBatisMapperFactoryBeanPostProcessor</span><span class="o">();</span>
        <span class="o">}</span>
    
        <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyBaitsRuntimeHintsRegistrar</span> <span class="kd">implements</span> <span class="nc">RuntimeHintsRegistrar</span> <span class="o">{</span>
    
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">registerHints</span><span class="o">(</span><span class="nc">RuntimeHints</span> <span class="n">hints</span><span class="o">,</span> <span class="nc">ClassLoader</span> <span class="n">classLoader</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">Stream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">RawLanguageDriver</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="c1">// TODO 增加了MybatisXMLLanguageDriver.class</span>
                        <span class="nc">XMLLanguageDriver</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MybatisXMLLanguageDriver</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">RuntimeSupport</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">ProxyFactory</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">Slf4jImpl</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">Log</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">JakartaCommonsLoggingImpl</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">Log4j2Impl</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">Jdk14LoggingImpl</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">StdOutImpl</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">NoLoggingImpl</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">SqlSessionFactory</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">PerpetualCache</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">FifoCache</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">LruCache</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">SoftCache</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">WeakCache</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="c1">//TODO 增加了MybatisSqlSessionFactoryBean.class</span>
                        <span class="nc">SqlSessionFactoryBean</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MybatisSqlSessionFactoryBean</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">ArrayList</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">HashMap</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">TreeSet</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                        <span class="nc">HashSet</span><span class="o">.</span><span class="na">class</span>
                <span class="o">).</span><span class="na">forEach</span><span class="o">(</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">values</span><span class="o">()));</span>
                <span class="nc">Stream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
                        <span class="s">"org/apache/ibatis/builder/xml/*.dtd"</span><span class="o">,</span>
                        <span class="s">"org/apache/ibatis/builder/xml/*.xsd"</span>
                <span class="o">).</span><span class="na">forEach</span><span class="o">(</span><span class="n">hints</span><span class="o">.</span><span class="na">resources</span><span class="o">()::</span><span class="n">registerPattern</span><span class="o">);</span>
    
                <span class="n">hints</span><span class="o">.</span><span class="na">serialization</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">SerializedLambda</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">serialization</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">SFunction</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">serialization</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">invoke</span><span class="o">.</span><span class="na">SerializedLambda</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">SFunction</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">SerializedLambda</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">invoke</span><span class="o">.</span><span class="na">SerializedLambda</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
    
                <span class="n">hints</span><span class="o">.</span><span class="na">proxies</span><span class="o">().</span><span class="na">registerJdkProxy</span><span class="o">(</span><span class="nc">StatementHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">proxies</span><span class="o">().</span><span class="na">registerJdkProxy</span><span class="o">(</span><span class="nc">Executor</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">proxies</span><span class="o">().</span><span class="na">registerJdkProxy</span><span class="o">(</span><span class="nc">ResultSetHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">proxies</span><span class="o">().</span><span class="na">registerJdkProxy</span><span class="o">(</span><span class="nc">ParameterHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
    
                <span class="c1">//        hints.reflection().registerType(MybatisPlusInterceptor.class);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">AbstractWrapper</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">values</span><span class="o">());</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">LambdaQueryWrapper</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">values</span><span class="o">());</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">LambdaUpdateWrapper</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">values</span><span class="o">());</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">UpdateWrapper</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">values</span><span class="o">());</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">QueryWrapper</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">values</span><span class="o">());</span>
    
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">BoundSql</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">DECLARED_FIELDS</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">RoutingStatementHandler</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">DECLARED_FIELDS</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">BaseStatementHandler</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">DECLARED_FIELDS</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">MybatisParameterHandler</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">DECLARED_FIELDS</span><span class="o">);</span>
    
    
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">IEnum</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">INVOKE_PUBLIC_METHODS</span><span class="o">);</span>
                <span class="c1">// register typeHandler</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">CompositeEnumTypeHandler</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">INVOKE_PUBLIC_CONSTRUCTORS</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">FastjsonTypeHandler</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">INVOKE_PUBLIC_CONSTRUCTORS</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">GsonTypeHandler</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">INVOKE_PUBLIC_CONSTRUCTORS</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">JacksonTypeHandler</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">INVOKE_PUBLIC_CONSTRUCTORS</span><span class="o">);</span>
                <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="nc">MybatisEnumTypeHandler</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">INVOKE_PUBLIC_CONSTRUCTORS</span><span class="o">);</span>
            <span class="o">}</span>
        <span class="o">}</span>
    
        <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyBatisBeanFactoryInitializationAotProcessor</span>
                <span class="kd">implements</span> <span class="nc">BeanFactoryInitializationAotProcessor</span><span class="o">,</span> <span class="nc">BeanRegistrationExcludeFilter</span> <span class="o">{</span>
    
            <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">Class</span><span class="o">&lt;?&gt;&gt;</span> <span class="n">excludeClasses</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashSet</span><span class="o">&lt;&gt;();</span>
    
            <span class="nc">MyBatisBeanFactoryInitializationAotProcessor</span><span class="o">()</span> <span class="o">{</span>
                <span class="n">excludeClasses</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="nc">MapperScannerConfigurer</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="o">}</span>
    
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">isExcludedFromAotProcessing</span><span class="o">(</span><span class="nc">RegisteredBean</span> <span class="n">registeredBean</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">return</span> <span class="n">excludeClasses</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">registeredBean</span><span class="o">.</span><span class="na">getBeanClass</span><span class="o">());</span>
            <span class="o">}</span>
    
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="nc">BeanFactoryInitializationAotContribution</span> <span class="nf">processAheadOfTime</span><span class="o">(</span><span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">String</span><span class="o">[]</span> <span class="n">beanNames</span> <span class="o">=</span> <span class="n">beanFactory</span><span class="o">.</span><span class="na">getBeanNamesForType</span><span class="o">(</span><span class="nc">MapperFactoryBean</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">beanNames</span><span class="o">.</span><span class="na">length</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
                    <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
                <span class="o">}</span>
                <span class="k">return</span> <span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">code</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="o">{</span>
                    <span class="nc">RuntimeHints</span> <span class="n">hints</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">getRuntimeHints</span><span class="o">();</span>
                    <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span> <span class="o">:</span> <span class="n">beanNames</span><span class="o">)</span> <span class="o">{</span>
                        <span class="nc">BeanDefinition</span> <span class="n">beanDefinition</span> <span class="o">=</span> <span class="n">beanFactory</span><span class="o">.</span><span class="na">getBeanDefinition</span><span class="o">(</span><span class="n">beanName</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span>
                        <span class="nc">PropertyValue</span> <span class="n">mapperInterface</span> <span class="o">=</span> <span class="n">beanDefinition</span><span class="o">.</span><span class="na">getPropertyValues</span><span class="o">().</span><span class="na">getPropertyValue</span><span class="o">(</span><span class="s">"mapperInterface"</span><span class="o">);</span>
                        <span class="k">if</span> <span class="o">(</span><span class="n">mapperInterface</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">mapperInterface</span><span class="o">.</span><span class="na">getValue</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                            <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">mapperInterfaceType</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?&gt;)</span> <span class="n">mapperInterface</span><span class="o">.</span><span class="na">getValue</span><span class="o">();</span>
                            <span class="k">if</span> <span class="o">(</span><span class="n">mapperInterfaceType</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                                <span class="n">registerReflectionTypeIfNecessary</span><span class="o">(</span><span class="n">mapperInterfaceType</span><span class="o">,</span> <span class="n">hints</span><span class="o">);</span>
                                <span class="n">hints</span><span class="o">.</span><span class="na">proxies</span><span class="o">().</span><span class="na">registerJdkProxy</span><span class="o">(</span><span class="n">mapperInterfaceType</span><span class="o">);</span>
                                <span class="n">hints</span><span class="o">.</span><span class="na">resources</span><span class="o">()</span>
                                        <span class="o">.</span><span class="na">registerPattern</span><span class="o">(</span><span class="n">mapperInterfaceType</span><span class="o">.</span><span class="na">getName</span><span class="o">().</span><span class="na">replace</span><span class="o">(</span><span class="sc">'.'</span><span class="o">,</span> <span class="sc">'/'</span><span class="o">).</span><span class="na">concat</span><span class="o">(</span><span class="s">".xml"</span><span class="o">));</span>
                                <span class="n">registerMapperRelationships</span><span class="o">(</span><span class="n">mapperInterfaceType</span><span class="o">,</span> <span class="n">hints</span><span class="o">);</span>
                            <span class="o">}</span>
                        <span class="o">}</span>
                    <span class="o">}</span>
                <span class="o">};</span>
            <span class="o">}</span>
    
            <span class="kd">private</span> <span class="kt">void</span> <span class="nf">registerMapperRelationships</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">mapperInterfaceType</span><span class="o">,</span> <span class="nc">RuntimeHints</span> <span class="n">hints</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">Method</span><span class="o">[]</span> <span class="n">methods</span> <span class="o">=</span> <span class="nc">ReflectionUtils</span><span class="o">.</span><span class="na">getAllDeclaredMethods</span><span class="o">(</span><span class="n">mapperInterfaceType</span><span class="o">);</span>
                <span class="k">for</span> <span class="o">(</span><span class="nc">Method</span> <span class="n">method</span> <span class="o">:</span> <span class="n">methods</span><span class="o">)</span> <span class="o">{</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">method</span><span class="o">.</span><span class="na">getDeclaringClass</span><span class="o">()</span> <span class="o">!=</span> <span class="nc">Object</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="o">{</span>
                        <span class="nc">ReflectionUtils</span><span class="o">.</span><span class="na">makeAccessible</span><span class="o">(</span><span class="n">method</span><span class="o">);</span>
                        <span class="n">registerSqlProviderTypes</span><span class="o">(</span><span class="n">method</span><span class="o">,</span> <span class="n">hints</span><span class="o">,</span> <span class="nc">SelectProvider</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nl">SelectProvider:</span><span class="o">:</span><span class="n">value</span><span class="o">,</span> <span class="nl">SelectProvider:</span><span class="o">:</span><span class="n">type</span><span class="o">);</span>
                        <span class="n">registerSqlProviderTypes</span><span class="o">(</span><span class="n">method</span><span class="o">,</span> <span class="n">hints</span><span class="o">,</span> <span class="nc">InsertProvider</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nl">InsertProvider:</span><span class="o">:</span><span class="n">value</span><span class="o">,</span> <span class="nl">InsertProvider:</span><span class="o">:</span><span class="n">type</span><span class="o">);</span>
                        <span class="n">registerSqlProviderTypes</span><span class="o">(</span><span class="n">method</span><span class="o">,</span> <span class="n">hints</span><span class="o">,</span> <span class="nc">UpdateProvider</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nl">UpdateProvider:</span><span class="o">:</span><span class="n">value</span><span class="o">,</span> <span class="nl">UpdateProvider:</span><span class="o">:</span><span class="n">type</span><span class="o">);</span>
                        <span class="n">registerSqlProviderTypes</span><span class="o">(</span><span class="n">method</span><span class="o">,</span> <span class="n">hints</span><span class="o">,</span> <span class="nc">DeleteProvider</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nl">DeleteProvider:</span><span class="o">:</span><span class="n">value</span><span class="o">,</span> <span class="nl">DeleteProvider:</span><span class="o">:</span><span class="n">type</span><span class="o">);</span>
                        <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">returnType</span> <span class="o">=</span> <span class="nc">MyBatisMapperTypeUtils</span><span class="o">.</span><span class="na">resolveReturnClass</span><span class="o">(</span><span class="n">mapperInterfaceType</span><span class="o">,</span> <span class="n">method</span><span class="o">);</span>
                        <span class="n">registerReflectionTypeIfNecessary</span><span class="o">(</span><span class="n">returnType</span><span class="o">,</span> <span class="n">hints</span><span class="o">);</span>
                        <span class="nc">MyBatisMapperTypeUtils</span><span class="o">.</span><span class="na">resolveParameterClasses</span><span class="o">(</span><span class="n">mapperInterfaceType</span><span class="o">,</span> <span class="n">method</span><span class="o">)</span>
                                <span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="n">registerReflectionTypeIfNecessary</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">hints</span><span class="o">));</span>
                    <span class="o">}</span>
                <span class="o">}</span>
            <span class="o">}</span>
    
            <span class="nd">@SafeVarargs</span>
            <span class="kd">private</span> <span class="o">&lt;</span><span class="no">T</span> <span class="kd">extends</span> <span class="nc">Annotation</span><span class="o">&gt;</span> <span class="kt">void</span> <span class="nf">registerSqlProviderTypes</span><span class="o">(</span>
                    <span class="nc">Method</span> <span class="n">method</span><span class="o">,</span> <span class="nc">RuntimeHints</span> <span class="n">hints</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">annotationType</span><span class="o">,</span> <span class="nc">Function</span><span class="o">&lt;</span><span class="no">T</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;?&gt;&gt;...</span> <span class="n">providerTypeResolvers</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">for</span> <span class="o">(</span><span class="no">T</span> <span class="n">annotation</span> <span class="o">:</span> <span class="n">method</span><span class="o">.</span><span class="na">getAnnotationsByType</span><span class="o">(</span><span class="n">annotationType</span><span class="o">))</span> <span class="o">{</span>
                    <span class="k">for</span> <span class="o">(</span><span class="nc">Function</span><span class="o">&lt;</span><span class="no">T</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;?&gt;&gt;</span> <span class="n">providerTypeResolver</span> <span class="o">:</span> <span class="n">providerTypeResolvers</span><span class="o">)</span> <span class="o">{</span>
                        <span class="n">registerReflectionTypeIfNecessary</span><span class="o">(</span><span class="n">providerTypeResolver</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">annotation</span><span class="o">),</span> <span class="n">hints</span><span class="o">);</span>
                    <span class="o">}</span>
                <span class="o">}</span>
            <span class="o">}</span>
    
            <span class="kd">private</span> <span class="kt">void</span> <span class="nf">registerReflectionTypeIfNecessary</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">type</span><span class="o">,</span> <span class="nc">RuntimeHints</span> <span class="n">hints</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">if</span> <span class="o">(!</span><span class="n">type</span><span class="o">.</span><span class="na">isPrimitive</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">type</span><span class="o">.</span><span class="na">getName</span><span class="o">().</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"java"</span><span class="o">))</span> <span class="o">{</span>
                    <span class="n">hints</span><span class="o">.</span><span class="na">reflection</span><span class="o">().</span><span class="na">registerType</span><span class="o">(</span><span class="n">type</span><span class="o">,</span> <span class="nc">MemberCategory</span><span class="o">.</span><span class="na">values</span><span class="o">());</span>
                <span class="o">}</span>
            <span class="o">}</span>
    
        <span class="o">}</span>
    
        <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyBatisMapperTypeUtils</span> <span class="o">{</span>
            <span class="kd">private</span> <span class="nf">MyBatisMapperTypeUtils</span><span class="o">()</span> <span class="o">{</span>
                <span class="c1">// NOP</span>
            <span class="o">}</span>
    
            <span class="kd">static</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">resolveReturnClass</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">mapperInterface</span><span class="o">,</span> <span class="nc">Method</span> <span class="n">method</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">Type</span> <span class="n">resolvedReturnType</span> <span class="o">=</span> <span class="nc">TypeParameterResolver</span><span class="o">.</span><span class="na">resolveReturnType</span><span class="o">(</span><span class="n">method</span><span class="o">,</span> <span class="n">mapperInterface</span><span class="o">);</span>
                <span class="k">return</span> <span class="nf">typeToClass</span><span class="o">(</span><span class="n">resolvedReturnType</span><span class="o">,</span> <span class="n">method</span><span class="o">.</span><span class="na">getReturnType</span><span class="o">());</span>
            <span class="o">}</span>
    
            <span class="kd">static</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">Class</span><span class="o">&lt;?&gt;&gt;</span> <span class="n">resolveParameterClasses</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">mapperInterface</span><span class="o">,</span> <span class="nc">Method</span> <span class="n">method</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">return</span> <span class="nc">Stream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="nc">TypeParameterResolver</span><span class="o">.</span><span class="na">resolveParamTypes</span><span class="o">(</span><span class="n">method</span><span class="o">,</span> <span class="n">mapperInterface</span><span class="o">))</span>
                        <span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">x</span> <span class="o">-&gt;</span> <span class="n">typeToClass</span><span class="o">(</span><span class="n">x</span><span class="o">,</span> <span class="n">x</span> <span class="k">instanceof</span> <span class="nc">Class</span> <span class="o">?</span> <span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?&gt;)</span> <span class="n">x</span> <span class="o">:</span> <span class="nc">Object</span><span class="o">.</span><span class="na">class</span><span class="o">)).</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toSet</span><span class="o">());</span>
            <span class="o">}</span>
    
            <span class="kd">private</span> <span class="kd">static</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">typeToClass</span><span class="o">(</span><span class="nc">Type</span> <span class="n">src</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">fallback</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">result</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">src</span> <span class="k">instanceof</span> <span class="nc">Class</span><span class="o">&lt;?&gt;)</span> <span class="o">{</span>
                    <span class="k">if</span> <span class="o">(((</span><span class="nc">Class</span><span class="o">&lt;?&gt;)</span> <span class="n">src</span><span class="o">).</span><span class="na">isArray</span><span class="o">())</span> <span class="o">{</span>
                        <span class="n">result</span> <span class="o">=</span> <span class="o">((</span><span class="nc">Class</span><span class="o">&lt;?&gt;)</span> <span class="n">src</span><span class="o">).</span><span class="na">getComponentType</span><span class="o">();</span>
                    <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                        <span class="n">result</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?&gt;)</span> <span class="n">src</span><span class="o">;</span>
                    <span class="o">}</span>
                <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">src</span> <span class="k">instanceof</span> <span class="nc">ParameterizedType</span><span class="o">)</span> <span class="o">{</span>
                    <span class="nc">ParameterizedType</span> <span class="n">parameterizedType</span> <span class="o">=</span> <span class="o">(</span><span class="nc">ParameterizedType</span><span class="o">)</span> <span class="n">src</span><span class="o">;</span>
                    <span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="o">(</span><span class="n">parameterizedType</span><span class="o">.</span><span class="na">getRawType</span><span class="o">()</span> <span class="k">instanceof</span> <span class="nc">Class</span>
                            <span class="o">&amp;&amp;</span> <span class="nc">Map</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">isAssignableFrom</span><span class="o">((</span><span class="nc">Class</span><span class="o">&lt;?&gt;)</span> <span class="n">parameterizedType</span><span class="o">.</span><span class="na">getRawType</span><span class="o">())</span>
                            <span class="o">&amp;&amp;</span> <span class="n">parameterizedType</span><span class="o">.</span><span class="na">getActualTypeArguments</span><span class="o">().</span><span class="na">length</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="o">)</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="o">;</span>
                    <span class="nc">Type</span> <span class="n">actualType</span> <span class="o">=</span> <span class="n">parameterizedType</span><span class="o">.</span><span class="na">getActualTypeArguments</span><span class="o">()[</span><span class="n">index</span><span class="o">];</span>
                    <span class="n">result</span> <span class="o">=</span> <span class="n">typeToClass</span><span class="o">(</span><span class="n">actualType</span><span class="o">,</span> <span class="n">fallback</span><span class="o">);</span>
                <span class="o">}</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                    <span class="n">result</span> <span class="o">=</span> <span class="n">fallback</span><span class="o">;</span>
                <span class="o">}</span>
                <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
            <span class="o">}</span>
    
        <span class="o">}</span>
    
        <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyBatisMapperFactoryBeanPostProcessor</span> <span class="kd">implements</span> <span class="nc">MergedBeanDefinitionPostProcessor</span><span class="o">,</span> <span class="nc">BeanFactoryAware</span> <span class="o">{</span>
    
            <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">org</span><span class="o">.</span><span class="na">apache</span><span class="o">.</span><span class="na">commons</span><span class="o">.</span><span class="na">logging</span><span class="o">.</span><span class="na">Log</span> <span class="no">LOG</span> <span class="o">=</span> <span class="nc">LogFactory</span><span class="o">.</span><span class="na">getLog</span><span class="o">(</span>
                    <span class="nc">MyBatisMapperFactoryBeanPostProcessor</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
    
            <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">MAPPER_FACTORY_BEAN</span> <span class="o">=</span> <span class="s">"org.mybatis.spring.mapper.MapperFactoryBean"</span><span class="o">;</span>
    
            <span class="kd">private</span> <span class="nc">ConfigurableBeanFactory</span> <span class="n">beanFactory</span><span class="o">;</span>
    
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setBeanFactory</span><span class="o">(</span><span class="nc">BeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">this</span><span class="o">.</span><span class="na">beanFactory</span> <span class="o">=</span> <span class="o">(</span><span class="nc">ConfigurableBeanFactory</span><span class="o">)</span> <span class="n">beanFactory</span><span class="o">;</span>
            <span class="o">}</span>
    
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">postProcessMergedBeanDefinition</span><span class="o">(</span><span class="nc">RootBeanDefinition</span> <span class="n">beanDefinition</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">beanType</span><span class="o">,</span> <span class="nc">String</span> <span class="n">beanName</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">if</span> <span class="o">(</span><span class="nc">ClassUtils</span><span class="o">.</span><span class="na">isPresent</span><span class="o">(</span><span class="no">MAPPER_FACTORY_BEAN</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">beanFactory</span><span class="o">.</span><span class="na">getBeanClassLoader</span><span class="o">()))</span> <span class="o">{</span>
                    <span class="n">resolveMapperFactoryBeanTypeIfNecessary</span><span class="o">(</span><span class="n">beanDefinition</span><span class="o">);</span>
                <span class="o">}</span>
            <span class="o">}</span>
    
            <span class="kd">private</span> <span class="kt">void</span> <span class="nf">resolveMapperFactoryBeanTypeIfNecessary</span><span class="o">(</span><span class="nc">RootBeanDefinition</span> <span class="n">beanDefinition</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">if</span> <span class="o">(!</span><span class="n">beanDefinition</span><span class="o">.</span><span class="na">hasBeanClass</span><span class="o">()</span> <span class="o">||</span> <span class="o">!</span><span class="nc">MapperFactoryBean</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">isAssignableFrom</span><span class="o">(</span><span class="n">beanDefinition</span><span class="o">.</span><span class="na">getBeanClass</span><span class="o">()))</span> <span class="o">{</span>
                    <span class="k">return</span><span class="o">;</span>
                <span class="o">}</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">beanDefinition</span><span class="o">.</span><span class="na">getResolvableType</span><span class="o">().</span><span class="na">hasUnresolvableGenerics</span><span class="o">())</span> <span class="o">{</span>
                    <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">mapperInterface</span> <span class="o">=</span> <span class="n">getMapperInterface</span><span class="o">(</span><span class="n">beanDefinition</span><span class="o">);</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">mapperInterface</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                        <span class="c1">// Exposes a generic type information to context for prevent early initializing</span>
                        <span class="nc">ConstructorArgumentValues</span> <span class="n">constructorArgumentValues</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConstructorArgumentValues</span><span class="o">();</span>
                        <span class="n">constructorArgumentValues</span><span class="o">.</span><span class="na">addGenericArgumentValue</span><span class="o">(</span><span class="n">mapperInterface</span><span class="o">);</span>
                        <span class="n">beanDefinition</span><span class="o">.</span><span class="na">setConstructorArgumentValues</span><span class="o">(</span><span class="n">constructorArgumentValues</span><span class="o">);</span>
                        <span class="n">beanDefinition</span><span class="o">.</span><span class="na">setTargetType</span><span class="o">(</span><span class="nc">ResolvableType</span><span class="o">.</span><span class="na">forClassWithGenerics</span><span class="o">(</span><span class="n">beanDefinition</span><span class="o">.</span><span class="na">getBeanClass</span><span class="o">(),</span> <span class="n">mapperInterface</span><span class="o">));</span>
                        <span class="n">beanDefinition</span><span class="o">.</span><span class="na">getPropertyValues</span><span class="o">().</span><span class="na">addPropertyValue</span><span class="o">(</span>
                                <span class="s">"sqlSessionTemplate"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">RuntimeBeanReference</span><span class="o">(</span><span class="nc">SqlSessionTemplate</span><span class="o">.</span><span class="na">class</span><span class="o">));</span>
                    <span class="o">}</span>
                <span class="o">}</span>
            <span class="o">}</span>
    
            <span class="kd">private</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">getMapperInterface</span><span class="o">(</span><span class="nc">RootBeanDefinition</span> <span class="n">beanDefinition</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">try</span> <span class="o">{</span>
                    <span class="k">return</span> <span class="o">(</span><span class="nc">Class</span><span class="o">&lt;?&gt;)</span> <span class="n">beanDefinition</span><span class="o">.</span><span class="na">getPropertyValues</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="s">"mapperInterface"</span><span class="o">);</span>
                <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                    <span class="no">LOG</span><span class="o">.</span><span class="na">debug</span><span class="o">(</span><span class="s">"Fail getting mapper interface type."</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
                    <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
                <span class="o">}</span>
            <span class="o">}</span>
    
        <span class="o">}</span>
    <span class="o">}</span>

</code></pre></div></div>
<p>这里我项目没有使用Lambda表达式，所以没有修复Lambda表达式相关的问题。至此依赖都已解决。</p>
<h1 id="打包运行报错">打包运行报错</h1>
<h2 id="直接使用spring-boot打包运行">直接使用spring-boot打包运行</h2>
<p>命令行运行 <code class="language-plaintext highlighter-rouge">mvn clean package -Pnative</code> 后,构建成native镜像 <code class="language-plaintext highlighter-rouge">mvn spring-boot:build-image</code>，然后运行 <code class="language-plaintext highlighter-rouge">docker run -it --rm -v ./logs/:/workspace/logs/   --name my-app my-app:0.0.1-SNAPSHOT</code></p>

<p>由于项目使用了日志框架LogBack,用于自定义日志等级格式，此时启动时会报错，报错信息如下：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Logging system failed to initialize using configuration from 'null'
java.lang.IllegalStateException: Logback configuration error detected: 
ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file_info] - openFile(logs/application-info.log,true) call failed. java.io.FileNotFoundException: logs/application-info.log (Permission denied)
ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file_error] - openFile(logs/application-error.log,true) call failed. java.io.FileNotFoundException: logs/application-error.log (Permission denied)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.reportConfigurationErrorsIfNecessary(LogbackLoggingSystem.java:282)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.initializeFromAotGeneratedArtifactsIfPossible(LogbackLoggingSystem.java:218)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:192)
        at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:332)
        at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:298)
        at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:246)
        at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:223)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:138)
        at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:136)
        at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:81)
        at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:64)
        at java.base@21.0.2/java.lang.Iterable.forEach(Iterable.java:75)
        at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118)
        at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112)
        at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:63)
        at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:370)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:330)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
        at com.hozon.filestorage.FileStorageApplication.main(FileStorageApplication.java:35)
        at java.base@21.0.2/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
        Suppressed: java.io.FileNotFoundException: logs/application-info.log (Permission denied)
                at java.base@21.0.2/java.io.FileOutputStream.open0(Native Method)
                at java.base@21.0.2/java.io.FileOutputStream.open(FileOutputStream.java:289)
                at java.base@21.0.2/java.io.FileOutputStream.&lt;init&gt;(FileOutputStream.java:230)
                at ch.qos.logback.core.recovery.ResilientFileOutputStream.&lt;init&gt;(ResilientFileOutputStream.java:26)
                at ch.qos.logback.core.FileAppender.openFile(FileAppender.java:206)
                at ch.qos.logback.core.FileAppender.start(FileAppender.java:126)
                at ch.qos.logback.core.rolling.RollingFileAppender.start(RollingFileAppender.java:103)
                at ch.qos.logback.core.model.processor.AppenderModelHandler.postHandle(AppenderModelHandler.java:84)
                at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:257)
                at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:253)
                at ch.qos.logback.core.model.processor.DefaultProcessor.traversalLoop(DefaultProcessor.java:90)
                at ch.qos.logback.core.model.processor.DefaultProcessor.process(DefaultProcessor.java:106)
                at ch.qos.logback.core.joran.GenericXMLConfigurator.processModel(GenericXMLConfigurator.java:216)
                at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.processModel(SpringBootJoranConfigurator.java:133)
                at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.configureUsingAotGeneratedArtifacts(SpringBootJoranConfigurator.java:126)
                at org.springframework.boot.logging.logback.LogbackLoggingSystem.initializeFromAotGeneratedArtifactsIfPossible(LogbackLoggingSystem.java:216)
                ... 22 more
        Suppressed: java.io.FileNotFoundException: logs/application-error.log (Permission denied)
                at java.base@21.0.2/java.io.FileOutputStream.open0(Native Method)
                at java.base@21.0.2/java.io.FileOutputStream.open(FileOutputStream.java:289)
                at java.base@21.0.2/java.io.FileOutputStream.&lt;init&gt;(FileOutputStream.java:230)
                at ch.qos.logback.core.recovery.ResilientFileOutputStream.&lt;init&gt;(ResilientFileOutputStream.java:26)
                at ch.qos.logback.core.FileAppender.openFile(FileAppender.java:206)
                at ch.qos.logback.core.FileAppender.start(FileAppender.java:126)
                at ch.qos.logback.core.rolling.RollingFileAppender.start(RollingFileAppender.java:103)
                at ch.qos.logback.core.model.processor.AppenderModelHandler.postHandle(AppenderModelHandler.java:84)
                at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:257)
                at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:253)
                at ch.qos.logback.core.model.processor.DefaultProcessor.traversalLoop(DefaultProcessor.java:90)
                at ch.qos.logback.core.model.processor.DefaultProcessor.process(DefaultProcessor.java:106)
                at ch.qos.logback.core.joran.GenericXMLConfigurator.processModel(GenericXMLConfigurator.java:216)
                at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.processModel(SpringBootJoranConfigurator.java:133)
                at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.configureUsingAotGeneratedArtifacts(SpringBootJoranConfigurator.java:126)
                at org.springframework.boot.logging.logback.LogbackLoggingSystem.initializeFromAotGeneratedArtifactsIfPossible(LogbackLoggingSystem.java:216)
                ... 22 more
Application run failed
java.lang.IllegalStateException: java.lang.IllegalStateException: Logback configuration error detected: 
ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file_info] - openFile(logs/application-info.log,true) call failed. java.io.FileNotFoundException: logs/application-info.log (Permission denied)
ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file_error] - openFile(logs/application-error.log,true) call failed. java.io.FileNotFoundException: logs/application-error.log (Permission denied)
        at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:347)
        at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:298)
        at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:246)
        at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:223)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:138)
        at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:136)
        at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:81)
        at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:64)
        at java.base@21.0.2/java.lang.Iterable.forEach(Iterable.java:75)
        at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118)
        at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112)
        at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:63)
        at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:370)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:330)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
        at com.hozon.filestorage.FileStorageApplication.main(FileStorageApplication.java:35)
        at java.base@21.0.2/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.lang.IllegalStateException: Logback configuration error detected: 
ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file_info] - openFile(logs/application-info.log,true) call failed. java.io.FileNotFoundException: logs/application-info.log (Permission denied)
ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file_error] - openFile(logs/application-error.log,true) call failed. java.io.FileNotFoundException: logs/application-error.log (Permission denied)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.reportConfigurationErrorsIfNecessary(LogbackLoggingSystem.java:282)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.initializeFromAotGeneratedArtifactsIfPossible(LogbackLoggingSystem.java:218)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:192)
        at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:332)
        ... 20 more
        Suppressed: java.io.FileNotFoundException: logs/application-info.log (Permission denied)
                at java.base@21.0.2/java.io.FileOutputStream.open0(Native Method)
                at java.base@21.0.2/java.io.FileOutputStream.open(FileOutputStream.java:289)
                at java.base@21.0.2/java.io.FileOutputStream.&lt;init&gt;(FileOutputStream.java:230)
                at ch.qos.logback.core.recovery.ResilientFileOutputStream.&lt;init&gt;(ResilientFileOutputStream.java:26)
                at ch.qos.logback.core.FileAppender.openFile(FileAppender.java:206)
                at ch.qos.logback.core.FileAppender.start(FileAppender.java:126)
                at ch.qos.logback.core.rolling.RollingFileAppender.start(RollingFileAppender.java:103)
                at ch.qos.logback.core.model.processor.AppenderModelHandler.postHandle(AppenderModelHandler.java:84)
                at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:257)
                at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:253)
                at ch.qos.logback.core.model.processor.DefaultProcessor.traversalLoop(DefaultProcessor.java:90)
                at ch.qos.logback.core.model.processor.DefaultProcessor.process(DefaultProcessor.java:106)
                at ch.qos.logback.core.joran.GenericXMLConfigurator.processModel(GenericXMLConfigurator.java:216)
                at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.processModel(SpringBootJoranConfigurator.java:133)
                at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.configureUsingAotGeneratedArtifacts(SpringBootJoranConfigurator.java:126)
                at org.springframework.boot.logging.logback.LogbackLoggingSystem.initializeFromAotGeneratedArtifactsIfPossible(LogbackLoggingSystem.java:216)
                ... 22 more
        Suppressed: java.io.FileNotFoundException: logs/application-error.log (Permission denied)
                at java.base@21.0.2/java.io.FileOutputStream.open0(Native Method)
                at java.base@21.0.2/java.io.FileOutputStream.open(FileOutputStream.java:289)
                at java.base@21.0.2/java.io.FileOutputStream.&lt;init&gt;(FileOutputStream.java:230)
                at ch.qos.logback.core.recovery.ResilientFileOutputStream.&lt;init&gt;(ResilientFileOutputStream.java:26)
                at ch.qos.logback.core.FileAppender.openFile(FileAppender.java:206)
                at ch.qos.logback.core.FileAppender.start(FileAppender.java:126)
                at ch.qos.logback.core.rolling.RollingFileAppender.start(RollingFileAppender.java:103)
                at ch.qos.logback.core.model.processor.AppenderModelHandler.postHandle(AppenderModelHandler.java:84)
                at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:257)
                at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:253)
                at ch.qos.logback.core.model.processor.DefaultProcessor.traversalLoop(DefaultProcessor.java:90)
                at ch.qos.logback.core.model.processor.DefaultProcessor.process(DefaultProcessor.java:106)
                at ch.qos.logback.core.joran.GenericXMLConfigurator.processModel(GenericXMLConfigurator.java:216)
                at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.processModel(SpringBootJoranConfigurator.java:133)
                at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.configureUsingAotGeneratedArtifacts(SpringBootJoranConfigurator.java:126)
                at org.springframework.boot.logging.logback.LogbackLoggingSystem.initializeFromAotGeneratedArtifactsIfPossible(LogbackLoggingSystem.java:216)
                ... 22 more
Exception in thread "main" java.lang.IllegalStateException: java.lang.IllegalStateException: Logback configuration error detected: 
ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file_info] - openFile(logs/application-info.log,true) call failed. java.io.FileNotFoundException: logs/application-info.log (Permission denied)
ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file_error] - openFile(logs/application-error.log,true) call failed. java.io.FileNotFoundException: logs/application-error.log (Permission denied)
        at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:347)
        at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:298)
        at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:246)
        at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:223)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:138)
        at org.springframework.boot.context.event.EventPublishingRunListener.multicastInitialEvent(EventPublishingRunListener.java:136)
        at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:81)
        at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:64)
        at java.base@21.0.2/java.lang.Iterable.forEach(Iterable.java:75)
        at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118)
        at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112)
        at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:63)
        at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:370)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:330)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
        at com.hozon.filestorage.FileStorageApplication.main(FileStorageApplication.java:35)
        at java.base@21.0.2/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.lang.IllegalStateException: Logback configuration error detected: 
ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file_info] - openFile(logs/application-info.log,true) call failed. java.io.FileNotFoundException: logs/application-info.log (Permission denied)
ERROR in ch.qos.logback.core.rolling.RollingFileAppender[file_error] - openFile(logs/application-error.log,true) call failed. java.io.FileNotFoundException: logs/application-error.log (Permission denied)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.reportConfigurationErrorsIfNecessary(LogbackLoggingSystem.java:282)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.initializeFromAotGeneratedArtifactsIfPossible(LogbackLoggingSystem.java:218)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:192)
        at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:332)
        ... 20 more
        Suppressed: java.io.FileNotFoundException: logs/application-info.log (Permission denied)
                at java.base@21.0.2/java.io.FileOutputStream.open0(Native Method)
                at java.base@21.0.2/java.io.FileOutputStream.open(FileOutputStream.java:289)
                at java.base@21.0.2/java.io.FileOutputStream.&lt;init&gt;(FileOutputStream.java:230)
                at ch.qos.logback.core.recovery.ResilientFileOutputStream.&lt;init&gt;(ResilientFileOutputStream.java:26)
                at ch.qos.logback.core.FileAppender.openFile(FileAppender.java:206)
                at ch.qos.logback.core.FileAppender.start(FileAppender.java:126)
                at ch.qos.logback.core.rolling.RollingFileAppender.start(RollingFileAppender.java:103)
                at ch.qos.logback.core.model.processor.AppenderModelHandler.postHandle(AppenderModelHandler.java:84)
                at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:257)
                at ch.qos.logback.core.model.processor.DefaultProcessor.secondPhaseTraverse(DefaultProcessor.java:253)
                at ch.qos.logback.core.model.processor.DefaultProcessor.traversalLoop(DefaultProcessor.java:90)
                at ch.qos.logback.core.model.processor.DefaultProcessor.process(DefaultProcessor.java:106)
                at ch.qos.logback.core.joran.GenericXMLConfigurator.processModel(GenericXMLConfigurator.java:216)
                at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.processModel(SpringBootJoranConfigurator.java:133)
                at org.springframework.boot.logging.logback.SpringBootJoranConfigurator.configureUsingAotGeneratedArtifacts(SpringBootJoranConfigurator.java:126)
                at org.springframework.boot.logging.logback.LogbackLoggingSystem.initializeFromAotGeneratedArtifactsIfPossible(LogbackLoggingSystem.java:216)
                ... 22 more
        Suppressed: java.io.FileNotFoundException: logs/application-error.log (Permission denied)
                at java.base@21.0.2/java.io.FileOutputStream.open0(Native Method)
                at java.base@21.0.2/java.io.FileOutputStream.open(FileOutputStream.java:289)
</code></pre></div></div>
<p>这里一直提示权限不够，尝试修改文件权限为777后依旧无法解决，最后通过自定义Dockerfile文件手动打包解决</p>
<h2 id="创建dockerfile文件">创建Dockerfile文件</h2>
<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    FROM alpine
    ENV TZ Asia/Shanghai
    RUN apk -U upgrade &amp;&amp; apk add tzdata &amp;&amp; cp /usr/share/zoneinfo/${TZ} /etc/localtime &amp;&amp; echo ${TZ} &gt; /etc/timezone
    EXPOSE 8080
    COPY target/app app
    RUN apk add libc6-compat
    CMD ["/app"]
</code></pre></div></div>
<p>这里使用了Alpine镜像，并安装<code class="language-plaintext highlighter-rouge">tzdata</code>和<code class="language-plaintext highlighter-rouge">libc6-compat</code>，设置时区为Asia/Shanghai。
注意这里的<code class="language-plaintext highlighter-rouge">libc6-compat</code>是必需的，如果不安装，可能会出现应用启动失败，提示<code class="language-plaintext highlighter-rouge">no-such-file-or-directory</code>，当然，你也可以选择使用其他镜像。</p>
<h2 id="构建镜像">构建镜像</h2>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    docker build <span class="nt">-t</span> my-app:0.0.1-SNAPSHOT <span class="nb">.</span>
</code></pre></div></div>
<p>此时再次执行</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    docker run <span class="nt">-it</span> <span class="nt">--rm</span> <span class="nt">-v</span> ./logs/:/workspace/logs/   <span class="nt">--name</span> my-app my-app:0.0.1-SNAPSHOT
</code></pre></div></div>
<p>运行成功，日志文件生成在<code class="language-plaintext highlighter-rouge">./logs/</code>目录下，可以查看日志文件内容。</p>]]></content><author><name>2han9wen71an</name></author><category term="代码笔记" /><category term="spring native docker" /><summary type="html"><![CDATA[前言 最近有一个小项目，没有历史包裹，直接使用最新的spring native进行构建。 在打包的时候，遇到了很多坑，虽然打包成功，但是在运行时，还是报错，特此写一篇文章记录下来。 首先是项目配置 我这边使用的是最新的spring-boot版本3.3.2 &lt;parent&gt; &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt; &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt; &lt;version&gt;3.3.2&lt;/version&gt; &lt;relativePath/&gt; &lt;!-- lookup parent from repository --&gt; &lt;/parent&gt; 由于项目中使用了AWS的S3 SDK，所以需要添加依赖 &lt;dependency&gt; &lt;groupId&gt;com.amazonaws&lt;/groupId&gt; &lt;artifactId&gt;aws-java-sdk-s3&lt;/artifactId&gt; &lt;version&gt;1.12.767&lt;/version&gt; &lt;/dependency&gt; 遇到的问题 本地打包成二进制启动报错 AWS SDK报错 Caused by: java.lang.ExceptionInInitializerError: null at com.amazonaws.util.VersionInfoUtils.userAgent(VersionInfoUtils.java:142) ~[na:na] at com.amazonaws.util.VersionInfoUtils.initializeUserAgent(VersionInfoUtils.java:137) ~[na:na] at com.amazonaws.util.VersionInfoUtils.getUserAgent(VersionInfoUtils.java:100) ~[na:na] at com.amazonaws.internal.EC2ResourceFetcher.&lt;clinit&gt;(EC2ResourceFetcher.java:44) ~[awsnativedemo:na] at java.base@20.0.2/java.lang.Class.ensureInitialized(DynamicHub.java:579) ~[awsnativedemo:na] at com.amazonaws.auth.InstanceMetadataServiceCredentialsFetcher.&lt;init&gt;(InstanceMetadataServiceCredentialsFetcher.java:37) ~[na:na] at com.amazonaws.auth.InstanceProfileCredentialsProvider.&lt;init&gt;(InstanceProfileCredentialsProvider.java:111) ~[na:na] at com.amazonaws.auth.InstanceProfileCredentialsProvider.&lt;init&gt;(InstanceProfileCredentialsProvider.java:91) ~[na:na] at com.amazonaws.auth.InstanceProfileCredentialsProvider.&lt;init&gt;(InstanceProfileCredentialsProvider.java:75) ~[na:na] at com.amazonaws.auth.InstanceProfileCredentialsProvider.&lt;clinit&gt;(InstanceProfileCredentialsProvider.java:58) ~[na:na] at com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper.initializeProvider(EC2ContainerCredentialsProviderWrapper.java:64) ~[na:na] at com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper.&lt;init&gt;(EC2ContainerCredentialsProviderWrapper.java:53) ~[na:na] at com.amazonaws.auth.DefaultAWSCredentialsProviderChain.&lt;init&gt;(DefaultAWSCredentialsProviderChain.java:49) ~[awsnativedemo:na] at com.amazonaws.auth.DefaultAWSCredentialsProviderChain.&lt;clinit&gt;(DefaultAWSCredentialsProviderChain.java:43) ~[awsnativedemo:na] at java.base@20.0.2/java.lang.Class.ensureInitialized(DynamicHub.java:579) ~[awsnativedemo:na] at com.amazonaws.services.s3.AmazonS3ClientBuilder.standard(AmazonS3ClientBuilder.java:46) ~[awsnativedemo:na] at com.example.awsnativedemo.S3ClientConfig.s3Client(S3ClientConfig.kt:19) ~[awsnativedemo:na] at com.example.awsnativedemo.S3ClientConfig$$SpringCGLIB$$0.CGLIB$s3Client$0(&lt;generated&gt;) ~[awsnativedemo:na] at com.example.awsnativedemo.S3ClientConfig$$SpringCGLIB$$2.invoke(&lt;generated&gt;) ~[awsnativedemo:na] at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[awsnativedemo:6.0.12] at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[na:na] at com.example.awsnativedemo.S3ClientConfig$$SpringCGLIB$$0.s3Client(&lt;generated&gt;) ~[awsnativedemo:na] at com.example.awsnativedemo.S3ClientConfig__BeanDefinitions.lambda$getSClientInstanceSupplier$1(S3ClientConfig__BeanDefinitions.java:37) ~[na:na] at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63) ~[awsnativedemo:6.0.12] at org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51) ~[awsnativedemo:6.0.12] at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$withGenerator$0(BeanInstanceSupplier.java:167) ~[na:na] at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68) ~[awsnativedemo:6.0.12] at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[awsnativedemo:6.0.12] at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:202) ~[na:na] at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[awsnativedemo:6.0.12] at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[awsnativedemo:6.0.12] at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:214) ~[na:na] at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:202) ~[na:na] at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:947) ~[awsnativedemo:6.0.12] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1214) ~[awsnativedemo:6.0.12] ... 34 common frames omitted Caused by: java.lang.IllegalArgumentException: null at com.amazonaws.internal.config.InternalConfig.loadfrom(InternalConfig.java:260) ~[na:na] at com.amazonaws.internal.config.InternalConfig.load(InternalConfig.java:274) ~[na:na] at com.amazonaws.internal.config.InternalConfig$Factory.&lt;clinit&gt;(InternalConfig.java:347) ~[na:na] ... 69 common frames omitted 参考AWS的issueInternalConfig throws exception in runtime with native application，解决方法如下： ```java package com.amazonaws.http.conn;]]></summary></entry><entry><title type="html">Linux按时间筛选日志的方法</title><link href="/Toss-notes/linux-filter-log-by-time.html" rel="alternate" type="text/html" title="Linux按时间筛选日志的方法" /><published>2024-05-11T06:18:55+00:00</published><updated>2024-05-11T06:18:55+00:00</updated><id>/Toss-notes/Linux%E6%8C%89%E6%97%B6%E9%97%B4%E7%AD%9B%E9%80%89%E6%97%A5%E5%BF%97%E7%9A%84%E6%96%B9%E6%B3%95</id><content type="html" xml:base="/Toss-notes/linux-filter-log-by-time.html"><![CDATA[<p>在日常筛查日志时，通常我们会使用ELK工具进行查询和过滤。但如果没有工具支持，我们可以通过其他方式进行操作。</p>

<p>一种常见的方法是通过运维导出日志，然后使用grep、sed、awk等命令结合正则表达式或编写Shell脚本进行分析和筛选。</p>
<h1 id="使用grep和awk">使用grep和awk</h1>
<p>举个例子，通常我们可以使用以下命令来查找错误日志并输出相关的前后几行：</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 显示匹配'nick'的行及其前后5行</span>
<span class="nb">cat </span>error.log | <span class="nb">grep</span> <span class="nt">-C</span> 5 <span class="s1">'nick'</span>
<span class="c"># 显示匹配'nick'的行及其前5行</span>
<span class="nb">cat </span>error.log | <span class="nb">grep</span> <span class="nt">-B</span> 5 <span class="s1">'nick'</span>
<span class="c"># 显示匹配'nick'的行及其后5行</span>
<span class="nb">cat </span>error.log | <span class="nb">grep</span> <span class="nt">-A</span> 5 <span class="s1">'nick'</span>
</code></pre></div></div>
<p>在某些情况下，我们可能需要根据时间戳来筛选日志。我们可以使用grep的-m参数来限制匹配的行数，然后使用awk来提取时间戳。</p>

<p>以下是一个示例：</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat </span>error.log | <span class="nb">grep</span> <span class="nt">-m</span> 1 <span class="s1">'nick'</span> | <span class="nb">awk</span> <span class="s1">'{print $1}'</span> | xargs <span class="nt">-I</span> <span class="o">{}</span> <span class="nb">grep</span> <span class="nt">-A</span> 5 <span class="o">{}</span> error.log
</code></pre></div></div>
<p>这个脚本首先使用grep -m 1 ‘nick’来查找匹配’nick’的行，然后使用awk ‘{print $1}’来提取时间戳，最后使用xargs -I {} grep -A 5 {} error.log来查找匹配时间戳的行并输出其前后5行。
这个脚本的优点是简单易用，缺点是只适用于简单的情况。如果日志格式复杂，或者需要更复杂的筛选条件，可能需要使用其他工具或脚本来完成。</p>
<h1 id="使用sed">使用sed</h1>
<p>sed是一种流编辑器命令，它是文本处理中非常中的工具，能够完美的配合正则表达式使用。使用sed命令选定行的范围语法，既是逗号语
命令如下所示：</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sed</span> <span class="nt">-n</span> <span class="s1">'/2024-05-01 14:30:00/,/2024-05-01 18:00:00/p'</span> error.log | <span class="nb">grep</span>  <span class="s1">'nick'</span>
</code></pre></div></div>
<p>该sed命令含义是选定行范围：从第一行“2024-05-01 14:30:00”开始，到第一行“2024-05-01 18:00:00”结束</p>

<h1 id="命令对比">命令对比</h1>
<p>sed命令运行时间跟grep相近，但是sed命令对时间段适应性较好。如果时间段扩大，grep命令修改较大，而sed命令修改较小。</p>
<h1 id="总结">总结</h1>
<p>grep和sed命令都是常用的文本处理工具，但是它们在处理时间戳时，grep命令的-C参数和awk命令的xargs -I {} grep -A 5 {} error.log命令可以更好的实现时间戳的筛选，而sed命令则更适合时间段的筛选。</p>]]></content><author><name>2han9wen71an</name></author><category term="折腾笔记" /><category term="linux log" /><summary type="html"><![CDATA[在日常筛查日志时，通常我们会使用ELK工具进行查询和过滤。但如果没有工具支持，我们可以通过其他方式进行操作。]]></summary></entry><entry><title type="html">设计模式-VS-设计原则</title><link href="/CodeNotes/design-patterns-vs-design-principles.html" rel="alternate" type="text/html" title="设计模式-VS-设计原则" /><published>2024-01-10T02:54:10+00:00</published><updated>2024-01-10T02:54:10+00:00</updated><id>/CodeNotes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-VS-%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99</id><content type="html" xml:base="/CodeNotes/design-patterns-vs-design-principles.html"><![CDATA[<p>我写了一些关于设计模式的文章。一些例子是观察者模式和策略模式。在编写软件时，这两种模式都非常有效。但我也提到了SOLID，这是一个设计原则的集合。但设计模式和设计原则之间有什么区别呢？我们什么时候谈论设计模式，什么时候谈论设计原则呢？</p>

<p>设计模式和原则都在帮助我们编写更优秀的代码。其中一个更侧重于架构和指南，而另一个更侧重于代码中的解决方案。我们——开发者——在进行工作时需要有某种指导原则和解决方案。但请记住，我们正在处理的是软件：应预料到意外，并且需要了解并非所有环境都一样。</p>

<p>作为开发人员，我接触过很多原则和模式。有些是由团队成员想出来的，适用于该解决方案，有些则由伟大的思想家定义，仍在维护，并且至今仍在使用。本文是关于后者的。</p>

<h1 id="定义">定义</h1>

<p>设计模式和设计原则都有各自不同的定义，这已经展示了它们之间的巨大差异。设计模式是某一特定问题的解决方案。大多数定义都可以在代码中显示。因为它可以翻译成代码，所以这个解决方案通常是可重用的，可以帮助解决反复出现的设计问题。</p>

<p>设计原则是一种指南，它向开发者解释了某些解决方案或指导方针，使得解决代码中可能出现的问题变得更容易。这些指导方针在此是为了帮助我们预防问题。设计问题帮我们创建健壮、易维护、灵活的软件。设计原则并不是代码，只是理论。</p>

<h1 id="设计模式">设计模式</h1>

<p>最为人所知的设计模式之一就是单例模式。这种模式规定并显示，一个类应该只被初始化一次，这个类实例在应用程序的生命周期内一直存在。该模式可用于使用类和初始化类的所有编程语言。</p>

<p>另一个示例是策略模式。这种模式是一种行为模式，它允许在运行时动态选择策略。你可以简化复杂的if语句，或在运行时做出动态选择。</p>

<p>还有其他的示例，如观察者模式和工厂模式，但是还有更多。你可以把它们分为三类：创建性设计、结构设计和行为设计。下面的图片给你一个好的理念，哪些模式属于哪个类别。
<img src="\assets\images\post\design-patterns.png" alt="设计模式分类" />
你几乎不可能记住并运用所有的模式。我的建议是：理解他们的基本思想，知道在什么时候可以使用他们，当你处在一个可能会想到“噢，我知道一个模式可以解决这个问题！”的情况下，弄清楚它们是如何工作的。</p>

<h1 id="设计原则">设计原则</h1>

<p>如果你看一下设计原则，我们会看到其他的名字出现。像SOLID、DRY、KISS，以及其他一些有趣的缩写。</p>

<p>DRY代表的是“不要重复自己”（Don’t Repeat Yourself），它的原则是你不应该写重复的代码。重复的代码在你需要改变一些东西时可能会造成很多问题。要避免这些问题，把你的代码写一次，然后在你的应用程序的其他部分重用它。</p>

<p>KISS代表的是“保持简单，愚蠢”（Keep It Simple Stupid），尽管在现代通常省略了最后的’s’。这个原则规定代码应该对于他人来说是简单和可理解的。你越是使它变得复杂，对于其他人理解就变得越困难。</p>

<p>SOLID是一组不同原则的集合：单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。我可能会为所有这些原则举例，但那将会使你阅读量增大（也会使我书写量增大）。如果你在Wikipedia上查看SOLID的定义，你会得到很多信息。</p>

<h1 id="使用场景">使用场景</h1>

<p>使用原则或模式的时刻也有所不同。没有一种解决方案适用于所有情况，而且你可能根本不会使用它们。设计模式通常在编码过程中使用，因为它们为特定的代码问题提供了解决方案。模式可以在途中添加或移除。</p>

<p>在创建软件的过程中，你的观点可能会变化（或者你的经理的观点会变）。这使你会有不同的决定，你可能想添加或移除一个模式。这是可以的。例如，一个简单的if语句突然变成了一个由elseif-elseif-elseif-elseif-等构成的怪物。那么也许是时候实施策略模式了。</p>

<p>设计原则通常在开发的早期阶段应用。它们主要是关于架构设计和规划。但也包括高级设计。这些是你在编码你的应用程序一半时不能改变的事情。</p>

<p>单一责任原则（Single Responsibility Principle，SRP）非常重要。如果你失去了对这个原则的控制，类和方法开始承担多重责任，你可能最终会重构你的代码，并且打破了许多方法，因为责任可能被分散到了多个方法和类中。</p>

<h1 id="结论">结论</h1>

<p>设计模式是经过验证的解决方案，可以帮助解决反复出现的问题。设计原则是指导方针，可以帮助你为你的软件提供结构和架构。就这样，仅此而已。</p>

<p>你不需要使用所有存在的模式和原则。实际上，在单个软件应用程序中使用所有SOLID原则几乎是不可能的。我曾经做到过一次，那是一个示例项目。</p>

<p>查看设计原则和设计模式的区别并知道何时使用哪一个是一种很好的实践。再说一次，你几乎不可能记住所有的模式和原则，但是要知道他们的名字和背后的基本理念。</p>]]></content><author><name>2han9wen71an</name></author><category term="代码笔记" /><category term="设计模式 设计原则" /><summary type="html"><![CDATA[我写了一些关于设计模式的文章。一些例子是观察者模式和策略模式。在编写软件时，这两种模式都非常有效。但我也提到了SOLID，这是一个设计原则的集合。但设计模式和设计原则之间有什么区别呢？我们什么时候谈论设计模式，什么时候谈论设计原则呢？]]></summary></entry><entry><title type="html">API-Gateway-vs-Reverse-Proxy-vs-Load-Balancer</title><link href="/Toss-notes/api-gateway-vs-reverse-proxy-vs-load-balancer.html" rel="alternate" type="text/html" title="API-Gateway-vs-Reverse-Proxy-vs-Load-Balancer" /><published>2024-01-05T09:28:18+00:00</published><updated>2024-01-05T09:28:18+00:00</updated><id>/Toss-notes/API-Gateway-vs-Reverse-Proxy-vs-Load-Balancer</id><content type="html" xml:base="/Toss-notes/api-gateway-vs-reverse-proxy-vs-load-balancer.html"><![CDATA[<h1 id="什么是-api-网关">什么是 API 网关？</h1>
<p>API 网关（API Gateway）是一种软件服务，它位于客户端和服务器之间，用于提供一个中介，以便客户端和服务器之间可以更轻松地进行通信。</p>

<h2 id="api-网关的主要功能是">API 网关的主要功能是：</h2>

<ul>
  <li>请求路由：将传入请求定向到适当的服务。</li>
  <li>API 组合: 它可以将多个 API 聚合到一个单一的 API 接口中，从而简化客户端的开发。</li>
  <li>API 限流：控制用户在特定时间范围内可以向 API 发送的请求数量。</li>
  <li>安全性：提供身份验证和授权等功能。</li>
</ul>

<h2 id="api-网关的工作原理">API 网关的工作原理：</h2>
<ul>
  <li>客户端向 API 网关发送请求。</li>
  <li>API 网关根据请求的路由规则将请求定向到适当的服务。</li>
  <li>服务处理请求并返回响应。</li>
  <li>API 网关将响应返回给客户端。</li>
</ul>

<h2 id="使用-api-网关的好处">使用 API 网关的好处</h2>
<ul>
  <li>集中管理,简化客户端的开发。</li>
  <li>增强安全性,保护后端服务。</li>
  <li>监控和管理。</li>
  <li>负载均衡,限流。减少服务器的负载，提高性能。</li>
</ul>

<h1 id="什么是负载均衡">什么是负载均衡？</h1>
<p>负载均衡（Load Balancing）是一种网络技术，它将网络流量分配到多个服务器上，以提供更高的可用性和更高的性能。</p>
<h2 id="负载均衡的类型">负载均衡的类型</h2>
<ul>
  <li>硬件负载均衡：针对分配网络流量而优化的物理设备。</li>
  <li>网络负载均衡：使用软件实现的负载均衡。</li>
  <li>内容分发网络（CDN）：将静态资源存储在离用户最近的地点，以减少网络延迟。
    <h2 id="负载均衡机制">负载均衡机制：</h2>
  </li>
  <li>轮询：将流量分配给服务器列表中的服务器，并将请求依次发送到服务器。</li>
  <li>加权轮询：根据服务器的响应时间来分配流量。</li>
  <li>最少连接：将流量分配给服务器列表中的服务器，并将请求分配到最空闲的服务器。</li>
  <li>最快响应：将流量分配给服务器列表中的服务器，并将请求分配到响应时间最快的服务器。
    <h2 id="使用负载均衡的好处">使用负载均衡的好处</h2>
  </li>
  <li>提高应用程序可用性</li>
  <li>可扩展性</li>
  <li>高效的流量分配</li>
  <li>容错能力</li>
</ul>

<h1 id="什么是反向代理">什么是反向代理？</h1>
<p>反向代理（Reverse Proxy）是一个位于客户端和服务器之间的服务器，它接收来自客户端的请求，并将请求转发到服务器上，并将服务器的响应返回给客户端。</p>
<h2 id="反向代理的主要功能是">反向代理的主要功能是：</h2>
<ul>
  <li>缓存：缓存可以减少后端服务器的负载，并提高性能</li>
  <li>压缩：可以减少网络流量。</li>
  <li>负载均衡：将传入请求分发到后端服务器。</li>
  <li>SSL Termination: 解密传入请求并加密服务器响应。
    <h2 id="反向代理例子">反向代理例子</h2>
  </li>
  <li>安全性：隐藏后端服务器的身份。</li>
  <li>性能优化：减少后端服务器的负载。</li>
  <li>应用程序防火墙：防范 Web 应用程序威胁。</li>
</ul>

<h2 id="反向代理的工作流程">反向代理的工作流程：</h2>
<ol>
  <li>客户端向反向代理发出请求。</li>
  <li>反向代理将请求转发到后端服务器。</li>
  <li>后端服务器处理请求并返回响应。</li>
  <li>反向代理将响应返回给客户端。</li>
</ol>

<h2 id="使用反向代理的好处">使用反向代理的好处</h2>
<ul>
  <li>应用防火墙。</li>
  <li>缓存可以减少后端服务器的负载，并提高性能。</li>
  <li>压缩可以减少网络流量。</li>
</ul>

<h1 id="api网关负载均衡和反向代理的比较">API网关,负载均衡和反向代理的比较</h1>
<p>虽然功能上存在一些重叠，但每个组件都有其独特的优势和场景。</p>
<h2 id="异同">异同</h2>
<p>| 功能 | API 网关 | 负载均衡 | 反向代理 |
| — | — | — | — |
| 功能 | 集中管理,简化客户端的开发,提供身份验证和授权等功能 | 负载均衡,限流,提高性能 | 隐藏后端服务器的身份,缓存,负载均衡,压缩 |
| 场景 | 适用于 Web 应用程序 | 适用于 Web 应用程序,企业应用程序 | 适用于 Web
API 网关专注于管理 API，而负载均衡则分配网络流量，反向代理则确保请求转发和安全性。
API 网关和反向代理都可以处理请求路由，但它们的主要目的不同。
负载均衡主要工作在传输层，而 API 网关和反向代理则工作在应用层。</p>
<h2 id="潜在的问题">潜在的问题</h2>
<ul>
  <li>API网关：复杂性、潜在的单点故障</li>
  <li>负载均衡：需要定期更新，代价高昂</li>
  <li>反向代理：可能会引入延迟、配置复杂性</li>
</ul>

<h2 id="何时使用哪个">何时使用哪个？</h2>
<ul>
  <li>当您需要集中式 API 管理时，使用 API 网关。</li>
  <li>当您需要有效分配传入的 Web 流量时，请选择负载均衡。</li>
  <li>实现安全和特定于应用程序的流量管理，请选择反向代理。</li>
</ul>

<h1 id="例子">例子</h1>
<ul>
  <li>API 网关：Netflix 的 API 网关处理数十亿个微服务请求。</li>
  <li>负载均衡：Amazon 的 Elastic Load Balancing 为 AWS 服务分配流量。</li>
  <li>反向代理：Nginx 充当反向代理来管理 Web 服务器的流量。</li>
</ul>

<h1 id="faqs">FAQs</h1>
<ul>
  <li>API 网关的主要用途是什么？ API 网关管理和保护 API 流量，充当客户端和后端服务之间的看门人。</li>
  <li>负载均衡如何增强 Web 应用程序性能？通过在多个服务器之间分配传入流量，确保高效的负载分配和高可用性。</li>
  <li>为什么在 Web 服务器前面使用反向代理？用于安全、性能优化和特定流量管理。</li>
  <li>API网关和反向代理可以一起使用吗？是的，它们可以组合起来管理。</li>
  <li>API 网关和负载均衡之间的区别是什么？API 网关用于管理 API 流量，而负载均衡用于分配网络流量。</li>
  <li>API 网关和反向代理哪个更安全？两者都提供安全功能，但 API 网关主要面向单个接口管理，而反向代理主要用于整个应用</li>
</ul>]]></content><author><name>2han9wen71an</name></author><category term="折腾笔记" /><category term="API网关 反向代理 负载均衡" /><summary type="html"><![CDATA[API 网关、负载均衡和反向代理等术语经常出现。虽然它们看起来很相似，但它们都有独特的用途，用于管理网络流量和确保 Web 应用程序的最佳性能。]]></summary></entry></feed>