<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://chenchi-cc.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://chenchi-cc.github.io/" rel="alternate" type="text/html" /><updated>2026-03-30T12:00:58+00:00</updated><id>https://chenchi-cc.github.io/feed.xml</id><title type="html">DarrenChan陈驰</title><subtitle>陈驰的技术博客</subtitle><author><name>DarrenChan陈驰</name></author><entry><title type="html">简历项目</title><link href="https://chenchi-cc.github.io/2025/08/20/resume-projects/" rel="alternate" type="text/html" title="简历项目" /><published>2025-08-20T00:00:00+00:00</published><updated>2025-08-20T00:00:00+00:00</updated><id>https://chenchi-cc.github.io/2025/08/20/resume-projects</id><content type="html" xml:base="https://chenchi-cc.github.io/2025/08/20/resume-projects/"><![CDATA[<h2 id="0综述">0.综述</h2>
<p>简历项目共包括4部分：</p>
<ul>
  <li>数据治理&amp;性能调优</li>
  <li>离线数仓&amp;ETL</li>
  <li>实时计算</li>
  <li>数据分析&amp;AI</li>
</ul>

<h2 id="1数据治理性能调优">1.数据治理&amp;性能调优</h2>
<ul>
  <li><strong>技术栈：</strong> Scala + Shell + Hadoop + Hive + Spark SQL + Scheduler</li>
  <li><strong>负责：</strong>
    <ul>
      <li>PUSH方向的数据治理：主要包括代码治理、任务治理，规范治理，资源治理和时效性治理。</li>
    </ul>
  </li>
  <li><strong>具体工作：</strong>
    <ul>
      <li><strong>代码治理：</strong> 原来PUSH离线任务多为MR任务，sql脚本杂乱无章，无版本概念，且多在无人维护的旧调度平台。将代码统一用spark3重写，提交到代码库进行管理，并迁移到新的调度平台。
        <ul>
          <li>制定项目结构，做到代码分层和直接运行sql文件，参考：<a href="https://github.com/chenchi-cc/data-warehouse-etl-framework">https://github.com/chenchi-cc/data-warehouse-etl-framework</a></li>
          <li>因成本问题去udw表（一种自研hive表），需要将非核心表迁移到HDFS读写。为提高易用性，将HDFS文件读写（parquet）统一抽象成公共方法，封装schema信息，支持多天查询，兼容通过新增的字段查询旧文件；</li>
        </ul>
      </li>
      <li><strong>任务治理：</strong> 针对PUSH任务数据分层不合理，自助化率低的现状，进行了重构的工作。
        <ul>
          <li>报表逻辑转移至数仓，去除无用中间层逻辑，重复字段的清洗复用同一套代码；</li>
          <li>公共代码抽取，降低代码耦合度，复杂逻辑全部通过udf重写；</li>
          <li>充分利用spark3特性，进行参数性能调优，调整并行度，消除cache滥用，去除逻辑中不等join，全部通过等值join来替代；</li>
          <li>进行分而治之的思想，减少shuffle，将能广播的任务，配置广播；</li>
          <li>引入remote shuffle service，将shuffle数据写到HDFS，防止单台机器被打满，在大规模数据量和非标准队列条件下，保障任务的稳定性；</li>
          <li>针对小文件场景，前置方案设计自适应的分区数（考虑压缩，数据量 / HDFS 块大小），调参动态合并小文件，采用高效parquet格式；后置方案编写脚本，自动化定期合并；</li>
        </ul>
      </li>
      <li><strong>规范治理：</strong> 对设计、流程以及质量层面做了相应的规范性约束。
        <ul>
          <li>设计规范：涵盖表命名，字段命名，数仓模型建设，代码层级规范等；</li>
          <li>流程规范：针对过往排期混乱、需求滞后偏多等问题，规范化了提需流程，需求插入流程，开发流程以及上线流程等；</li>
          <li>质量规范：核心任务全部配置DQC监控，前置在数仓产出前会进行量级校验，后置在报表指标层会对核心指标波动报警；任务失败和延迟都配置了对应的值班机制；</li>
        </ul>
      </li>
      <li><strong>资源治理：</strong> 物理机，计算队列，HDFS存储均会有不同程度的打满，配置了多层监控以及自动清理脚本，推动值班机制进行可靠性保障。
        <ul>
          <li>针对计算队列多而杂的问题，将同地域合并，将队列划分为线上队列（核心+非核心），测试队列和回溯队列；</li>
          <li>针对HDFS存储资源不够现状，进行地域置换，跨地域迁移（distcp），尽量保证和计算队列同一地域；</li>
          <li>针对全量任务梳理，下线无用的任务，清理存储；</li>
        </ul>
      </li>
      <li><strong>时效性治理：</strong> 缩短数仓链路，解决不合理的数据依赖，推动上游签署SLA，切换更快产出的数据源，通过性能调优缩短任务本身的执行时间。</li>
    </ul>
  </li>
  <li><strong>难点：</strong>
    <ul>
      <li><strong>数据倾斜问题优化：</strong> 针对Group By场景以及Join场景（大表join大表，大表join小表）进行优化，提升任务的稳定性和时效性。
        <ul>
          <li>第一步：脏数据的过滤，union数据类型的统一，压缩的调整（当对文件使用GZIP压缩等不支持文件分割操作的压缩方式，在日后有作业涉及读取压缩后的文件时，该压缩文件只会被一个任务所读取。如果该压缩文件很大，则处理该文件的Map需要花费的时间会远多于读取普通文件的Map时间）。</li>
          <li>第二步：想尽一切办法避免shuffle，消除Exchange。
            <ul>
              <li>看小表能否被广播出去：spark.sql.autoBroadcastJoinThreshold默认10MB，调整成2G或更大；或者利用Join Hint强制广播。需要预估小表的大小（磁盘+内存）。限制是不能full outer join，也不能广播外表（例如左关联，广播左边的表）。</li>
              <li>小表不能广播：看能否过滤后被广播。常见的嵌套查询、子查询利用filter做谓词下推；多个JOIN的场景，就要用AQE（spark.sql.adaptive.enabled=true）的JOIN策略调整（DemoteBroadcastHashJoin），基于shuffle map的中间值。</li>
              <li>小表不能广播：Join Key 远大于 Payload。多个Join Key进行合并，两次hash取值连接，进行join。这样小表就能被广播出去了。</li>
              <li>小表不能广播：小表拆分列，一般按日期，多个和大表关联，每一个都转成广播。大表要避免重复扫描：cache住或者利用DPP机制。</li>
              <li>分桶join（Bucket join），本身也是一种shuffle free的方式，同时还能通过存储桶修剪（Bucket pruning）功能减少IO。需要注意的是，必须按同一个join key做分桶表，且分桶数量要大于等于shuffle分区数量，否则至少一侧还是会触发shuffle（Spark 3.1.1做了增强，不考虑分区数量，只要两者成倍数就能shuffle free）。</li>
            </ul>
          </li>
          <li>第三步：必须shuffle的情况，分为Task粒度和Executor粒度。Task粒度开启AQE即可，重点说下Executor粒度，思想就是：<strong>加盐+两阶段shuffle</strong>。
            <ul>
              <li>Group By场景：非去重类指标直接通过随机数加盐，去重类指标需要按照去重id哈希取模来加盐。同时如果只有几个大key可以单独进行处理。</li>
              <li>Join场景：如果是一个表有很多大key，另一个表比较均匀，将数据倾斜表全部加盐，另外一个表数据增到到原来的N倍（随机数个数，笛卡尔积），然后将二者Join并去盐。同时如果只有几个大key可以单独进行处理。</li>
            </ul>
          </li>
        </ul>
      </li>
      <li><strong>维度爆炸背景下uv计算实践：</strong> 优化大数据计算中多维度用户数统计的方法，通过数据打标的方式避免数据膨胀和冗余传输，同时通过编号化结果维度信息，采用更小的数据结构进行存储，提高性能并减少计算成本。
        <ul>
          <li>具体参考：<a href="https://mp.weixin.qq.com/s/kHfc2783V5GV3dxWU95odQ">https://mp.weixin.qq.com/s/kHfc2783V5GV3dxWU95odQ</a></li>
          <li>PUSH增量数据发送为160亿，到达为60亿，点击为3000万，其中发送数据一天增量约为16T，在做报表聚合时，需要从不同维度去看对应的用户数，由于用户数不同维度不可累加的特性，基本上所有维度的用户数都需要单独计算，维度少可以直接count distinct计算，但是维度多的话这种计算相当痛苦。</li>
          <li>常规方式按照 cuid+初始维度 去重后，使用 lateral view explode(array(维度名, ‘all’)) 将数据从一行膨胀成多行，然后直接 distinct 的数据计算方式。新的优化思路可以理解为在数据处理过程中采用一种“数据打标”策略，通过在数据去重的基础上生成用户粒度的中间数据，并在此基础上动态附加所需的结果维度信息。</li>
          <li>通过将不同维度（含all）的组合进行维度编码，然后将维度编码聚合到用户cuid粒度上，最后再拆解聚合后的维度编码去计算uv，理论上整个计算过程数据量是呈收敛聚合的，不会膨胀。例如001用户有两条数据，分别是产品线=1 &amp; 付费类型=免费，和产品线=1 &amp; 付费类型=付费，那么第一条数据就会编码为((1,all),(all,免费),(1,免费),(all,all))，我们不妨简化为(1,3,5,9)，同理第二条数据为(1,4,6,9)，这样聚合到用户粒度就是001用户的编码集为(1,3,4,5,6,9)，反过来1，3，4，5，6，9每个维度组合都有用户001。</li>
          <li>具体计算可以先利用dense_rank生成编码集，维度集含有dim_A,…,dim_N的具体值以及对应的编号，将该数据集广播，去和原日志表join（key就是多个dim）获取cuid，如果dim过多超过阈值无法广播，可以把所有join key拼接在一起去进行哈希（防止碰撞可考虑二次哈希），然后利用hash key进行关联，cuid聚合编码集可利用array_distinct函数。</li>
          <li>执行时间可由原2小时缩短到30分钟内，且任务稳定性增加，随着维度的增加，性能优势逐渐明显。</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>效果：</strong>
    <ul>
      <li>核心数据产出时效由T+1日22点提升至T+1日12点；</li>
      <li>减少研发临时跑数1人力的工作量，需求完成率达到100%；</li>
      <li>减少70%以上的冗余代码，计算资源减少50%以上；</li>
      <li>任务的稳定性得到极大提升，核心任务SLA满足率100%。</li>
    </ul>
  </li>
</ul>

<h2 id="2离线数仓etl">2.离线数仓&amp;ETL</h2>
<ul>
  <li><strong>技术栈：</strong> Scala + Java + Shell + Hadoop + Hive + Spark SQL + Doris + ClickHouse + MySQL + SpringBoot（SSM）</li>
  <li><strong>负责：</strong>
    <ul>
      <li><strong>PUSH数仓基建：</strong> 主要采用的Kimball维度建模，实时和离线采用的Lambda架构，后续离线和实时共用一套ODS层数据。</li>
      <li><strong>百家号大B端数仓基建：</strong> 主要采用的宽表建模（进一步降低冗余，减少存储，增强易用性），核心字段来自于实时数据（全量同步+增量同步），采用列式存储和高效压缩，降低了存储，事实表与维度表进一步打平。
        <ul>
          <li>数仓具体参考：<a href="https://mp.weixin.qq.com/s/z3gXT53Hl2t5REdhlZGo9w">https://mp.weixin.qq.com/s/z3gXT53Hl2t5REdhlZGo9w</a></li>
          <li>数据产品为CMS内容分析平台：<a href="https://baijiahao.baidu.com/">https://baijiahao.baidu.com/</a>，负责ToC数据的展示</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>具体工作：</strong>
    <ul>
      <li>PUSH数仓按照明确数据域，构建业务总线矩阵，明确统计指标的步骤整体设计的，下游使用方主要是是报表层和算法策略数据。
        <ul>
          <li>横向分层为ODS、DWD、DWS、ADS以及DIM层；纵向划分数据域，包括流量域、用户域和物料域；</li>
          <li>流量域是行为日志，含有发送、到达、点击、首调归因4种行为，为事务型事实表；用户域来源于日活用户日志和cuid-厂商token-系统channelid映射表，物料域来源于业务底表，均作为维度表使用（可理解为全量快照表），维度设计便于统计，采用星型模型；</li>
          <li>明确行为日志粒度为cuid（设备id）+appid（设备APP）+taskid（物料id），根据4种业务过程和维度信息构建业务总线矩阵；</li>
          <li>明确统计指标，划分原子、派生和衍生指标，其中派生指标为原子+统计周期+业务限定+统计粒度，衍生指标为复合指标逻辑（比率），进行指标的复用；</li>
          <li>DWS汇总层继承了明细层的粒度，JOIN上了全量维度信息，在其上ADS层服务于报表会进一步上卷（多为Doris和CK表）；</li>
        </ul>
      </li>
      <li>百家号数仓数据按照宽表建模，简化了分层和数据域的概念，减少数据分层，底层多为文件，上层主要分为作者和文章两大主题宽表（明细粒度），将多个业务域（百家号、动态、好看、feed）统一，解决了数据孤岛问题，宽表分多版本产出，会将宽表同步CK，方便使用方一站式自助分析。
        <ul>
          <li>数据孤岛问题：原始数仓各个业务口径、业务覆盖差异，数据流通、业务沟通、数据开发成本很高，通过责权分明和技术统一，做了高效的融合，融合后的宽表含有1000+字段，口径更清晰，同时业务使用上更方便，效率更高；</li>
          <li>木桶效应问题：30+上游，到位时间不同会形成木桶效应，为了复杂逻辑进行解耦，引入了分版本输出方案（见难点1）；</li>
          <li>存储冗余问题：简化多层数仓，降低冗余，总存储下降30%左右；</li>
          <li>回溯成本问题：开发通用型工具，迭代需要更新的字段，其他字段取原表数据，merge在一起后写入临时目录，最后再rename回原表目录；</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>难点：</strong>
    <ul>
      <li><strong>百家号数仓分版本产出方案：</strong> 以文章宽表为例，最细粒度是nid粒度，整体还是采用“全量同步+增量同步”的模式，共分为5个版本产出不同类型的数据，下游使用方通过对应的版本产出标识配置依赖（可以根据sql字段自动提取对应的版本）。
        <ul>
          <li>V1是数仓的base表，最先产出，确定量级、唯一主键等，V1产出时非V1版本的其他字段置NULL；
            <ul>
              <li>V1版本数据，主要是从物料库和正排库透传nid粒度的属性和加工一些核心字段逻辑（比如是否历史可分发，首次发布成功时间等）；</li>
              <li>主表（左表）是全量的nid信息，是通过常驻的spark程序每天进行近实时的merge，用以涵盖整个文章表的nid范围以及一些type、category等基础信息；</li>
              <li>从表1是物料库表，从表2是正排库表，物料库和正排库都有实时订阅（schema包括nid，update_time，json_value），发生update就会订阅到，然后按照小时聚合-&gt;天聚合-&gt;merge历史存量-&gt;解析物料库字段和正排库字段，有些nid只有物料库信息，有些只有正排库信息，有些都有，需要做对应处理，通过关联从表1和从表2来获取物料库和正排库的透传属性；</li>
              <li>从表3是作者表（作者表也是分版本），用以关联作者对应属性，会有头部机构作者发文上千万，需要处理数据倾斜问题；</li>
            </ul>
          </li>
          <li>V2-V5按照数据类型划分（带货，后验，审核，收入），均分为两个阶段：单独写HDFS文件和通过主键merge回原表，第一阶段互不影响（谁先依赖就绪谁先运行），第二阶段属于竞争关系，通过抢占锁机制，先获取锁的任务优先merge（非当前版本的字段取表中的原值），没抢到锁会原地等待；
            <ul>
              <li>V1数据存到base_path/库名/表名/日期/V1目录，然后挂载到hive表分区下；</li>
              <li>V2-V5分别把各自nid粒度的计算结果存入HDFS，然后merge回原表，merge后的表存到base_path/库名/表名/日期/V2-V5对应目录，每次通过挂载的方式，改变hive表的指向；</li>
            </ul>
          </li>
          <li>每个版本会有一个当前版本字段的配置文件，获取表元数据metadata的全量字段，两者diff就是非当前版本的字段，配置文件会接入数据平台服务，方便配置依赖关系；</li>
          <li>多个版本同时merge会出现数据异常，因此通过锁机制解决，锁机制通过 表+分区 粒度进行事务隔离，具体如下：
            <ul>
              <li>表+分区粒度在MySQL中存放版本锁标识，每次任务merge前先查看版本锁是否占用，没占用就加版本锁，然后执行merge，执行完、占用时间超时或者异常都会释放；</li>
              <li>如果当前版本锁已被占用，则进行等待，按照3分钟的频次轮询；</li>
              <li>极端情况，两个事务线程同时查看版本锁状态，MySQL InnoDB默认采用REPEATABLE READ隔离级别，通过MVCC乐观锁使得读写之间不阻塞，在第一次SELECT时生成并复用该ReadView，但是可能造成两个事务同时获取未加版本锁的状态。因此将采用MySQL中的排他锁，在SELECT的时候加上FOR UPDATE；</li>
            </ul>
          </li>
        </ul>
      </li>
      <li><strong>百家号活动作者应用数据：</strong> 每个百家号作者在活动期间仅会引入一次，引入日期固定，需要计算每位作者自引入日期开始，当月、次1月……一直到次12月的留存、发文和分发情况，“月”按照滚动30日窗口计算，不满一整月按实际天数计算。
        <ul>
          <li>留存和发文可根据快照表时间判定是否活跃和是否发文，分发需要关联文章行为事实数据，为增量数据，关联多少天分区根据实际而定；</li>
          <li>考虑到未来扩展性，当月，次1、次2采用一个新列period_type来表示，分别取值为0-12；</li>
          <li>每位作者的时间周期都不相同，实际是按照每位作者做一个窗口大小为30（不满30按照实际天数）的滚动窗口；</li>
          <li>经分析每位作者只有在最近30天的数据有变化，其余周期数据固定不变，为避免重复计算，需做成与前一日增量聚合的效果；</li>
          <li>因每位作者时间周期不同，在最近30天的时间范围也不同，因此关联是不等join，优化为提前算最近1-30日的数据，采用等值join进行关联；</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>效果：</strong>
    <ul>
      <li>PUSH数仓支持下游工程团队分析和算法团队模型训练，促进了PUSH dau的增长。</li>
      <li>百家号数仓需求平均交付时长由4.7d下降为2.8d，数仓存储平均节省了2.6PB，易用性得到极大的提升。</li>
      <li>整体达到高效率，高性能，高质量，低成本的数仓建设目标。</li>
    </ul>
  </li>
</ul>

<h2 id="3实时计算">3.实时计算</h2>
<ul>
  <li><strong>技术栈：</strong> Scala + Java + Shell + Structured Streaming + Flink + TM（百度自研流式框架）+ Kafka + BigPipe（百度自研消息队列） + Hadoop + Redis + Doris + MySQL + HBase + Google Guava</li>
  <li><strong>负责：</strong>
    <ul>
      <li><strong>PUSH实时架构设计和维护：</strong> PUSH实时流共有100+条流，涵盖矩阵、中台、折损、地理位置等业务线；业务同离线类似，核心数据还是来自发送、到达、点击以及首调数据；技术栈因历史原因，全部由Structured Streaming编写，后期部分迁移到了TM和Flink；数据流下游包含Doris，HDFS，Redis，Kafka/BigPipe，MySQL等，除了实时报表分析监控外，还涉及线上的服务的调用和算法团队的模型训练。</li>
      <li><strong>PUSH实时监控工具：</strong> 包含任务延迟监控，任务异常监控，实时流量监控和实时报表监控。</li>
    </ul>
  </li>
  <li><strong>具体工作：</strong>
    <ul>
      <li>PUSH实时架构主要采用Structured Streaming，在物理机打包，利用shell脚本进行集群提交，通过Yarn管理调度，迭代运维整个流式任务。
        <ul>
          <li>Structured Streaming任务没有设置WaterMark，针对写Doris、Redis这种可update的场景，采用event_time聚合，update数据即可；针对HDFS这种只能append的场景，采用系统时间作为分区时间（5min粒度），因下游只有策略团队使用，需要保证时效性，能接受数据的延迟；</li>
          <li>在Flink任务中针对乱序数据处理思路一般为：加WaterMark延迟触发计算时间，设置allowedLateness延迟关闭窗口时间，侧输出流来进行兜底；</li>
          <li>针对写HDFS的任务，会写入一个_spark_metadata目录，每10个文件聚合成一个compact，随着增大会导致任务越来越慢，做了例行化检测清理；</li>
          <li>通过参数调优、控制拉取数据量、调整读取写入计算并行度、调整trigger时间、Kafka/BigPipe扩容、背压机制来应对高流量带来的挑战，其中调整shuffle并行度需要同步修改checkpoint目录下offsets内的最新文件，否则任务重启还会从checkpoint恢复配置，修改不生效；</li>
          <li>通过checkpoint来保证容错性，由于Structured Streaming 本身并不支持像 Flink 那样的内置两阶段提交机制，因此原则上只支持At least once，除非下游是幂等性，针对PUSH业务来说，允许数据有一定的精度误差；</li>
        </ul>
      </li>
      <li>PUSH监控分为不同层面：
        <ul>
          <li>延迟监控：通过开发监控工具嵌入代码（获取数据源后调用），获取当前数据中的水印（max_eventtime），与当前系统时间对比，超过阈值，则会触发报警，消息发送到报警群，为了降低告警频次，会进行抽样；</li>
          <li>异常监控：通过例行shell脚本，爬取yarn界面的任务列表，针对失败任务和单Job超时任务重启，重复运行任务远程杀死；</li>
          <li>流量监控：通过Python小时级爬取BigPipe的发布流量和订阅流量，监控是否触发发布流控和订阅流控，并将当天0点截止到当前小时的流量聚合，分别与昨日同时段以及一周前同时段对比计算日环比和周同比，触发阈值报警，小时聚合流量数据存储在Redis；</li>
          <li>报表监控：针对核心指标配置日环比和周同比的波动阈值，触发会在值班群报警；</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>难点：</strong>
    <ul>
      <li><strong>流批关联：</strong> 流批关联的场景分为两类，一类是时效性要求低通过天级别离线灌Redis库，实时流通过Redis查询对应数据，比如月活基准灌库来判断是否月活用户，cuid_mapping灌库来通过token、channel反查cuid，一般能命中95%左右；另一类是时效性要求高，比如物料拼接场景，重点说明：
        <ul>
          <li>物料拼接一条流是行为数据流（发送、到达、点击行为），批数据是通过python不间断拉取业务MySQL表（源表很大），取需要的字段灌入我们自运维的一个MySQL表（dim_task），然后每次实时流启动会加载近90天的物料信息到内存中，后续每次查询也会不断增量更新；</li>
          <li>首次加载到内存比较慢，所有的机器都要查询dim_task近90天数据，数据在内存中以Google Guava存储，相对于HashMap，可以设置过期策略（只保留近90天），容量限制（防止数据过大），自动加载以及线程安全；</li>
          <li>拼接物料就是每个微批数据，从内存Guava Cache中按照taskid获取物料信息的过程；</li>
          <li>Guava Cache数据会增量更新，以2秒为间隔循环拉取，每次会记录前一次拉取的MySQL数据主键id（Volatile保存），在最新的位置进行拉取；</li>
          <li>时间不同步问题，Guava Cache加载数据过慢，会通过行为数据流最新的一次事件时间判断是否要等待，比较最新事件时间和上一次拉取MySQL数据封装Guava Cache的时间差，大于阈值13秒则会等待；</li>
        </ul>
      </li>
      <li><strong>双流关联：</strong> 双流关联会涉及多条行为数据流之间的关联比较，比如商业化点击的判断和首调数据判断，基本思想都是其中要比较的流写入Redis，另外一条流从Redis取出对应的数据，因为有时间不一致的问题，所以均会有一个调起时间的判断，这里以首调数据重点说明：
        <ul>
          <li>计算逻辑是对于同一组cuid+appid计算first_open_timestamp = MIN(运营调起当日最早时间戳, 主动调起当日最早时间戳, PUSH调起当日最早时间戳)，如果first_open_timestamp = push调起当日最早时间戳，则当日push调起+1；</li>
          <li>运营调起、主动调起和PUSH调起是3条不同的流，除PUSH调起外另外两条流会写入Redis（key为cuid_appid，value为时间戳），PUSH调起流会查询当前自身的时间戳和另外两个时间戳的大小，并同时判断是否已经算入当天首调了（如果已经算过首调也会在Redis保存），只有满足比其他两个时间戳小并且当日还没算入首调，才会把该cuid_appid归为首调数据；</li>
          <li>如果运营调起、主动调起数据流延迟，也会记录每条流最新的事件时间，一旦超过阈值30秒，PUSH调起流则会循环等待，同时报警让值班同学介入处理；</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>效果：</strong>
    <ul>
      <li>保障了PUSH实时数据的稳定性，为PUSH业务工程侧监控、PUSH策略在线模型特征、特征工程服务等均提供了数据支撑。</li>
    </ul>
  </li>
</ul>

<h2 id="4数据分析ai">4.数据分析&amp;AI</h2>
<ul>
  <li><strong>技术栈：</strong> Scala + Java +  Spark SQL + ClickHouse + SpringBoot + SpringAI + LLM + RAG + Python + Sklearn + Xgboost</li>
  <li><strong>负责：</strong>
    <ul>
      <li><strong>归因分析框架：</strong> 通过自定义需要分析的层级、维度和指标等，自动化生成SQL，并生成对应的归因分析数据集。为了进一步简化排查目标，引入deepseek大模型，可以针对数据集快速总结，给出对当前指标产生影响最大的维度以及后续的运营建议。</li>
    </ul>
  </li>
  <li><strong>具体工作：</strong>
    <ul>
      <li>采用预聚合的思想，把不同维度按照配置信息进行交叉组合，产出每种交叉维度对当前指标带来的影响贡献度（本期值，上期值，偏移量，波动率，偏移量占比等），从而定位出对当前指标产生影响最大的维度信息。
        <ul>
          <li>项目框架参考：<a href="https://github.com/chenchi-cc/data-attribution-analysis-framework">https://github.com/chenchi-cc/data-attribution-analysis-framework</a></li>
          <li>由于不同归因分析需求整体类似，只存在数据源、过滤条件、分析维度和分析指标4点不同，因此引入了配置化思想，通过在配置文件中配置关键信息，自动化生成sql语句。这里的过滤条件和分析维度不相同，过滤条件的组合是全组合，共有2^n种组合；过滤条件的组合是树状结构，只有在父节点不为ALL时，子节点才有取值；</li>
          <li>读取配置文件以后，分层分析采用一种二进制的思想，利用sql中自带的GROUPING__ID进行改造并结合GROUPING_SETS，所有维度按照优先级排列，用到该维度则设为1，否则为0，这样会生成一个包含0和1的二进制数据，该二进制值则是当前聚合维度的体现；</li>
          <li>分层深度支持到3层，不同层级配置的维度可能重复，即dim_A即可以在第一层级，也可以在第二层级，此时dim_A和dim_A是不能交叉的，需要进行去重。同样相同维度组合，虽然维度所属层级不同，但是只需要计算一次；</li>
          <li>sql生成后，交给SparkSQL引擎进行数据计算，产出hive表，同步到CK做成数据集，支持数据集对接内部工具进行“拖拉拽”分析；</li>
          <li>通过SpringAI服务接口调用deepseek大模型，为防止知识局限性和幻觉现象，引入RAG检索增强生成，把业务知识库作为检索源，System Prompt构建上下文，利用大模型读取数据集并返回数据集整体的分析结果（影响指标波动的核心因素），并给出相对应的后续运营策略；</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>难点：</strong>
    <ul>
      <li><strong>归因分析预聚合产生的性能瓶颈：</strong> 这里可以采用HLL以及bitmap进行优化。
        <ul>
          <li>针对可模糊去重的场景，采取hyperloglog的思想，直接采用approx_count_distinct函数；</li>
          <li>针对精确去重场景，采用自定义bitmap UDF函数来替代 count(distinct uid)，同时UDF提供在 hive、spark 中bitmap的灵活运算，比如：交集、并集、差集运算，计算后bitmap 也可以直接写入 hive 表中，同时bitmap还对数据进行了压缩 ，减少了存储成本；</li>
        </ul>
      </li>
      <li><strong>归因分析需要解决”归一“和”回补“的问题：</strong>
        <ul>
          <li>比如按照dim_A、dim_B聚合和按照dim_B、dim_A聚合效果是一样的，只需要计算一遍，那就需要给不同维度定义一个优先级，来确保各自的占位情况，返回数据根据字段优先级归一化，比如dim_A优先级更高，那么dim_A就放在第一层级，dim_B就放在第二层级；</li>
          <li>那么如果确实有dim_B处在第一层级分析的情况，还需要把这部分缺失的数据回补，需要计算全排列组合然后排除掉当前情况，将剩余组合UNION回来，2个维度不明显，这在3个维度时最多会有6种交叉，需要补回5种情况。之所以回补是因为dim_A做第二层级和dim_B做第二层级，它们所对应的当前选中的父层级是不同的，因此得到的贡献度也是不同的；</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>效果：</strong>
    <ul>
      <li>运营单次进行指标异常归因的耗时，由线下分析4h减少到1h，借助大模型能力，能快速定位出影响指标波动的关键因素。</li>
    </ul>
  </li>
</ul>]]></content><author><name>DarrenChan陈驰</name></author><category term="技术文档" /><summary type="html"><![CDATA[我的简历项目详细版本]]></summary></entry><entry><title type="html">我的技术笔记</title><link href="https://chenchi-cc.github.io/2025/06/23/technology-summary/" rel="alternate" type="text/html" title="我的技术笔记" /><published>2025-06-23T00:00:00+00:00</published><updated>2025-06-23T00:00:00+00:00</updated><id>https://chenchi-cc.github.io/2025/06/23/technology-summary</id><content type="html" xml:base="https://chenchi-cc.github.io/2025/06/23/technology-summary/"><![CDATA[<h2 id="技术文档一览">技术文档一览</h2>
<p>因为本人喜欢将笔记总结在有道云笔记，本着技术开源的原则，特此将有道云笔记的链接分享出来，供大家参考。</p>

<h3 id="1-spark">1. Spark</h3>
<ul>
  <li><a href="https://note.youdao.com/s/63bUFChm">Spark性能调优：原理篇</a></li>
  <li><a href="https://note.youdao.com/s/XtWyM7Kh">Spark性能调优：通用性能篇</a></li>
  <li><a href="https://note.youdao.com/s/IWZyOm96">Spark性能调优：通用性能篇（2）</a></li>
  <li><a href="https://note.youdao.com/s/ZNUMMpkw">Spark性能调优：Spark SQL篇</a></li>
  <li><a href="https://note.youdao.com/s/BbHvFdOc">Spark性能调优：Spark SQL篇（2）</a></li>
</ul>

<h3 id="2-sql">2. SQL</h3>
<ul>
  <li><a href="https://note.youdao.com/s/EfQef2Gb">SQL：最全题库1</a></li>
  <li><a href="https://note.youdao.com/s/bAtKlozY">SQL：最全题库2</a></li>
  <li><a href="https://note.youdao.com/s/Vyk6xa6t">SQL：最全题库3</a></li>
</ul>

<h3 id="3-编程语言">3. 编程语言</h3>
<ul>
  <li><a href="https://note.youdao.com/s/5I0jIHCU">编程语言：Scala</a></li>
  <li><a href="https://note.youdao.com/s/9L2B3FEz">编程语言：Java</a></li>
</ul>

<h3 id="4数仓理论">4.数仓理论</h3>
<ul>
  <li><a href="https://note.youdao.com/s/FHLX7WnX">数仓理论：通用数仓模型</a></li>
  <li><a href="https://note.youdao.com/s/I1bxD5O5">数仓理论：拉链表</a></li>
</ul>

<h3 id="5流式计算">5.流式计算</h3>
<ul>
  <li><a href="https://share.note.youdao.com/s/VATJaiBq">流式计算：实时数仓架构+Flink调优</a></li>
  <li><a href="https://share.note.youdao.com/s/1Vx1SImh">流式计算：Flink架构和API</a></li>
  <li><a href="https://share.note.youdao.com/s/ZPOZYXm6">流式计算：Flink SQL</a></li>
  <li><a href="https://note.youdao.com/s/8Yhrx5tK">流式计算：Flink容错和一致性</a></li>
  <li><a href="https://note.youdao.com/s/MS5UoBWC">流式计算：Spark Structured Streaming</a></li>
  <li><a href="https://note.youdao.com/s/QgxRYh5j">流式计算：Kafka</a></li>
</ul>

<h3 id="6olap引擎">6.OLAP引擎</h3>
<ul>
  <li><a href="https://note.youdao.com/s/dQlzacx9">OLAP引擎：架构对比</a></li>
  <li><a href="https://note.youdao.com/s/RyQIrWHd">OLAP引擎：ck和doris</a></li>
</ul>

<h3 id="7数据库">7.数据库</h3>
<ul>
  <li><a href="https://note.youdao.com/s/93p3YG9S">数据库：MySQL索引及调优</a></li>
  <li><a href="https://note.youdao.com/s/GfGabC5c">数据库：MySQL事务基础和事务日志</a></li>
  <li><a href="https://note.youdao.com/s/JBs5o84a">数据库：MySQL事务锁和MVCC</a></li>
  <li><a href="https://note.youdao.com/s/EnLutPkN">数据库：Redis</a></li>
</ul>

<h3 id="8微服务">8.微服务</h3>
<ul>
  <li><a href="https://share.note.youdao.com/s/JWFtJEna">微服务：SpringCloud架构图</a></li>
  <li><a href="https://share.note.youdao.com/s/OrLNX35i">微服务：SpringCloud笔记</a></li>
</ul>

<h3 id="9机器学习">9.机器学习</h3>
<ul>
  <li><a href="https://share.note.youdao.com/s/R8GNopJ5">机器学习概述</a></li>
</ul>]]></content><author><name>DarrenChan陈驰</name></author><category term="技术文档" /><summary type="html"><![CDATA[各种技术文档均汇总在这里]]></summary></entry><entry><title type="html">我的算法笔记</title><link href="https://chenchi-cc.github.io/2025/06/20/algorithm-summary/" rel="alternate" type="text/html" title="我的算法笔记" /><published>2025-06-20T00:00:00+00:00</published><updated>2025-06-20T00:00:00+00:00</updated><id>https://chenchi-cc.github.io/2025/06/20/algorithm-summary</id><content type="html" xml:base="https://chenchi-cc.github.io/2025/06/20/algorithm-summary/"><![CDATA[<h2 id="算法文档一览">算法文档一览</h2>
<p>因为本人喜欢将笔记总结在有道云笔记，本着技术开源的原则，特此将有道云笔记的链接分享出来，供大家参考。</p>

<h3 id="二分专题">二分专题</h3>
<ul>
  <li><a href="https://share.note.youdao.com/s/DzVKmk8b">二分专题</a></li>
</ul>

<h3 id="双指针专题">双指针专题</h3>
<ul>
  <li><a href="https://share.note.youdao.com/s/KrNxaML3">同向双指针</a></li>
  <li><a href="https://share.note.youdao.com/s/3x2j97P3">相向双指针</a></li>
  <li><a href="https://share.note.youdao.com/s/O2d9eLtr">Quick Select第k大元素</a></li>
</ul>

<h3 id="宽度优先搜索">宽度优先搜索</h3>
<ul>
  <li><a href="https://share.note.youdao.com/s/bf72hHuu">BFS和拓扑排序</a></li>
</ul>

<h3 id="深度优先搜索">深度优先搜索</h3>
<ul>
  <li><a href="https://share.note.youdao.com/s/Dqa4ebLs">二叉树DFS 分治法</a></li>
  <li><a href="https://share.note.youdao.com/s/n8nWOsl">组合型搜索和记忆化搜索</a></li>
  <li><a href="https://share.note.youdao.com/s/CB9yRaHL">基于排列、图的深度优先遍历</a></li>
</ul>

<h3 id="数据结构">数据结构</h3>
<ul>
  <li><a href="https://share.note.youdao.com/s/ZeOlIBMM">链表专题</a></li>
  <li><a href="https://share.note.youdao.com/s/aOjZMdKT">字符串专题</a></li>
  <li><a href="https://share.note.youdao.com/s/KTEpKyDG">队列和栈专题</a></li>
  <li><a href="https://share.note.youdao.com/s/VfcjqczU">单调栈</a></li>
  <li><a href="https://share.note.youdao.com/s/dIe0lkvq">并查集</a></li>
  <li><a href="https://share.note.youdao.com/s/SlNBjoh1">Trie字典树</a></li>
</ul>

<h3 id="动态规划">动态规划</h3>
<ul>
  <li><a href="https://share.note.youdao.com/s/bZndjwJI">动态规划（1）</a></li>
  <li><a href="https://share.note.youdao.com/s/GS6tHTGg">动态规划（2）</a></li>
  <li><a href="https://share.note.youdao.com/s/8QUsN5F3">动态规划（3）</a></li>
  <li><a href="https://share.note.youdao.com/s/WMYlGfUZ">动态规划（4）</a></li>
</ul>

<h3 id="其他">其他</h3>
<ul>
  <li><a href="https://share.note.youdao.com/s/JSNhINk8">K路归并算法</a></li>
  <li><a href="https://share.note.youdao.com/s/MsMNYO42">最长回文子串</a></li>
  <li><a href="https://share.note.youdao.com/s/BdArfSIk">LRU和LFU</a></li>
  <li><a href="https://share.note.youdao.com/s/Bj77gY8d">其他题目（线段重合，排序，矩阵打印）</a></li>
</ul>]]></content><author><name>DarrenChan陈驰</name></author><category term="技术文档" /><summary type="html"><![CDATA[各种算法文档均汇总在这里]]></summary></entry><entry><title type="html">自我剖析</title><link href="https://chenchi-cc.github.io/2025/06/05/introduce-myself/" rel="alternate" type="text/html" title="自我剖析" /><published>2025-06-05T00:00:00+00:00</published><updated>2025-06-05T00:00:00+00:00</updated><id>https://chenchi-cc.github.io/2025/06/05/introduce-myself</id><content type="html" xml:base="https://chenchi-cc.github.io/2025/06/05/introduce-myself/"><![CDATA[<h2 id="吾日三省吾身">吾日三省吾身</h2>
<ul>
  <li>自我价值清晰认知，你的核心卖点是什么？</li>
  <li>你自己取得的成绩是平台成就的，还是通过能力得来的？</li>
  <li>是否有破圈心态，是否已成为复合型人才，是否有敢于换工作换行业的底气和能力？</li>
</ul>

<h2 id="个人优缺点">个人优缺点</h2>
<h3 id="优点">优点</h3>
<ul>
  <li>喜欢拓宽技术广度，重要技术有深度研究的决心，有持续学习的自驱力</li>
  <li>有一定的抗压能力，能承担重要方向</li>
  <li>简单可依赖，没出现过项目延期或者业务方负向反馈，职责范围内靠谱负责</li>
  <li>有自己的想法和追求</li>
</ul>

<h3 id="缺点">缺点</h3>
<ul>
  <li>项目需要学一个新技术，总是想完全学完再开始，项目初期总是想设计滴水不漏（高可用高并发）再开始，执行力可以适当提升</li>
  <li>总是想一口气吃成个胖子，喜欢制定短期计划，忽略了学习是个长线的过程</li>
  <li>并行做多件事会比较焦虑</li>
  <li>在人前发言比较紧张，害怕自己的观点不被认可</li>
  <li>提高自己技术影响力的事情做的少，有时候100分的工作，说成80分</li>
</ul>

<h2 id="未来5年的职业规划">未来5年的职业规划</h2>
<ul>
  <li>提升技术能力，离线+实时，数据架构层面，数据平台层面，写博客录视频，提升技术影响力</li>
  <li>扩展项目管理技能，更多的跨部门项目，学习如何更有效地协调团队工作，管理项目进度和资源，从而确保项目的高效交付</li>
  <li>希望做一些创新的事情，比如大模型，探索新的数据处理方法和技术解决方案</li>
</ul>

<h2 id="喜欢什么样的公司">喜欢什么样的公司？</h2>
<ul>
  <li>钱多事少</li>
  <li>开明的老板，扁平化管理</li>
  <li>友爱的同事</li>
  <li>上升的赛道</li>
</ul>

<p>我不喜欢把自己比喻成牛马，勤劳吃苦还落不到一个好下场，我喜欢蜜蜂，如果把公司当成一朵花，把上班当成是采蜜，是不是更好玩？</p>

<h2 id="为什么喜欢大数据">为什么喜欢大数据？</h2>
<ul>
  <li>数据不是简单的功能实现，可以帮助企业做决策，推动业务发展，给算法提供支撑</li>
  <li>Java系方向，涉及复杂的技术挑战，比如大规模数据处理、数据清洗、数据集成和优化查询性能
    <ul>
      <li>数据仓库：数据治理</li>
      <li>数据平台：后端</li>
      <li>数据框架：源码定制</li>
      <li>数据分析：AI</li>
      <li>数据环境：云计算</li>
    </ul>
  </li>
  <li>持续学习与成长，新技术和新工具层出不穷，促使我持续学习</li>
</ul>]]></content><author><name>DarrenChan陈驰</name></author><category term="Blog" /><summary type="html"><![CDATA[自我剖析]]></summary></entry><entry><title type="html">LLM：Spring AI</title><link href="https://chenchi-cc.github.io/2025/05/30/spring-ai/" rel="alternate" type="text/html" title="LLM：Spring AI" /><published>2025-05-30T00:00:00+00:00</published><updated>2025-05-30T00:00:00+00:00</updated><id>https://chenchi-cc.github.io/2025/05/30/spring-ai</id><content type="html" xml:base="https://chenchi-cc.github.io/2025/05/30/spring-ai/"><![CDATA[<h1 id="springai">SpringAI</h1>

<h1 id="第一章-spring-ai简介">第一章 Spring AI简介</h1>

<p>在当今这样一个快速发展的技术时代，人工智能（AI）已经成为各行各业的一种标配。而作为一款主流的Java应用开发框架Spring，肯定会紧跟时代的潮流，所以，推出了Spring AI框架。</p>

<h2 id="11-spring-ai是什么">1.1 Spring AI是什么</h2>

<h3 id="111-官网描述">1.1.1 官网描述</h3>

<ul>
  <li><strong>官网地址</strong>  <a href="https://spring.io/projects/spring-ai">https://spring.io/projects/spring-ai</a></li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250514153245329.png" alt="image-20250514153245329" /></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250514154438093.png" alt="image-20250514154438093" /></p>

<ul>
  <li>Spring AI是一个AI工程领域的应用程序框架；</li>
  <li>Spring AI 是 AI 工程的应用框架。其目标是将 Spring 生态系统设计原则（如可移植性和模块化设计）应用于 AI 领域，并促进使用 POJO 作为应用程序的构建块到 AI 领域。</li>
  <li>它的目标是将Spring生态系统的设计原则应用于 AI 领域，比如Spring生态系统的可移植性和模块化设计，并促进使用 POJO 作为应用程序的构建块到 AI 领域；</li>
  <li>Spring AI 的核心是提供了开发 AI 大模型应用所需的基本抽象模型，这些抽象拥有多种实现方式，使得开发者可以用很少的代码改动就能实现组件的轻松替换；</li>
  <li>简言之，Spring AI 是一个 AI 工程师的应用框架，它提供了一个友好的 API 和开发 AI 应用的抽象，旨在简化 AI 大模型应用的开发工作。</li>
</ul>

<h3 id="112-发布版本">1.1.2 发布版本</h3>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250517104954450.png" alt="image-20250517104954450" /></p>

<ul>
  <li>
    <p>目前Spring AI 推出了一个预览版（PRE）和一个快照版（SNAPSHOT），GA版尚未发布，大家可以提前学习，等GA版发布后再真正用在项目中。</p>

    <p>SNAPSHOT指的是快照版，会在此版本上持续更新。
PRE指的是预览版，主要提供给开发测试人员找bug，不到修改完善的。
GA指的是 General Availability，意为正式发布的版本，推荐使用（主要是稳定）。</p>
  </li>
</ul>

<h2 id="12-spring-ai的主要功能">1.2 Spring AI的主要功能</h2>

<ul>
  <li>第一、 对主流 AI 大模型供应商提供了支持，比如：OpenAI、DeepSeek、Microsoft、Ollama、Amazon、Google HuggingFace等。</li>
  <li>第二、 支持AI大模型类型包括：聊天、文本到图像、文本到声音等。</li>
  <li>第三、 支持主流的Embedding Models（嵌入模型）和向量数据库，比如：Azure Vector Search、Chroma、Milvus、Neo4j、Redis、PineCone、PostgreSQL/PGVector 等。</li>
  <li>第四、 把 AI 大模型输出映射到简单的 Java 对象（POJOs）上。</li>
  <li>第五、 支持了函数调用（Function calling）功能。</li>
  <li>第六、 为数据工程提供 ETL（数据抽取、转换和加载）框架。</li>
  <li>第七、 支持 Spring Boot 自动配置和快速启动，便于运行 AI 模型和管理向量库。</li>
</ul>

<h1 id="第二章-spring-ai快速入门">第二章 Spring AI快速入门</h1>

<h2 id="21-准备工作">2.1 准备工作</h2>

<h3 id="211-什么是deepseek">2.1.1 什么是DeepSeek</h3>

<p>DeepSeek 是一款由深度求索所开发的 AI 人工智能大模型，其基于深度学习和多模态数据融合技术，采用先进的 Transformer 架构和跨模态协同算法，可实现对复杂文档和图像的自动化解析与结构化信息提取。</p>

<p>依托于最新推出的“深度思考”模式（R1），这款AI大模型在极低成本下实现了与国际顶尖模型ChatGPT-o1相媲美的性能表现，其中文理解与输出能力更是远超ChatGPT、Claude等顶尖模型。再加上极具竞争力的API定价和全面开源的策略，让这款AI大模型成功在国际上火爆出圈</p>

<p>如果说AI是一个广泛的概念，那么DeepSeek就是是AI领域中的一个具体产品。</p>

<p><strong>DeepSeek的特点</strong>‌：</p>

<ul>
  <li>‌<strong>成本</strong>‌：DeepSeek致力于降低AI应用的成本。通过采用先进的技术和独特的模型架构，DeepSeek在保持高性能的同时，显著降低了推理和训练的成本。</li>
  <li>‌<strong>性能</strong>‌：DeepSeek在性能上表现出色。它使用强化学习技术训练，推理过程中包含大量反思与验证，能够处理更加复杂的数据和任务。在一些benchmark测试中，其性能与OpenAI的模型相当，但推理成本远低于同类产品。</li>
  <li>‌<strong>功能</strong>‌：DeepSeek擅长处理数学、编程和复杂逻辑推理等任务。它的推理能力源于深度思考特性，推理长度与准确率呈正相关。此外，DeepSeek还支持多模态信息处理，能够应对更加多样化的应用场景。</li>
  <li>‌<strong>应用领域</strong>‌：DeepSeek在多个领域展现出巨大的应用潜力。无论是在医疗、教育、交通等传统领域，还是在智能制造、智慧城市等新兴领域，DeepSeek都有望发挥重要作用。</li>
</ul>

<p>综上所述，AI是一个广泛的概念，涵盖了人工智能领域的所有技术和应用。而DeepSeek则是AI领域中的一个具体产品，它在成本、性能、功能和应用领域等方面都有着独特的特点和优势。两者之间的关系可以理解为：DeepSeek是AI领域中的一个具体实现和优秀代表。</p>

<p><strong>如何使用Java集成DeepSeek：</strong></p>

<p>DeepSeek 作为一款卓越的国产 AI 模型，越来越多的公司考虑在自己的应用中集成。对于 Java 应用来说，我们可以借助 Spring AI 集成 DeepSeek，非常简单方便！</p>

<h3 id="212-deepseek开放平台创建api-key">2.1.2 DeepSeek开放平台创建API KEY</h3>

<ul>
  <li>进入DeepSeek官网 <a href="https://www.deepseek.com/">https://www.deepseek.com/</a>  点击右上角的 <strong>API开放平台</strong></li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306195235004.png" alt="image-20250306195235004" /></p>

<ul>
  <li>进入API开放平台，注册用户</li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306195401240.png" alt="image-20250306195401240" /></p>

<ul>
  <li>创建API key</li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306195507880.png" alt="image-20250306195507880" /></p>

<ul>
  <li>根据自己需要，自行充值</li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306195554534.png" alt="image-20250306195554534" /></p>

<ul>
  <li>Spring AI 的 openai starter 本质上是通过 RestTemplate 发请求</li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306195728571.png" alt="image-20250306195728571" /></p>

<h2 id="22-创建springboot工程">2.2 创建SpringBoot工程</h2>

<h3 id="221-引入依赖">2.2.1 引入依赖</h3>

<ul>
  <li><strong>SpringAI对于SpringBoot和JDK的版本要求</strong></li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250307140919085.png" alt="image-20250307140919085" /></p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>

    <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.8<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>

    <span class="nt">&lt;groupId&gt;</span>org.example<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>springai-deepseek<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>

    <span class="nt">&lt;properties&gt;</span>
        <span class="nt">&lt;maven.compiler.source&gt;</span>17<span class="nt">&lt;/maven.compiler.source&gt;</span>
        <span class="nt">&lt;maven.compiler.target&gt;</span>17<span class="nt">&lt;/maven.compiler.target&gt;</span>
        <span class="nt">&lt;spring-ai.version&gt;</span>1.0.0-M5<span class="nt">&lt;/spring-ai.version&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>

    <span class="nt">&lt;dependencies&gt;</span>
        <span class="nt">&lt;dependency&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-web<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
        
        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>org.springframework.ai<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>spring-ai-openai-spring-boot-starter<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>

        <span class="nt">&lt;dependency&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-test<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;/dependencies&gt;</span>
    <span class="nt">&lt;dependencyManagement&gt;</span>
        <span class="nt">&lt;dependencies&gt;</span>
            <span class="nt">&lt;dependency&gt;</span>
                <span class="nt">&lt;groupId&gt;</span>org.springframework.ai<span class="nt">&lt;/groupId&gt;</span>
                <span class="nt">&lt;artifactId&gt;</span>spring-ai-bom<span class="nt">&lt;/artifactId&gt;</span>
                <span class="nt">&lt;version&gt;</span>${spring-ai.version}<span class="nt">&lt;/version&gt;</span>
                <span class="nt">&lt;type&gt;</span>pom<span class="nt">&lt;/type&gt;</span>
                <span class="nt">&lt;scope&gt;</span>import<span class="nt">&lt;/scope&gt;</span>
            <span class="nt">&lt;/dependency&gt;</span>
        <span class="nt">&lt;/dependencies&gt;</span>
    <span class="nt">&lt;/dependencyManagement&gt;</span>

<span class="nt">&lt;/project&gt;</span>
</code></pre></div></div>

<h3 id="222-创建配置文件">2.2.2 创建配置文件</h3>

<p><strong>application.properties</strong></p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">server.port</span><span class="p">=</span><span class="s">8899</span>
<span class="py">spring.application.name</span><span class="p">=</span><span class="s">spring-ai-deepseek-demo</span>

<span class="py">spring.ai.openai.api-key</span><span class="p">=</span><span class="s">sk-139298b9e929496290******</span>
<span class="py">spring.ai.openai.base-url</span><span class="p">=</span><span class="s">https://api.deepseek.com</span>
<span class="py">spring.ai.openai.chat.options.model</span><span class="p">=</span><span class="s">deepseek-chat</span>
<span class="py">spring.ai.openai.chat.options.temperature</span><span class="p">=</span><span class="s">0.7</span>
</code></pre></div></div>

<ul>
  <li>
    <p>temperature参数用于控制生成文本的多样性。具体来说：</p>

    <p>‌值越高‌，生成的文本越多样化，但也可能包含更多的随机性和不可预测的内容。
‌值越低‌，生成的文本越接近于确定性的结果，即生成的文本会更加一致和可预测。</p>
  </li>
</ul>

<h3 id="223-创建启动类">2.2.3 创建启动类</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@SpringBootApplication</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SpringAiDemoApplication</span> <span class="o">{</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">SpringApplication</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">SpringAiDemoApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
    <span class="o">}</span>

<span class="o">}</span>
</code></pre></div></div>

<h3 id="224-创建controller">2.2.4 创建Controller</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ChatDeepSeekController</span> <span class="o">{</span>

    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">OpenAiChatModel</span> <span class="n">chatModel</span><span class="o">;</span>

    <span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/ai/generate"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">generate</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"message"</span><span class="o">,</span> <span class="n">defaultValue</span> <span class="o">=</span> <span class="s">"hello"</span><span class="o">)</span>
                           <span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">String</span> <span class="n">response</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">chatModel</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="n">message</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"response : "</span><span class="o">+</span><span class="n">response</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">response</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="225-测试">2.2.5 测试</h3>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306200808047.png" alt="image-20250306200808047" /></p>

<h1 id="第三章-spring-ai的聊天模型">第三章 Spring AI的聊天模型</h1>

<h2 id="31-概述">3.1 概述</h2>

<ul>
  <li>Spring AI的聊天模型API为开发者提供了一条便捷通道，能够将强大的AI驱动的聊天完成功能无缝集成到各类应用中。借助预先训练的语言模型，如广为人知的GPT，它能够依据用户输入生成自然流畅、类人化的回复。这一API不仅工作机制高效，而且设计理念极为先进，旨在实现简单易用与高度可移植性，让开发者能以极少的代码改动在不同AI模型间自由切换，充分契合Spring框架一贯秉持的模块化与可互换性原则。</li>
</ul>

<h2 id="32-chatclient接口">3.2 ChatClient接口</h2>

<p><strong>ChatClient 是一个接口，它定义了一个与聊天服务交互的客户端。这个接口主要用于创建聊天客户端对象，设置请求规范，以及发起聊天请求。</strong></p>

<h3 id="321-实现简单的对话">3.2.1 实现简单的对话</h3>

<h4 id="1-需求">1 需求</h4>

<p>用户输入设置用户消息的内容，通过SpringBoot AI封装的方法向 AI 模型发送请求，以字符串形式返回 AI 模型的响应。</p>

<h4 id="2-编写controller方法">2 编写Controller方法</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ChatDeepSeekController</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ChatClient</span> <span class="n">chatClient</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">ChatDeepSeekController</span><span class="o">(</span><span class="nc">ChatClient</span><span class="o">.</span><span class="na">Builder</span> <span class="n">chatClientBuilder</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">chatClient</span> <span class="o">=</span> <span class="n">chatClientBuilder</span><span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/chat"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">chat</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"msg"</span><span class="o">,</span><span class="n">defaultValue</span> <span class="o">=</span> <span class="s">"给我讲个笑话"</span><span class="o">)</span>
                           <span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">//prompt:提示词</span>
        <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">chatClient</span><span class="o">.</span><span class="na">prompt</span><span class="o">()</span>
                <span class="c1">//用户输入的信息</span>
                <span class="o">.</span><span class="na">user</span><span class="o">(</span><span class="n">message</span><span class="o">)</span>
                <span class="c1">//请求大模型</span>
                <span class="o">.</span><span class="na">call</span><span class="o">()</span>
                <span class="c1">//返回文本</span>
                <span class="o">.</span><span class="na">content</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="3-测试结果">3 测试结果</h4>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250515210014987.png" alt="image-20250515210014987" /></p>

<h4 id="4-总结">4 总结</h4>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ChatClient 接口提供了构建和配置聊天客户端对象的灵活性，以及发起和处理聊天请求的能力。用户可以通过 ChatClient.Builder 来定制客户端的行为，然后使用 prompt() 和 prompt(Prompt prompt) 方法设置请求规范，最后通过 call() 方法发起聊天请求。
</code></pre></div></div>

<h3 id="322-实现角色预设">3.2.2 实现角色预设</h3>

<h4 id="1-配置默认角色">1 配置默认角色</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AIConfig</span> <span class="o">{</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="nc">ChatClient</span> <span class="nf">chatClient</span><span class="o">(</span><span class="nc">ChatClient</span><span class="o">.</span><span class="na">Builder</span> <span class="n">builder</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">builder</span><span class="o">.</span><span class="na">defaultSystem</span><span class="o">(</span><span class="s">"你是尚硅谷教育的一名老师，你精通Java开发，"</span> <span class="o">+</span>
                <span class="s">"你的名字叫尚硅谷。"</span><span class="o">).</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="2-编写controller">2 编写controller</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="s">"/ai"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AIController</span> <span class="o">{</span>

    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">ChatClient</span> <span class="n">chatClient</span><span class="o">;</span>

    <span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/chat"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">chat</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"msg"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">chatClient</span><span class="o">.</span><span class="na">prompt</span><span class="o">().</span><span class="na">user</span><span class="o">(</span><span class="n">message</span><span class="o">).</span><span class="na">call</span><span class="o">().</span><span class="na">content</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="3-测试结果-1">3 测试结果</h4>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250515211213816.png" alt="image-20250515211213816" /></p>

<h3 id="323-实现流式响应">3.2.3 实现流式响应</h3>

<h4 id="1-call和stream的区别">1 call和stream的区别</h4>

<p>（1）非流式输出 call：等待大模型把回答结果全部生成后输出给用户；</p>

<p>（2）流式输出stream：逐个字符输出，一方面符合大模型生成方式的本质，另一方面当模型推理效率不是很高时，流式输出比起全部生成后再输出大大提高用户体验。</p>

<h4 id="2-编写controller-1">2 编写Controller</h4>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@GetMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/chat/stream"</span><span class="o">,</span><span class="n">produces</span><span class="o">=</span><span class="s">"text/html;charset=UTF-8"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Flux</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="nf">chatStream</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"msg"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">chatClient</span><span class="o">.</span><span class="na">prompt</span><span class="o">().</span><span class="na">user</span><span class="o">(</span><span class="n">message</span><span class="o">).</span><span class="na">stream</span><span class="o">().</span><span class="na">content</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="3-测试结果-2">3 测试结果</h4>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250515211501513.png" alt="image-20250515211501513" /></p>

<h2 id="33-chatmodel接口">3.3 ChatModel接口</h2>

<h3 id="331-概述">3.3.1 概述</h3>

<p>ChatModel接口作为核心，定义了与AI模型交互的基本方法。它继承自Model&lt;Prompt, ChatResponse&gt;，提供了两个重载的call方法：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ChatModel</span> <span class="kd">extends</span> <span class="nc">Model</span><span class="o">&lt;</span><span class="nc">Prompt</span><span class="o">,</span> <span class="nc">ChatResponse</span><span class="o">&gt;</span> <span class="o">{</span>
    <span class="k">default</span> <span class="nc">String</span> <span class="nf">call</span><span class="o">(</span><span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{...}</span>
    <span class="nd">@Override</span>
    <span class="nc">ChatResponse</span> <span class="nf">call</span><span class="o">(</span><span class="nc">Prompt</span> <span class="n">prompt</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>在ChatModel接口中，带有String参数的call()方法简化了实际的使用，避免了更复杂的Prompt和 ChatResponse类的复杂性。但是在实际应用程序中，更常见的是使用ChatResponse call()方法，该方法采用Prompt实例并返回ChatResponse。</p>

<p>我们使用的ChatClient底层是使用ChatModel作为属性的，在初始化ChatClient的时候可以指定ChatModel，这里我们直接看底层源码：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//ChatClient（部分构造器代码）</span>
<span class="kd">static</span> <span class="nc">ChatClient</span> <span class="nf">create</span><span class="o">(</span><span class="nc">ChatModel</span> <span class="n">chatModel</span><span class="o">)</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nf">create</span><span class="o">(</span><span class="n">chatModel</span><span class="o">,</span> <span class="nc">ObservationRegistry</span><span class="o">.</span><span class="na">NOOP</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="332-实现简单的对话">3.3.2 实现简单的对话</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DeepSeekController</span> <span class="o">{</span>

    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">ChatModel</span> <span class="n">chatModel</span><span class="o">;</span>

    <span class="nd">@GetMapping</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">chat</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="s">"msg"</span><span class="o">)</span><span class="nc">String</span> <span class="n">msg</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">chatModel</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="n">msg</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/openai"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">openai</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="s">"msg"</span><span class="o">)</span><span class="nc">String</span> <span class="n">msg</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">ChatResponse</span> <span class="n">call</span> <span class="o">=</span> <span class="n">chatModel</span><span class="o">.</span><span class="na">call</span><span class="o">(</span>
                <span class="k">new</span> <span class="nf">Prompt</span><span class="o">(</span>
                        <span class="n">msg</span><span class="o">,</span>
                        <span class="nc">OpenAiChatOptions</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
                                <span class="c1">//可以更换成其他大模型，如Anthropic3ChatOptions亚马逊</span>
                                <span class="o">.</span><span class="na">model</span><span class="o">(</span><span class="s">"deepseek-chat"</span><span class="o">)</span>
                                <span class="o">.</span><span class="na">temperature</span><span class="o">(</span><span class="mf">0.8</span><span class="o">)</span>
                                <span class="o">.</span><span class="na">build</span><span class="o">()</span>
                <span class="o">)</span>
        <span class="o">);</span>
        <span class="k">return</span> <span class="n">call</span><span class="o">.</span><span class="na">getResult</span><span class="o">().</span><span class="na">getOutput</span><span class="o">().</span><span class="na">getContent</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="333-提示词">3.3.3 提示词</h3>

<ul>
  <li>提示词是引导大模型生成特定输出的输入，提示词的设计和措辞会极大地影响模型的响应结果</li>
  <li>Prompt 提示词是与模型交互的一种输入数据组织方式，本质上是一种复合结构的输入，在 prompt 我们是可以包含多组不同角色（System、User、Aissistant等）的信息。如何管理好 Prompt 是简化 AI 应用开发的关键环节。</li>
  <li>Spring AI 提供了 Prompt Template 提示词模板管理抽象，开发者可以预先定义好模板，并在运行时替换模板中的关键词。在 Spring AI 与大模型交互的过程中，处理提示词首先要创建包含动态内容占位符 {占位符} 的模板，然后，这些占位符会根据用户请求或应用程序中的其他代码进行替换。在提示词模板中，{占位符} 可以用 Map 中的变量动态替换。</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/prompt"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">prompt</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="s">"name"</span><span class="o">)</span>
                         <span class="nc">String</span> <span class="n">name</span><span class="o">,</span>
                     <span class="nd">@RequestParam</span><span class="o">(</span><span class="s">"voice"</span><span class="o">)</span>
                         <span class="nc">String</span> <span class="n">voice</span><span class="o">){</span>
    <span class="nc">String</span> <span class="n">userText</span><span class="o">=</span> <span class="s">"""
            给我推荐北京的至少三种美食
            """</span><span class="o">;</span>
    <span class="nc">UserMessage</span> <span class="n">userMessage</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UserMessage</span><span class="o">(</span><span class="n">userText</span><span class="o">);</span>
    <span class="nc">String</span> <span class="n">systemText</span><span class="o">=</span> <span class="s">"""
            你是一个美食咨询助手，可以帮助人们查询美食信息。
            你的名字是{name},
            你应该用你的名字和{voice}的饮食习惯回复用户的请求。
            """</span><span class="o">;</span>
    <span class="nc">SystemPromptTemplate</span> <span class="n">systemPromptTemplate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SystemPromptTemplate</span><span class="o">(</span><span class="n">systemText</span><span class="o">);</span>
    <span class="c1">//替换占位符</span>
    <span class="nc">Message</span> <span class="n">systemMessage</span> <span class="o">=</span> <span class="n">systemPromptTemplate</span>
            <span class="o">.</span><span class="na">createMessage</span><span class="o">(</span><span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"name"</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="s">"voice"</span><span class="o">,</span> <span class="n">voice</span><span class="o">));</span>
    <span class="nc">Prompt</span> <span class="n">prompt</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Prompt</span><span class="o">(</span><span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">userMessage</span><span class="o">,</span> <span class="n">systemMessage</span><span class="o">));</span>
    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Generation</span><span class="o">&gt;</span> <span class="n">results</span> <span class="o">=</span> <span class="n">chatModel</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="n">prompt</span><span class="o">).</span><span class="na">getResults</span><span class="o">();</span>
    <span class="k">return</span> <span class="n">results</span><span class="o">.</span><span class="na">stream</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">x</span><span class="o">.</span><span class="na">getOutput</span><span class="o">().</span><span class="na">getContent</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">joining</span><span class="o">(</span><span class="s">""</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>

<h1 id="第四章-spring-ai的函数调用">第四章 Spring AI的函数调用</h1>

<h2 id="41-概述">4.1 概述</h2>

<ul>
  <li>
    <p>Spring AI 的函数调用（Function Calling）功能允许大语言模型在生成回答时触发预定义的外部函数，从而实现动态数据获取或业务逻辑操作（如查询数据库、调用 API 等）。</p>
  </li>
  <li>
    <p>SpringAI 帮我们规范了函数定义、注册等过程，并在发起模型请求之前自动将函数注入到 Prompt 中，而当模型决策在合适的时候去调用某个函数时，Spring AI 完成函数调用动作，最终将函数执行结果与原始问题再一并发送给模型，模型根据新的输入决策下一步动作。这其中涉及与大模型的多次交互过程，一次函数调用就是一次完成的交互过程。</p>
  </li>
  <li>
    <p><strong>函数调用的核心流程</strong></p>
  </li>
  <li>
    <p>第一步，定义函数：声明可供模型调用的函数（名称、描述、参数结构）。</p>
  </li>
  <li>
    <p>第二步，模型交互：将函数信息与用户输入一起发送给模型，模型决定是否需要调用函数。</p>
  </li>
  <li>
    <p>第三步，执行函数：解析模型的函数调用请求，执行对应的业务逻辑。</p>
  </li>
  <li>
    <p>第四步，返回结果：将函数执行结果返回给模型，生成最终回答。</p>
  </li>
</ul>

<h2 id="42-函数调用实现">4.2 函数调用实现</h2>

<h3 id="421-创建自定义的function">4.2.1 创建自定义的Function</h3>

<ul>
  <li>下面CalculatorService类自定义了加法和乘法运算的函数</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CalculatorService</span> <span class="o">{</span>

    <span class="kd">public</span> <span class="n">record</span> <span class="nf">AddOperation</span><span class="o">(</span><span class="kt">int</span> <span class="n">a</span><span class="o">,</span> <span class="kt">int</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>

    <span class="o">}</span>

    <span class="kd">public</span> <span class="n">record</span> <span class="nf">MulOperation</span><span class="o">(</span><span class="kt">int</span> <span class="n">m</span><span class="o">,</span> <span class="kt">int</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span>

    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="nd">@Description</span><span class="o">(</span><span class="s">"加法运算"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">Function</span><span class="o">&lt;</span><span class="nc">AddOperation</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">&gt;</span> <span class="nf">addOperation</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">request</span> <span class="o">-&gt;</span> <span class="o">{</span>
            <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="na">a</span> <span class="o">+</span> <span class="n">request</span><span class="o">.</span><span class="na">b</span><span class="o">;</span>
        <span class="o">};</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="nd">@Description</span><span class="o">(</span><span class="s">"乘法运算"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">Function</span><span class="o">&lt;</span><span class="nc">MulOperation</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">&gt;</span> <span class="nf">mulOperation</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">request</span> <span class="o">-&gt;</span> <span class="o">{</span>
            <span class="k">return</span> <span class="n">request</span><span class="o">.</span><span class="na">m</span> <span class="o">*</span> <span class="n">request</span><span class="o">.</span><span class="na">n</span><span class="o">;</span>
        <span class="o">};</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li><strong>总结：</strong></li>
  <li>Spring AI 使自定义函数这个过程变得简单，只需定义一个返回 java.util.Function 的 @Bean 定义，并在调用 ChatModel 时将 bean 名称作为选项进行注册即可。在底层，Spring 会用适当的适配器代码包装你的函数，以便与 AI 模型进行交互，免去了编写繁琐的代码。</li>
</ul>

<h3 id="422-编写controller">4.2.2 编写Controller</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="nd">@GetMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/chat"</span><span class="o">,</span> <span class="n">produces</span> <span class="o">=</span> <span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_STREAM_JSON_VALUE</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">ragJsonText</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"userMessage"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">userMessage</span><span class="o">){</span>
        <span class="k">return</span> <span class="nc">ChatClient</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="n">chatModel</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">()</span>
                <span class="o">.</span><span class="na">prompt</span><span class="o">()</span>
                <span class="o">.</span><span class="na">system</span><span class="o">(</span><span class="s">"""
您是算术计算器的代理。
您能够支持加法运算、乘法运算等操作，其余功能将在后续版本中添加，如果用户问的问题不支持请告知详情。
在提供加法运算、乘法运算等操作之前，您必须从用户处获取如下信息：两个数字，运算类型。
请调用自定义函数执行加法运算、乘法运算。
请讲中文。
"""</span><span class="o">)</span>
                <span class="o">.</span><span class="na">user</span><span class="o">(</span><span class="n">userMessage</span><span class="o">)</span>
                <span class="o">.</span><span class="na">functions</span><span class="o">(</span><span class="s">"addOperation"</span><span class="o">,</span> <span class="s">"mulOperation"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">call</span><span class="o">()</span>
                <span class="o">.</span><span class="na">content</span><span class="o">();</span>
    <span class="o">}</span>
</code></pre></div></div>

<ul>
  <li>为了让模型知道并调用你的自定义函数，您需要在 Prompt 请求中启用它，如上述代码，在functions(“addOperation”, “mulOperation”)中告知ChatClient要使用这两个自定义函数。</li>
  <li>另外指定了System Prompt：要求AI 模型被设定为一个算术计算器代理，能够执行加法和乘法运算，并且要求用户提供两个数字和运算类型。这个提示词内容很关键，如何让AI按照自己的意图去执行，要不断测试提示词内容。</li>
</ul>

<h3 id="423-测试">4.2.3 测试</h3>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250516003801707.png" alt="image-20250516003801707" /></p>

<h1 id="第五章-spring-ai调用ollama">第五章 Spring AI调用Ollama</h1>

<h2 id="51-下载并安装-ollama">5.1 下载并安装 Ollama</h2>

<p>官网：<a href="https://ollama.com/">https://ollama.com/</a></p>

<h3 id="511-下载ollama">5.1.1 下载Ollama</h3>

<p>Ollama 是一个用于本地化部署和管理大型语言模型（LLM）的工具。它支持多种开源模型（如 LLaMA、Alpaca 等），并提供了简单的 API 接口，方便开发者调用。Ollama可以让你在自己的电脑上运行各种强大的 AI 模型，就像运行普通软件一样简单。</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306201028630.png" alt="image-20250306201028630" /></p>

<h3 id="512-安装ollama">5.1.2 安装Ollama</h3>

<ul>
  <li>
    <p>模型默认安装在C盘，可以修改安装路径</p>
  </li>
  <li>
    <p>点击环境变量，选择下面新建一个系统环境变量 OLLAMA_MODELS ，然后指定想要安装模型的路径 ，比如   “D:\Deepseek”</p>
  </li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306201749040.png" alt="image-20250306201749040" /></p>

<ul>
  <li>需要重启 Ollama生效</li>
</ul>

<h3 id="513-拉取-deepseek-模型">5.1.3 拉取 DeepSeek 模型</h3>

<p><strong>硬件配置建议</strong></p>

<ul>
  <li>
    <p><strong>GPU选择（根据模型大小灵活调整）：</strong>
入门配置：NVIDIA显卡（≥8GB显存） → 适合7B/8B模型。
高性能配置：NVIDIA显卡（≥16GB显存） → 支持14B大模型。
无独立显卡用户：可使用CPU模式运行（速度约为GPU的20%）。</p>
  </li>
  <li>
    <p><strong>内存要求</strong>：≥16GB（推荐32GB，处理长文本时不易卡顿）</p>
  </li>
  <li>
    <p><strong>存储空间</strong>：≥50GB可用空间（建议SSD硬盘，加快模型加载速度）</p>
  </li>
  <li>
    <p><strong>操作系统</strong>：
Windows 10/11（21H2及以上版本）。
macOS Ventura 13.4+。
Ubuntu 22.04 LTS/24.04 LTS</p>
  </li>
</ul>

<p>选择适合自己的版本 <a href="https://ollama.com/library/deepseek-r1">https://ollama.com/library/deepseek-r1</a></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306201323644.png" alt="image-20250306201323644" /></p>

<p>以windows为例，根据不同版本，执行不同的命令拉取模型</p>

<p>比如，下载1.5b，执行下面命令</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ollama pull deepseek-r1:1.5b
</code></pre></div></div>

<h3 id="514-启动ollama服务测试">5.1.4 启动Ollama服务测试</h3>

<p>启动 Ollama 服务，默认会监听 <a href="http://localhost:11434">http://localhost:11434</a></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ollama run deepseek-r1:1.5b
</code></pre></div></div>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306202101617.png" alt="image-20250306202101617" /></p>

<h2 id="52-spring-ai代码测试">5.2 Spring AI代码测试</h2>

<h3 id="521-创建springboot工程">5.2.1 创建SpringBoot工程</h3>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>

    <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.8<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>

    <span class="nt">&lt;groupId&gt;</span>org.example<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>springai-deepseek<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>

    <span class="nt">&lt;properties&gt;</span>
        <span class="nt">&lt;maven.compiler.source&gt;</span>17<span class="nt">&lt;/maven.compiler.source&gt;</span>
        <span class="nt">&lt;maven.compiler.target&gt;</span>17<span class="nt">&lt;/maven.compiler.target&gt;</span>
        <span class="nt">&lt;spring-ai.version&gt;</span>1.0.0-M5<span class="nt">&lt;/spring-ai.version&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>

    <span class="nt">&lt;dependencies&gt;</span>
        <span class="nt">&lt;dependency&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-web<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>

        <span class="nt">&lt;dependency&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-test<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>

        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>org.springframework.ai<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>spring-ai-ollama-spring-boot-starter<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;version&gt;</span>1.0.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;/dependencies&gt;</span>
    <span class="nt">&lt;dependencyManagement&gt;</span>
        <span class="nt">&lt;dependencies&gt;</span>
            <span class="nt">&lt;dependency&gt;</span>
                <span class="nt">&lt;groupId&gt;</span>org.springframework.ai<span class="nt">&lt;/groupId&gt;</span>
                <span class="nt">&lt;artifactId&gt;</span>spring-ai-bom<span class="nt">&lt;/artifactId&gt;</span>
                <span class="nt">&lt;version&gt;</span>${spring-ai.version}<span class="nt">&lt;/version&gt;</span>
                <span class="nt">&lt;type&gt;</span>pom<span class="nt">&lt;/type&gt;</span>
                <span class="nt">&lt;scope&gt;</span>import<span class="nt">&lt;/scope&gt;</span>
            <span class="nt">&lt;/dependency&gt;</span>
        <span class="nt">&lt;/dependencies&gt;</span>
    <span class="nt">&lt;/dependencyManagement&gt;</span>

    <span class="c">&lt;!--下载spring-ai相关包需要用到的仓库地址--&gt;</span>
    <span class="nt">&lt;repositories&gt;</span>
        <span class="nt">&lt;repository&gt;</span>
            <span class="nt">&lt;id&gt;</span>spring-milestones<span class="nt">&lt;/id&gt;</span>
            <span class="nt">&lt;name&gt;</span>Spring Milestones<span class="nt">&lt;/name&gt;</span>
            <span class="nt">&lt;url&gt;</span>https://repo.spring.io/milestone<span class="nt">&lt;/url&gt;</span>
            <span class="nt">&lt;snapshots&gt;</span>
                <span class="nt">&lt;enabled&gt;</span>false<span class="nt">&lt;/enabled&gt;</span>
            <span class="nt">&lt;/snapshots&gt;</span>
        <span class="nt">&lt;/repository&gt;</span>
        <span class="nt">&lt;repository&gt;</span>
            <span class="nt">&lt;id&gt;</span>spring-snapshots<span class="nt">&lt;/id&gt;</span>
            <span class="nt">&lt;name&gt;</span>Spring Snapshots<span class="nt">&lt;/name&gt;</span>
            <span class="nt">&lt;url&gt;</span>https://repo.spring.io/snapshot<span class="nt">&lt;/url&gt;</span>
            <span class="nt">&lt;releases&gt;</span>
                <span class="nt">&lt;enabled&gt;</span>false<span class="nt">&lt;/enabled&gt;</span>
            <span class="nt">&lt;/releases&gt;</span>
        <span class="nt">&lt;/repository&gt;</span>
    <span class="nt">&lt;/repositories&gt;</span>

<span class="nt">&lt;/project&gt;</span>
</code></pre></div></div>

<h3 id="522-创建配置文件">5.2.2 创建配置文件</h3>

<p><strong>application.properties</strong></p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">server.port</span><span class="p">=</span><span class="s">8899</span>
<span class="py">spring.application.name</span><span class="p">=</span><span class="s">spring-ai-deepseek-demo</span>

<span class="py">spring.ai.ollama.base-url</span><span class="p">=</span><span class="s">http://localhost:11434</span>
<span class="py">spring.ai.ollama.chat.options.model</span><span class="p">=</span><span class="s">deepseek-r1:1.5b</span>
<span class="py">spring.ai.ollama.chat.options.temperature</span><span class="p">=</span><span class="s">0.7</span>
</code></pre></div></div>

<h3 id="523-创建启动类">5.2.3 创建启动类</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@SpringBootApplication</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SpringAiDemoApplication</span> <span class="o">{</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">SpringApplication</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">SpringAiDemoApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
    <span class="o">}</span>

<span class="o">}</span>
</code></pre></div></div>

<h3 id="524-创建controller">5.2.4 创建Controller</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ChatDeepSeekController</span> <span class="o">{</span>

    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">OllamaChatModel</span> <span class="n">ollamaChatModel</span><span class="o">;</span>

    <span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/ai/test"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">generate</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"message"</span><span class="o">,</span> <span class="n">defaultValue</span> <span class="o">=</span> <span class="s">"hello"</span><span class="o">)</span>
                           <span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">String</span> <span class="n">response</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">ollamaChatModel</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="n">message</span><span class="o">);</span>
        <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"response : "</span><span class="o">+</span><span class="n">response</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">response</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="525-测试">5.2.5 测试</h3>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250306202859451.png" alt="image-20250306202859451" /></p>

<h1 id="第六章-spring-ai-alibaba">第六章 Spring AI Alibaba</h1>

<h2 id="61-概述">6.1 概述</h2>

<ul>
  <li>
    <p>随着生成式 AI 的快速发展，基于 AI 开发框架构建 AI 应用的诉求迅速增长，涌现出了包括 LangChain、LlamaIndex 等开发框架，但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言，并非十分友好和丝滑。</p>
  </li>
  <li>
    <p>因此，我们基于 Spring AI 发布并快速演进 Spring AI Alibaba，是阿里云通义系列模型及服务在 Java AI 应用开发领域的最佳实践，通过提供一种方便的 API 抽象，帮助 Java 开发者我们加速和简化 Java 智能体应用的开发。同时，提供了完整的开源配套，包括可观测、网关、消息队列、配置中心等。</p>
  </li>
  <li>
    <p>Spring AI Alibaba 已完整提供 Model、Prompt、RAG、Tools 等 AI 应用开发所需的必备能力，将兼具提示词模板、函数调用、格式化输出等低层次抽象，以及 RAG、智能体、对话记忆等高层次抽象。</p>
  </li>
</ul>

<h2 id="62-快速入门">6.2 快速入门</h2>

<h3 id="621-申请阿里云api-key">6.2.1 申请阿里云API Key</h3>

<ul>
  <li>访问阿里云百炼页面并登录账号 <a href="https://www.aliyun.com/product/bailian">https://www.aliyun.com/product/bailian</a></li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250516020053364.png" alt="image-20250516020053364" /></p>

<ul>
  <li>开通“百炼大模型推理”服务，获取API Key。</li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250516020114735.png" alt="image-20250516020114735" /></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250516020227227.png" alt="image-20250516020227227" /></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250516020305219.png" alt="image-20250516020305219" /></p>

<h3 id="622-创建springboot工程">6.2.2 创建SpringBoot工程</h3>

<p>官方文档 <a href="https://java2ai.com/docs/1.0.0-M6.1/get-started/?spm=4347728f.13ae80de.0.0.6fe5175cWxPTJZ">https://java2ai.com/docs/1.0.0-M6.1/get-started/?spm=4347728f.13ae80de.0.0.6fe5175cWxPTJZ</a></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250516022739062.png" alt="image-20250516022739062" /></p>

<ul>
  <li><strong>引入依赖</strong></li>
  <li><strong>Spring AI Alibaba 基于 Spring Boot 3.x 开发，因此本地 JDK 版本要求为 17 及以上</strong></li>
</ul>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.example<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>deepseek-apringai<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>

    <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.2.5<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>

    <span class="nt">&lt;properties&gt;</span>
        <span class="nt">&lt;maven.compiler.source&gt;</span>17<span class="nt">&lt;/maven.compiler.source&gt;</span>
        <span class="nt">&lt;maven.compiler.target&gt;</span>17<span class="nt">&lt;/maven.compiler.target&gt;</span>
        <span class="nt">&lt;spring-ai.version&gt;</span>1.0.0-M5<span class="nt">&lt;/spring-ai.version&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>

    <span class="nt">&lt;dependencies&gt;</span>
        <span class="nt">&lt;dependency&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-web<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>

        <span class="nt">&lt;dependency&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-test<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>

        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>com.alibaba.cloud.ai<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>spring-ai-alibaba-starter<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;version&gt;</span>1.0.0-M5.1<span class="nt">&lt;/version&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;/dependencies&gt;</span>
    
    <span class="nt">&lt;repositories&gt;</span>
        <span class="nt">&lt;repository&gt;</span>
            <span class="nt">&lt;id&gt;</span>spring-milestones<span class="nt">&lt;/id&gt;</span>
            <span class="nt">&lt;name&gt;</span>Spring Milestones<span class="nt">&lt;/name&gt;</span>
            <span class="nt">&lt;url&gt;</span>https://repo.spring.io/milestone<span class="nt">&lt;/url&gt;</span>
            <span class="nt">&lt;snapshots&gt;</span>
                <span class="nt">&lt;enabled&gt;</span>false<span class="nt">&lt;/enabled&gt;</span>
            <span class="nt">&lt;/snapshots&gt;</span>
        <span class="nt">&lt;/repository&gt;</span>
    <span class="nt">&lt;/repositories&gt;</span>
<span class="nt">&lt;/project&gt;</span>
</code></pre></div></div>

<h3 id="623-编写controller">6.2.3 编写Controller</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DeepSeekController</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">DEFAULT_PROMPT</span> <span class="o">=</span> <span class="s">"你是一个博学的智能聊天助手，请根据用户提问回答！"</span><span class="o">;</span>

    <span class="nd">@Autowired</span>
    <span class="kd">private</span> <span class="nc">ChatClient</span> <span class="n">dashScopeChatClient</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">DeepSeekController</span><span class="o">(</span><span class="nc">ChatClient</span><span class="o">.</span><span class="na">Builder</span> <span class="n">chatClientBuilder</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">dashScopeChatClient</span> <span class="o">=</span> <span class="n">chatClientBuilder</span>
                <span class="o">.</span><span class="na">defaultSystem</span><span class="o">(</span><span class="no">DEFAULT_PROMPT</span><span class="o">)</span>
                <span class="c1">// 实现 Chat Memory 的 Advisor</span>
                <span class="c1">// 在使用 Chat Memory 时，需要指定对话 ID，以便 Spring AI 处理上下文。</span>
                <span class="o">.</span><span class="na">defaultAdvisors</span><span class="o">(</span>
                        <span class="k">new</span> <span class="nf">MessageChatMemoryAdvisor</span><span class="o">(</span><span class="k">new</span> <span class="nc">InMemoryChatMemory</span><span class="o">())</span>
                <span class="o">)</span>
                <span class="c1">// 实现 Logger 的 Advisor</span>
                <span class="o">.</span><span class="na">defaultAdvisors</span><span class="o">(</span>
                        <span class="k">new</span> <span class="nf">SimpleLoggerAdvisor</span><span class="o">()</span>
                <span class="o">)</span>
                <span class="c1">// 设置 ChatClient 中 ChatModel 的 Options 参数</span>
                <span class="o">.</span><span class="na">defaultOptions</span><span class="o">(</span>
                        <span class="nc">DashScopeChatOptions</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
                                <span class="o">.</span><span class="na">withTopP</span><span class="o">(</span><span class="mf">0.7</span><span class="o">)</span>
                                <span class="o">.</span><span class="na">build</span><span class="o">()</span>
                <span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>


    <span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/simple/chat"</span><span class="o">)</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">simpleChat</span><span class="o">(</span><span class="nc">String</span> <span class="n">query</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">dashScopeChatClient</span><span class="o">.</span><span class="na">prompt</span><span class="o">(</span><span class="n">query</span><span class="o">).</span><span class="na">call</span><span class="o">().</span><span class="na">content</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="624-配置文件">6.2.4 配置文件</h3>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">server.port</span><span class="p">=</span><span class="s">8899</span>
<span class="py">spring.application.name</span><span class="p">=</span><span class="s">spring-ai-deepseek-demo</span>

<span class="py">spring.ai.dashscope.api-key</span><span class="p">=</span><span class="s">sk-026f3ee568f34cc3a237***</span>
</code></pre></div></div>

<h1 id="第七章-spring-ai的其他模型">第七章 Spring AI的其他模型</h1>

<h2 id="71-图像模型">7.1 图像模型</h2>

<h3 id="711-image-model-api概述">7.1.1 Image Model API概述</h3>

<ul>
  <li>
    <p>在Spring AI框架中，Image Model API旨在为与专注于图像生成的各种AI模型进行交互提供一个简单且可移植的接口，使开发者能够以最小的代码改动切换不同的图像相关模型。这一设计符合Spring模块化和互换性的理念，确保开发者可以快速调整其应用程序以适应不同的图像处理相关的AI能力。</p>
  </li>
  <li>
    <p>此外，通过支持像ImagePrompt这样的辅助类来进行输入封装以及使用ImageResponse来处理输出，图像模型API统一了与致力于图像生成的AI模型之间的通信。它管理请求准备和响应解析的复杂性，为图像生成功能提供直接而简化的API交互。</p>
  </li>
  <li>
    <p>Image Model API建立在Spring AI通用模型API之上，提供了特定于图像的抽象和实现。</p>
  </li>
</ul>

<h3 id="712-image-model-api接口及相关类">7.1.2 Image Model API接口及相关类</h3>

<h4 id="1-imagemodel图像模型">1 ImageModel（图像模型）</h4>

<p>这里展示的是ImageModel接口定义：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@FunctionalInterface</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ImageModel</span> <span class="kd">extends</span> <span class="nc">Model</span><span class="o">&lt;</span><span class="nc">ImagePrompt</span><span class="o">,</span> <span class="nc">ImageResponse</span><span class="o">&gt;</span> <span class="o">{</span>
	<span class="nc">ImageResponse</span> <span class="nf">call</span><span class="o">(</span><span class="nc">ImagePrompt</span> <span class="n">request</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="2-imageprompt图像提示">2 ImagePrompt（图像提示）</h4>

<p>ImagePrompt是一个封装了ImageMessage对象列表及可选模型请求选项的ModelRequest。下面显示的是ImagePrompt类的一个简化版本，省略了构造函数和其他工具方法：</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ImagePrompt</span> <span class="kd">implements</span> <span class="nc">ModelRequest</span><span class="o">&lt;</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">ImageMessage</span><span class="o">&gt;&gt;</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="kd">final</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">ImageMessage</span><span class="o">&gt;</span> <span class="n">messages</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">ImageOptions</span> <span class="n">imageModelOptions</span><span class="o">;</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">ImageMessage</span><span class="o">&gt;</span> <span class="nf">getInstructions</span><span class="o">()</span> <span class="o">{...}</span>
	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="nc">ImageOptions</span> <span class="nf">getOptions</span><span class="o">()</span> <span class="o">{...}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="3-imagemessage图像消息">3 ImageMessage（图像消息）</h4>

<p>ImageMessage类封装了用于影响生成图像的文本及其权重。对于支持权重的模型，它们可以是正数或负数。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ImageMessage</span> <span class="o">{</span>
	<span class="kd">private</span> <span class="nc">String</span> <span class="n">text</span><span class="o">;</span> 
	<span class="kd">private</span> <span class="nc">Float</span> <span class="n">weight</span><span class="o">;</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getText</span><span class="o">()</span> <span class="o">{...}</span>
	<span class="kd">public</span> <span class="nc">Float</span> <span class="nf">getWeight</span><span class="o">()</span> <span class="o">{...}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="4-imageoptions图像选项">4 ImageOptions（图像选项）</h4>

<p>表示可以传递给图像生成模型的选项。ImageOptions接口扩展了ModelOptions接口，并用于定义可以传递给AI模型的一些可移植选项。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ImageOptions</span> <span class="kd">extends</span> <span class="nc">ModelOptions</span> <span class="o">{</span>
	<span class="nc">Integer</span> <span class="nf">getN</span><span class="o">();</span>
	<span class="nc">String</span> <span class="nf">getModel</span><span class="o">();</span>
	<span class="nc">Integer</span> <span class="nf">getWidth</span><span class="o">();</span>
	<span class="nc">Integer</span> <span class="nf">getHeight</span><span class="o">();</span>
	<span class="nc">String</span> <span class="nf">getResponseFormat</span><span class="o">();</span> <span class="c1">// openai - url or base64 : stability ai byte[] or base64</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="5-imageresponse图像响应">5 ImageResponse（图像响应）</h4>

<p>持有AI模型的输出，每个ImageGeneration实例包含来自单一提示的可能多个输出结果之一。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ImageResponse</span> <span class="kd">implements</span> <span class="nc">ModelResponse</span><span class="o">&lt;</span><span class="nc">ImageGeneration</span><span class="o">&gt;</span> <span class="o">{</span>
	<span class="kd">private</span> <span class="kd">final</span> <span class="nc">ImageResponseMetadata</span> <span class="n">imageResponseMetadata</span><span class="o">;</span>
	<span class="kd">private</span> <span class="kd">final</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">ImageGeneration</span><span class="o">&gt;</span> <span class="n">imageGenerations</span><span class="o">;</span>
	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="nc">ImageGeneration</span> <span class="nf">getResult</span><span class="o">()</span> <span class="o">{</span>
		<span class="c1">// get the first result</span>
	<span class="o">}</span>
	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">ImageGeneration</span><span class="o">&gt;</span> <span class="nf">getResults</span><span class="o">()</span> <span class="o">{...}</span>
	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="nc">ImageResponseMetadata</span> <span class="nf">getMetadata</span><span class="o">()</span> <span class="o">{...}</span>
<span class="o">}</span>
</code></pre></div></div>

<h4 id="6-imagegeneration图像生成">6 ImageGeneration（图像生成）</h4>

<p>最终，ImageGeneration类扩展自ModelResult，代表输出响应及有关此结果的元数据。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ImageGeneration</span> <span class="kd">implements</span> <span class="nc">ModelResult</span><span class="o">&lt;</span><span class="nc">Image</span><span class="o">&gt;</span> <span class="o">{</span>
	<span class="kd">private</span> <span class="nc">ImageGenerationMetadata</span> <span class="n">imageGenerationMetadata</span><span class="o">;</span>
	<span class="kd">private</span> <span class="nc">Image</span> <span class="n">image</span><span class="o">;</span>
    <span class="nd">@Override</span>
	<span class="kd">public</span> <span class="nc">Image</span> <span class="nf">getOutput</span><span class="o">()</span> <span class="o">{...}</span>
	<span class="nd">@Override</span>
	<span class="kd">public</span> <span class="nc">ImageGenerationMetadata</span> <span class="nf">getMetadata</span><span class="o">()</span> <span class="o">{...}</span>
<span class="o">}</span><span class="err">。</span>
</code></pre></div></div>

<h3 id="713-spring-ai实现生成图像">7.1.3 Spring AI实现生成图像</h3>

<p>Spring AI框架的ImageModel API 抽象了应用程序通过模型调用实现“文生图”的交互过程，基本流程：应用程序接收文本，调用模型生成图片。ImageModel 的入参为包装类型ImagePrompt，输出类型为ImageResponse</p>

<p>本示例使用spring ai alibaba开源框架，spring-ai-alibaba-starter AutoConfiguration 默认初始化了 ImageModel 实例，我们可以选择直接注入并使用默认实例。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">DashScopeImageModel</span> <span class="n">imageModel</span><span class="o">;</span>

<span class="nd">@GetMapping</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">getImageabli</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"msg"</span><span class="o">,</span><span class="n">defaultValue</span> <span class="o">=</span> <span class="s">"生成一直小猫"</span><span class="o">)</span>
                           <span class="nc">String</span> <span class="n">msg</span><span class="o">,</span> <span class="nc">HttpServletResponse</span> <span class="n">res</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">ImageResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">imageModel</span><span class="o">.</span><span class="na">call</span><span class="o">(</span>
            <span class="k">new</span> <span class="nf">ImagePrompt</span><span class="o">(</span>
                    <span class="n">msg</span><span class="o">,</span>
                    <span class="nc">DashScopeImageOptions</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
                         <span class="o">.</span><span class="na">withModel</span><span class="o">(</span><span class="nc">DashScopeImageApi</span><span class="o">.</span><span class="na">DEFAULT_IMAGE_MODEL</span><span class="o">)</span>
                         <span class="o">.</span><span class="na">withN</span><span class="o">(</span><span class="mi">1</span><span class="o">)</span><span class="c1">//要生成的图像数。必须介于 1 和 10 之间。</span>
                         <span class="o">.</span><span class="na">withHeight</span><span class="o">(</span><span class="mi">1024</span><span class="o">)</span><span class="c1">//生成的图像的高宽度。</span>
                         <span class="o">.</span><span class="na">withWidth</span><span class="o">(</span><span class="mi">1024</span><span class="o">).</span><span class="na">build</span><span class="o">())</span>
    <span class="o">);</span>
	<span class="c1">//获取生成图像地址</span>
    <span class="nc">String</span> <span class="n">imageUrl</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getResult</span><span class="o">().</span><span class="na">getOutput</span><span class="o">().</span><span class="na">getUrl</span><span class="o">();</span>
    <span class="k">try</span> <span class="o">{</span>
        <span class="c1">//使用输出流在浏览器输出</span>
        <span class="no">URL</span> <span class="n">url</span> <span class="o">=</span> <span class="no">URI</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">imageUrl</span><span class="o">).</span><span class="na">toURL</span><span class="o">();</span>
        <span class="nc">InputStream</span> <span class="n">in</span> <span class="o">=</span> <span class="n">url</span><span class="o">.</span><span class="na">openStream</span><span class="o">();</span>
        <span class="n">res</span><span class="o">.</span><span class="na">setHeader</span><span class="o">(</span><span class="s">"Content-Type"</span><span class="o">,</span> <span class="nc">MediaType</span><span class="o">.</span><span class="na">IMAGE_PNG_VALUE</span><span class="o">);</span>
        <span class="n">res</span><span class="o">.</span><span class="na">getOutputStream</span><span class="o">().</span><span class="na">write</span><span class="o">(</span><span class="n">in</span><span class="o">.</span><span class="na">readAllBytes</span><span class="o">());</span>
        <span class="n">res</span><span class="o">.</span><span class="na">getOutputStream</span><span class="o">().</span><span class="na">flush</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="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="72-语音模型">7.2 语音模型</h2>

<h3 id="721-text-to-speech-api概述">7.2.1 Text-to-Speech API概述</h3>

<p>在Spring AI框架中，Text-to-Speech API提供了一个基于OpenAI的TTS（文本转语音）模型的语音端点，</p>

<p>使用户能够：</p>

<ul>
  <li>
    <p>朗读写好的博客文章。</p>
  </li>
  <li>
    <p>生成多种语言的语音音频。</p>
  </li>
  <li>
    <p>使用流媒体实现实时音频输出。</p>
  </li>
</ul>

<p>这一功能强大的API让用户可以轻松地将文字内容转化为语音内容，不仅支持多语言转换，还能满足实时语音输出的需求，极大地提升了内容的可访问性和用户的体验感。</p>

<h3 id="722-spring-ai实现文本转语音">7.2.2 Spring AI实现文本转语音</h3>

<ul>
  <li>
    <p>本示例基于spring ai alibaba开源框架</p>
  </li>
  <li>
    <p>SpeechSynthesisModel 类是Spring AI Alibaba框架中用于表示和管理文本转语音模型的核心组件之一。</p>
  </li>
  <li>
    <p>DashScopeSpeechSynthesisOptions 类通常用于配置文本转语音（TTS）服务的选项，这个类允许开发者指定一系列参数（比如：语速、音调、音量等）来定制化语音合成的结果，从而满足不同的应用场景需求。</p>
  </li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">DashScopeSpeechSynthesisModel</span> <span class="n">speechSynthesisModel</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">TEXT</span> <span class="o">=</span> <span class="s">"床前明月光， 疑是地上霜。 举头望明月， 低头思故乡。"</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">FILE_PATH</span> <span class="o">=</span> <span class="s">"src/main/resources/tts"</span><span class="o">;</span>

<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/tts"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">tts</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
    <span class="c1">// 使用构建器模式创建 DashScopeSpeechSynthesisOptions 实例并设置参数</span>
    <span class="nc">DashScopeSpeechSynthesisOptions</span> <span class="n">options</span> <span class="o">=</span> <span class="nc">DashScopeSpeechSynthesisOptions</span><span class="o">.</span><span class="na">builder</span><span class="o">()</span>
            <span class="o">.</span><span class="na">withSpeed</span><span class="o">(</span><span class="mf">1.0</span><span class="o">)</span>        <span class="c1">// 设置语速</span>
            <span class="o">.</span><span class="na">withPitch</span><span class="o">(</span><span class="mf">0.9</span><span class="o">)</span>         <span class="c1">// 设置音调</span>
            <span class="o">.</span><span class="na">withVolume</span><span class="o">(</span><span class="mi">60</span><span class="o">)</span>         <span class="c1">// 设置音量</span>
            <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="nc">SpeechSynthesisResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">speechSynthesisModel</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="k">new</span> <span class="nc">SpeechSynthesisPrompt</span><span class="o">(</span><span class="no">TEXT</span><span class="o">,</span><span class="n">options</span><span class="o">));</span>
    <span class="nc">File</span> <span class="n">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="no">FILE_PATH</span> <span class="o">+</span> <span class="s">"/output.mp3"</span><span class="o">);</span>
    <span class="k">try</span> <span class="o">(</span><span class="nc">FileOutputStream</span> <span class="n">fos</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FileOutputStream</span><span class="o">(</span><span class="n">file</span><span class="o">))</span> <span class="o">{</span>
        <span class="nc">ByteBuffer</span> <span class="n">byteBuffer</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="na">getResult</span><span class="o">().</span><span class="na">getOutput</span><span class="o">().</span><span class="na">getAudio</span><span class="o">();</span>
        <span class="n">fos</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">byteBuffer</span><span class="o">.</span><span class="na">array</span><span class="o">());</span>
    <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">throw</span> <span class="k">new</span> <span class="nf">IOException</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h1 id="第八章-spring-ai实现rag">第八章 Spring AI实现RAG</h1>

<h2 id="81-概述">8.1 概述</h2>

<h3 id="811-向量化">8.1.1 向量化</h3>

<ul>
  <li>
    <p>向量数据库（Vector Database）是一种以数学向量的形式存储数据集合的数据库，通过一个数字列表来表示维度空间中的一个位置。在这里，向量数据库的功能是可以基于相似性搜索进行识别，而不是精准匹配。比如说在使用一个商城系统的向量数据库进行查询的时候，用户输入“北京”，其可能返回的结果会是 “中国、北京、华北、首都、奥运会” 等信息；输入“沈阳”，其返回结果可能会是“东北、辽宁、雪花、重工业”等信息。当然，返回的信息取决于向量数据库中存在的数据。用户可以通过参数的设置来限定返回的情况，进而适配不同的需求。</p>
  </li>
  <li>
    <p>嵌入模型（Embedding Model）和向量数据库（Vector Database/Vector Store）是一对亲密无间的合作伙伴，也是 AI 技术栈中紧密关联的两大核心组件，两者的协同作用构成了现代语义搜索、推荐系统和 RAG（Retrieval Augmented Generation，检索增强生成）等应用的技术基础。</p>
  </li>
</ul>

<h3 id="812-rag">8.1.2 RAG</h3>

<h4 id="1-rag的基本概念">1 RAG的基本概念</h4>

<p>​	<strong>RAG</strong>，全称 <strong>Retrieval-Augmented Generation</strong> ，中文叫做<strong>检索增强生成</strong>。RAG是一种结合了检索系统和生成模型的新型技术框架，其主要目的有：</p>

<ul>
  <li>利用外部知识库</li>
  <li>帮助大模型生成<strong>更加准确、有依据、最新的回答</strong></li>
</ul>

<p>​	通过使用RAG，解决了传统LLM存在的两个主要问题：</p>

<ul>
  <li><strong>知识局限性</strong>：LLM的知识被固定在训练数据中，无法知道最新消息。</li>
  <li><strong>幻觉现象</strong>：LLM有时候会编造出并不存在的答案。</li>
</ul>

<p>​	通过检索外部知识，RAG让模型突破了知识局限性，也让LLM（大语言模型）的幻觉现象得到解决。</p>

<h4 id="2-rag的使用场景">2 RAG的使用场景</h4>

<p>​	RAG技术可以用于多种应用场景，根据不同的知识库与提示词，适配不同的需求。下面是一些常见的典型使用场景</p>

<ul>
  <li><strong>企业内部知识问答</strong>
    <ul>
      <li>需求：员工需要查询公司规章制度、流程文档、技术手册。</li>
      <li>RAG方案：
        <ul>
          <li>把企业文档库作为检索源。</li>
          <li>用户提问时，检索相关文档段落。再由模型总结回答</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>金融/法律领域应用</strong>
    <ul>
      <li>需求：解答合规、财税、法律问题，要求答案严谨。</li>
      <li>RAG方案：
        <ul>
          <li>检索法条、案例、内部政策文档。</li>
          <li>基于检索到的条款生成规范回答</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>电商/客服智能助理</strong>
    <ul>
      <li>需求：自动回答用户关于商品、物流、售后等问题</li>
      <li>RAG方案
        <ul>
          <li>检索商品知识库、FAQ文档</li>
          <li>给出准确、即时的答复</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><strong>医疗健康领域</strong>
    <ul>
      <li>需求：为患者或医生提供疾病知识、药物信息、医院信息、诊疗方案</li>
      <li>RAG方案
        <ul>
          <li>检索医疗文献、医院文档、指南资料</li>
          <li>给出专业、可靠的医学服务</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<h4 id="3-rag工作流程概述">3 RAG工作流程概述</h4>

<p><strong>第一，用户输入问题</strong></p>

<p>​	用户在输入窗口输入自己的问题，这一数据被接收，并作为后续处理的查询入口</p>

<p>例如：用户提问</p>

<blockquote>
  <p>“我的智能手表出现蓝牙连接问题，怎么办？”</p>
</blockquote>

<p><strong>第二，问题向量化</strong></p>

<p>​	根据用户初始输入的问题，调用Embedding模型，将问题转换为高维向量，以便于后续的想来那个相似度检索。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>文本："我的智能手表出现蓝牙连接问题，怎么办？"
→ 向量：[0.123, 0.582, ..., 0.001]
</code></pre></div></div>

<p><strong>第三，向量数据库检索</strong></p>

<p>​	系统会连接到一个向量数据库（如FAISS、Milvus、Pinecone、Weaviate）。然后用刚才生成的问题向量，检索知识库中与之最相似的文档片段。</p>

<p>​	当检索的时候，常见的检索参数包括：</p>

<ul>
  <li>Tok-K ：检索最相关的K条记录</li>
  <li>相似度阈值：控制检索到内容的相关性</li>
</ul>

<p>​	最后输出的结果往往是K条知识片段</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. "蓝牙连接问题通常可以通过重启设备和重新配对解决。"
2. "如果手表固件版本较旧，请更新到最新版本以兼容蓝牙。"
3. "某些环境下，如电磁干扰，也会导致连接失败。"
</code></pre></div></div>

<p><strong>第四，构建上下文</strong></p>

<p>这一阶段需要组织提示词（Prompt），让LLM更好地理解背景信息。</p>

<p>这一部分包括：</p>

<ul>
  <li>
    <p>系统提示词（System Prompt）</p>

    <p>提前告诉LLM需要遵循的行为规范，比如</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>你是一个专业的智能手表客服助理。请基于提供的背景资料，准确回答用户的问题。如果资料中没有明确答案，请如实告诉用户而不是编造。
</code></pre></div>    </div>

    <p>系统提示词可以有效地设定模型角色、控制回答风格、防止幻觉</p>
  </li>
  <li>
    <p>构造最终输入（Final Prompt）</p>

    <p>一般会结合以上内容，按照如下格式进行组织</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>【背景资料】
1. 蓝牙连接问题通常可以通过重启设备和重新配对解决。
2. 如果手表固件版本较旧，请更新到最新版本以兼容蓝牙。
3. 某些环境下，如电磁干扰，也会导致连接失败。

【用户问题】
我的智能手表出现蓝牙连接问题，怎么办？

【回答要求】
请结合以上资料，用简洁明了的方式回答用户的问题。如果答案无法直接从资料中找到，请礼貌告知用户。
</code></pre></div>    </div>
  </li>
</ul>

<p><strong>第五，调用LLM</strong></p>

<p>​	将构造好的Prompt提交给LLM（比如Deepseek、Qwen、GPT-4o、Claude等）</p>

<ul>
  <li>模型读取检索到的内容和问题</li>
  <li>组织自然、连贯、准确的回答</li>
</ul>

<p>生成结果示例：</p>

<blockquote>
  <p>“<strong>您好！</strong> 根据我们的资料，您可以尝试重启智能手表并重新进行蓝牙配对。如果问题仍未解决，请检查手表固件是否为最新版本。如处于高电磁干扰环境，也可能影响连接质量，建议更换使用环境。”</p>
</blockquote>

<p><strong>第六，返回最终回答给用户</strong></p>

<p>​	最终系统将生成的回答返回前端，展示给用户。</p>

<p><strong>总结：</strong></p>

<p>在RAG工作时，其运行流程大致为：</p>

<ol>
  <li>用户输入问题</li>
  <li>问题向量化</li>
  <li>向量数据库检索</li>
  <li>构建上下文（含系统提示词）</li>
  <li>携带检索内容，调用大模型进行回答</li>
  <li>返回最终答案给用户</li>
</ol>

<h2 id="82-spring-ai-实现基本-rag-流程">8.2 Spring AI 实现基本 RAG 流程</h2>

<h3 id="821-创建配置类">8.2.1 创建配置类</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RagConfig</span> <span class="o">{</span>

    <span class="nd">@Bean</span>
    <span class="nc">ChatClient</span> <span class="nf">chatClient</span><span class="o">(</span><span class="nc">ChatClient</span><span class="o">.</span><span class="na">Builder</span> <span class="n">builder</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">builder</span><span class="o">.</span><span class="na">defaultSystem</span><span class="o">(</span><span class="s">"你将作为一名Java开发语言的专家，对于用户的使用需求作出解答"</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nd">@Bean</span>
    <span class="nc">VectorStore</span> <span class="nf">vectorStore</span><span class="o">(</span><span class="nc">EmbeddingModel</span> <span class="n">embeddingModel</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">SimpleVectorStore</span> <span class="n">simpleVectorStore</span> <span class="o">=</span> <span class="nc">SimpleVectorStore</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="n">embeddingModel</span><span class="o">)</span>
                <span class="o">.</span><span class="na">build</span><span class="o">();</span>

        <span class="c1">// 生成一个说明的文档</span>
        <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Document</span><span class="o">&gt;</span> <span class="n">documents</span> <span class="o">=</span> <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span>
                <span class="k">new</span> <span class="nf">Document</span><span class="o">(</span><span class="s">"产品说明:名称：Java开发语言\n"</span> <span class="o">+</span>
                        <span class="s">"产品描述：Java是一种面向对象开发语言。\n"</span> <span class="o">+</span>
                        <span class="s">"特性：\n"</span> <span class="o">+</span>
                        <span class="s">"1. 封装\n"</span> <span class="o">+</span>
                        <span class="s">"2. 继承\n"</span> <span class="o">+</span>
                        <span class="s">"3. 多态\n"</span><span class="o">));</span>
        <span class="n">simpleVectorStore</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">documents</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">simpleVectorStore</span><span class="o">;</span>
    <span class="o">}</span>

<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li><strong>通过这个配置类，完成以下内容：</strong></li>
</ul>

<p>1、配置 ChatClient 作为 Bean，其中设置系统默认角色为Java开发语言专家， 负责处理用户查询并生成回答向量存储配置。</p>

<p>2、初始化 SimpleVectorStore，加载Java开发语言说明文档，将文档转换为向量形式存储。</p>

<h3 id="822-编写controller">8.2.2 编写Controller</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">ChatClient</span> <span class="n">dashScopeChatClient</span><span class="o">;</span>

<span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">VectorStore</span> <span class="n">vectorStore</span><span class="o">;</span>

<span class="nd">@GetMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/chat"</span><span class="o">,</span> <span class="n">produces</span> <span class="o">=</span> <span class="s">"text/plain; charset=UTF-8"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">generation</span><span class="o">(</span><span class="nc">String</span> <span class="n">userInput</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">// 发起聊天请求并处理响应</span>
    <span class="k">return</span> <span class="n">dashScopeChatClient</span><span class="o">.</span><span class="na">prompt</span><span class="o">()</span>
            <span class="o">.</span><span class="na">user</span><span class="o">(</span><span class="n">userInput</span><span class="o">)</span>
            <span class="o">.</span><span class="na">advisors</span><span class="o">(</span><span class="k">new</span> <span class="nc">QuestionAnswerAdvisor</span><span class="o">(</span><span class="n">vectorStore</span><span class="o">))</span>
            <span class="o">.</span><span class="na">call</span><span class="o">()</span>
            <span class="o">.</span><span class="na">content</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li>通过添加 QuestionAnswerAdvisor 并提供对应的向量存储，可以将之前放入的文档作为参考资料，并生成增强回答。</li>
</ul>

<h3 id="823-测试">8.2.3 测试</h3>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250516024405555.png" alt="image-20250516024405555" /></p>

<h1 id="第九章-spring-ai综合案例">第九章 Spring AI综合案例</h1>

<ul>
  <li><strong>智能简历筛选助手</strong></li>
  <li>该助手借助 Spring AI 的特性结合人工智能技术，为企业提供快速查看应聘候选人的信息及与候选人岗位的匹配度的服务。</li>
</ul>

<h2 id="91-搭建环境">9.1 搭建环境</h2>

<ul>
  <li>创建工程，引入依赖</li>
</ul>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.example<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>deepseek-apringai<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>

    <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.2.5<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>

    <span class="nt">&lt;properties&gt;</span>
        <span class="nt">&lt;maven.compiler.source&gt;</span>17<span class="nt">&lt;/maven.compiler.source&gt;</span>
        <span class="nt">&lt;maven.compiler.target&gt;</span>17<span class="nt">&lt;/maven.compiler.target&gt;</span>
        <span class="nt">&lt;spring-ai.version&gt;</span>1.0.0-M5<span class="nt">&lt;/spring-ai.version&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>

    <span class="nt">&lt;dependencies&gt;</span>
        <span class="nt">&lt;dependency&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-web<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
        <span class="nt">&lt;dependency&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-test<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>com.alibaba.cloud.ai<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>spring-ai-alibaba-starter<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;version&gt;</span>1.0.0-M5.1<span class="nt">&lt;/version&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;/dependencies&gt;</span>
    
    <span class="nt">&lt;repositories&gt;</span>
        <span class="nt">&lt;repository&gt;</span>
            <span class="nt">&lt;id&gt;</span>spring-milestones<span class="nt">&lt;/id&gt;</span>
            <span class="nt">&lt;name&gt;</span>Spring Milestones<span class="nt">&lt;/name&gt;</span>
            <span class="nt">&lt;url&gt;</span>https://repo.spring.io/milestone<span class="nt">&lt;/url&gt;</span>
            <span class="nt">&lt;snapshots&gt;</span>
                <span class="nt">&lt;enabled&gt;</span>false<span class="nt">&lt;/enabled&gt;</span>
            <span class="nt">&lt;/snapshots&gt;</span>
        <span class="nt">&lt;/repository&gt;</span>
    <span class="nt">&lt;/repositories&gt;</span>
<span class="nt">&lt;/project&gt;</span>
</code></pre></div></div>

<ul>
  <li>创建配置文件</li>
</ul>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">server.port</span><span class="p">=</span><span class="s">8899</span>
<span class="py">spring.application.name</span><span class="p">=</span><span class="s">spring-ai-deepseek-demo</span>
<span class="py">spring.ai.dashscope.api-key</span><span class="p">=</span><span class="s">sk-026f3ee568f34cc3a237ea272d****</span>
</code></pre></div></div>

<h2 id="92-创建rag-知识库">9.2 创建RAG 知识库</h2>

<ul>
  <li>用全部候选人的简历构建一个简历知识库</li>
</ul>

<h3 id="921-创建txt文本">9.2.1 创建txt文本</h3>

<p>命名“张三简历.txt”，放到resource目录下</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250516095923962.png" alt="image-20250516095923962" /></p>

<pre><code class="language-txt">教育经历
北京大学 软件工程 本科 2005.09 - 2009.06
主修课程：Java语言开发、C语言开发、操作系统、编译原理、计算机网络、算法导论、电商商务、电子基础、高等数学、概率论与统计、离散数学。
工作经验
阿里巴巴有限公司 算法工程师
2009-7-1 - 2015-7-10
拥有五年以上的算法工程师经验，熟悉各种开发语言的使用，比如Java、C++、C#等，熟练使用各种主流深度学习框架，能独立开发出高质量、高性能的算法模型，精通数据结构、算法及机器学习模型的实现与优化，在多个项目中负责算法模型的设计与开发，包括基于深度学习的图像识别、语音识别及自然语言处理等方向。。
小米科技有限公司 算法工程师
2015-8-1 - 2020-3-1
担任小米科技有限公司算法工程师，负责参与开发高性能机器学习算法。在项目中，我使用Python和MATLAB编写了多种算法模型，并且实现了GPU加速计算，使得算法在处理大规模复杂数据时表现优异。
就职于小米科技有限公司，作为算法工程师，全程参与了一款自动驾驶系统的开发。在项目中，我主要通过深度学习、目标检测等技术，实现了车辆识别、道路分割等多项技术难点，使得系统在真实道路环境下表现出了较高的稳定性和可靠性。
在小米科技有限公司，我作为算法工程师负责了一项推荐系统的研发。该系统基于用户行为数据，使用协同过滤和深度学习技术，为用户推荐最优质的内容。在项目中，我优化了多种推荐算法，优化推荐精度达到了90%以上。
</code></pre>

<h3 id="922-添加配置类">9.2.2 添加配置类</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Bean</span>
<span class="nc">VectorStore</span> <span class="nf">vectorStore</span><span class="o">(</span><span class="nc">EmbeddingModel</span> <span class="n">embeddingModel</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">SimpleVectorStore</span> <span class="n">simpleVectorStore</span> <span class="o">=</span> <span class="nc">SimpleVectorStore</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="n">embeddingModel</span><span class="o">)</span>
            <span class="o">.</span><span class="na">build</span><span class="o">();</span>
    <span class="c1">//1 提取文本内容</span>
    <span class="nc">String</span> <span class="n">filePath</span><span class="o">=</span><span class="s">"张三简历.txt"</span><span class="o">;</span>
    <span class="nc">TextReader</span> <span class="n">textReader</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TextReader</span><span class="o">(</span><span class="n">filePath</span><span class="o">);</span>
    <span class="n">textReader</span><span class="o">.</span><span class="na">getCustomMetadata</span><span class="o">().</span><span class="na">put</span><span class="o">(</span><span class="s">"filePath"</span><span class="o">,</span><span class="n">filePath</span><span class="o">);</span>
    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Document</span><span class="o">&gt;</span> <span class="n">documents</span> <span class="o">=</span> <span class="n">textReader</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
    <span class="c1">//2 文本切分段落</span>
    <span class="nc">TokenTextSplitter</span> <span class="n">splitter</span> <span class="o">=</span>
            <span class="k">new</span> <span class="nf">TokenTextSplitter</span><span class="o">(</span><span class="mi">1200</span><span class="o">,</span>
                    <span class="mi">350</span><span class="o">,</span> <span class="mi">5</span><span class="o">,</span>
                    <span class="mi">100</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
    <span class="n">splitter</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">documents</span><span class="o">);</span>
	<span class="c1">//3 添加</span>
    <span class="n">simpleVectorStore</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">documents</span><span class="o">);</span>
    <span class="k">return</span> <span class="n">simpleVectorStore</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li>通过以上过程，一个简单的 RAG 知识库就创建完成了，使用时直接在向量库中检索即可</li>
</ul>

<h2 id="93-创建工具类">9.3 创建工具类</h2>

<h3 id="931-创建工具">9.3.1 创建工具</h3>

<ul>
  <li>创建一个工具（Function），可以用来查询候选人应聘的岗位。</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">RecruitServiceFunction</span> <span class="kd">implements</span> <span class="nc">Function</span><span class="o">&lt;</span><span class="nc">RecruitServiceFunction</span><span class="o">.</span><span class="na">Request</span><span class="o">,</span> <span class="nc">RecruitServiceFunction</span><span class="o">.</span><span class="na">Response</span><span class="o">&gt;</span> <span class="o">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">Response</span> <span class="nf">apply</span><span class="o">(</span><span class="nc">Request</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">String</span> <span class="n">position</span><span class="o">=</span><span class="s">"未知"</span><span class="o">;</span>
        <span class="k">if</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">name</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="s">"张三"</span><span class="o">)){</span>
            <span class="n">position</span><span class="o">=</span><span class="s">"算法工程师"</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">Response</span><span class="o">(</span><span class="n">position</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="n">record</span> <span class="nf">Request</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">){</span> <span class="o">}</span>
    <span class="kd">public</span> <span class="n">record</span> <span class="nf">Response</span><span class="o">(</span><span class="nc">String</span> <span class="n">position</span><span class="o">){</span> <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="932-添加配置类">9.3.2 添加配置类</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Bean</span>
<span class="nd">@Description</span><span class="o">(</span><span class="s">"某某是否有资格面试"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">Function</span><span class="o">&lt;</span><span class="nc">RecruitServiceFunction</span><span class="o">.</span><span class="na">Request</span><span class="o">,</span> <span class="nc">RecruitServiceFunction</span><span class="o">.</span><span class="na">Response</span><span class="o">&gt;</span> <span class="nf">recruitServiceFunction</span><span class="o">(){</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">RecruitServiceFunction</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="94-编写应用的人设">9.4 编写应用的人设</h2>

<p>角色与目标：你是一个招聘助手，会针对用户的问题，结合候选人经历，岗位匹配度等专业知识，给用户提供指导。
指导原则：你需要确保给出的建议合理科学，不会对候选人的表现有言论侮辱。
限制：在提供建议时，需要强调在个性建议方面用户仍然需要线下寻求专业咨询。
澄清：在与用户交互过程中，你需要明确回答用户关于招聘方面的问题，对于非招聘方面的问题，你的回应是‘我只是一个招聘助手，不能回答这个问题哦’。
个性化：在回答时，你需要以专业可靠的预期回答，偶尔可以带点幽默感。调节气氛。</p>

<h2 id="95-编写controller">9.5 编写Controller</h2>

<p>将人设、知识库、工具通过 Spring AI 框架串联起了，搭建成应用</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Autowired</span>
<span class="kd">private</span> <span class="nc">ChatModel</span> <span class="n">chatModel</span><span class="o">;</span>

<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/ai/agent"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">rag</span><span class="o">(</span><span class="nd">@RequestParam</span><span class="o">(</span><span class="s">"query"</span><span class="o">)</span><span class="nc">String</span> <span class="n">query</span><span class="o">){</span>
    <span class="c1">//检索挂载信息</span>
    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Document</span><span class="o">&gt;</span> <span class="n">documents</span> <span class="o">=</span> <span class="n">vectorStore</span><span class="o">.</span><span class="na">similaritySearch</span><span class="o">(</span><span class="n">query</span><span class="o">);</span>

    <span class="c1">//提取相关信息</span>
    <span class="nc">String</span> <span class="n">info</span> <span class="o">=</span><span class="s">""</span><span class="o">;</span>
    <span class="k">if</span><span class="o">(</span><span class="n">documents</span><span class="o">.</span><span class="na">size</span><span class="o">()&gt;</span><span class="mi">0</span><span class="o">){</span>
        <span class="n">info</span><span class="o">=</span><span class="n">documents</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">).</span><span class="na">getContent</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="c1">//构造系统 prompt</span>
    <span class="nc">String</span> <span class="n">systemPrompt</span><span class="o">=</span> <span class="s">"""
            角色与目标：你是一个招聘助手，会针对用户的问题，结合候选人经历，岗位匹配度等专业知识，给用户提供指导。
            指导原则：你需要确保给出的建议合理科学，不会对候选人的表现有言论侮辱。
            限制：在提供建议时，需要强调在个性建议方面用户仍然需要线下寻求专业咨询。
            澄清：在与用户交互过程中，你需要明确回答用户关于招聘方面的问题，对于非招聘方面的问题，你的回应是‘我只是一个招聘助手，不能回答这个问题哦’。
            个性化：在回答时，你需要以专业可靠的预期回答，偶尔可以带点幽默感。调节气氛。
            给你提供一个数据参考，并且给你调用岗位投递检索公户
            请你跟进数据参考与工具返回结果回复用户的请求。
            """</span><span class="o">;</span>
    <span class="c1">//构造用户 prompt</span>
    <span class="nc">String</span> <span class="n">userPrompt</span><span class="o">=</span> <span class="s">"""
            给你提供一些数据参考：{info},请回答我的问题：{query}。
            请你跟进数据参考与工具返回结果回复用户的请求。
            """</span><span class="o">;</span>
    <span class="c1">//构造提示词</span>
    <span class="nc">SystemMessage</span> <span class="n">systemMessage</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SystemMessage</span><span class="o">(</span><span class="n">systemPrompt</span><span class="o">);</span>
    <span class="nc">PromptTemplate</span> <span class="n">promptTemplate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PromptTemplate</span><span class="o">(</span><span class="n">userPrompt</span><span class="o">);</span>
    <span class="nc">Message</span> <span class="n">userMessage</span> <span class="o">=</span>
            <span class="n">promptTemplate</span><span class="o">.</span><span class="na">createMessage</span><span class="o">(</span><span class="nc">Map</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"info"</span><span class="o">,</span> <span class="n">info</span><span class="o">,</span> <span class="s">"query"</span><span class="o">,</span> <span class="n">query</span><span class="o">));</span>
    <span class="nc">Prompt</span> <span class="n">prompt</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Prompt</span><span class="o">(</span>
            <span class="nc">List</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">userMessage</span><span class="o">,</span> <span class="n">systemMessage</span><span class="o">),</span>
        <span class="nc">DashScopeChatOptions</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">withFunctions</span><span class="o">(</span><span class="nc">Set</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"recruitServiceFunction"</span><span class="o">)).</span><span class="na">build</span><span class="o">()</span>
    <span class="o">);</span>

    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Generation</span><span class="o">&gt;</span> <span class="n">results</span> <span class="o">=</span> <span class="n">chatModel</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="n">prompt</span><span class="o">).</span><span class="na">getResults</span><span class="o">();</span>
    <span class="nc">String</span> <span class="n">content</span> <span class="o">=</span> <span class="n">results</span><span class="o">.</span><span class="na">stream</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">x</span><span class="o">.</span><span class="na">getOutput</span><span class="o">().</span><span class="na">getContent</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">joining</span><span class="o">());</span>
    <span class="k">return</span> <span class="n">content</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="96-测试">9.6 测试</h2>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/spring-ai/image-20250516101400644.png" alt="image-20250516101400644" /></p>]]></content><author><name>DarrenChan陈驰</name></author><category term="LLM" /><summary type="html"><![CDATA[大模型框架最快入门]]></summary></entry><entry><title type="html">Maven最全总结</title><link href="https://chenchi-cc.github.io/2025/05/01/maven/" rel="alternate" type="text/html" title="Maven最全总结" /><published>2025-05-01T00:00:00+00:00</published><updated>2025-05-01T00:00:00+00:00</updated><id>https://chenchi-cc.github.io/2025/05/01/maven</id><content type="html" xml:base="https://chenchi-cc.github.io/2025/05/01/maven/"><![CDATA[<h1 id="maven依赖管理项目构建工具">Maven依赖管理项目构建工具</h1>

<h2 id="目录">目录</h2>

<p>[TOC]</p>

<h2 id="一maven简介">一、Maven简介</h2>

<h3 id="1为什么学习maven">1、为什么学习Maven</h3>

<h4 id="11maven是一个依赖管理工具">1.1、Maven是一个依赖管理工具</h4>

<p>①jar 包的规模</p>

<p>随着我们使用越来越多的框架，或者框架封装程度越来越高，项目中使用的jar包也越来越多。项目中，一个模块里面用到上百个jar包是非常正常的。</p>

<p>比如下面的例子，我们只用到 SpringBoot、SpringCloud 框架中的三个功能：</p>

<ul>
  <li>Nacos 服务注册发现</li>
  <li>Web 框架环境</li>
  <li>视图模板技术 Thymeleaf</li>
</ul>

<p>最终却导入了 106 个 jar 包：</p>

<blockquote>
  <p>org.springframework.security:spring-security-rsa:jar:1.0.9.RELEASE:compile
com.netflix.ribbon: ribbon:jar:2.3.0:compile
org.springframework.boot:spring-boot-starter-thymeleaf:jar:2.3.6.RELEASE:compile
commons-configuration:commons-configuration:jar:1.8:compile
org.apache.logging.log4j:log4j-api:jar:2.13.3:compile
org.springframework:spring-beans:jar:5.2.11.RELEASE:compile
org.springframework.cloud:spring-cloud-starter-netflix-ribbon:jar:2.2.6.RELEASE:compile
org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.39:compile
com.alibaba.cloud:spring-cloud-alibaba-commons:jar:2.2.6.RELEASE:compile
org.bouncycastle:bcprov-jdk15on:jar:1.64:compile
org.springframework.security:spring-security-crypto:jar:5.3.5.RELEASE:compile
org.apache.httpcomponents:httpasyncclient:jar:4.1.4:compile
com.google.j2objc:j2objc-annotations:jar:1.3:compile
com.fasterxml.jackson.core:jackson-databind:jar:2.11.3:compile
io.reactivex:rxjava:jar:1.3.8:compile
ch.qos.logback:logback-classic:jar:1.2.3:compile
org.springframework:spring-web:jar:5.2.11.RELEASE:compile
io.reactivex:rxnetty-servo:jar:0.4.9:runtime
org.springframework:spring-core:jar:5.2.11.RELEASE:compile
io.github.openfeign.form:feign-form-spring:jar:3.8.0:compile
io.github.openfeign.form:feign-form:jar:3.8.0:compile
com.netflix.ribbon:ribbon-loadbalancer:jar:2.3.0:compile
org.apache.httpcomponents:httpcore:jar:4.4.13:compile
org.thymeleaf.extras:thymeleaf-extras-java8time:jar:3.0.4.RELEASE:compile
org.slf4j:jul-to-slf4j:jar:1.7.30:compile
com.atguigu.demo:demo09-base-entity:jar:1.0-SNAPSHOT:compile
org.yaml:snakeyaml:jar:1.26:compile
org.springframework.boot:spring-boot-starter-logging:jar:2.3.6.RELEASE:compile
io.reactivex:rxnetty-contexts:jar:0.4.9:runtime
org.apache.httpcomponents:httpclient:jar:4.5.13:compile
io.github.openfeign:feign-core:jar:10.10.1:compile
org.springframework.boot:spring-boot-starter-aop:jar:2.3.6.RELEASE:compile
org.hdrhistogram:HdrHistogram:jar:2.1.9:compile
org.springframework:spring-context:jar:5.2.11.RELEASE:compile
commons-lang:commons-lang:jar:2.6:compile
io.prometheus:simpleclient:jar:0.5.0:compile
ch.qos.logback:logback-core:jar:1.2.3:compile
org.springframework:spring-webmvc:jar:5.2.11.RELEASE:compile
com.sun.jersey:jersey-core:jar:1.19.1:runtime
javax.ws.rs:jsr311-api:jar:1.1.1:runtime
javax.inject:javax.inject:jar:1:runtime
org.springframework.cloud:spring-cloud-openfeign-core:jar:2.2.6.RELEASE:compile
com.netflix.ribbon:ribbon-core:jar:2.3.0:compile
com.netflix.hystrix:hystrix-core:jar:1.5.18:compile
com.netflix.ribbon:ribbon-transport:jar:2.3.0:runtime
org.springframework.boot:spring-boot-starter-json:jar:2.3.6.RELEASE:compile
org.springframework.cloud:spring-cloud-starter-openfeign:jar:2.2.6.RELEASE:compile
com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.11.3:compile
com.sun.jersey.contribs:jersey-apache-client4:jar:1.19.1:runtime
io.github.openfeign:feign-hystrix:jar:10.10.1:compile
io.github.openfeign:feign-slf4j:jar:10.10.1:compile
com.alibaba.nacos:nacos-client:jar:1.4.2:compile
org.apache.httpcomponents:httpcore-nio:jar:4.4.13:compile
com.sun.jersey:jersey-client:jar:1.19.1:runtime
org.springframework.cloud:spring-cloud-context:jar:2.2.6.RELEASE:compile
org.glassfish:jakarta.el:jar:3.0.3:compile
org.apache.logging.log4j:log4j-to-slf4j:jar:2.13.3:compile
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.11.3:compile
org.springframework.cloud:spring-cloud-commons:jar:2.2.6.RELEASE:compile
org.aspectj:aspectjweaver:jar:1.9.6:compile
com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:jar:2.2.6.RELEASE:compile
com.google.guava:listenablefuture:jar:9999.0-empty-to-avoid-conflict-with-guava:compile
com.alibaba.spring:spring-context-support:jar:1.0.10:compile
jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
org.bouncycastle:bcpkix-jdk15on:jar:1.64:compile
com.netflix.netflix-commons:netflix-commons-util:jar:0.3.0:runtime
com.fasterxml.jackson.core:jackson-annotations:jar:2.11.3:compile
com.google.guava:guava:jar:29.0-jre:compile
com.google.guava:failureaccess:jar:1.0.1:compile
org.springframework.boot:spring-boot:jar:2.3.6.RELEASE:compile
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.11.3:compile
com.atguigu.demo:demo08-base-api:jar:1.0-SNAPSHOT:compile
org.springframework.cloud:spring-cloud-starter-netflix-archaius:jar:2.2.6.RELEASE:compile
org.springframework.boot:spring-boot-autoconfigure:jar:2.3.6.RELEASE:compile
org.slf4j:slf4j-api:jar:1.7.30:compile
commons-io:commons-io:jar:2.7:compile
org.springframework.cloud:spring-cloud-starter:jar:2.2.6.RELEASE:compile
org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.39:compile
io.reactivex:rxnetty:jar:0.4.9:runtime
com.fasterxml.jackson.core:jackson-core:jar:2.11.3:compile
com.google.code.findbugs:jsr305:jar:3.0.2:compile
com.netflix.archaius:archaius-core:jar:0.7.6:compile
org.springframework.boot:spring-boot-starter-web:jar:2.3.6.RELEASE:compile
commons-codec:commons-codec:jar:1.14:compile
com.netflix.servo:servo-core:jar:0.12.21:runtime
com.google.errorprone:error_prone_annotations:jar:2.3.4:compile
org.attoparser:attoparser:jar:2.0.5.RELEASE:compile
com.atguigu.demo:demo10-base-util:jar:1.0-SNAPSHOT:compile
org.checkerframework:checker-qual:jar:2.11.1:compile
org.thymeleaf:thymeleaf-spring5:jar:3.0.11.RELEASE:compile
commons-fileupload:commons-fileupload:jar:1.4:compile
com.netflix.ribbon:ribbon-httpclient:jar:2.3.0:compile
com.netflix.netflix-commons:netflix-statistics:jar:0.1.1:runtime
org.unbescape:unbescape:jar:1.1.6.RELEASE:compile
org.springframework:spring-jcl:jar:5.2.11.RELEASE:compile
com.alibaba.nacos:nacos-common:jar:1.4.2:compile
commons-collections:commons-collections:jar:3.2.2:runtime
javax.persistence:persistence-api:jar:1.0:compile
com.alibaba.nacos:nacos-api:jar:1.4.2:compile
org.thymeleaf:thymeleaf:jar:3.0.11.RELEASE:compile
org.springframework:spring-aop:jar:5.2.11.RELEASE:compile
org.springframework.boot:spring-boot-starter:jar:2.3.6.RELEASE:compile
org.springframework.boot:spring-boot-starter-tomcat:jar:2.3.6.RELEASE:compile
org.springframework.cloud:spring-cloud-netflix-ribbon:jar:2.2.6.RELEASE:compile
org.springframework:spring-expression:jar:5.2.11.RELEASE:compile
org.springframework.cloud:spring-cloud-netflix-archaius:jar:2.2.6.RELEASE:compile</p>
</blockquote>

<p>而如果使用 Maven 来引入这些 jar 包只需要配置三个『<strong>依赖</strong>』：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- Nacos 服务注册发现启动器 --&gt;</span>
<span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>com.alibaba.cloud<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>spring-cloud-starter-alibaba-nacos-discovery<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>

<span class="c">&lt;!-- web启动器依赖 --&gt;</span>
<span class="nt">&lt;dependency&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-web<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>

<span class="c">&lt;!-- 视图模板技术 thymeleaf --&gt;</span>
<span class="nt">&lt;dependency&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-thymeleaf<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<p>②jar包的来源问题</p>

<ul>
  <li>这个jar包所属技术的官网。官网通常是英文界面，网站的结构又不尽相同，甚至找到下载链接还发现需要通过特殊的工具下载。</li>
  <li>第三方网站提供下载。问题是不规范，在使用过程中会出现各种问题。
    <ul>
      <li>jar包的名称</li>
      <li>jar包的版本</li>
      <li>jar包内的具体细节</li>
    </ul>
  </li>
  <li>而使用 Maven 后，依赖对应的 jar 包能够<strong>自动下载</strong>，方便、快捷又规范。</li>
</ul>

<p>③jar包的导入问题</p>

<p>在web工程中，jar包必须存放在指定位置：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021101825708.png" alt="image-20231021101825708" /></p>

<p>在使用Maven之后，通过配置依赖(jar包)的坐标，查找本地仓库中相应jar包，若本地仓库没有，则统一从镜像网站或中央仓库中下载：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021102831531.png" alt="image-20231021102831531" /></p>

<p>④jar包之间的依赖</p>

<p>框架中使用的 jar 包，不仅数量庞大，而且彼此之间存在错综复杂的依赖关系。依赖关系的复杂程度，已经上升到了完全不能靠人力手动解决的程度。另外，jar 包之间有可能产生冲突。进一步增加了我们在 jar 包使用过程中的难度。</p>

<p>下面是前面例子中 jar 包之间的依赖关系：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/img006.ab4f2e31.png" alt="images" /></p>

<p>而实际上 jar 包之间的依赖关系是普遍存在的，如果要由程序员手动梳理无疑会增加极高的学习成本，而这些工作又对实现业务功能毫无帮助。</p>

<p>而使用 Maven 则几乎不需要管理这些关系，极个别的地方调整一下即可，极大的减轻了我们的工作量。</p>

<h4 id="12maven是一个构建工具">1.2、Maven是一个构建工具</h4>

<p>①你没有注意过的构建</p>

<p>你可以不使用 Maven，但是构建必须要做。当我们使用 IDEA 进行开发时，构建是 IDEA 替我们做的。</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021103758624.png" alt="image-20231021103758624" /></p>

<p>②脱离 IDE 环境仍需构建</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image.png" alt="img" /></p>

<h4 id="13结论">1.3、结论</h4>

<ul>
  <li><strong>管理规模庞大的 jar 包，需要专门工具。</strong></li>
  <li><strong>脱离 IDE 环境执行构建操作，需要专门工具。</strong></li>
</ul>

<h3 id="2-maven介绍">2. Maven介绍</h3>

<p><a href="https://maven.apache.org/what-is-maven.html">https://maven.apache.org/what-is-maven.html</a></p>

<p>Maven 是一款为 Java 项目管理构建、依赖管理的工具（软件），使用 Maven 可以自动化构建、测试、打包和发布项目，大大提高了开发效率和质量。</p>

<p>Maven就是一个软件，掌握安装、配置、以及基本功能 <strong>（项目构建、依赖管理）</strong> 的理解和使用即可！</p>

<ol>
  <li>
    <p><strong>依赖管理：</strong></p>

    <p>Maven 可以管理项目的依赖，包括自动下载所需依赖库、自动下载依赖需要的依赖并且保证版本没有冲突、依赖版本管理等。通过 Maven，我们可以方便地维护项目所依赖的外部库，避免版本冲突和转换错误等，而我们仅仅需要编写配置即可。</p>
  </li>
  <li>
    <p><strong>构建管理：</strong></p>

    <p>项目构建是指将源代码、配置文件、资源文件等转化为能够运行或部署的应用程序或库的过程</p>

    <p>Maven 可以管理项目的编译、测试、打包、部署等构建过程。通过实现标准的构建生命周期，Maven 可以确保每一个构建过程都遵循同样的规则和最佳实践。同时，Maven 的插件机制也使得开发者可以对构建过程进行扩展和定制。主动触发构建，只需要简单的命令操作即可。</p>

    <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image_OSOE45UACw.png" alt="" /></p>
  </li>
</ol>

<p><strong>场景1：</strong> 例如我们项目需要第三方依赖如：Druid连接池、MySQL数据库驱动和Jackson JSON等处理。那么我们可以将想要的依赖项的信息编写到Maven工程的配置文件，Maven就会自动下载并复制这些依赖项到项目中，无需自己导入jar包，管理jar!</p>

<p><strong>场景2：</strong> 项目完成开发，我们想要打成war部署到服务器中，使用maven的构建命令可以快速打包！节省大量时间！</p>

<h3 id="3-maven软件工作原理模型图了解">3. Maven软件工作原理模型图（了解）</h3>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image_6AVFQbaXLj.png" alt="" /></p>

<h2 id="二maven安装和配置">二、Maven安装和配置</h2>

<h3 id="1-maven安装">1. Maven安装</h3>

<p><a href="https://maven.apache.org/docs/history.html">https://maven.apache.org/docs/history.html</a></p>

<p>各个工具选用版本：</p>

<table>
  <thead>
    <tr>
      <th>工具</th>
      <th>版本</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Maven</td>
      <td>3.8.8</td>
    </tr>
    <tr>
      <td>JDK</td>
      <td>17</td>
    </tr>
    <tr>
      <td>IDEA</td>
      <td>2022.2</td>
    </tr>
  </tbody>
</table>

<p><strong>安装条件：</strong> maven需要本机安装java环境、必需包含java_home环境变量！</p>

<p><strong>软件安装：</strong> 右键解压即可（绿色免安装）</p>

<p><strong>软件结构：</strong></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021110800113.png" alt="image-20231021110800113" /></p>

<p><strong>bin</strong>：含有Maven的运行脚本</p>

<p>boot：含有plexus-classworlds类加载器框架</p>

<p><strong>conf</strong>：含有Maven的核心配置文件</p>

<p>lib：含有Maven运行时所需要的Java类库</p>

<p>LICENSE、NOTICE、README.txt：针对Maven版本，第三方软件等简要介绍</p>

<h3 id="2-maven环境配置">2. Maven环境配置</h3>

<ol>
  <li>
    <p>配置MAVEN_HOME</p>

    <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021110938230.png" alt="image-20231021110938230" /></p>
  </li>
  <li>
    <p>配置Path</p>

    <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image_xNL5Fg_ucf.png" alt="" /></p>
  </li>
  <li>
    <p>命令测试（cmd窗口）</p>
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn <span class="nt">-v</span> 
<span class="c"># 输出版本信息即可，如果错误，请仔细检查环境变量即可！</span>
</code></pre></div>    </div>
  </li>
</ol>

<h3 id="3-maven功能配置">3. Maven功能配置</h3>

<blockquote>
  <p>我们需要需改<strong>maven/conf/settings.xml</strong>配置文件，来修改maven的一些默认配置。我们主要休要修改的有三个配置：</p>

  <p>1.依赖本地缓存位置（本地仓库位置）</p>

  <p>2.maven下载镜像</p>

  <p>3.maven选用编译项目的jdk版本</p>
</blockquote>

<ol>
  <li>配置本地仓库地址
    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c">&lt;!-- localRepository
   | The path to the local repository maven will use to store artifacts.
   |
   | Default: ${user.home}/.m2/repository
  &lt;localRepository&gt;/path/to/local/repo&lt;/localRepository&gt;
  --&gt;</span>
 <span class="c">&lt;!-- conf/settings.xml 55行 --&gt;</span>
 <span class="nt">&lt;localRepository&gt;</span>D:\maven-repository<span class="nt">&lt;/localRepository&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>配置国内阿里镜像
    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!--在mirrors节点(标签)下添加中央仓库镜像 160行附近--&gt;</span>
<span class="nt">&lt;mirror&gt;</span>
    <span class="nt">&lt;id&gt;</span>alimaven<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;name&gt;</span>aliyun maven<span class="nt">&lt;/name&gt;</span>
    <span class="nt">&lt;url&gt;</span>http://maven.aliyun.com/nexus/content/groups/public/<span class="nt">&lt;/url&gt;</span>
    <span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span>
<span class="nt">&lt;/mirror&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>配置jdk17版本项目构建
    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!--在profiles节点(标签)下添加jdk编译版本 268行附近--&gt;</span>
<span class="nt">&lt;profile&gt;</span>
    <span class="nt">&lt;id&gt;</span>jdk-17<span class="nt">&lt;/id&gt;</span>
    <span class="nt">&lt;activation&gt;</span>
      <span class="nt">&lt;activeByDefault&gt;</span>true<span class="nt">&lt;/activeByDefault&gt;</span>
      <span class="nt">&lt;jdk&gt;</span>17<span class="nt">&lt;/jdk&gt;</span>
    <span class="nt">&lt;/activation&gt;</span>
    <span class="nt">&lt;properties&gt;</span>
      <span class="nt">&lt;maven.compiler.source&gt;</span>17<span class="nt">&lt;/maven.compiler.source&gt;</span>
      <span class="nt">&lt;maven.compiler.target&gt;</span>17<span class="nt">&lt;/maven.compiler.target&gt;</span>
      <span class="nt">&lt;maven.compiler.compilerVersion&gt;</span>17<span class="nt">&lt;/maven.compiler.compilerVersion&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>
<span class="nt">&lt;/profile&gt;</span>
</code></pre></div>    </div>
  </li>
</ol>

<h3 id="4-idea配置本地maven软件">4. IDEA配置本地Maven软件</h3>

<blockquote>
  <p>我们需要将配置好的maven软件，配置到idea开发工具中即可！ 注意：idea工具默认自带maven配置软件，但是因为没有修改配置，建议替换成本地配置好的maven！</p>
</blockquote>

<p>选择本地maven软件</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021112046512.png" alt="image-20231021112046512" /></p>

<p><strong>注意</strong>：</p>

<p>1、如果本地仓库地址不变化，只有一个原因，就是maven/conf/settings.xml配置文件编写错误！仔细检查即可！</p>

<p>2、一定保证User settings file对应之前修改的settings.xml的路径，若 不一致，选中Override复选框，手动选择配置文件</p>

<h2 id="三基于idea创建maven工程">三、基于IDEA创建Maven工程</h2>

<h3 id="1-概念梳理maven工程的gavp">1. 概念梳理Maven工程的GAVP</h3>

<p>Maven工程相对之前的项目，多出一组gavp属性，gav需要我们在创建项目的时候指定，p有默认值，我们先行了解下这组属性的含义：</p>

<p>Maven 中的 GAVP 是指 GroupId、ArtifactId、Version、Packaging 等四个属性的缩写，其中前三个是必要的，而 Packaging 属性为可选项。这四个属性主要为每个项目在maven仓库中做一个标识，类似人的姓-名！有了具体标识，方便后期项目之间相互引用依赖等！</p>

<p>GAV遵循一下规则：</p>

<p>​	1） <strong>GroupID 格式</strong>：com.{公司/BU }.业务线.[子业务线]，最多 4 级。</p>

<p>​		说明：{公司/BU} 例如：alibaba/taobao/tmall/aliexpress 等 BU 一级；子业务线可选。</p>

<p>​		正例：com.taobao.tddl 或 com.alibaba.sourcing.multilang</p>

<p>​	2） <strong>ArtifactID 格式</strong>：产品线名-模块名。语义不重复不遗漏，先到仓库中心去查证一下。</p>

<p>​		正例：tc-client / uic-api / tair-tool / bookstore</p>

<p>​	3） <strong>Version版本号格式推荐</strong>：主版本号.次版本号.修订号</p>

<p>​		1） 主版本号：当做了不兼容的 API 修改，或者增加了能改变产品方向的新功能。</p>

<p>​		2） 次版本号：当做了向下兼容的功能性新增（新增类、接口等）。</p>

<p>​		3） 修订号：修复 bug，没有修改方法签名的功能加强，保持 API 兼容性。</p>

<p>​		例如： 初始→1.0.0  修改bug → 1.0.1  功能调整 → 1.1.1等</p>

<p><strong>Packaging定义规则：</strong></p>

<p>​	指示将项目打包为什么类型的文件，idea根据packaging值，识别maven项目类型！</p>

<p>​	packaging 属性为 jar（默认值），代表普通的Java工程，打包以后是.jar结尾的文件。</p>

<p>​	packaging 属性为 war，代表Java的web工程，打包以后.war结尾的文件。</p>

<p>​	packaging 属性为 pom，代表不会打包，用来做继承的父工程。</p>

<h3 id="2-idea构建maven-java-se工程">2. Idea构建Maven Java SE工程</h3>

<p>注意：此处省略了version，直接给了一个默认值：<strong>1.0-SNAPSHOT</strong></p>

<p>自己后期可以在项目中随意修改！</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021143559114.png" alt="image-20231021143559114" /></p>

<p>创建工程之后，若第一次使用maven，或者使用的是新的<strong>本地仓库</strong>，idea右下角会出现以下进度条，表示maven正在下载相关插件，等待下载完毕，进度条消失即可</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021145024505.png" alt="image-20231021145024505" /></p>

<p>验证maven工程是否创建成功，当创建完毕maven工程之后，idea中会自动打开Maven视图，如下图：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021145713433.png" alt="image-20231021145713433" /></p>

<h3 id="3-idea构建maven-java-web工程">3. Idea构建Maven Java Web工程</h3>

<ol>
  <li>手动创建
    <ol>
      <li>
        <p>创建一个maven的javase工程</p>

        <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021150134082.png" alt="image-20231021150134082" /></p>
      </li>
      <li>
        <p>修改pom.xml文件打包方式</p>

        <p>修改位置：项目下/pom.xml</p>

        <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;groupId&gt;</span>com.atguigu<span class="nt">&lt;/groupId&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>maven_web<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
<span class="c">&lt;!-- 新增一列打包方式packaging --&gt;</span>
<span class="nt">&lt;packaging&gt;</span>war<span class="nt">&lt;/packaging&gt;</span>
</code></pre></div>        </div>
      </li>
      <li>
        <p>设置<strong>web资源路径</strong>和<strong>web.xml路径</strong></p>

        <p>点击File–&gt;Project Structure</p>

        <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021151040531.png" alt="image-20231021151040531" /></p>

        <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021151627161.png" alt="image-20231021151627161" /></p>

        <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021151753318.png" alt="image-20231021151753318" /></p>
      </li>
      <li>
        <p>刷新和校验</p>

        <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021152310802.png" alt="image-20231021152310802" /></p>

        <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021151921943.png" alt="image-20231021151921943" /></p>
      </li>
    </ol>
  </li>
  <li>插件创建
    <ol>
      <li>
        <p>安装插件JBLJavaToWeb</p>

        <p>file / settings / plugins / marketplace</p>

        <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image_cHUU_rABB6.png" alt="" /></p>
      </li>
      <li>创建一个javasemaven工程</li>
      <li>
        <p>右键、使用插件快速补全web项目</p>

        <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image_ZAPkM7VLgJ.png" alt="" /></p>
      </li>
    </ol>
  </li>
</ol>

<h3 id="4-maven工程项目结构说明">4. Maven工程项目结构说明</h3>

<p>Maven 是一个强大的构建工具，它提供一种标准化的项目结构，可以帮助开发者更容易地管理项目的依赖、构建、测试和发布等任务。以下是 Maven Web 程序的文件结构及每个文件的作用：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>|-- pom.xml                               # Maven 项目管理文件 
|-- src
    |-- main                              # 项目主要代码
    |   |-- java                          # Java 源代码目录
    |   |   `-- com/example/myapp         # 开发者代码主目录
    |   |       |-- controller            # 存放 Controller 层代码的目录
    |   |       |-- service               # 存放 Service 层代码的目录
    |   |       |-- dao                   # 存放 DAO 层代码的目录
    |   |       `-- model                 # 存放数据模型的目录
    |   |-- resources                     # 资源目录，存放配置文件、静态资源等
    |   |   |-- log4j.properties          # 日志配置文件
    |   |   |-- spring-mybatis.xml        # Spring Mybatis 配置文件
    |   |   `-- static                    # 存放静态资源的目录
    |   |       |-- css                   # 存放 CSS 文件的目录
    |   |       |-- js                    # 存放 JavaScript 文件的目录
    |   |       `-- images                # 存放图片资源的目录
    |   `-- webapp                        # 存放 WEB 相关配置和资源
    |       |-- WEB-INF                   # 存放 WEB 应用配置文件
    |       |   |-- web.xml               # Web 应用的部署描述文件
    |       |   `-- classes               # 存放编译后的 class 文件
    |       `-- index.html                # Web 应用入口页面
    `-- test                              # 项目测试代码
        |-- java                          # 单元测试目录
        `-- resources                     # 测试资源目录
</code></pre></div></div>

<ul>
  <li>pom.xml：Maven 项目管理文件，用于描述项目的依赖和构建配置等信息。</li>
  <li>src/main/java：存放项目的 Java 源代码。</li>
  <li>src/main/resources：存放项目的资源文件，如配置文件、静态资源等。</li>
  <li>src/main/webapp/WEB-INF：存放 Web 应用的配置文件。</li>
  <li>src/main/webapp/index.jsp：Web 应用的入口页面。</li>
  <li>src/test/java：存放项目的测试代码。</li>
  <li>src/test/resources：存放测试相关的资源文件，如测试配置文件等。</li>
</ul>

<h2 id="四基于idea进行maven工程构建">四、基于IDEA进行Maven工程构建</h2>

<h3 id="1-构建概念和构建过程">1. 构建概念和构建过程</h3>

<p>项目构建是指将源代码、依赖库和资源文件等转换成可执行或可部署的应用程序的过程，在这个过程中包括编译源代码、链接依赖库、打包和部署等多个步骤。</p>

<p>项目构建是软件开发过程中至关重要的一部分，它能够大大提高软件开发效率，使得开发人员能够更加专注于应用程序的开发和维护，而不必关心应用程序的构建细节。</p>

<p>同时，项目构建还能够将多个开发人员的代码汇合到一起，并能够自动化项目的构建和部署，大大降低了项目的出错风险和提高开发效率。常见的构建工具包括 Maven、Gradle、Ant 等。</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image_REm5kk7DnX.png" alt="" /></p>

<h3 id="2-命令方式项目构建">2. 命令方式项目构建</h3>

<table>
  <thead>
    <tr>
      <th>命令</th>
      <th>描述</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>mvn compile</td>
      <td>编译项目，生成target文件</td>
    </tr>
    <tr>
      <td>mvn package</td>
      <td>打包项目，生成jar或war文件</td>
    </tr>
    <tr>
      <td>mvn clean</td>
      <td>清理编译或打包后的项目结构</td>
    </tr>
    <tr>
      <td>mvn install</td>
      <td>打包后上传到maven本地仓库</td>
    </tr>
    <tr>
      <td>mvn deploy</td>
      <td>只打包，上传到maven私服仓库</td>
    </tr>
    <tr>
      <td>mvn site</td>
      <td>生成站点</td>
    </tr>
    <tr>
      <td>mvn test</td>
      <td>执行测试源码</td>
    </tr>
  </tbody>
</table>

<p>war包打包插件和jdk版本不匹配：pom.xml 添加以下代码即可</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;build&gt;</span>
    <span class="c">&lt;!-- jdk17 和 war包版本插件不匹配 --&gt;</span>
    <span class="nt">&lt;plugins&gt;</span>
        <span class="nt">&lt;plugin&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>org.apache.maven.plugins<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>maven-war-plugin<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;version&gt;</span>3.2.2<span class="nt">&lt;/version&gt;</span>
        <span class="nt">&lt;/plugin&gt;</span>
    <span class="nt">&lt;/plugins&gt;</span>
<span class="nt">&lt;/build&gt;</span>
</code></pre></div></div>

<p>命令触发练习：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn 命令 命令

<span class="c">#清理</span>
mvn clean
<span class="c">#清理，并重新打包</span>
mvn clean package
<span class="c">#执行测试代码</span>
mvn <span class="nb">test</span>
</code></pre></div></div>

<h3 id="3-可视化方式项目构建">3. 可视化方式项目构建</h3>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021153444864.png" alt="image-20231021153444864" /></p>

<p>注意：打包（package）和安装（install）的区别是什么</p>

<p>打包是将工程打成jar或war文件，保存在target目录下</p>

<p>安装是将当前工程所生成的jar或war文件，安装到本地仓库，会按照坐标保存到指定位置</p>

<h3 id="4-构建插件命令生命周期命令之间关系">4. 构建插件、命令、生命周期命令之间关系</h3>

<ul>
  <li>
    <p><strong>构建生命周期</strong></p>

    <p>我们发现一个情况！当我们执行package命令也会自动执行compile命令！</p>

    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ mybatis-base-curd ---
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ mybatis-base-curd ---
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ mybatis-base-curd ---
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ mybatis-base-curd ---
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ mybatis-base-curd ---
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ mybatis-base-curd ---
[INFO] Building jar: D:\javaprojects\backend-engineering\part03-mybatis\mybatis-base-curd\target\mybatis-base-curd-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.013 s
[INFO] Finished at: 2023-06-05T10:03:47+08:00
[INFO] ------------------------------------------------------------------------
</code></pre></div>    </div>

    <p>这种行为就是因为构建生命周期产生的！构建生命周期可以理解成是一组固定构建命令的有序集合，触发周期后的命令，会自动触发周期前的命令！！！</p>

    <p><strong>构建周期作用：会简化构建过程</strong></p>

    <p>例如：项目打包   mvn clean package即可。 </p>

    <p>主要两个构建生命周期：</p>

    <ul>
      <li>
        <p>清理周期：主要是对项目编译生成文件进行清理</p>

        <p>包含命令：clean </p>
      </li>
    </ul>
  </li>
  <li>
    <p>默认周期：定义了真正构件时所需要执行的所有步骤，它是生命周期中最核心的部分</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>包含命令：compile -  test - package - install - deploy
</code></pre></div>    </div>
  </li>
  <li>
    <p><strong>插件、命令、周期三者关系（了解）</strong></p>

    <p>周期→包含若干命令→包含若干插件</p>

    <p>使用周期命令构建，简化构建过程！</p>

    <p>最终进行构建的是插件！</p>
  </li>
</ul>

<h2 id="五基于idea-进行maven依赖管理">五、基于IDEA 进行Maven依赖管理</h2>

<h3 id="1-依赖管理概念">1. 依赖管理概念</h3>

<p>Maven 依赖管理是 Maven 软件中最重要的功能之一。Maven 的依赖管理能够帮助开发人员自动解决软件包依赖问题，使得开发人员能够轻松地将其他开发人员开发的模块或第三方框架集成到自己的应用程序或模块中，避免出现版本冲突和依赖缺失等问题。</p>

<p>我们通过定义 POM 文件，Maven 能够自动解析项目的依赖关系，并通过 Maven <strong>仓库自动</strong>下载和管理依赖，从而避免了手动下载和管理依赖的繁琐工作和可能引发的版本冲突问题。</p>

<p>总之，Maven 的依赖管理是 Maven 软件的一个核心功能之一，使得软件包依赖的管理和使用更加智能和方便，简化了开发过程中的工作，并提高了软件质量和可维护性。</p>

<h3 id="2-maven工程核心信息配置和解读gavp">2. Maven工程核心信息配置和解读（GAVP）</h3>

<p>位置：pom.xml</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- 模型版本 --&gt;</span>
<span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>
<span class="c">&lt;!-- 公司或者组织的唯一标志，并且配置时生成的路径也是由此生成， 如com.companyname.project-group，maven会将该项目打成的jar包放本地路径：/com/companyname/project-group --&gt;</span>
<span class="nt">&lt;groupId&gt;</span>com.companyname.project-group<span class="nt">&lt;/groupId&gt;</span>
<span class="c">&lt;!-- 项目的唯一ID，一个groupId下面可能多个项目，就是靠artifactId来区分的 --&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>project<span class="nt">&lt;/artifactId&gt;</span>
<span class="c">&lt;!-- 版本号 --&gt;</span>
<span class="nt">&lt;version&gt;</span>1.0.0<span class="nt">&lt;/version&gt;</span>

<span class="c">&lt;!--打包方式
    默认：jar
    jar指的是普通的java项目打包方式！ 项目打成jar包！
    war指的是web项目打包方式！项目打成war包！
    pom不会讲项目打包！这个项目作为父工程，被其他工程聚合或者继承！后面会讲解两个概念
--&gt;</span>
<span class="nt">&lt;packaging&gt;</span>jar/pom/war<span class="nt">&lt;/packaging&gt;</span>
</code></pre></div></div>

<h3 id="3-maven工程依赖管理配置">3. Maven工程依赖管理配置</h3>

<p>位置：pom.xml</p>

<p>依赖管理和依赖添加</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- 
   通过编写依赖jar包的gav必要属性，引入第三方依赖！
   scope属性是可选的，可以指定依赖生效范围！
   依赖信息查询方式：
      1. maven仓库信息官网 https://mvnrepository.com/
      2. mavensearch插件搜索
 --&gt;</span>
<span class="nt">&lt;dependencies&gt;</span>
    <span class="c">&lt;!-- 引入具体的依赖包 --&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>log4j<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>log4j<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;version&gt;</span>1.2.17<span class="nt">&lt;/version&gt;</span>
        <span class="c">&lt;!-- 依赖范围 --&gt;</span>
        <span class="nt">&lt;scope&gt;</span>runtime<span class="nt">&lt;/scope&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>

<span class="nt">&lt;/dependencies&gt;</span>
</code></pre></div></div>

<p>依赖版本统一提取和维护</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!--声明版本--&gt;</span>
<span class="nt">&lt;properties&gt;</span>
  <span class="c">&lt;!--命名随便,内部制定版本号即可！--&gt;</span>
  <span class="nt">&lt;junit.version&gt;</span>4.12<span class="nt">&lt;/junit.version&gt;</span>
  <span class="c">&lt;!-- 也可以通过 maven规定的固定的key，配置maven的参数！如下配置编码格式！--&gt;</span>
  <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span>
  <span class="nt">&lt;project.reporting.outputEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.reporting.outputEncoding&gt;</span>
<span class="nt">&lt;/properties&gt;</span>

<span class="nt">&lt;dependencies&gt;</span>
  <span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>junit<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>junit<span class="nt">&lt;/artifactId&gt;</span>
    <span class="c">&lt;!--引用properties声明版本 --&gt;</span>
    <span class="nt">&lt;version&gt;</span>${junit.version}<span class="nt">&lt;/version&gt;</span>
  <span class="nt">&lt;/dependency&gt;</span>
<span class="nt">&lt;/dependencies&gt;</span>
</code></pre></div></div>

<h3 id="4-依赖范围">4. 依赖范围</h3>

<p>通过设置坐标的依赖范围(scope)，可以设置 对应jar包的作用范围：编译环境、测试环境、运行环境</p>

<table>
  <thead>
    <tr>
      <th>依赖范围</th>
      <th>描述</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>compile</strong></td>
      <td>编译依赖范围，scope 元素的缺省值。使用此依赖范围的 Maven 依赖，对于三种 classpath 均有效，即该 Maven 依赖在上述三种 classpath 均会被引入。例如，log4j 在编译、测试、运行过程都是必须的。</td>
    </tr>
    <tr>
      <td><strong>test</strong></td>
      <td>测试依赖范围。使用此依赖范围的 Maven 依赖，只对测试 classpath 有效。例如，Junit 依赖只有在测试阶段才需要。</td>
    </tr>
    <tr>
      <td><strong>provided</strong></td>
      <td>已提供依赖范围。使用此依赖范围的 Maven 依赖，只对编译 classpath 和测试 classpath 有效。例如，servlet-api 依赖对于编译、测试阶段而言是需要的，但是运行阶段，由于外部容器已经提供，故不需要 Maven 重复引入该依赖。</td>
    </tr>
    <tr>
      <td>runtime</td>
      <td>运行时依赖范围。使用此依赖范围的 Maven 依赖，只对测试 classpath、运行 classpath 有效。例如，JDBC 驱动实现依赖，其在编译时只需 JDK 提供的 JDBC 接口即可，只有测试、运行阶段才需要实现了 JDBC 接口的驱动。</td>
    </tr>
    <tr>
      <td>system</td>
      <td>系统依赖范围，其效果与 provided 的依赖范围一致。其用于添加非 Maven 仓库的本地依赖，通过依赖元素 dependency 中的 systemPath 元素指定本地依赖的路径。鉴于使用其会导致项目的可移植性降低，一般不推荐使用。</td>
    </tr>
    <tr>
      <td>import</td>
      <td>导入依赖范围，该依赖范围只能与 dependencyManagement 元素配合使用，其功能是将目标 pom.xml 文件中 dependencyManagement 的配置导入合并到当前 pom.xml 的 dependencyManagement 中。</td>
    </tr>
  </tbody>
</table>

<h3 id="5-maven工程依赖下载失败错误解决重点">5. Maven工程依赖下载失败错误解决（重点）</h3>

<p>在使用 Maven 构建项目时，可能会发生依赖项下载错误的情况，主要原因有以下几种：</p>

<ol>
  <li>下载依赖时出现网络故障或仓库服务器宕机等原因，导致无法连接至 Maven 仓库，从而无法下载依赖。</li>
  <li>依赖项的版本号或配置文件中的版本号错误，或者依赖项没有正确定义，导致 Maven 下载的依赖项与实际需要的不一致，从而引发错误。</li>
  <li>本地 Maven 仓库或缓存被污染或损坏，导致 Maven 无法正确地使用现有的依赖项。</li>
</ol>

<p>解决方案：</p>

<ol>
  <li>
    <p>检查网络连接和 Maven 仓库服务器状态。</p>
  </li>
  <li>
    <p>确保依赖项的版本号与项目对应的版本号匹配，并检查 POM 文件中的依赖项是否正确。</p>
  </li>
  <li>
    <p>清除本地 Maven 仓库缓存（lastUpdated 文件），因为只要存在lastupdated缓存文件，刷新也不会重新下载。本地仓库中，根据依赖的gav属性依次向下查找文件夹，最终删除内部的文件，刷新重新下载即可！</p>

    <p>例如： pom.xml依赖</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.alibaba<span class="nt">&lt;/groupId&gt;</span>
  <span class="nt">&lt;artifactId&gt;</span>druid<span class="nt">&lt;/artifactId&gt;</span>
  <span class="nt">&lt;version&gt;</span>1.2.8<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div>    </div>

    <p>文件：</p>

    <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image_m3iQtBLARz.png" alt="" /></p>
  </li>
  <li>
    <p>或者可以将清除<strong>lastUpdated文件</strong>的操作写在一个脚本文件中，手动创建文件”clearLastUpdated.bat”，名字任意，但是后缀必须是bat，将以下内容复制到文件中</p>

    <div class="language-bat highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cls</span> 
@ECHO <span class="kd">OFF</span> 
<span class="kd">SET</span> <span class="kd">CLEAR_PATH</span><span class="o">=</span><span class="kd">D</span>: 
<span class="kd">SET</span> <span class="kd">CLEAR_DIR</span><span class="o">=</span><span class="kd">D</span>:\maven<span class="na">-repository</span><span class="o">(</span>本地仓库路径<span class="o">)</span>
<span class="nb">color</span> <span class="m">0</span><span class="kd">a</span> 
<span class="kd">TITLE</span> <span class="kd">ClearLastUpdated</span> <span class="kd">For</span> <span class="kd">Windows</span> 
<span class="kd">GOTO</span> <span class="kd">MENU</span> 
<span class="nl">:MENU</span> 
<span class="kd">CLS</span>
<span class="kd">ECHO</span>. 
<span class="kd">ECHO</span>. <span class="o">*</span> <span class="o">*</span> <span class="o">*</span> <span class="o">*</span>  <span class="kd">ClearLastUpdated</span> <span class="kd">For</span> <span class="kd">Windows</span>  <span class="o">*</span> <span class="o">*</span> <span class="o">*</span> <span class="o">*</span> 
<span class="kd">ECHO</span>. <span class="o">*</span> <span class="o">*</span> 
<span class="kd">ECHO</span>. <span class="o">*</span> <span class="m">1</span> 清理<span class="o">*</span>.lastUpdated <span class="o">*</span> 
<span class="kd">ECHO</span>. <span class="o">*</span> <span class="o">*</span> 
<span class="kd">ECHO</span>. <span class="o">*</span> <span class="m">2</span> 查看<span class="o">*</span>.lastUpdated <span class="o">*</span> 
<span class="kd">ECHO</span>. <span class="o">*</span> <span class="o">*</span> 
<span class="kd">ECHO</span>. <span class="o">*</span> <span class="m">3</span> 退 出 <span class="o">*</span> 
<span class="kd">ECHO</span>. <span class="o">*</span> <span class="o">*</span> 
<span class="kd">ECHO</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="o">*</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="o">*</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="o">*</span> <span class="o">*</span> <span class="o">*</span> <span class="o">*</span> 
<span class="kd">ECHO</span>. 
<span class="kd">ECHO</span>.请输入选择项目的序号： 
<span class="kd">set</span> <span class="na">/p </span><span class="kd">ID</span><span class="o">=</span> 
<span class="kd">IF</span> <span class="s2">"</span><span class="nv">%id%</span><span class="s2">"</span><span class="o">==</span><span class="s2">"1"</span> <span class="kd">GOTO</span> <span class="kd">cmd1</span> 
<span class="kd">IF</span> <span class="s2">"</span><span class="nv">%id%</span><span class="s2">"</span><span class="o">==</span><span class="s2">"2"</span> <span class="kd">GOTO</span> <span class="kd">cmd2</span> 
<span class="kd">IF</span> <span class="s2">"</span><span class="nv">%id%</span><span class="s2">"</span><span class="o">==</span><span class="s2">"3"</span> <span class="kd">EXIT</span> 
<span class="kd">PAUSE</span> 
<span class="nl">:cmd</span><span class="m">1</span> 
<span class="kd">ECHO</span>. 开始清理
<span class="nv">%CLEAR_PATH%</span>
<span class="nb">cd</span> <span class="nv">%CLEAR_DIR%</span>
<span class="k">for</span> <span class="na">/r </span><span class="vm">%%i</span> <span class="k">in</span> <span class="o">(*</span>.lastUpdated<span class="o">)</span> <span class="k">do</span> <span class="nb">del</span> <span class="vm">%%i</span>
<span class="kd">ECHO</span>.OK 
<span class="kd">PAUSE</span> 
<span class="kd">GOTO</span> <span class="kd">MENU</span> 
<span class="nl">:cmd</span><span class="m">2</span> 
<span class="kd">ECHO</span>. 查看<span class="o">*</span>.lastUpdated文件
<span class="nv">%CLEAR_PATH%</span>
<span class="nb">cd</span> <span class="nv">%CLEAR_DIR%</span>
<span class="k">for</span> <span class="na">/r </span><span class="vm">%%i</span> <span class="k">in</span> <span class="o">(*</span>.lastUpdated<span class="o">)</span> <span class="k">do</span> <span class="nb">echo</span> <span class="vm">%%i</span>
<span class="kd">ECHO</span>.OK 
<span class="kd">PAUSE</span> 
<span class="kd">GOTO</span> <span class="kd">MENU</span> 
</code></pre></div>    </div>

    <p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021161615994.png" alt="image-20231021161615994" /></p>
  </li>
</ol>

<h3 id="6-maven工程build构建配置">6. Maven工程Build构建配置</h3>

<p>项目构建是指将源代码、依赖库和资源文件等转换成可执行或可部署的应用程序的过程，在这个过程中包括编译源代码、链接依赖库、打包和部署等多个步骤。</p>

<p>默认情况下，构建不需要额外配置，都有对应的缺省配置。当然了，我们也可以在pom.xml定制一些配置，来修改默认构建的行为和产物！</p>

<p>例如：</p>

<ol>
  <li>指定构建打包文件的名称，非默认名称</li>
  <li>制定构建打包时，指定包含文件格式和排除文件</li>
  <li>打包插件版本过低，配置更高版本插件</li>
</ol>

<p>构建配置是在pom.xml / build标签中指定！</p>

<p><strong>指定打包命名</strong></p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- 默认的打包名称：artifactid+verson.打包方式 --&gt;</span>
<span class="nt">&lt;build&gt;</span>
  <span class="nt">&lt;finalName&gt;</span>定义打包名称<span class="nt">&lt;/finalName&gt;</span>
<span class="nt">&lt;/build&gt;</span>  
</code></pre></div></div>

<p><strong>指定打包文件</strong></p>

<p>如果在java文件夹中添加java类，会自动打包编译到classes文件夹下！</p>

<p>但是在java文件夹中添加xml文件，默认不会被打包！</p>

<p>默认情况下，按照maven工程结构放置的文件会默认被编译和打包！</p>

<p>除此之外、我们可以使用resources标签，指定要打包资源的文件夹要把哪些静态资源打包到 classes根目录下！</p>

<p>应用场景：mybatis中有时会将用于编写SQL语句的映射文件和mapper接口都写在src/main/java下的某个包中，此时映射文件就不会被打包，如何解决</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;build&gt;</span>
    <span class="c">&lt;!--设置要打包的资源位置--&gt;</span>
    <span class="nt">&lt;resources&gt;</span>
        <span class="nt">&lt;resource&gt;</span>
            <span class="c">&lt;!--设置资源所在目录--&gt;</span>
            <span class="nt">&lt;directory&gt;</span>src/main/java<span class="nt">&lt;/directory&gt;</span>
            <span class="nt">&lt;includes&gt;</span>
                <span class="c">&lt;!--设置包含的资源类型--&gt;</span>
                <span class="nt">&lt;include&gt;</span>**/*.xml<span class="nt">&lt;/include&gt;</span>
            <span class="nt">&lt;/includes&gt;</span>
        <span class="nt">&lt;/resource&gt;</span>
    <span class="nt">&lt;/resources&gt;</span>
<span class="nt">&lt;/build&gt;</span>
</code></pre></div></div>

<p><strong>配置依赖插件</strong></p>

<p>dependencies标签下引入开发需要的jar包！我们可以在build/plugins/plugin标签引入插件！</p>

<p>常用的插件：修改jdk版本、tomcat插件、mybatis分页插件、mybatis逆向工程插件等等！</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;build&gt;</span>
  <span class="nt">&lt;plugins&gt;</span>
      <span class="c">&lt;!-- java编译插件，配jdk的编译版本 --&gt;</span>
      <span class="nt">&lt;plugin&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>org.apache.maven.plugins<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>maven-compiler-plugin<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;configuration&gt;</span>
          <span class="nt">&lt;source&gt;</span>1.8<span class="nt">&lt;/source&gt;</span>
          <span class="nt">&lt;target&gt;</span>1.8<span class="nt">&lt;/target&gt;</span>
          <span class="nt">&lt;encoding&gt;</span>UTF-8<span class="nt">&lt;/encoding&gt;</span>
        <span class="nt">&lt;/configuration&gt;</span>
      <span class="nt">&lt;/plugin&gt;</span>
      <span class="c">&lt;!-- tomcat插件 --&gt;</span>
      <span class="nt">&lt;plugin&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>org.apache.tomcat.maven<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>tomcat7-maven-plugin<span class="nt">&lt;/artifactId&gt;</span>
         <span class="nt">&lt;version&gt;</span>2.2<span class="nt">&lt;/version&gt;</span>
          <span class="nt">&lt;configuration&gt;</span>
          <span class="nt">&lt;port&gt;</span>8090<span class="nt">&lt;/port&gt;</span>
          <span class="nt">&lt;path&gt;</span>/<span class="nt">&lt;/path&gt;</span>
          <span class="nt">&lt;uriEncoding&gt;</span>UTF-8<span class="nt">&lt;/uriEncoding&gt;</span>
          <span class="nt">&lt;server&gt;</span>tomcat7<span class="nt">&lt;/server&gt;</span>
        <span class="nt">&lt;/configuration&gt;</span>
      <span class="nt">&lt;/plugin&gt;</span>
    <span class="nt">&lt;/plugins&gt;</span>
<span class="nt">&lt;/build&gt;</span>
</code></pre></div></div>

<h2 id="六maven依赖传递和依赖冲突">六、Maven依赖传递和依赖冲突</h2>

<h3 id="1-maven依赖传递特性">1. Maven依赖传递特性</h3>

<p><strong>概念</strong></p>

<p>假如有Maven项目A，项目B依赖A，项目C依赖B。那么我们可以说 C依赖A。也就是说，依赖的关系为：C—&gt;B—&gt;A， 那么我们执行项目C时，会自动把B、A都下载导入到C项目的jar包文件夹中，这就是依赖的传递性。</p>

<p><strong>作用</strong></p>

<ul>
  <li>简化依赖导入过程</li>
  <li>确保依赖版本正确</li>
</ul>

<p><strong>传递的原则</strong></p>

<p>在 A 依赖 B，B 依赖 C 的前提下，C 是否能够传递到 A，取决于 B 依赖 C 时使用的依赖范围以及配置</p>

<ul>
  <li>
    <p>B 依赖 C 时使用 compile 范围：可以传递</p>
  </li>
  <li>
    <p>B 依赖 C 时使用 test 或 provided 范围：不能传递，所以需要这样的 jar 包时，就必须在需要的地方明确配置依赖才可以。</p>
  </li>
  <li>
    <p>B 依赖 C 时，若配置了以下标签，则不能传递</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.alibaba<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>druid<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.2.15<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;optional&gt;</span>true<span class="nt">&lt;/optional&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div>    </div>
  </li>
</ul>

<p><strong>依赖传递终止</strong></p>

<ul>
  <li>非compile范围进行依赖传递</li>
  <li>使用optional配置终止传递</li>
  <li>依赖冲突（传递的依赖已经存在）</li>
</ul>

<p><strong>案例：导入jackson依赖</strong></p>

<p>分析：jackson需要三个依赖</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image_9ViibmeAvU.png" alt="" /></p>

<p>依赖传递关系：data-bind中，依赖其他两个依赖</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image_Wl0Lsj_BLk.png" alt="" /></p>

<p>最佳导入：直接可以导入data-bind，自动依赖传递需要的依赖</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --&gt;</span>
<span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>com.fasterxml.jackson.core<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>jackson-databind<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>2.10.0<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>

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

<h3 id="2-maven依赖冲突特性">2. Maven依赖冲突特性</h3>

<p>当直接引用或者间接引用出现了相同的jar包! 这时呢，一个项目就会出现相同的重复jar包，这就算作冲突！依赖冲突避免出现重复依赖，并且终止依赖传递！</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image_km7_szBRUw.png" alt="" /></p>

<p>maven自动解决依赖冲突问题能力，会按照自己的原则，进行重复依赖选择。同时也提供了手动解决的冲突的方式，不过不推荐！</p>

<p><strong>解决依赖冲突（如何选择重复依赖）方式：</strong></p>

<ol>
  <li>自动选择原则
    <ul>
      <li>
        <p>短路优先原则（第一原则）</p>

        <p>A—&gt;B—&gt;C—&gt;D—&gt;E—&gt;X(version 0.0.1)</p>

        <p>A—&gt;F—&gt;X(version 0.0.2)</p>

        <p>则A依赖于X(version 0.0.2)。</p>
      </li>
      <li>
        <p>依赖路径长度相同情况下，则“先声明优先”（第二原则）</p>

        <p>A—&gt;E—&gt;X(version 0.0.1)</p>

        <p>A—&gt;F—&gt;X(version 0.0.2)</p>

        <p>在&lt;depencies&gt;&lt;/depencies&gt;中，先声明的，路径相同，会优先选择！</p>
      </li>
    </ul>
  </li>
  <li>手动排除
    <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.atguigu.maven<span class="nt">&lt;/groupId&gt;</span>
  <span class="nt">&lt;artifactId&gt;</span>pro01-maven-java<span class="nt">&lt;/artifactId&gt;</span>
  <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
  <span class="nt">&lt;scope&gt;</span>compile<span class="nt">&lt;/scope&gt;</span>
  <span class="c">&lt;!-- 使用excludes标签配置依赖的排除  --&gt;</span>
  <span class="nt">&lt;exclusions&gt;</span>
    <span class="c">&lt;!-- 在exclude标签中配置一个具体的排除 --&gt;</span>
    <span class="nt">&lt;exclusion&gt;</span>
      <span class="c">&lt;!-- 指定要排除的依赖的坐标（不需要写version） --&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>commons-logging<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>commons-logging<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;/exclusion&gt;</span>
  <span class="nt">&lt;/exclusions&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>小案例</p>

    <p>伪代码如下：</p>

    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>前提：
   A 1.1 -&gt; B 1.1 -&gt; C 1.1 
   F 2.2 -&gt; B 2.2 
       
pom声明：
   F 2.2
   A 1.1 
</code></pre></div>    </div>

    <p>请问最终会导入哪些依赖和对应版本？</p>
  </li>
</ol>

<h2 id="七maven工程继承和聚合关系">七、Maven工程继承和聚合关系</h2>

<h3 id="1-maven工程继承关系">1. Maven工程继承关系</h3>

<ol>
  <li>
    <p>继承概念</p>

    <p>Maven 继承是指在 Maven 的项目中，让一个项目从另一个项目中继承配置信息的机制。继承可以让我们在多个项目中共享同一配置信息，简化项目的管理和维护工作。</p>
  </li>
  <li>
    <p>继承作用</p>

    <p>在父工程中统一管理项目中的依赖信息。</p>

    <p>它的背景是：</p>

    <ul>
      <li>对一个比较大型的项目进行了模块拆分。</li>
      <li>一个 project 下面，创建了很多个 module。</li>
      <li>每一个 module 都需要配置自己的依赖信息。</li>
    </ul>

    <p>它背后的需求是：</p>

    <ul>
      <li>在每一个 module 中各自维护各自的依赖信息很容易发生出入，不易统一管理。</li>
      <li>使用同一个框架内的不同 jar 包，它们应该是同一个版本，所以整个项目中使用的框架版本需要统一。</li>
      <li>使用框架时所需要的 jar 包组合（或者说依赖信息组合）需要经过长期摸索和反复调试，最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的项目中重新摸索。
通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的 jar 包；又能够将以往的经验沉淀下来，节约时间和精力。</li>
    </ul>
  </li>
  <li>继承语法
    <ul>
      <li>父工程
        <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;groupId&gt;</span>com.atguigu.maven<span class="nt">&lt;/groupId&gt;</span>
  <span class="nt">&lt;artifactId&gt;</span>pro03-maven-parent<span class="nt">&lt;/artifactId&gt;</span>
  <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
  <span class="c">&lt;!-- 当前工程作为父工程，它要去管理子工程，所以打包方式必须是 pom --&gt;</span>
  <span class="nt">&lt;packaging&gt;</span>pom<span class="nt">&lt;/packaging&gt;</span>

</code></pre></div>        </div>
      </li>
      <li>子工程
        <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- 使用parent标签指定当前工程的父工程 --&gt;</span>
<span class="nt">&lt;parent&gt;</span>
  <span class="c">&lt;!-- 父工程的坐标 --&gt;</span>
  <span class="nt">&lt;groupId&gt;</span>com.atguigu.maven<span class="nt">&lt;/groupId&gt;</span>
  <span class="nt">&lt;artifactId&gt;</span>pro03-maven-parent<span class="nt">&lt;/artifactId&gt;</span>
  <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/parent&gt;</span>

<span class="c">&lt;!-- 子工程的坐标 --&gt;</span>
<span class="c">&lt;!-- 如果子工程坐标中的groupId和version与父工程一致，那么可以省略 --&gt;</span>
<span class="c">&lt;!-- &lt;groupId&gt;com.atguigu.maven&lt;/groupId&gt; --&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>pro04-maven-module<span class="nt">&lt;/artifactId&gt;</span>
<span class="c">&lt;!-- &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt; --&gt;</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>父工程依赖统一管理
    <ul>
      <li>父工程声明版本
        <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- 使用dependencyManagement标签配置对依赖的管理 --&gt;</span>
<span class="c">&lt;!-- 被管理的依赖并没有真正被引入到工程 --&gt;</span>
<span class="nt">&lt;dependencyManagement&gt;</span>
  <span class="nt">&lt;dependencies&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>spring-core<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>6.0.10<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>spring-beans<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>6.0.10<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>spring-context<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>6.0.10<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>spring-expression<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>6.0.10<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;dependency&gt;</span>
      <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
      <span class="nt">&lt;artifactId&gt;</span>spring-aop<span class="nt">&lt;/artifactId&gt;</span>
      <span class="nt">&lt;version&gt;</span>6.0.10<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;/dependency&gt;</span>
  <span class="nt">&lt;/dependencies&gt;</span>
<span class="nt">&lt;/dependencyManagement&gt;</span>
</code></pre></div>        </div>
      </li>
      <li>子工程引用版本
        <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- 子工程引用父工程中的依赖信息时，可以把版本号去掉。  --&gt;</span>
<span class="c">&lt;!-- 把版本号去掉就表示子工程中这个依赖的版本由父工程决定。 --&gt;</span>
<span class="c">&lt;!-- 具体来说是由父工程的dependencyManagement来决定。 --&gt;</span>
<span class="nt">&lt;dependencies&gt;</span>
  <span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>spring-core<span class="nt">&lt;/artifactId&gt;</span>
  <span class="nt">&lt;/dependency&gt;</span>
  <span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>spring-beans<span class="nt">&lt;/artifactId&gt;</span>
  <span class="nt">&lt;/dependency&gt;</span>
  <span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>spring-context<span class="nt">&lt;/artifactId&gt;</span>
  <span class="nt">&lt;/dependency&gt;</span>
  <span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>spring-expression<span class="nt">&lt;/artifactId&gt;</span>
  <span class="nt">&lt;/dependency&gt;</span>
  <span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>spring-aop<span class="nt">&lt;/artifactId&gt;</span>
  <span class="nt">&lt;/dependency&gt;</span>
<span class="nt">&lt;/dependencies&gt;</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ol>

<h3 id="2-maven工程聚合关系">2. Maven工程聚合关系</h3>

<ol>
  <li>
    <p>聚合概念</p>

    <p>Maven 聚合是指将多个项目组织到一个父级项目中，以便一起构建和管理的机制。聚合可以帮助我们更好地管理一组相关的子项目，同时简化它们的构建和部署过程。</p>
  </li>
  <li>聚合作用
    <ol>
      <li>管理多个子项目：通过聚合，可以将多个子项目组织在一起，方便管理和维护。</li>
      <li>构建和发布一组相关的项目：通过聚合，可以在一个命令中构建和发布多个相关的项目，简化了部署和维护工作。</li>
      <li>优化构建顺序：通过聚合，可以对多个项目进行顺序控制，避免出现构建依赖混乱导致构建失败的情况。</li>
      <li>统一管理依赖项：通过聚合，可以在父项目中管理公共依赖项和插件，避免重复定义。</li>
    </ol>
  </li>
  <li>
    <p>聚合语法</p>

    <p>父项目中包含的子项目列表。</p>

    <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;project&gt;</span>
  <span class="nt">&lt;groupId&gt;</span>com.example<span class="nt">&lt;/groupId&gt;</span>
  <span class="nt">&lt;artifactId&gt;</span>parent-project<span class="nt">&lt;/artifactId&gt;</span>
  <span class="nt">&lt;packaging&gt;</span>pom<span class="nt">&lt;/packaging&gt;</span>
  <span class="nt">&lt;version&gt;</span>1.0.0<span class="nt">&lt;/version&gt;</span>
  <span class="nt">&lt;modules&gt;</span>
    <span class="nt">&lt;module&gt;</span>child-project1<span class="nt">&lt;/module&gt;</span>
    <span class="nt">&lt;module&gt;</span>child-project2<span class="nt">&lt;/module&gt;</span>
  <span class="nt">&lt;/modules&gt;</span>
<span class="nt">&lt;/project&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>聚合演示</p>

    <p>通过触发父工程构建命令、引发所有子模块构建！产生反应堆！</p>
  </li>
</ol>

<h2 id="八maven私服">八、Maven私服</h2>

<h3 id="1-maven私服简介">1. Maven私服简介</h3>

<p>①私服简介</p>

<p>Maven 私服是一种特殊的Maven远程仓库，它是架设在局域网内的仓库服务，用来代理位于外部的远程仓库（中央仓库、其他远程公共仓库）。</p>

<blockquote>
  <p>当然也并不是说私服只能建立在局域网，也有很多公司会直接把私服部署到公网，具体还是得看公司业务的性质是否是保密的等等，因为局域网的话只能在公司用，部署到公网的话员工在家里也可以办公使用。</p>
</blockquote>

<p>建立了 Maven 私服后，当局域网内的用户需要某个构件时，会按照如下顺序进行请求和下载。</p>

<p>请求本地仓库，若本地仓库不存在所需构件，则跳转到第 2 步；
请求 Maven 私服，将所需构件下载到本地仓库，若私服中不存在所需构件，则跳转到第 3 步。
请求外部的远程仓库，将所需构件下载并缓存到 Maven 私服，若外部远程仓库不存在所需构件，则 Maven 直接报错。</p>

<p>此外，一些无法从外部仓库下载到的构件，也能从本地上传到私服供其他人使用。</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231021164631791.png" alt="image-20231021164631791" /></p>

<p>②Maven私服的优势</p>

<ol>
  <li>
    <p>节省外网带宽
消除对外部远程仓库的大量重复请求（会消耗很大量的带宽），降低外网带宽压力。</p>
  </li>
  <li>
    <p>下载速度更快
Maven私服位于局域网内，从私服下载构建更快更稳定。</p>
  </li>
  <li>
    <p>便于部署第三方构件
有些构件无法从任何一个远程仓库中获得（如：公司或组织内部的私有构件、Oracle的JDBC驱动等），建立私服之后，就可以将这些构件部署到私服中，供内部Maven项目使用。</p>
  </li>
  <li>
    <p>提高项目的稳定性，增强对项目的控制
如果不建立私服，那么Maven项目的构件就高度依赖外部的远程仓库，若外部网络不稳定，则项目的构建过程也会变得不稳定。建立私服后，即使外部网络状况不佳甚至中断，只要私服中已经缓存了所需的构件，Maven也能够正常运行。私服软件（如：Nexus）提供了很多控制功能（如：权限管理、RELEASE/SNAPSHOT版本控制等），可以对仓库进行一些更加高级的控制。</p>
  </li>
  <li>
    <p>降低中央仓库得负荷压力
由于私服会缓存中央仓库得构件，避免了很多对中央仓库的重复下载，降低了中央仓库的负荷。</p>
  </li>
</ol>

<p>③常见的Maven私服产品</p>

<ol>
  <li>Apache的Archiva</li>
  <li>JFrog的Artifactory</li>
  <li>Sonatype的Nexus（[ˈneksəs]）（当前最流行、使用最广泛）</li>
</ol>

<h3 id="2-nexus下载安装">2. Nexus下载安装</h3>

<p>下载地址：<a href="https://help.sonatype.com/repomanager3/product-information/download">https://help.sonatype.com/repomanager3/product-information/download</a></p>

<p>解压，以管理员身份打开CMD，进入bin目录下，执行./nexus /run命令启动</p>

<p>访问 Nexus 首页</p>

<p>首页地址：<a href="http://localhost:8081/，8081为默认端口号">http://localhost:8081/，8081为默认端口号</a></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/img001.612496a3.png" alt="images" /></p>

<h3 id="3-初始设置">3. 初始设置</h3>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/img002.e1ac8197.png" alt="images" /></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231031171116756.png" alt="image-20231031171116756" /></p>

<p>这里参考提示：</p>

<ul>
  <li>用户名：admin</li>
  <li>密码：查看 <strong>E:\Server\nexus-3.61.0-02-win64\sonatype-work\nexus3\admin.password</strong> 文件</li>
</ul>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231031171242874.png" alt="image-20231031171242874" /></p>

<p>继续执行初始化：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/img005.4b81e5ab.png" alt="images" /></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/img006.43ebb0ac.png" alt="images" /></p>

<p>匿名登录，启用还是禁用？由于启用匿名登录后，后续操作比较简单，这里我们演示禁用匿名登录的操作：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231031171607378.png" alt="image-20231031171607378" /></p>

<p>初始化完毕：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231031171708085.png" alt="image-20231031171708085" /></p>

<h3 id="4-nexus上的各种仓库">4. Nexus上的各种仓库</h3>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/img009.7f737ed7.png" alt="images" /></p>

<table>
  <thead>
    <tr>
      <th>仓库类型</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>proxy</td>
      <td>某个远程仓库的代理</td>
    </tr>
    <tr>
      <td>group</td>
      <td>存放：通过 Nexus 获取的第三方 jar 包</td>
    </tr>
    <tr>
      <td>hosted</td>
      <td>存放：本团队其他开发人员部署到 Nexus 的 jar 包</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th>仓库名称</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>maven-central</td>
      <td>Nexus 对 Maven 中央仓库的代理</td>
    </tr>
    <tr>
      <td>maven-public</td>
      <td>Nexus 默认创建，供开发人员下载使用的组仓库</td>
    </tr>
    <tr>
      <td>maven-releases</td>
      <td>Nexus 默认创建，供开发人员部署自己 jar 包的宿主仓库 要求 releases 版本</td>
    </tr>
    <tr>
      <td>maven-snapshots</td>
      <td>Nexus 默认创建，供开发人员部署自己 jar 包的宿主仓库 要求 snapshots 版本</td>
    </tr>
  </tbody>
</table>

<p>初始状态下，这几个仓库都没有内容：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/img010.e3573d0b.png" alt="images" /></p>

<h3 id="5-通过-nexus-下载-jar-包">5. 通过 Nexus 下载 jar 包</h3>

<p>修改本地maven的核心配置文件settings.xml，设置新的本地仓库地址</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- 配置一个新的 Maven 本地仓库 --&gt;</span>
<span class="nt">&lt;localRepository&gt;</span>D:/maven-repository-new<span class="nt">&lt;/localRepository&gt;</span>
</code></pre></div></div>

<p>把我们原来配置阿里云仓库地址的 mirror 标签改成下面这样：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;mirror&gt;</span>
	<span class="nt">&lt;id&gt;</span>nexus-mine<span class="nt">&lt;/id&gt;</span>
	<span class="nt">&lt;mirrorOf&gt;</span>central<span class="nt">&lt;/mirrorOf&gt;</span>
	<span class="nt">&lt;name&gt;</span>Nexus mine<span class="nt">&lt;/name&gt;</span>
	<span class="nt">&lt;url&gt;</span>http://localhost:8081/repository/maven-public/<span class="nt">&lt;/url&gt;</span>
<span class="nt">&lt;/mirror&gt;</span>
</code></pre></div></div>

<p>这里的 url 标签是这么来的：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/img012.5a3b1f11.png" alt="images" /></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231031172137288.png" alt="image-20231031172137288" /></p>

<p>把上图中看到的地址复制出来即可。如果我们在前面允许了匿名访问，到这里就够了。但如果我们禁用了匿名访问，那么接下来我们还要继续配置 settings.xml：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;server&gt;</span>
  <span class="nt">&lt;id&gt;</span>nexus-mine<span class="nt">&lt;/id&gt;</span>
  <span class="nt">&lt;username&gt;</span>admin<span class="nt">&lt;/username&gt;</span>
  <span class="nt">&lt;password&gt;</span>atguigu<span class="nt">&lt;/password&gt;</span>
<span class="nt">&lt;/server&gt;</span>
</code></pre></div></div>

<p>这里需要<strong>格外注意</strong>：server 标签内的 id 标签值必须和 mirror 标签中的 id 值一样。</p>

<p>找一个用到框架的 Maven 工程，执行命令：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn clean compile
</code></pre></div></div>

<p>下载过程日志：</p>

<blockquote>
  <p>Downloading from nexus-mine: <a href="http://localhost:8081/repository/maven-public/com/jayway/jsonpath/json-path/2.4.0/json-path-2.4.0.pom">http://localhost:8081/repository/maven-public/com/jayway/jsonpath/json-path/2.4.0/json-path-2.4.0.pom</a>
Downloaded from nexus-mine: <a href="http://localhost:8081/repository/maven-public/com/jayway/jsonpath/json-path/2.4.0/json-path-2.4.0.pom">http://localhost:8081/repository/maven-public/com/jayway/jsonpath/json-path/2.4.0/json-path-2.4.0.pom</a> (2.6 kB at 110 kB/s)
Downloading from nexus-mine: <a href="http://localhost:8081/repository/maven-public/net/minidev/json-smart/2.3/json-smart-2.3.pom">http://localhost:8081/repository/maven-public/net/minidev/json-smart/2.3/json-smart-2.3.pom</a>
Downloaded from nexus-mine: <a href="http://localhost:8081/repository/maven-public/net/minidev/json-smart/2.3/json-smart-2.3.pom">http://localhost:8081/repository/maven-public/net/minidev/json-smart/2.3/json-smart-2.3.pom</a> (9.0 kB at 376 kB/s)
Downloading from nexus-mine: <a href="http://localhost:8081/repository/maven-public/net/minidev/minidev-parent/2.3/minidev-parent-2.3.pom">http://localhost:8081/repository/maven-public/net/minidev/minidev-parent/2.3/minidev-parent-2.3.pom</a>
Downloaded from nexus-mine: <a href="http://localhost:8081/repository/maven-public/net/minidev/minidev-parent/2.3/minidev-parent-2.3.pom">http://localhost:8081/repository/maven-public/net/minidev/minidev-parent/2.3/minidev-parent-2.3.pom</a> (8.5 kB at 404 kB/s)
Downloading from nexus-mine: <a href="http://localhost:8081/repository/maven-public/net/minidev/accessors-smart/1.2/accessors-smart-1.2.pom">http://localhost:8081/repository/maven-public/net/minidev/accessors-smart/1.2/accessors-smart-1.2.pom</a>
Downloaded from nexus-mine: <a href="http://localhost:8081/repository/maven-public/net/minidev/accessors-smart/1.2/accessors-smart-1.2.pom">http://localhost:8081/repository/maven-public/net/minidev/accessors-smart/1.2/accessors-smart-1.2.pom</a> (12 kB at 463 kB/s)</p>
</blockquote>

<p>下载后，Nexus 服务器上就有了 jar 包：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/img014.cc0e87c3.png" alt="images" /></p>

<p>若下载速度太慢，可以设置私服中中央仓库的地址为阿里云仓库地址</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231031175035345.png" alt="image-20231031175035345" /></p>

<p>修改为：<a href="http://maven.aliyun.com/nexus/content/groups/public/">http://maven.aliyun.com/nexus/content/groups/public/</a></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231031175134745.png" alt="image-20231031175134745" /></p>

<h3 id="6-将-jar-包部署到-nexus">6. 将 jar 包部署到 Nexus</h3>

<p>maven工程中配置：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;distributionManagement&gt;</span>
    <span class="nt">&lt;snapshotRepository&gt;</span>
        <span class="nt">&lt;id&gt;</span>nexus-mine<span class="nt">&lt;/id&gt;</span>
        <span class="nt">&lt;name&gt;</span>Nexus Snapshot<span class="nt">&lt;/name&gt;</span>
        <span class="nt">&lt;url&gt;</span>http://localhost:8081/repository/maven-snapshots/<span class="nt">&lt;/url&gt;</span>
    <span class="nt">&lt;/snapshotRepository&gt;</span>
<span class="nt">&lt;/distributionManagement&gt;</span>
</code></pre></div></div>

<p>注意：这里 snapshotRepository 的 id 标签必须和 settings.xml 中指定的 mirror 标签的 id 属性一致。</p>

<p>执行部署命令：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn deploy
</code></pre></div></div>

<blockquote>
  <p>Uploading to nexus-mine: <a href="http://localhost:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/1.0-SNAPSHOT/maven-metadata.xml">http://localhost:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/1.0-SNAPSHOT/maven-metadata.xml</a>
Uploaded to nexus-mine: <a href="http://localhost:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/1.0-SNAPSHOT/maven-metadata.xml">http://localhost:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/1.0-SNAPSHOT/maven-metadata.xml</a> (786 B at 19 kB/s)
Uploading to nexus-mine: <a href="http://localhost:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/maven-metadata.xml">http://localhost:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/maven-metadata.xml</a>
Uploaded to nexus-mine: <a href="http://localhost:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/maven-metadata.xml">http://localhost:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/maven-metadata.xml</a> (300 B at 6.5 kB/s)
[INFO] ————————————————————————
[INFO] Reactor Summary:
[INFO]
[INFO] demo-imperial-court-ms-show 1.0-SNAPSHOT ……….. SUCCESS [ 1.875 s]
[INFO] demo09-base-entity …………………………… SUCCESS [ 21.883 s]
[INFO] demo10-base-util …………………………….. SUCCESS [ 0.324 s]
[INFO] demo08-base-api ……………………………… SUCCESS [ 1.171 s]
[INFO] demo01-imperial-court-gateway …………………. SUCCESS [ 0.403 s]
[INFO] demo02-user-auth-center ………………………. SUCCESS [ 2.932 s]
[INFO] demo03-emp-manager-center …………………….. SUCCESS [ 0.312 s]
[INFO] demo04-memorials-manager-center ……………….. SUCCESS [ 0.362 s]
[INFO] demo05-working-manager-center …………………. SUCCESS [ 0.371 s]
[INFO] demo06-mysql-data-provider ……………………. SUCCESS [ 6.779 s]
[INFO] demo07-redis-data-provider 1.0-SNAPSHOT ………… SUCCESS [ 0.273 s]</p>
</blockquote>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/img015.b413af9d.png" alt="images" /></p>

<h3 id="7-引用别人部署的-jar-包">7. 引用别人部署的 jar 包</h3>

<p>maven工程中配置：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;repositories&gt;</span>
    <span class="nt">&lt;repository&gt;</span>
        <span class="nt">&lt;id&gt;</span>nexus-mine<span class="nt">&lt;/id&gt;</span>
        <span class="nt">&lt;name&gt;</span>nexus-mine<span class="nt">&lt;/name&gt;</span>
        <span class="nt">&lt;url&gt;</span>http://localhost:8081/repository/maven-snapshots/<span class="nt">&lt;/url&gt;</span>
        <span class="nt">&lt;snapshots&gt;</span>
            <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span>
        <span class="nt">&lt;/snapshots&gt;</span>
        <span class="nt">&lt;releases&gt;</span>
            <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span>
        <span class="nt">&lt;/releases&gt;</span>
    <span class="nt">&lt;/repository&gt;</span>
<span class="nt">&lt;/repositories&gt;</span>
</code></pre></div></div>

<h2 id="九maven综合案例">九、Maven综合案例</h2>

<h3 id="1-项目需求和结构分析">1. 项目需求和结构分析</h3>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image123.png" alt="img" /></p>

<p>需求案例：搭建一个电商平台项目，该平台包括用户服务、订单服务、通用工具模块等。</p>

<p>项目架构：</p>

<ol>
  <li>用户服务：负责处理用户相关的逻辑，例如用户信息的管理、用户注册、登录等。
    <ul>
      <li>spring-context 6.0.6</li>
      <li>spring-core 6.0.6</li>
      <li>spring-beans 6.0.6</li>
      <li>common-service</li>
    </ul>
  </li>
  <li>订单服务：负责处理订单相关的逻辑，例如订单的创建、订单支付、退货、订单查看等。
    <ul>
      <li>spring-context 6.0.6</li>
      <li>spring-core 6.0.6</li>
      <li>spring-beans 6.0.6</li>
      <li>spring-security 6.0.6</li>
      <li>common-service</li>
    </ul>
  </li>
  <li>通用模块：负责存储其他服务需要通用工具类，其他服务依赖此模块。
    <ul>
      <li>commons-io 2.11.0</li>
      <li>junit 5.9.2</li>
    </ul>
  </li>
</ol>

<h3 id="2-项目搭建和统一构建">2. 项目搭建和统一构建</h3>

<h4 id="父模块-micro-shop">①父模块 (micro-shop)</h4>

<p>创建工程：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231023112630117.png" alt="image-20231023112630117" /></p>

<p>pom.xml配置：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>

    <span class="nt">&lt;groupId&gt;</span>com.atguigu<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>micro-shop<span class="nt">&lt;/artifactId&gt;</span>
    <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
    <span class="c">&lt;!--知识点：父工程的打包方式为pom--&gt;</span>
    <span class="nt">&lt;packaging&gt;</span>pom<span class="nt">&lt;/packaging&gt;</span>

    <span class="nt">&lt;properties&gt;</span>
        <span class="nt">&lt;spring.version&gt;</span>6.0.6<span class="nt">&lt;/spring.version&gt;</span>
        <span class="nt">&lt;jackson.version&gt;</span>2.15.0<span class="nt">&lt;/jackson.version&gt;</span>
        <span class="nt">&lt;commons.version&gt;</span>2.11.0<span class="nt">&lt;/commons.version&gt;</span>
        <span class="nt">&lt;junit.version&gt;</span>5.9.2<span class="nt">&lt;/junit.version&gt;</span>
        <span class="nt">&lt;maven.compiler.source&gt;</span>17<span class="nt">&lt;/maven.compiler.source&gt;</span>
        <span class="nt">&lt;maven.compiler.target&gt;</span>17<span class="nt">&lt;/maven.compiler.target&gt;</span>
        <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>

    <span class="c">&lt;!-- 依赖管理 --&gt;</span>
    <span class="nt">&lt;dependencyManagement&gt;</span>
        <span class="nt">&lt;dependencies&gt;</span>
            <span class="c">&lt;!-- spring-context会依赖传递core/beans --&gt;</span>
            <span class="nt">&lt;dependency&gt;</span>
                <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
                <span class="nt">&lt;artifactId&gt;</span>spring-context<span class="nt">&lt;/artifactId&gt;</span>
                <span class="nt">&lt;version&gt;</span>${spring.version}<span class="nt">&lt;/version&gt;</span>
            <span class="nt">&lt;/dependency&gt;</span>

            <span class="c">&lt;!-- jackson-databind会依赖传递core/annotations --&gt;</span>
            <span class="nt">&lt;dependency&gt;</span>
                <span class="nt">&lt;groupId&gt;</span>com.fasterxml.jackson.core<span class="nt">&lt;/groupId&gt;</span>
                <span class="nt">&lt;artifactId&gt;</span>jackson-databind<span class="nt">&lt;/artifactId&gt;</span>
                <span class="nt">&lt;version&gt;</span>${jackson.version}<span class="nt">&lt;/version&gt;</span>
            <span class="nt">&lt;/dependency&gt;</span>

            <span class="c">&lt;!-- commons-io --&gt;</span>
            <span class="nt">&lt;dependency&gt;</span>
                <span class="nt">&lt;groupId&gt;</span>commons-io<span class="nt">&lt;/groupId&gt;</span>
                <span class="nt">&lt;artifactId&gt;</span>commons-io<span class="nt">&lt;/artifactId&gt;</span>
                <span class="nt">&lt;version&gt;</span>${commons.version}<span class="nt">&lt;/version&gt;</span>
            <span class="nt">&lt;/dependency&gt;</span>

            <span class="c">&lt;!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --&gt;</span>
            <span class="nt">&lt;dependency&gt;</span>
                <span class="nt">&lt;groupId&gt;</span>org.junit.jupiter<span class="nt">&lt;/groupId&gt;</span>
                <span class="nt">&lt;artifactId&gt;</span>junit-jupiter-api<span class="nt">&lt;/artifactId&gt;</span>
                <span class="nt">&lt;version&gt;</span>${junit.version}<span class="nt">&lt;/version&gt;</span>
                <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span>
            <span class="nt">&lt;/dependency&gt;</span>

        <span class="nt">&lt;/dependencies&gt;</span>
    <span class="nt">&lt;/dependencyManagement&gt;</span>

    <span class="c">&lt;!-- 统一更新子工程打包插件--&gt;</span>
    <span class="nt">&lt;build&gt;</span>
        <span class="c">&lt;!-- jdk17 和 war包版本插件不匹配 --&gt;</span>
        <span class="nt">&lt;plugins&gt;</span>
            <span class="nt">&lt;plugin&gt;</span>
                <span class="nt">&lt;groupId&gt;</span>org.apache.maven.plugins<span class="nt">&lt;/groupId&gt;</span>
                <span class="nt">&lt;artifactId&gt;</span>maven-war-plugin<span class="nt">&lt;/artifactId&gt;</span>
                <span class="nt">&lt;version&gt;</span>3.2.2<span class="nt">&lt;/version&gt;</span>
            <span class="nt">&lt;/plugin&gt;</span>
        <span class="nt">&lt;/plugins&gt;</span>
    <span class="nt">&lt;/build&gt;</span>

<span class="nt">&lt;/project&gt;</span>
</code></pre></div></div>

<p>可选操作：删除src目录</p>

<h4 id="通用模块-common-service">②通用模块 (common-service)</h4>

<p>创建工程：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231023114531521.png" alt="image-20231023114531521" /></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231023114649705.png" alt="image-20231023114649705" /></p>

<p>pom.xml配置：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;parent&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>micro-shop<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>com.atguigu<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;/parent&gt;</span>
    <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>

    <span class="nt">&lt;artifactId&gt;</span>common-service<span class="nt">&lt;/artifactId&gt;</span>
    <span class="c">&lt;!--知识点：打包方式默认就是jar，因此可以省略--&gt;</span>
    <span class="nt">&lt;packaging&gt;</span>jar<span class="nt">&lt;/packaging&gt;</span>

    <span class="nt">&lt;properties&gt;</span>
        <span class="nt">&lt;maven.compiler.source&gt;</span>17<span class="nt">&lt;/maven.compiler.source&gt;</span>
        <span class="nt">&lt;maven.compiler.target&gt;</span>17<span class="nt">&lt;/maven.compiler.target&gt;</span>
        <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>

    <span class="nt">&lt;dependencies&gt;</span>
        <span class="c">&lt;!-- 配置spring-context，继承父工程版本，自动传递 core / beans --&gt;</span>
        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>org.springframework<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>spring-context<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
        <span class="c">&lt;!-- 配置jackson-databind，继承父工程版本，自动传递 core / annotations --&gt;</span>
        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>com.fasterxml.jackson.core<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>jackson-databind<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
        <span class="c">&lt;!-- 配置commons-io，继承父工程版本 --&gt;</span>
        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>commons-io<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>commons-io<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
        <span class="c">&lt;!-- 配置junit，继承父工程版本 --&gt;</span>
        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>org.junit.jupiter<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>junit-jupiter-api<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;/dependencies&gt;</span>

<span class="nt">&lt;/project&gt;</span>
</code></pre></div></div>

<h4 id="用户模块-user-service">③用户模块 (user-service)</h4>

<p>创建工程：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231023115404302.png" alt="image-20231023115404302" /></p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231023115707282.png" alt="image-20231023115707282" /></p>

<p>pom.xml配置：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;parent&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>micro-shop<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>com.atguigu<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;/parent&gt;</span>
    <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>

    <span class="nt">&lt;artifactId&gt;</span>user-service<span class="nt">&lt;/artifactId&gt;</span>
    <span class="c">&lt;!-- web工程打包方式为war --&gt;</span>
    <span class="nt">&lt;packaging&gt;</span>war<span class="nt">&lt;/packaging&gt;</span>

    <span class="nt">&lt;properties&gt;</span>
        <span class="nt">&lt;maven.compiler.source&gt;</span>17<span class="nt">&lt;/maven.compiler.source&gt;</span>
        <span class="nt">&lt;maven.compiler.target&gt;</span>17<span class="nt">&lt;/maven.compiler.target&gt;</span>
        <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>

    <span class="nt">&lt;dependencies&gt;</span>
        <span class="c">&lt;!-- 配置common-service，所需依赖会传递到当前工程（仅限compile范围） --&gt;</span>
        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>com.atguigu<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>common-service<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;/dependencies&gt;</span>

<span class="nt">&lt;/project&gt;</span>
</code></pre></div></div>

<p>依赖传递结果：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231023120621402.png" alt="image-20231023120621402" /></p>

<h4 id="订单模块-order-service">④订单模块 (order-service)</h4>

<p>创建工程，并使用插件转为web工程：</p>

<p><img src="https://raw.githubusercontent.com/chenchi-cc/image-bed/main/maven/image-20231023120733029.png" alt="image-20231023120733029" /></p>

<p>pom.xml配置：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;project</span> <span class="na">xmlns=</span><span class="s">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="na">xmlns:xsi=</span><span class="s">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="na">xsi:schemaLocation=</span><span class="s">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;parent&gt;</span>
        <span class="nt">&lt;artifactId&gt;</span>micro-shop<span class="nt">&lt;/artifactId&gt;</span>
        <span class="nt">&lt;groupId&gt;</span>com.atguigu<span class="nt">&lt;/groupId&gt;</span>
        <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
    <span class="nt">&lt;/parent&gt;</span>
    <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span>

    <span class="nt">&lt;artifactId&gt;</span>order-service<span class="nt">&lt;/artifactId&gt;</span>
    <span class="c">&lt;!-- web工程打包方式为war --&gt;</span>
    <span class="nt">&lt;packaging&gt;</span>war<span class="nt">&lt;/packaging&gt;</span>

    <span class="nt">&lt;properties&gt;</span>
        <span class="nt">&lt;maven.compiler.source&gt;</span>17<span class="nt">&lt;/maven.compiler.source&gt;</span>
        <span class="nt">&lt;maven.compiler.target&gt;</span>17<span class="nt">&lt;/maven.compiler.target&gt;</span>
        <span class="nt">&lt;project.build.sourceEncoding&gt;</span>UTF-8<span class="nt">&lt;/project.build.sourceEncoding&gt;</span>
    <span class="nt">&lt;/properties&gt;</span>

    <span class="nt">&lt;dependencies&gt;</span>
        <span class="c">&lt;!-- 配置common-service，所需依赖会传递到当前工程（仅限compile范围） --&gt;</span>
        <span class="nt">&lt;dependency&gt;</span>
            <span class="nt">&lt;groupId&gt;</span>com.atguigu<span class="nt">&lt;/groupId&gt;</span>
            <span class="nt">&lt;artifactId&gt;</span>common-service<span class="nt">&lt;/artifactId&gt;</span>
            <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span>
        <span class="nt">&lt;/dependency&gt;</span>
    <span class="nt">&lt;/dependencies&gt;</span>

<span class="nt">&lt;/project&gt;</span>
</code></pre></div></div>

<p>此时，查看父工程的pom.xml，会发现其中已经自动聚合了子工程：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;modules&gt;</span>
    <span class="nt">&lt;module&gt;</span>common-service<span class="nt">&lt;/module&gt;</span>
    <span class="nt">&lt;module&gt;</span>user-service<span class="nt">&lt;/module&gt;</span>
    <span class="nt">&lt;module&gt;</span>order-service<span class="nt">&lt;/module&gt;</span>
<span class="nt">&lt;/modules&gt;</span>
</code></pre></div></div>]]></content><author><name>DarrenChan陈驰</name></author><category term="基础工具" /><summary type="html"><![CDATA[Maven最全总结]]></summary></entry><entry><title type="html">一份简明的 Markdown 笔记与教程</title><link href="https://chenchi-cc.github.io/2025/01/01/markdown-intro/" rel="alternate" type="text/html" title="一份简明的 Markdown 笔记与教程" /><published>2025-01-01T00:00:00+00:00</published><updated>2025-01-01T00:00:00+00:00</updated><id>https://chenchi-cc.github.io/2025/01/01/markdown-intro</id><content type="html" xml:base="https://chenchi-cc.github.io/2025/01/01/markdown-intro/"><![CDATA[<p>为部门内知识分享准备的素材，记录了 Markdown 的优点、应用场景和编辑工具，介绍了标准语法与扩展语法，以及一些应用 Markdown 的奇技淫巧。个人使用 Markdown 的经验持续补充中，最新完整版请参见</p>

<p><a href="https://github.com/mzlogin/markdown-intro">https://github.com/mzlogin/markdown-intro</a></p>

<hr />

<p>自从 2014 年左右接触到 Markdown 以来，对它的使用就一发而不可收拾。从最开始使用它在 GitHub Pages 里写博客，到用它编辑项目的 README 文件，再到撰写开发文档，编辑微信公众号文章和邮件内容等等，这期间也见证了它在各类平台和网站上的普及和被原生支持，可以说，Markdown 如今已经渗透了我在技术和网络活动的方方面面，成为了我撰写文本文档的首选。</p>

<p>那么首先我们一起来看一下它的「定义」：</p>

<blockquote>
  <p>Markdown 是一种轻量级标记语言，创始人为 John Gruber。它允许人们「使用易读易写的纯文本格式编写文档，然后转换成有效的 XHTML（或者 HTML）文档」。——维基百科</p>
</blockquote>

<p>本文档的目的不在于面面俱到地介绍 Markdown，只是作为我对其理解的笔记整理，希望能同时帮助一些对 Markdown 感兴趣的人快速上手，或是作为一个工具，供对其已经有所了解的人在需要时参考。</p>

<p>接下来请随我一起深入了解这门并不神秘的实用标记语言。</p>

<p><strong>目录</strong></p>

<ul id="markdown-toc">
  <li><a href="#背景" id="markdown-toc-背景">背景</a>    <ul>
      <li><a href="#优点" id="markdown-toc-优点">优点</a></li>
      <li><a href="#使用场景" id="markdown-toc-使用场景">使用场景</a></li>
      <li><a href="#编辑工具" id="markdown-toc-编辑工具">编辑工具</a></li>
    </ul>
  </li>
  <li><a href="#语法" id="markdown-toc-语法">语法</a>    <ul>
      <li><a href="#标题" id="markdown-toc-标题">标题</a></li>
    </ul>
  </li>
  <li><a href="#atx-style-一级标题" id="markdown-toc-atx-style-一级标题">atx-style 一级标题</a>    <ul>
      <li><a href="#二级标题" id="markdown-toc-二级标题">二级标题</a>        <ul>
          <li><a href="#六级标题" id="markdown-toc-六级标题">六级标题</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#setext-style-一级标题" id="markdown-toc-setext-style-一级标题">Setext-style 一级标题</a>    <ul>
      <li><a href="#二级标题-1" id="markdown-toc-二级标题-1">二级标题</a>        <ul>
          <li><a href="#段落" id="markdown-toc-段落">段落</a></li>
          <li><a href="#行内格式" id="markdown-toc-行内格式">行内格式</a></li>
          <li><a href="#引用块" id="markdown-toc-引用块">引用块</a></li>
          <li><a href="#引用块内的标题" id="markdown-toc-引用块内的标题">引用块内的标题</a></li>
          <li><a href="#超链接" id="markdown-toc-超链接">超链接</a></li>
          <li><a href="#图片" id="markdown-toc-图片">图片</a></li>
          <li><a href="#列表" id="markdown-toc-列表">列表</a></li>
          <li><a href="#代码块" id="markdown-toc-代码块">代码块</a></li>
          <li><a href="#水平分割线" id="markdown-toc-水平分割线">水平分割线</a></li>
          <li><a href="#嵌入-html" id="markdown-toc-嵌入-html">嵌入 HTML</a></li>
        </ul>
      </li>
      <li><a href="#扩展语法" id="markdown-toc-扩展语法">扩展语法</a>        <ul>
          <li><a href="#表格" id="markdown-toc-表格">表格</a></li>
          <li><a href="#任务列表" id="markdown-toc-任务列表">任务列表</a></li>
          <li><a href="#删除线" id="markdown-toc-删除线">删除线</a></li>
          <li><a href="#自动链接" id="markdown-toc-自动链接">自动链接</a></li>
          <li><a href="#emoji" id="markdown-toc-emoji">emoji</a></li>
        </ul>
      </li>
      <li><a href="#奇技淫巧" id="markdown-toc-奇技淫巧">奇技淫巧</a>        <ul>
          <li><a href="#画流程图和时序图" id="markdown-toc-画流程图和时序图">画流程图和时序图</a></li>
          <li><a href="#插入数学公式" id="markdown-toc-插入数学公式">插入数学公式</a></li>
          <li><a href="#用-markdown-做-ppt" id="markdown-toc-用-markdown-做-ppt">用 Markdown 做 PPT</a></li>
          <li><a href="#用-markdown-写微信公众号" id="markdown-toc-用-markdown-写微信公众号">用 Markdown 写微信公众号</a></li>
          <li><a href="#更多" id="markdown-toc-更多">更多</a></li>
        </ul>
      </li>
      <li><a href="#参考" id="markdown-toc-参考">参考</a></li>
    </ul>
  </li>
</ul>

<h2 id="背景">背景</h2>

<h3 id="优点">优点</h3>

<ol>
  <li>
    <p>专注于文字内容；</p>
  </li>
  <li>
    <p>纯文本，易读易写，可以方便地纳入版本控制；</p>
  </li>
  <li>
    <p>语法简单，没有什么学习成本，能轻松在码字的同时做出美观大方的排版。</p>
  </li>
</ol>

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

<ul>
  <li>
    <p>各类代码托管平台</p>

    <p>主流的代码托管平台，如 GitHub、GitLab、BitBucket、Coding、Gitee 等等，都支持 Markdown 语法，很多开源项目的 README、开发文档、帮助文档、Wiki 等都用 Markdown 写作。</p>
  </li>
  <li>
    <p>技术社区和写作平台</p>

    <p>StackOverflow、CSDN、掘金、简书、GitBook、有道云笔记</p>
  </li>
  <li>
    <p>论坛</p>

    <p>V2EX、光谷社区</p>
  </li>
</ul>

<p>个人感觉比较遗憾的一点是各平台可能采用不同语言实现的 Markdown 解析引擎，或采用同一解析引擎的不同版本，而且可能有不同程度的定制与扩展，这导致在不同平台上使用 Markdown 写作时体验并不完全一致。不过幸好对于大家公认的一些标准语法，各家都是支持的。</p>

<h3 id="编辑工具">编辑工具</h3>

<p>理论上任何一款文本编辑器都能用于编辑 Markdown 文档，它们分别提供了不同程度的语法高亮、预览等功能，以下只是列举其中一部分，选择自己称手的即可。</p>

<ul>
  <li>
    <p>现代编辑器</p>

    <p>VSCode / Atom</p>
  </li>
  <li>
    <p>传统编辑器</p>

    <p>Vim / Emacs / Sublime Text / Notepad++</p>
  </li>
  <li>
    <p>IDE 自带编辑器</p>

    <p>IntelliJ IDEA / Android Studio / WebStorm</p>
  </li>
  <li>
    <p>专用编辑器</p>

    <p>Ulysses / Mou / Typora / Markpad</p>
  </li>
  <li>
    <p>在线编辑器</p>

    <p>各种支持 Markdown 的网站都提供了在线编辑器</p>
  </li>
</ul>

<h2 id="语法">语法</h2>

<h3 id="标题">标题</h3>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># atx-style 一级标题

## 二级标题

###### 六级标题

Setext-style 一级标题
===

二级标题
---
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<blockquote>
  <h1 id="atx-style-一级标题">atx-style 一级标题</h1>

  <h2 id="二级标题">二级标题</h2>

  <h6 id="六级标题">六级标题</h6>

  <h1 id="setext-style-一级标题">Setext-style 一级标题</h1>

  <h2 id="二级标题-1">二级标题</h2>
</blockquote>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h1&gt;</span>atx-style 一级标题<span class="nt">&lt;/h1&gt;</span>

<span class="nt">&lt;h2&gt;</span>二级标题<span class="nt">&lt;/h2&gt;</span>

<span class="nt">&lt;h6&gt;</span>六级标题<span class="nt">&lt;/h6&gt;</span>

<span class="nt">&lt;h1&gt;</span>Setext-style 一级标题<span class="nt">&lt;/h1&gt;</span>

<span class="nt">&lt;h2&gt;</span>二级标题<span class="nt">&lt;/h2&gt;</span>
</code></pre></div></div>

<h3 id="段落">段落</h3>

<p>中间没有空行的连续不断的几行文字被视为一个段落。</p>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>白日依山尽，

黄河入海流。
（句号后面没空格）

欲穷千里目，

更上一层楼。  
（句号后面有俩空格）
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<p>白日依山尽，</p>

<p>黄河入海流。
（句号后面没空格）</p>

<p>欲穷千里目，</p>

<p>更上一层楼。<br />
（句号后面有俩空格）</p>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>白日依山尽，<span class="nt">&lt;/p&gt;</span>

<span class="nt">&lt;p&gt;</span>黄河入海流。
（句号后面没有空格）<span class="nt">&lt;/p&gt;</span>

<span class="nt">&lt;p&gt;</span>欲穷千里目，<span class="nt">&lt;/p&gt;</span>

<span class="nt">&lt;p&gt;</span>
  更上一层楼。
  <span class="nt">&lt;br&gt;</span>
  （句号后面有俩空格）
<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<h3 id="行内格式">行内格式</h3>

<p>对段落或者部分文本的强调效果。</p>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>后面俩字**加黑**

后面俩字*斜体*
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<p>后面俩字<strong>加黑</strong></p>

<p>后面俩字<em>斜体</em></p>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>
  后面俩字
  <span class="nt">&lt;strong&gt;</span>加黑<span class="nt">&lt;/strong&gt;</span>
<span class="nt">&lt;/p&gt;</span>
<span class="nt">&lt;p&gt;</span>
  后面俩字
  <span class="nt">&lt;em&gt;</span>斜体<span class="nt">&lt;/em&gt;</span>
<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<h3 id="引用块">引用块</h3>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; 引用块段落一。
&gt;
&gt; 引用块段落二。
&gt;&gt; 内嵌引用块段落一。
&gt;
&gt; ### 引用块内的标题
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<blockquote>
  <p>引用块段落一。</p>

  <p>引用块段落二。</p>

  <blockquote>
    <p>内嵌引用块段落一。</p>
  </blockquote>

  <h3 id="引用块内的标题">引用块内的标题</h3>
</blockquote>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;blockquote&gt;</span>
  <span class="nt">&lt;p&gt;</span>引用块段落一。<span class="nt">&lt;/p&gt;</span>
  <span class="nt">&lt;p&gt;</span>引用块段落二。<span class="nt">&lt;/p&gt;</span>
  <span class="nt">&lt;blockquote&gt;</span>
    <span class="nt">&lt;p&gt;</span>内嵌引用块段落一。<span class="nt">&lt;/p&gt;</span>
  <span class="nt">&lt;/blockquote&gt;</span>
  <span class="nt">&lt;h3</span> <span class="na">id=</span><span class="s">"引用块内的标题"</span><span class="nt">&gt;</span>引用块内的标题<span class="nt">&lt;/h3&gt;</span>
<span class="nt">&lt;/blockquote&gt;</span>
</code></pre></div></div>

<h3 id="超链接">超链接</h3>

<p>Markdown 支持行内式链接和引用式链接。</p>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>行内式 [博客](https://mazhuang.org "我的个人博客") 链接，带 title。

行内式 [GitHub](https://github.com/mzlogin) 链接。

引用式 [博客][1] 链接。

引用式 [GitHub][2] 链接，带 title。

[1]: https://mazhuang.org
[2]: https://github.com/mzlogin "我的 GitHub 主页"
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<p>行内式 <a href="https://mazhuang.org" title="我的个人博客">博客</a> 链接，带 title。</p>

<p>行内式 <a href="https://github.com/mzlogin">GitHub</a> 链接。</p>

<p>引用式 <a href="https://mazhuang.org">博客</a> 链接。</p>

<p>引用式 <a href="https://github.com/mzlogin" title="我的 GitHub 主页">GitHub</a> 链接，带 title。</p>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>行内式 <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"https://mazhuang.org"</span> <span class="na">title=</span><span class="s">"我的个人博客"</span><span class="nt">&gt;</span>博客<span class="nt">&lt;/a&gt;</span> 链接，带 title。<span class="nt">&lt;/p&gt;</span>

<span class="nt">&lt;p&gt;</span>行内式 <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"https://github.com/mzlogin"</span><span class="nt">&gt;</span>GitHub<span class="nt">&lt;/a&gt;</span> 链接。<span class="nt">&lt;/p&gt;</span>

<span class="nt">&lt;p&gt;</span>引用式 <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"https://mazhuang.org"</span><span class="nt">&gt;</span>博客<span class="nt">&lt;/a&gt;</span> 链接。<span class="nt">&lt;/p&gt;</span>

<span class="nt">&lt;p&gt;</span>引用式 <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"https://github.com/mzlogin"</span> <span class="na">title=</span><span class="s">"我的 GitHub 主页"</span><span class="nt">&gt;</span>GitHub<span class="nt">&lt;/a&gt;</span> 链接，带 title。<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<h3 id="图片">图片</h3>

<p>在超链接的写法前加一个 <code class="language-plaintext highlighter-rouge">!</code>，就是引用图片的方法。</p>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>![Alt text](https://mazhuang.org/favicon.ico "favicon")
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<p><img src="https://mazhuang.org/favicon.ico" alt="Alt text" title="favicon" /></p>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">"https://mazhuang.org/favicon.ico"</span> <span class="na">alt=</span><span class="s">"Alt text"</span> <span class="na">title=</span><span class="s">"favicon"</span><span class="nt">&gt;</span>
</code></pre></div></div>

<h3 id="列表">列表</h3>

<p>包括有序列表和无序列表。</p>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- 苹果
- 葡萄
- 榴莲

1. 苹果
2. 葡萄
3. 榴莲
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<ul>
  <li>苹果</li>
  <li>葡萄</li>
  <li>榴莲</li>
</ul>

<ol>
  <li>苹果</li>
  <li>葡萄</li>
  <li>榴莲</li>
</ol>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;ul&gt;</span>
  <span class="nt">&lt;li&gt;</span>苹果<span class="nt">&lt;/li&gt;</span>
  <span class="nt">&lt;li&gt;</span>葡萄<span class="nt">&lt;/li&gt;</span>
  <span class="nt">&lt;li&gt;</span>榴莲<span class="nt">&lt;/li&gt;</span>
<span class="nt">&lt;/ul&gt;</span>
<span class="nt">&lt;ol&gt;</span>
  <span class="nt">&lt;li&gt;</span>苹果<span class="nt">&lt;/li&gt;</span>
  <span class="nt">&lt;li&gt;</span>葡萄<span class="nt">&lt;/li&gt;</span>
  <span class="nt">&lt;li&gt;</span>榴莲<span class="nt">&lt;/li&gt;</span>
<span class="nt">&lt;/ol&gt;</span>
</code></pre></div></div>

<p>其中无序列表的标记可以使用 <code class="language-plaintext highlighter-rouge">+</code>、<code class="language-plaintext highlighter-rouge">-</code> 或 <code class="language-plaintext highlighter-rouge">*</code>，有序列表前的数字可以是乱序的。</p>

<h3 id="代码块">代码块</h3>

<p>支持行内代码和代码块。</p>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Android 里使用 `TextUtils` 类的 `isEmpty` 方法来判断字符串是否为空。

```java
if (TextUtils.isEmpty(text)) {
    return null;
}
```
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<p>Android 里使用 <code class="language-plaintext highlighter-rouge">TextUtils</code> 类的 <code class="language-plaintext highlighter-rouge">isEmpty</code> 方法来判断字符串是否为空。</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">(</span><span class="nc">TextUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">text</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>
</code></pre></div></div>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>Android 里使用 <span class="nt">&lt;code&gt;</span>TextUtils<span class="nt">&lt;/code&gt;</span> 类的 <span class="nt">&lt;code&gt;</span>isEmpty<span class="nt">&lt;/code&gt;</span> 方法来判断字符串是否为空。<span class="nt">&lt;/p&gt;</span>

<span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"highlight highlight-source-java"</span><span class="nt">&gt;&lt;pre&gt;&lt;span</span> <span class="na">class=</span><span class="s">"pl-k"</span><span class="nt">&gt;</span>if<span class="nt">&lt;/span&gt;</span> (<span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">"pl-smi"</span><span class="nt">&gt;</span>TextUtils<span class="nt">&lt;/span&gt;&lt;span</span> <span class="na">class=</span><span class="s">"pl-k"</span><span class="nt">&gt;</span>.<span class="nt">&lt;/span&gt;</span>isEmpty(text)) {
    <span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">"pl-k"</span><span class="nt">&gt;</span>return<span class="nt">&lt;/span&gt;</span> <span class="nt">&lt;span</span> <span class="na">class=</span><span class="s">"pl-c1"</span><span class="nt">&gt;</span>null<span class="nt">&lt;/span&gt;</span>;
}<span class="nt">&lt;/pre&gt;&lt;/div&gt;</span>
</code></pre></div></div>

<p>上例中的语言标记 <code class="language-plaintext highlighter-rouge">java</code> 可选填，可用于在编辑器和渲染后的效果里添加语法高亮。</p>

<p>块式代码也可以对整个代码段缩进四个空格，或一个 Tab 来实现。</p>

<h3 id="水平分割线">水平分割线</h3>

<p>使用一个单独行里的三个或以上 <code class="language-plaintext highlighter-rouge">*</code>、<code class="language-plaintext highlighter-rouge">-</code> 来生产一条水平分割线，它们之间可以有空格。</p>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>***

-----

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

<p><strong>预览效果：</strong></p>

<hr />

<hr />

<hr />

<p><strong>对应 HTML：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;hr /&gt;

&lt;hr /&gt;

&lt;hr /&gt;
</code></pre></div></div>

<h3 id="嵌入-html">嵌入 HTML</h3>

<p>Markdown 标记语言的目的不是替代 HTML，也不是发明一种更便捷的插入 HTML 标签的方式。它对应的只是 HTML 标签的一个很小的子集。</p>

<p>对于那些没有办法用 Markdown 语法来对应的 HTML 标签，直接使用 HTML 来写就好了。</p>

<h2 id="扩展语法">扩展语法</h2>

<p>本节的内容是介绍一些受到广泛支持的 Markdown 扩展语法。</p>

<h3 id="表格">表格</h3>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>| 编号  | 姓名（左） | 年龄（右） | 性别（中） |
| ----- | :--------  | ---------: | :------:   |
| 0     | 张三       | 28         | 男         |
| 1     | 李四       | 29         | 男         |
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<table>
  <thead>
    <tr>
      <th>编号</th>
      <th style="text-align: left">姓名（左）</th>
      <th style="text-align: right">年龄（右）</th>
      <th style="text-align: center">性别（中）</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td style="text-align: left">张三</td>
      <td style="text-align: right">28</td>
      <td style="text-align: center">男</td>
    </tr>
    <tr>
      <td>1</td>
      <td style="text-align: left">李四</td>
      <td style="text-align: right">29</td>
      <td style="text-align: center">男</td>
    </tr>
  </tbody>
</table>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;table&gt;</span>
  <span class="nt">&lt;thead&gt;</span>
    <span class="nt">&lt;tr&gt;</span>
      <span class="nt">&lt;th&gt;</span>编号<span class="nt">&lt;/th&gt;</span>
      <span class="nt">&lt;th</span> <span class="na">align=</span><span class="s">"left"</span><span class="nt">&gt;</span>姓名（左）<span class="nt">&lt;/th&gt;</span>
      <span class="nt">&lt;th</span> <span class="na">align=</span><span class="s">"right"</span><span class="nt">&gt;</span>年龄（右）<span class="nt">&lt;/th&gt;</span>
      <span class="nt">&lt;th</span> <span class="na">align=</span><span class="s">"center"</span><span class="nt">&gt;</span>性别（中）<span class="nt">&lt;/th&gt;</span>
    <span class="nt">&lt;/tr&gt;</span>
  <span class="nt">&lt;/thead&gt;</span>
  <span class="nt">&lt;tbody&gt;</span>
    <span class="nt">&lt;tr&gt;</span>
      <span class="nt">&lt;td&gt;</span>0<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;td</span> <span class="na">align=</span><span class="s">"left"</span><span class="nt">&gt;</span>张三<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;td</span> <span class="na">align=</span><span class="s">"right"</span><span class="nt">&gt;</span>28<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;td</span> <span class="na">align=</span><span class="s">"center"</span><span class="nt">&gt;</span>男<span class="nt">&lt;/td&gt;</span>
    <span class="nt">&lt;/tr&gt;</span>
    <span class="nt">&lt;tr&gt;</span>
      <span class="nt">&lt;td&gt;</span>1<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;td</span> <span class="na">align=</span><span class="s">"left"</span><span class="nt">&gt;</span>李四<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;td</span> <span class="na">align=</span><span class="s">"right"</span><span class="nt">&gt;</span>29<span class="nt">&lt;/td&gt;</span>
      <span class="nt">&lt;td</span> <span class="na">align=</span><span class="s">"center"</span><span class="nt">&gt;</span>男<span class="nt">&lt;/td&gt;</span>
    <span class="nt">&lt;/tr&gt;</span>
  <span class="nt">&lt;/tbody&gt;</span>
<span class="nt">&lt;/table&gt;</span>
</code></pre></div></div>

<h3 id="任务列表">任务列表</h3>

<p>在 GitHub / GitLab 里有较好的支持。</p>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- [x] 洗碗
- [ ] 清洗油烟机
- [ ] 拖地
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />洗碗</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />清洗油烟机</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />拖地</li>
</ul>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;ul</span> <span class="na">class=</span><span class="s">"contains-task-list"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;li</span> <span class="na">class=</span><span class="s">"task-list-item"</span><span class="nt">&gt;&lt;input</span> <span class="na">type=</span><span class="s">"checkbox"</span> <span class="na">id=</span><span class="s">""</span> <span class="na">disabled=</span><span class="s">""</span> <span class="na">class=</span><span class="s">"task-list-item-checkbox"</span> <span class="na">checked=</span><span class="s">""</span><span class="nt">&gt;</span> 洗碗<span class="nt">&lt;/li&gt;</span>
  <span class="nt">&lt;li</span> <span class="na">class=</span><span class="s">"task-list-item"</span><span class="nt">&gt;&lt;input</span> <span class="na">type=</span><span class="s">"checkbox"</span> <span class="na">id=</span><span class="s">""</span> <span class="na">disabled=</span><span class="s">""</span> <span class="na">class=</span><span class="s">"task-list-item-checkbox"</span><span class="nt">&gt;</span> 清洗油烟机<span class="nt">&lt;/li&gt;</span>
  <span class="nt">&lt;li</span> <span class="na">class=</span><span class="s">"task-list-item"</span><span class="nt">&gt;&lt;input</span> <span class="na">type=</span><span class="s">"checkbox"</span> <span class="na">id=</span><span class="s">""</span> <span class="na">disabled=</span><span class="s">""</span> <span class="na">class=</span><span class="s">"task-list-item-checkbox"</span><span class="nt">&gt;</span> 拖地<span class="nt">&lt;/li&gt;</span>
<span class="nt">&lt;/ul&gt;</span>
</code></pre></div></div>

<p>如果是在 GitHub / GitLab 的 Issue 里，会附赠任务完成比例提示效果：</p>

<p><img src="https://raw.githubusercontent.com/mzlogin/markdown-intro/master/assets/task-list-1.png" alt="task list 1" /></p>

<p>还可以直接在网页上拖动调整顺序，勾选和取消勾选。</p>

<p><img src="https://raw.githubusercontent.com/mzlogin/markdown-intro/master/assets/task-list-2.png" alt="task list 2" /></p>

<h3 id="删除线">删除线</h3>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>后面三个字打上~~删除线~~。
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<p>后面三个字打上<del>删除线</del>。</p>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>后面三个字打上<span class="nt">&lt;del&gt;</span>删除线<span class="nt">&lt;/del&gt;</span>。<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<h3 id="自动链接">自动链接</h3>

<p>自动链接扩展，即：当识别到 URL，或用 <code class="language-plaintext highlighter-rouge">&lt;</code>、<code class="language-plaintext highlighter-rouge">&gt;</code> 包括的 URL 时，会自动为其生成 <code class="language-plaintext highlighter-rouge">a</code> 标签。</p>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://github.com

&lt;example@gmail.com&gt;
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<p>https://github.com</p>

<p><a href="mailto:example@gmail.com">example@gmail.com</a></p>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;&lt;a</span> <span class="na">href=</span><span class="s">"https://github.com"</span><span class="nt">&gt;</span>https://github.com<span class="nt">&lt;/a&gt;&lt;/p&gt;</span>

<span class="nt">&lt;p&gt;&lt;a</span> <span class="na">href=</span><span class="s">"mailto:example@gmail.com"</span><span class="nt">&gt;</span>example@gmail.com<span class="nt">&lt;/a&gt;&lt;/p&gt;</span>
</code></pre></div></div>

<h3 id="emoji">emoji</h3>

<p>以 GitHub Pages 为例。</p>

<p><strong>Markdown：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:camel: :blush: :smile:
</code></pre></div></div>

<p><strong>预览效果：</strong></p>

<p>:camel: :blush: :smile:</p>

<p><strong>对应 HTML：</strong></p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>
  <span class="nt">&lt;img</span> <span class="na">class=</span><span class="s">"emoji"</span> <span class="na">title=</span><span class="s">":camel:"</span> <span class="na">alt=</span><span class="s">":camel:"</span> <span class="na">src=</span><span class="s">"https://assets-cdn.github.com/images/icons/emoji/unicode/1f42b.png"</span> <span class="na">height=</span><span class="s">"20"</span> <span class="na">width=</span><span class="s">"20"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;img</span> <span class="na">class=</span><span class="s">"emoji"</span> <span class="na">title=</span><span class="s">":blush:"</span> <span class="na">alt=</span><span class="s">":blush:"</span> <span class="na">src=</span><span class="s">"https://assets-cdn.github.com/images/icons/emoji/unicode/1f60a.png"</span> <span class="na">height=</span><span class="s">"20"</span> <span class="na">width=</span><span class="s">"20"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;img</span> <span class="na">class=</span><span class="s">"emoji"</span> <span class="na">title=</span><span class="s">":smile:"</span> <span class="na">alt=</span><span class="s">":smile:"</span> <span class="na">src=</span><span class="s">"https://assets-cdn.github.com/images/icons/emoji/unicode/1f604.png"</span> <span class="na">height=</span><span class="s">"20"</span> <span class="na">width=</span><span class="s">"20"</span><span class="nt">&gt;</span>
<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<h2 id="奇技淫巧">奇技淫巧</h2>

<p>脑洞清奇的工程师们还发掘了很多使用 Markdown 的方法，大部分都是引入第三方 JavaScript 插件来实现。对这部分我只做简述，对其中的部分功能比如作图等，还是推荐用专门的可视化工具去做。</p>

<h3 id="画流程图和时序图">画流程图和时序图</h3>

<p>有部分网站和编辑器实现了对 Markdown 里流程图和时序图的支持，比如我们使用的项目管理工具 TAPD 的在线编辑器，还有 VSCode + 插件 Markdown Preview Enhanced 等。</p>

<p>以我们使用的项目管理工具 TAPD 的在线编辑器为例：</p>

<p><img src="https://raw.githubusercontent.com/mzlogin/markdown-intro/master/assets/tapd-markdown-flowchart.png" alt="流程图" /></p>

<p><img src="https://raw.githubusercontent.com/mzlogin/markdown-intro/master/assets/tapd-markdown-seq.png" alt="时序图" /></p>

<h3 id="插入数学公式">插入数学公式</h3>

<p>仍然以 TAPD 为例：</p>

<p><img src="https://raw.githubusercontent.com/mzlogin/markdown-intro/master/assets/tapd-markdown-math.png" alt="数学公式" /></p>

<p>应该是利用 JavaScript 支持了 LaTeX 公式语法。</p>

<h3 id="用-markdown-做-ppt">用 Markdown 做 PPT</h3>

<p>有专门的工具 <a href="https://github.com/yhatt/marp">Marp</a>，另外使用 VSCode + 插件 Markdown Preview Enhanced 也可以实现。</p>

<h3 id="用-markdown-写微信公众号">用 Markdown 写微信公众号</h3>

<p>可以将公众号素材用 Markdown 编辑好后，贴到在线排版工具以后，复制到公众号编辑器里即可。有多种页面主题和代码主题可选择。</p>

<p>我维护的工具地址：<a href="https://md.mazhuang.org">https://md.mazhuang.org</a></p>

<p><img src="https://raw.githubusercontent.com/mzlogin/markdown-intro/master/assets/wechat-markdown.png" alt="微信公众号" /></p>

<h3 id="更多">更多</h3>

<p>想象力丰富的工程师们还扩展了很多基于 Markdown 的玩法，包括但不限于：</p>

<ul>
  <li>自动生成 / 更新 Table of Contents</li>
  <li>流程图 / 时序图</li>
  <li>制作幻灯片</li>
  <li>集成 PlantUML / GraphViz 的能力</li>
  <li>导出 HTML / PDF / 电子书</li>
  <li>…</li>
</ul>

<p>以上功能基本都可以用 VSCode + 插件 Markdown Preview Enhanced 实现。</p>

<p>另外可以参考我以前的一篇博客 <a href="https://mazhuang.org/2017/09/01/markdown-odd-skills/">关于 Markdown 的一些奇技淫巧</a>。</p>

<h2 id="参考">参考</h2>

<ul>
  <li><a href="https://daringfireball.net/projects/markdown/syntax">Markdown: Syntax - DARING FIREBALL</a></li>
  <li><a href="https://zh.wikipedia.org/wiki/Markdown">Markdown - 维基百科</a></li>
  <li><a href="https://github.github.com/gfm/">GitHub Flavored Markdown Spec</a></li>
  <li><a href="https://mazhuang.org/2017/09/01/markdown-odd-skills/">关于 Markdown 的一些奇技淫巧</a></li>
</ul>

<hr />

<p>欢迎关注我的微信公众号，接收 markdown-intro 最新动态。</p>

<div align="center"><img width="192px" height="192px" src="https://mazhuang.org/assets/images/qrcode.jpg" /></div>]]></content><author><name>DarrenChan陈驰</name></author><category term="Markdown" /><summary type="html"><![CDATA[一份简明的 Markdown 笔记与教程]]></summary></entry></feed>