<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>(●ˇ∀ˇ●)</title>
  
  <subtitle>个人博客，总结一些编程知识的网站，希望可以帮到各位！</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://www.tengfei.eu.org/"/>
  <updated>2025-09-02T10:54:52.863Z</updated>
  <id>https://www.tengfei.eu.org/</id>
  
  <author>
    <name>腾飞</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>&#39;Vue3和Vue2的区别&#39;</title>
    <link href="https://www.tengfei.eu.org/article/4e47a843.html"/>
    <id>https://www.tengfei.eu.org/article/4e47a843.html</id>
    <published>2025-09-01T10:24:05.000Z</published>
    <updated>2025-09-02T10:54:52.863Z</updated>
    
    <content type="html"><![CDATA[<!-- # Vue3和Vue2的区别 --><blockquote><p>Vue.js作为最受欢迎的前端框架之一，其第三个主要版本Vue 3带来了许多重要的改进和新特性。本文将详细介绍Vue3相较于Vue2的主要区别。</p></blockquote><a id="more"></a><h2 id="1-性能提升"><a href="#1-性能提升" class="headerlink" title="1. 性能提升"></a>1. 性能提升</h2><h3 id="更小的包体积"><a href="#更小的包体积" class="headerlink" title="更小的包体积"></a>更小的包体积</h3><ul><li>Vue3通过Tree-shaking技术，使得最终打包的文件体积比Vue2更小</li><li>核心代码进行了优化，运行时性能得到显著提升</li></ul><h3 id="更快的渲染速度"><a href="#更快的渲染速度" class="headerlink" title="更快的渲染速度"></a>更快的渲染速度</h3><ul><li>重写了虚拟DOM实现，优化了diff算法</li><li>组件初始化和更新速度比Vue2快约1.3到2倍</li></ul><h2 id="2-Composition-API"><a href="#2-Composition-API" class="headerlink" title="2. Composition API"></a>2. Composition API</h2><p>这是Vue3最重要的新特性之一：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Vue2 - Options API</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  data() &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      count: <span class="number">0</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  methods: &#123;</span><br><span class="line">    increment() &#123;</span><br><span class="line">      <span class="keyword">this</span>.count++</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Vue3 - Composition API</span></span><br><span class="line"><span class="keyword">import</span> &#123; ref &#125; <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  setup() &#123;</span><br><span class="line">    <span class="keyword">const</span> count = ref(<span class="number">0</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> increment = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">      count.value++</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      count,</span><br><span class="line">      increment</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Composition API的优势：</p><ul><li>更好的逻辑复用</li><li>更灵活的代码组织方式</li><li>更容易进行类型推导</li></ul><h2 id="3-多根节点支持"><a href="#3-多根节点支持" class="headerlink" title="3. 多根节点支持"></a>3. 多根节点支持</h2><p>Vue3支持组件拥有多个根节点：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- Vue2中会报错 --&gt;</span><br><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;div&gt;第一个根节点&lt;/div&gt;</span><br><span class="line">  &lt;div&gt;第二个根节点&lt;/div&gt;</span><br><span class="line">&lt;/template&gt;</span><br><span class="line"></span><br><span class="line">&lt;!-- Vue3中完全支持 --&gt;</span><br><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;header&gt;头部&lt;/header&gt;</span><br><span class="line">  &lt;main&gt;主要内容&lt;/main&gt;</span><br><span class="line">  &lt;footer&gt;底部&lt;/footer&gt;</span><br><span class="line">&lt;/template&gt;</span><br></pre></td></tr></table></figure><h2 id="4-Teleport组件"><a href="#4-Teleport组件" class="headerlink" title="4. Teleport组件"></a>4. Teleport组件</h2><p>Vue3引入了<code>&lt;teleport&gt;</code>组件，可以将子组件渲染到DOM中的任何位置：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;div&gt;</span><br><span class="line">    &lt;teleport to=&quot;body&quot;&gt;</span><br><span class="line">      &lt;modal v-if=&quot;showModal&quot; /&gt;</span><br><span class="line">    &lt;/teleport&gt;</span><br><span class="line">  &lt;/div&gt;</span><br><span class="line">&lt;/template&gt;</span><br></pre></td></tr></table></figure><h2 id="5-更好的TypeScript支持"><a href="#5-更好的TypeScript支持" class="headerlink" title="5. 更好的TypeScript支持"></a>5. 更好的TypeScript支持</h2><p>Vue3使用TypeScript重写，提供了更好的类型支持：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineComponent, ref &#125; <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> User &#123;</span><br><span class="line">  name: <span class="built_in">string</span></span><br><span class="line">  age: <span class="built_in">number</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> defineComponent(&#123;</span><br><span class="line">  setup() &#123;</span><br><span class="line">    <span class="keyword">const</span> user = ref&lt;User | <span class="literal">null</span>&gt;(<span class="literal">null</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      user</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="6-新的生命周期钩子"><a href="#6-新的生命周期钩子" class="headerlink" title="6. 新的生命周期钩子"></a>6. 新的生命周期钩子</h2><p>Vue3调整了生命周期钩子的命名：</p><div class="table-container"><table><thead><tr><th>Vue2</th><th>Vue3</th></tr></thead><tbody><tr><td>beforeCreate</td><td>setup()</td></tr><tr><td>created</td><td>setup()</td></tr><tr><td>beforeMount</td><td>onBeforeMount</td></tr><tr><td>mounted</td><td>onMounted</td></tr><tr><td>beforeUpdate</td><td>onBeforeUpdate</td></tr><tr><td>updated</td><td>onUpdated</td></tr><tr><td>beforeDestroy</td><td>onBeforeUnmount</td></tr><tr><td>destroyed</td><td>onUnmounted</td></tr></tbody></table></div><h2 id="7-Suspense组件"><a href="#7-Suspense组件" class="headerlink" title="7. Suspense组件"></a>7. Suspense组件</h2><p>Vue3引入了<code>&lt;suspense&gt;</code>组件来处理异步依赖：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;suspense&gt;</span><br><span class="line">    &lt;template #default&gt;</span><br><span class="line">      &lt;async-component /&gt;</span><br><span class="line">    &lt;/template&gt;</span><br><span class="line">    &lt;template #fallback&gt;</span><br><span class="line">      &lt;div&gt;Loading...&lt;/div&gt;</span><br><span class="line">    &lt;/template&gt;</span><br><span class="line">  &lt;/suspense&gt;</span><br><span class="line">&lt;/template&gt;</span><br></pre></td></tr></table></figure><h2 id="8-全局API修改"><a href="#8-全局API修改" class="headerlink" title="8. 全局API修改"></a>8. 全局API修改</h2><p>Vue3对全局API进行了重构，支持tree-shaking：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Vue2</span></span><br><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line">Vue.use(Vuex)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Vue3</span></span><br><span class="line"><span class="keyword">import</span> &#123; createApp &#125; <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> &#123; createStore &#125; <span class="keyword">from</span> <span class="string">'vuex'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = createApp(&#123;&#125;)</span><br><span class="line"><span class="keyword">const</span> store = createStore(&#123;&#125;)</span><br><span class="line">app.use(store)</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Vue3在保持Vue核心理念的同时，通过一系列改进大大提升了开发体验和应用性能。虽然学习曲线略有增加，但Composition API等新特性使得大型应用的开发变得更加容易和灵活。对于新项目，建议直接使用Vue3；对于现有Vue2项目，可以根据实际情况考虑升级计划。</p>]]></content>
    
    <summary type="html">
    
      &lt;!-- # Vue3和Vue2的区别 --&gt;
&lt;blockquote&gt;
&lt;p&gt;Vue.js作为最受欢迎的前端框架之一，其第三个主要版本Vue 3带来了许多重要的改进和新特性。本文将详细介绍Vue3相较于Vue2的主要区别。&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
    
      <category term="Vue" scheme="https://www.tengfei.eu.org/tags/Vue/"/>
    
  </entry>
  
  <entry>
    <title>ERP系统开发功能文档</title>
    <link href="https://www.tengfei.eu.org/article/5dada370.html"/>
    <id>https://www.tengfei.eu.org/article/5dada370.html</id>
    <published>2025-06-22T08:41:21.000Z</published>
    <updated>2025-08-20T01:13:44.127Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>  ERP系统开发功能文档，记录ERP系统开发过程中的功能需求。</p></blockquote><a id="more"></a><h2 id="一、系统整体目标"><a href="#一、系统整体目标" class="headerlink" title="一、系统整体目标"></a>一、系统整体目标</h2><p>本 ERP 系统旨在整合物业公司核心业务流程，通过自动化和信息化手段提高运营效率，降低人力成本，实现财务、人资、运营等多模块协同工作与数据共享。系统将融合天问互联收费管理和金蝶财务管理的优势功能，打造一体化管理平台，为物业公司提供全面、高效、智能的管理解决方案。</p><p><img src="/article/5dada370/43c17afddca8f2a4737a9c980902e34a_compress.jpg" alt></p><h2 id="二、财务模块"><a href="#二、财务模块" class="headerlink" title="二、财务模块"></a>二、财务模块</h2><h3 id="复刻天问系统收费管理功能"><a href="#复刻天问系统收费管理功能" class="headerlink" title="复刻天问系统收费管理功能"></a>复刻天问系统收费管理功能</h3><h4 id="（一）费用项目管理"><a href="#（一）费用项目管理" class="headerlink" title="（一）费用项目管理"></a>（一）费用项目管理</h4><ol><li><strong>自定义设置</strong></li></ol><ul><li><p>支持灵活定义各类收费项目，除常见的物业费、水电费、停车费、垃圾处理费等，还可根据不同物业类型（如商业综合体、住宅小区、工业园区等）新增特色收费项目，如商业综合体的广告位租赁费、公共区域清洁费等。针对每个项目，可详细配置收费标准，包括按面积、数量、时长、固定金额等不同方式设定单价。例如，物业费可按每平方米 [X] 元收取，车位费可按每个车位每月 [X] 元收取，水电费按实际用量乘以对应单价计费。</p></li><li><p>能够设置多样化的计费周期，如常见的月付、季付、年付，也可根据特殊需求自定义周期，如按周计费、按特定活动周期计费等。对于某些临时项目，还可设置一次性收费周期。</p></li><li><p>精准界定收费项目的适用范围，可具体到特定楼栋、户型、楼层、业主群体或房屋使用状态。比如，某栋楼因特殊服务需求，物业费收费标准与其他楼栋不同；或者针对商业户型和住宅户型，水电费收费标准有差异；对长期租户和短期租户，停车费优惠政策不同；房屋状态与收费标准自动关联（如未售、装修、入住不同费率）。</p></li></ul><ol><li><strong>批量操作功能</strong></li></ol><ul><li><p>提供便捷的批量导入功能，物业人员可通过 Excel 等模板文件，一次性将大量收费项目信息导入系统。模板格式规范，包含收费项目名称、收费标准、计费周期、适用范围、是否为新增项目等必填字段，大大减少手动逐个录入的工作量。系统会对导入的数据进行格式校验和逻辑检查，若存在错误或不完整的数据，会提示物业人员进行修改。</p></li><li><p>同时支持批量导出功能，方便物业对已设置的收费项目信息进行备份、查看和二次编辑。导出的数据以清晰易读的表格形式呈现，便于整理和分析。可选择导出全部收费项目信息或根据特定条件筛选后导出部分信息。</p></li></ul><ol><li><strong>设置收费日期</strong></li></ol><ul><li><p>支持针对不同收费项目设置固定的收费日期，如将物业费的收费日期统一设置为每月 5 日，水电费为每月 10 日等。系统会在设置的收费日期前自动发出提醒，通知业主即将进行该项目的收费。提醒方式可包括短信、微信、公众号推送、系统站内信等，物业人员可根据实际情况选择合适的提醒方式。</p></li><li><p>可设置收费周期内的缴费截止日期，超过截止日期未缴费的，系统自动按预设规则计算滞纳金。滞纳金计算规则可根据不同收费项目和业主群体进行灵活设置，如物业费逾期 1 - 10 天，按每日 [X]% 收取滞纳金；逾期 11 - 30 天，按每日 [X]% 收取滞纳金等。</p></li><li><p>合同计费功能，租赁合同条款自动触发费用生成。当租赁合同签订或变更时，系统自动根据合同条款生成相应的费用项目和收费计划，并关联到对应的业主和房源信息。</p></li></ul><ol><li><strong>多渠道收费</strong></li></ol><ul><li><p>支持现场缴费、银行代收、移动支付（微信、支付宝、银联云闪付等）、托收、代付等多种收款方式。物业人员可在系统中配置不同收款方式的相关参数，如银行账号、支付渠道密钥等。</p></li><li><p>同一票据支持混合支付（部分现金 + 部分刷卡）。系统会自动记录每种支付方式的金额和支付时间，并生成相应的支付记录和票据。</p></li><li><p>自动对账功能，实时核销收款与应收款项。系统会定期与各支付渠道进行数据对接，获取实际收款信息，并与系统内的应收款项进行比对，自动核销已收款的账单。对于未匹配的款项，系统会标记为异常，提醒物业人员进行人工处理。</p></li></ul><ol><li><strong>智能催缴</strong></li></ol><ul><li><p>欠费自动分级（30/60/90 天），生成催缴清单。系统会根据业主的欠费时长和欠费金额对欠费情况进行分级管理，为不同级别的欠费业主生成相应的催缴清单。催缴清单可按楼栋、户型、业主群体等进行分类展示，方便物业人员进行针对性催缴。</p></li><li><p>支持短信、微信、公众号等多渠道自动推送催缴通知。通知内容可根据欠费级别和业主信息进行个性化定制，如提醒业主欠费金额、欠费项目、缴费截止日期、滞纳金计算方式等，并提供便捷的缴费链接。</p></li><li><p>欠费原因记录与分析，生成欠费统计报表。物业人员可在系统中记录业主的欠费原因，如对收费标准有异议、资金周转困难等。系统会对欠费原因进行统计分析，生成欠费统计报表，为物业制定催缴策略和改进收费管理提供数据支持。</p></li></ul><h4 id="（二）业主信息与房源关联"><a href="#（二）业主信息与房源关联" class="headerlink" title="（二）业主信息与房源关联"></a>（二）业主信息与房源关联</h4><ol><li><strong>信息库建设</strong></li></ol><ul><li><p>建立全面且详细的业主信息库，涵盖业主姓名、性别、联系方式（手机号码、电子邮箱）、身份证号、工作单位、紧急联系人等基本信息。同时，对业主对应的房源信息进行精准记录，包括房号、建筑面积、套内面积、户型结构、房屋用途（住宅、商业等）、房屋朝向、装修情况等。</p></li><li><p>为每个业主和房源生成唯一标识编码，确保信息的准确关联和快速查询。例如，业主编码为 “YZ -[地区编码] -[顺序号]”，房源编码为 “FY -[地区编码] -[楼栋号] -[房号]”。系统会在业主信息或房源信息发生变更时，自动更新相应的标识编码，保证编码的唯一性和准确性。</p></li></ul><ol><li><strong>信息维护与同步</strong></li></ol><ul><li><p>支持对业主信息进行增删改查操作，可单个处理，也能进行批量操作。如批量修改业主联系方式（适用于业主群体变更联系方式的情况）、批量更新业主身份信息（如业主更换身份证）等。系统会记录所有信息修改的历史记录，包括修改时间、修改人、修改内容等，方便进行审计和追溯。</p></li><li><p>当房源信息发生变更，如房屋面积调整（因改造等原因）、户型变更（重新布局）、房屋用途变更（住宅改为商业）等，系统自动检测并同步更新与之关联的收费项目信息，确保收费依据的准确性，避免因信息不一致导致的收费错误。同时，系统会向相关业主发送信息变更通知，告知业主房源信息的变更情况和可能影响的收费项目。</p></li></ul><h4 id="（三）账单生成与管理"><a href="#（三）账单生成与管理" class="headerlink" title="（三）账单生成与管理"></a>（三）账单生成与管理</h4><ol><li><strong>自动生成机制</strong></li></ol><ul><li><p>依据预设的收费项目、标准和计费周期，系统在每个计费周期开始时自动触发账单生成流程。例如，每月 1 号自动生成当月的物业费账单，每季度首月 1 号生成本季度的水电费账单。系统会根据业主信息和房源信息，准确计算每个业主的应缴费用，并生成详细的账单。</p></li><li><p>生成的账单内容清晰明了，详细展示各项费用的明细，包括收费项目名称、收费标准、计费周期内的用量或时长、金额、计费依据（如面积、用量计算过程）等信息。以水电费账单为例，会显示水表、电表的起止读数、实际用量、单价以及费用总额。账单格式可根据物业的需求进行自定义设置，如添加物业 logo、联系方式、温馨提示等。</p></li></ul><ol><li><strong>手动调整功能</strong></li></ol><ul><li><p>当出现特殊情况，如房屋面积调整、优惠活动（新业主首年物业费 8 折）、临时减免费用等，需修改账单金额时，系统支持手动调整功能。但调整需经过严格的审批流程，审批流程可根据物业的组织架构和管理要求进行自定义设置，如由物业收费员发起调整申请，经物业经理审批通过后生效。审批流程可抓取企微审批流程，实现线上化审批。</p></li><li><p>审批通过后，系统自动记录调整原因（如 “因房屋实测面积变更，物业费调整”）、操作人、操作时间等轨迹信息，以便后续追溯和审计。同时，系统会向相关业主发送账单调整通知，告知业主账单调整的原因和调整后的金额。</p></li></ul><h4 id="（四）收费处理"><a href="#（四）收费处理" class="headerlink" title="（四）收费处理"></a>（四）收费处理</h4><ol><li><strong>多元化收费方式</strong></li></ol><ul><li><p><strong>线上支付</strong>：无缝对接微信、支付宝、银行卡转账等主流线上支付渠道。业主在收到账单后，可通过物业提供的线上缴费入口，选择相应支付方式进行缴费。支付成功后，系统自动与支付渠道进行数据交互，实时获取支付结果，并将账单状态更新为 “已支付”。同时，向业主发送支付成功通知，通知内容包括支付金额、支付时间、账单明细等信息。系统会记录每笔线上支付的交易流水号和支付渠道信息，方便进行对账和查询。</p></li><li><p><strong>线下支付</strong>：支持现金、POS 机刷卡等线下支付方式。物业人员在收到业主线下支付款项后，可通过扫描票据上的二维码（如 POS 机小票二维码）或手动录入支付信息（金额、支付方式、对应账单编号）到系统中，系统自动匹配对应的账单，并将其标记为 “已支付”。对于现金支付，系统自动记录收款金额、收款时间、收款人等信息。系统会对线下支付信息进行审核，确保支付信息的准确性和完整性。</p></li></ul><ol><li><strong>重复支付处理</strong></li></ol><ul><li><p>系统具备智能识别重复支付的能力，通过对支付信息（支付金额、支付时间、账单编号、业主信息等）的综合比对，及时发现同一账单的多次支付情况。一旦检测到重复支付，系统立即发出提醒，通知物业人员。提醒方式可包括系统弹窗、短信提醒等。</p></li><li><p>同时，为业主提供便捷的退款处理指引，物业人员可在系统中发起退款流程，经审批后将重复支付款项原路退回给业主，有效避免资金纠纷。退款流程可设置不同的审批环节，如由物业收费员发起退款申请，经财务人员审核、物业经理审批通过后，由财务人员进行退款操作。系统会记录退款的详细信息，包括退款金额、退款时间、退款方式、审批人员等，方便进行审计和查询。</p></li></ul><h4 id="（五）财务报表生成与分析"><a href="#（五）财务报表生成与分析" class="headerlink" title="（五）财务报表生成与分析"></a>（五）财务报表生成与分析</h4><ol><li><strong>原有报表生成</strong></li></ol><ul><li><p>系统自动生成各类财务报表，包括收费汇总报表、欠费明细报表、收费率分析报表等。收费汇总报表按时间周期（日、月、季、年）展示所有收费项目的总收入金额，以及各项目的收入占比情况等报表。欠费明细报表详细列出每个欠费业主的姓名、房号、欠费项目、欠费金额、欠费时长等信息。收费率分析报表通过计算已缴费金额与应收金额的比例，直观展示各收费项目、各楼栋、各业主群体的收费率情况。报表格式支持 Excel、PDF 等常见格式导出，方便物业人员进行数据存档、打印和进一步分析。</p></li><li><p>报表可根据物业的需求进行自定义设置，如选择不同的时间范围、收费项目、楼栋、业主群体等进行数据筛选和统计，生成个性化的报表。同时，报表可设置定时生成和自动发送功能，物业人员可根据需要设置报表的生成周期和接收人，系统会自动将生成的报表发送到指定的邮箱或系统账号。</p></li></ul><ol><li><strong>新增财务数据报表</strong></li></ol><ul><li><p><strong>收支对比报表</strong>：按日、周、月、季、年等时间维度生成，清晰展示每个时间段的各项收入（物业费收入、水电费代缴收入、停车费收入等）和支出（物业人员工资、公共设施维护费用等）情况。报表中会列出收入总金额、支出总金额以及收支差额，同时通过柱状图、折线图等可视化形式直观呈现收支变化趋势，帮助物业管理者快速了解资金流动状况。报表可进一步细化收入和支出的分类，如将物业费收入按不同收费项目进行细分，将公共设施维护费用按不同设施类型进行细分，以便更深入地分析收支情况。</p></li><li><p><strong>成本分析报表</strong>：针对物业运营过程中的各项成本进行详细分析，如人工成本、维修成本、物料采购成本等。报表按成本类别分类统计，展示每个类别的总成本、占总运营成本的比例以及不同时间段的成本变化情况。例如，人工成本可细化到各岗位人员的工资支出，维修成本可按公共设施类型（电梯、水泵、路灯等）分别统计，便于物业优化成本结构。报表可提供成本预警功能，当某项成本超过预设的阈值时，系统会自动发出预警提醒，帮助物业及时采取措施控制成本。</p></li><li><p><strong>应收账款报表</strong>：详细记录所有未收回的费用款项，包括业主姓名、房号、欠费项目、欠费金额、欠费起始日期、逾期天数等信息。报表支持按欠费金额大小、逾期天数长短等方式排序，同时标注长期拖欠费用的业主，为催缴工作提供重点目标，有助于提高资金回笼效率。报表可与智能催缴功能进行关联，当业主的欠费情况发生变化时，系统会自动更新报表信息，并根据预设的催缴规则发送催缴通知。</p></li><li><p><strong>应付账款报表</strong>：统计物业需要支付给外部单位或个人的款项，如水电费代缴后需支付给供水供电部门的费用、维修工程欠款等。报表包含应付对象名称、应付项目、应付金额、应付款日期、已付金额、未付金额等内容，系统会在应付款日期前自动发出提醒，避免逾期付款产生不良影响。报表可设置付款提醒的方式和时间间隔，物业人员可根据实际情况进行调整。同时，报表可与财务审批流程进行关联，当需要支付款项时，系统会自动发起审批流程，确保付款的合规性。</p></li><li><p><strong>利润分析报表</strong>：以月度、季度、年度为周期生成，综合计算物业的营业收入、运营成本、税金等数据，得出每期的净利润额和利润率。报表中会详细列出各项收入和成本的构成，分析影响利润的关键因素，如某季度利润下降是由于维修成本增加还是物业费收缴率降低等，为物业制定盈利策略提供数据支持。报表可提供利润预测功能，根据历史数据和业务发展趋势，预测未来一段时间的利润情况，帮助物业提前做好规划和决策。</p></li><li><p><strong>资金流量报表</strong>：实时追踪资金的流入和流出情况，包括各项收费的资金到账、退款支出、成本费用支付等。报表按资金流动的时间顺序记录，展示每笔资金的金额、来源或去向、交易时间等信息，并计算每个时间段的资金净流量，帮助财务人员掌握资金的实时动态，合理安排资金使用。报表可提供资金预警功能，当资金净流量低于预设的阈值时，系统会自动发出预警提醒，提示财务人员及时采取措施保障资金安全。</p></li><li><p><strong>利润表</strong>：按月度、季度、年度生成，清晰呈现企业在特定时期内的经营成果。包含营业收入（如各项人力资源服务收入等）、营业成本（如人力成本、培训成本等）、税金及附加、销售费用、管理费用、财务费用、营业利润、利润总额、净利润等项目。通过该报表可分析企业盈利状况及盈利能力变化趋势，为经营决策提供依据。报表可与其他财务报表进行关联分析，如与收支对比报表、成本分析报表等进行对比，更全面地了解企业的财务状况。</p></li><li><p><strong>现金流量表</strong>：实时追踪企业现金的流入和流出情况，按经营活动、投资活动、筹资活动分类列示。经营活动现金流量包括收取的各项人力资源服务款项、支付的员工薪酬等；投资活动现金流量涉及与人力资源相关的资产购置与处置等；筹资活动现金流量与企业融资等相关。报表能反映企业现金的生成能力和偿债能力，帮助管理者合理安排资金。报表可提供现金流量预测功能，根据历史数据和业务发展趋势，预测未来一段时间的现金流量情况，帮助企业提前做好资金规划。</p></li><li><p><strong>资产负债表</strong>：反映企业在某一特定日期的财务状况，包含资产、负债和所有者权益三大类项目。与人力资源管理相关的资产项目如人力资产（可根据一定规则核算）等；负债项目如应付员工薪酬、应付社保公积金等。通过该报表可了解企业的财务结构、偿债能力和财务风险。报表可提供资产负债分析功能，如计算资产负债率、流动比率等财务指标，帮助企业评估财务状况和风险水平。</p></li></ul><ol><li><strong>报表生成与导出</strong></li></ol><ul><li>新增报表同样支持 Excel、PDF 等格式导出，方便物业人员进行数据存档、打印和进一步分析。导出的报表可设置加密和水印功能，保障数据的安全性和保密性。</li></ul><ol><li><strong>数据分析</strong></li></ol><ul><li><p>提供数据分析功能，帮助物业了解收费趋势和业主缴费习惯。例如，通过分析不同时间段的收费数据，找出收费高峰期和低谷期，以便合理安排人力；分析不同业主群体的缴费及时性，针对缴费不及时的群体制定相应的催缴策略；对比不同年份或季度的收费率，评估物业收费工作的成效和改进空间。</p></li><li><p>新增报表的数据分析可结合对应报表数据进行深入分析。系统可提供数据挖掘和机器学习算法，对大量的财务数据进行分析和预测，为物业提供更精准的决策支持。例如，通过关联分析找出影响收费率的关键因素，通过时间序列分析预测未来的收费趋势等。</p></li></ul><p><img src="/article/5dada370/002c92d29f60aae88b7cb2e48f95de68_compress.jpg" alt></p><h4 id="（六）催缴管理"><a href="#（六）催缴管理" class="headerlink" title="（六）催缴管理"></a>（六）催缴管理</h4><ol><li><strong>催缴提醒设置</strong></li></ol><ul><li><p>物业可根据实际情况设置催缴提醒规则，包括提醒时间节点（如账单逾期 3 天、7 天、15 天分别提醒）、提醒方式（短信、微信推送、系统站内信、电话等）。针对不同收费项目和业主群体，可设置差异化的提醒规则。例如，对于物业费，逾期 3 天发送短信提醒，逾期 7 天再发送微信推送；对于商业租户的租金，逾期 1 天即发送短信和微信推送提醒，逾期 3 天进行电话催缴。</p></li><li><p>催缴提醒规则可根据业主的历史缴费记录和欠费情况进行动态调整。对于长期按时缴费的业主，可适当延长提醒时间间隔；对于多次欠费的业主，可缩短提醒时间间隔，并增加提醒方式。</p></li></ul><ol><li><strong>催缴记录跟踪</strong></li></ol><ul><li>系统自动记录每次催缴操作的详细信息，包括催缴时间、催缴方式、催缴对象、是否成功送达（针对短信和微信推送，可获取送达状态）、业主反馈等。物业人员可随时查看每个业主的催缴记录，了解催缴进展情况，以便针对性地采取进一步措施。催缴记录可按时间、催缴方式、业主等维度进行分类查询和统计，方便物业人员进行分析和总结。</li></ul><h4 id="（七）补票管理"><a href="#（七）补票管理" class="headerlink" title="（七）补票管理"></a>（七）补票管理</h4><ol><li><strong>批量导入功能</strong></li></ol><ul><li><p>补票功能支持批量导入，物业人员可通过 Excel 模板填写补票相关信息（如业主房号、补票项目、补票金额、补票原因等），然后将模板文件导入系统。系统自动对导入的补票信息进行校验，校验通过后批量生成补票记录，并关联至相应业主的账单中。导入模板可提供示例文件和详细的填写说明，帮助物业人员准确填写补票信息。</p></li><li><p>系统会对导入的补票信息进行合法性检查，如检查业主房号是否存在、补票项目是否合法、补票金额是否合理等。对于不符合要求的补票信息，系统会提示物业人员进行修改。</p></li></ul><ol><li><strong>记录与审计</strong></li></ol><ul><li>补票记录同样会记录操作人、操作时间、补票原因等信息，便于后续查询和审计。补票记录可与财务审计流程进行关联，当进行财务审计时，可快速查阅补票记录的详细信息，确保补票操作的合规性。</li></ul><h4 id="（八）员工端与管理员后台权限区分​"><a href="#（八）员工端与管理员后台权限区分​" class="headerlink" title="（八）员工端与管理员后台权限区分​"></a>（八）员工端与管理员后台权限区分​</h4><ol><li><strong>员工使用端权限​</strong></li></ol><ul><li>仅能查看本人操作范围内的账单数据（按片区 / 楼栋权限隔离）​</li><li>具备基础收费录入、简单账单查询、催费记录登记功能​</li><li>无法修改收费标准、删除账单数据及查看财务核心报表​</li><li>移动端适配：企微工作台支持账单扫码核销、收款记录快速录入、催费短信一键发送​</li></ul><ol><li><strong>管理员后台权限​</strong></li></ol><ul><li>全量数据访问权限，支持跨片区数据汇总分析​</li><li>配置收费项目参数、审批账单调整申请、管理用户权限​</li><li>查看所有财务报表及操作日志，具备数据导出权限​</li><li>系统参数配置：设置滞纳金规则、自动催缴阈值、数据备份策略等​</li></ul><h4 id="（九）重点标注功能实现​"><a href="#（九）重点标注功能实现​" class="headerlink" title="（九）重点标注功能实现​"></a>（九）重点标注功能实现​</h4><ol><li><strong>账单状态可视化标注​</strong></li></ol><ul><li>逾期超 30 天账单自动标红并置顶显示​</li><li>重点业主（如 VIP 客户 / 长期欠费户）账单添加特殊标识​</li><li>批量操作项添加醒目提示（如 “批量删除” 按钮添加二次确认弹窗）​</li></ul><ol><li><strong>操作轨迹高亮记录​</strong></li></ol><ul><li>账单金额调整、删除等关键操作记录用橙色高亮显示​</li><li>财务审核通过的凭证添加绿色印章标识​</li><li>异常交易（如大额退款、凌晨缴费）自动标记并触发复核流程​</li></ul><h4 id="（十）系统原数据迁移方案​"><a href="#（十）系统原数据迁移方案​" class="headerlink" title="（十）系统原数据迁移方案​"></a>（十）系统原数据迁移方案​</h4><ol><li><strong>迁移范围</strong>​</li></ol><ul><li>历史账单数据（含已结清 / 未结清状态）​</li><li>业主信息及房源关联关系​</li><li>收费项目配置及历史调价记录​</li><li>财务凭证及发票数据​</li></ul><ol><li><strong>迁移保障措施</strong>​</li></ol><ul><li>迁移前自动生成数据校验报告，包含数据完整性 / 一致性检查结果​</li><li>采用增量迁移 + 全量校验模式，分批次完成历史数据迁移​</li><li>迁移后保留 3 个月双系统并行期，支持数据对比查询​</li><li>迁移过程全程日志记录，可追溯每笔数据迁移状态<h3 id="财务收支明细管理功能"><a href="#财务收支明细管理功能" class="headerlink" title="财务收支明细管理功能"></a>财务收支明细管理功能</h3></li></ul><h4 id="（一）预算管理"><a href="#（一）预算管理" class="headerlink" title="（一）预算管理"></a>（一）预算管理</h4><ul><li><p>按项目 / 部门 / 周期设置预算，实时监控执行情况。预算设置可根据物业的组织架构和业务流程进行灵活配置，如按不同的物业管理区域、业务模块、费用类型等设置预算。系统会实时更新预算执行数据，展示预算金额、实际支出金额、预算剩余金额等信息，并通过图表和报表直观呈现预算执行情况。</p></li><li><p>超预算自动预警，支持预算调整流程。当实际支出接近或超过预算时，系统会自动发出预警提醒，通知相关人员采取措施。预算调整需经过严格的审批流程，审批通过后，系统自动更新预算数据，并记录调整原因、调整时间、调整人等信息，以便后续追溯和审计。</p></li><li><p>基于历史数据自动生成收入预算报表。系统会分析历史的收入数据，结合业务发展趋势和市场情况，自动生成收入预算报表。收入预算报表可按不同的时间周期和收入项目进行细分，为物业制定经营计划和决策提供参考。</p></li></ul><h4 id="（二）报销管理"><a href="#（二）报销管理" class="headerlink" title="（二）报销管理"></a>（二）报销管理</h4><ul><li><p>智能票据识别，支持拍照 / 扫描自动提取发票信息。系统采用先进的光学字符识别（OCR）技术，能够准确识别发票上的关键信息，如发票号码、开票日期、金额、商品名称等，并自动录入系统。识别准确率高，可大大提高报销效率。</p></li><li><p>费用标准自动校验，违规报销自动拦截。系统会根据预设的费用标准对报销信息进行校验，如差旅费的住宿标准、餐饮标准等。对于超出费用标准的报销申请，系统会自动拦截并提示申请人进行修改。</p></li><li><p>全流程电子化审批，移动端随时处理。报销申请可通过系统在线提交，审批流程可根据物业的组织架构和管理要求进行自定义设置。审批人员可通过移动端随时随地处理报销申请，提高审批效率。系统会记录报销申请的审批状态和审批意见，方便申请人和相关人员查询。</p></li></ul><h4 id="（三）成本分析"><a href="#（三）成本分析" class="headerlink" title="（三）成本分析"></a>（三）成本分析</h4><ul><li><p>多维成本统计（人工、能耗、维护等）。系统会对物业运营过程中的各项成本进行分类统计，不仅包括人工成本、能耗成本、维护成本等常见成本项目，还可根据物业的实际情况增加其他成本项目。成本统计可按不同的时间周期、部门、项目等维度进行细分，为物业深入分析成本结构提供数据支持。</p></li><li><p>成本异常自动检测，关联业务数据追溯原因。系统会对成本数据进行实时监测，当发现成本异常波动时，自动发出预警提醒。同时，系统会关联相关的业务数据，如设备运行记录、人员考勤记录等，帮助物业追溯成本异常的原因，及时采取措施进行调整。</p></li><li><p>自动生成成本节约建议报告。系统会根据成本分析结果，结合物业的业务特点和管理经验，自动生成成本节约建议报告。报告中会提出具体的成本节约措施和建议，如优化人员配置、降低能耗、合理安排维修计划等，为物业降低运营成本提供参考。</p></li></ul><h4 id="（四）企业微信接口开发​"><a href="#（四）企业微信接口开发​" class="headerlink" title="（四）企业微信接口开发​"></a>（四）企业微信接口开发​</h4><ol><li><strong>审批流数据对接​</strong></li></ol><ul><li>企微审批模板自定义：支持费用报销、采购申请等 12 类审批表单配置​</li><li>实时同步审批状态：审批中 / 已通过 / 已驳回状态实时回传物业系统​</li><li>审批附件自动归档：报销单据扫描件自动关联对应财务凭证​</li></ul><ol><li><strong>数据交互安全机制</strong>​</li></ol><ul><li>采用企业微信官方 API 接口，支持 IP 白名单限制​</li><li>敏感数据传输加密，对接日志留存 180 天​</li><li>接口调用失败自动重试，超次数触发告警机制​</li></ul><h4 id="（五）数据驾驶舱功能​"><a href="#（五）数据驾驶舱功能​" class="headerlink" title="（五）数据驾驶舱功能​"></a>（五）数据驾驶舱功能​</h4><ol><li><strong>对比分析维度​</strong></li></ol><ul><li>同期数据对比：本月 vs 上月 / 本年 vs 上年收入结构对比​</li><li>区域对标分析：各片区收费率 / 欠费金额排名对比​</li><li>预算执行分析：实际收支 vs 预算金额偏差率可视化​</li></ul><ol><li><strong>移动报表功能​</strong></li></ol><ul><li>资产分析：固定资产 / 流动资产占比及变动趋势​</li><li>财务健康度指标：流动比率、资产负债率等关键指标实时计算​</li><li>支持报表订阅，定时推送至指定人员企微工作台​</li></ul><ol><li><strong>实时工作台组件​</strong></li></ol><ul><li>逾期户数动态看板：按片区 / 楼栋分类展示​</li><li>欠费提醒清单：支持按催缴优先级排序​</li><li>移动收款模块：集成微信 / 支付宝收款码，收款后自动核销账单<h3 id="其他财务功能"><a href="#其他财务功能" class="headerlink" title="其他财务功能"></a>其他财务功能</h3></li></ul><h4 id="（一）发票、报表"><a href="#（一）发票、报表" class="headerlink" title="（一）发票、报表"></a>（一）发票、报表</h4><ol><li><strong>税务合规</strong></li></ol><ul><li><p>自动获取进项 / 销项发票信息，生成税务凭证。系统可与税务机关的发票管理系统进行对接，自动获取企业的进项和销项发票信息，并根据发票信息生成相应的税务凭证。税务凭证的生成符合国家税收法规的要求，确保企业税务处理的准确性和合规性。</p></li><li><p>税务风险自动检测（如发票异常、申报差异）。系统会对发票信息和税务申报数据进行实时监测，自动检测税务风险，如发票真伪验证、发票重复使用、申报数据与发票数据不符等。当发现税务风险时，系统会及时发出预警提醒，帮助企业及时采取措施进行处理。</p></li><li><p>税务政策库自动更新，影响分析。系统会实时关注国家税收政策的变化，自动更新税务政策库。同时，系统会分析税收政策变化对企业的影响，为企业提供相应的税务筹划建议，帮助企业合理降低税务负担。</p></li></ul><ol><li><strong>报表生成</strong></li></ol><ul><li><p>自动生成利润表、资产负债表、现金流量表。系统会根据企业的财务数据自动生成标准的财务报表，报表格式和内容符合国家财务会计准则的要求。财务报表可按不同的时间周期（月度、季度、年度）生成，为企业提供全面、准确的财务信息。</p></li><li><p>收费明细表、欠费统计表、收款率分析表。这些报表能够详细展示企业的收费情况、欠费情况和收款率情况，帮助企业及时掌握资金回笼情况和业主缴费情况。报表可按不同的时间周期、收费项目、楼栋、业主群体等进行细分，为企业制定收费策略和催缴计划提供数据支持。</p></li><li><p>费用支出分类统计表、预算执行差异表。费用支出分类统计表能够清晰展示企业各项费用的支出情况，帮助企业分析费用结构和成本控制情况。预算执行差异表能够对比预算金额和实际支出金额，找出预算执行过程中的差异和问题，为企业调整预算和优化资源配置提供参考。</p></li></ul><h4 id="（二）物联网智能化功能"><a href="#（二）物联网智能化功能" class="headerlink" title="（二）物联网智能化功能"></a>（二）物联网智能化功能</h4><ol><li><strong>水电自动抄表</strong></li></ol><ul><li><p>系统与智能水表、电表通过物联网技术进行对接，实现自动抄表功能。智能表具定时（如每日凌晨）将实时的水电用量数据传输至系统，无需人工上门抄表。系统支持多种通信协议和接口，可与不同品牌和型号的智能表具进行兼容对接。</p></li><li><p>系统对接收的水电数据进行自动校验，与历史用量数据进行比对，若出现异常波动（如用量骤增或骤减），自动发出预警，提醒物业人员进行核查。预警信息可通过短信、系统弹窗等方式发送给相关人员，同时系统会记录异常波动的详细信息，如波动时间、波动幅度、可能的原因等，方便物业人员进行分析和处理。</p></li><li><p>自动生成水电用量记录，并根据预设的水电收费标准，自动计算出业主的水电费用，直接关联至账单生成系统，无需人工干预。水电用量记录可按不同的时间周期（日、月、季、年）进行统计和查询，方便物业人员进行数据分析和管理。</p></li><li><p>支持查看每个智能表具的运行状态，如是否在线、是否出现故障等，便于物业及时进行维护。系统会实时监控智能表具的运行状态，当发现表具故障或离线时，自动发出故障预警，提醒物业人员进行维修和处理。同时，系统会记录表具的维修历史和维护记录，为物业进行设备管理提供依据。</p></li></ul><ol><li><strong>道闸门禁系统和车位费管理关联</strong></li></ol><ul><li><p>道闸门禁系统通过车牌识别、蓝牙卡等方式识别车辆信息，并将车辆进出记录实时同步至收费系统。系统支持多种车辆识别方式，可根据物业的实际情况进行选择和配置。车辆进出记录包括车辆牌照号码、进出时间、进出地点等信息，方便物业进行车辆管理和收费统计。</p></li><li><p>对于固定车位业主，系统将道闸识别到的车辆信息与对应的车位信息及车位费缴纳情况进行关联。当车位费未到期时，道闸自动放行；当车位费到期或欠费时，道闸系统发出提示，同时收费系统向业主发送车位费催缴通知，经业主补缴费用或人工确认后，道闸方可放行。系统会实时更新车位费的缴纳情况，确保道闸放行的准确性和安全性。</p></li><li><p>对于临时停车车辆，道闸记录车辆入场时间，出场时根据停车时长及临时停车收费标准，通过收费系统自动计算停车费用，支持业主扫码支付，支付成功后道闸自动开启。系统自动将临时停车费记录至财务数据中，并生成相应账单。临时停车收费标准可根据物业的实际情况进行灵活设置，如按小时计费、按次计费等。</p></li></ul><h4 id="（三）业主端功能"><a href="#（三）业主端功能" class="headerlink" title="（三）业主端功能"></a>（三）业主端功能</h4><ol><li><strong>房源认证机制</strong>​</li></ol><ul><li>支持房产证 / 购房合同拍照上传自动识别房源信息​</li><li>人脸识别辅助认证：业主上传人脸照片与物业存档比对​</li><li>多房源管理：同一业主名下多套房产集中展示​</li></ul><ol><li><strong>缴费体验升级</strong>​</li></ol><ul><li>一键缴费：支持勾选多笔账单合并支付​</li><li>缴费凭证自动归档：电子收据生成后同步至业主微信卡包​</li><li>缴费提醒个性化设置：支持按项目 / 金额阈值设置提醒频率</li></ul><ol><li><strong>查看缴费记录和余额</strong></li></ol><ul><li>业主登录系统业主端后，可清晰查看自己所有的缴费记录，包括各项费用的缴费时间、金额、缴费方式等。同时，显示账户内的预存余额，让业主实时了解自己的费用情况。缴费记录可按时间、费用项目等进行分类查询和筛选，方便业主进行查看和管理。</li></ul><ol><li><strong>历史水电缴费自动合计</strong></li></ol><ul><li>系统自动对业主历史的水电缴费金额进行合计，可按年度、季度等时间维度展示，方便业主了解自己不同时期的水电消费情况。水电缴费合计数据可通过图表和报表的形式进行展示，直观呈现业主的水电消费趋势。</li></ul><ol><li><strong>费用到期、不足提醒显示</strong></li></ol><ul><li>业主端首页设置专门的提醒区域，显示即将到期的费用（如物业费还有 3 天到期）、余额不足的费用（如预存水电费余额低于 50 元）等信息，提醒业主及时缴费。提醒信息可根据业主的设置进行个性化定制，如提醒方式（短信、微信、系统站内信）、提醒时间等。</li></ul><ol><li><strong>商圈合作对接</strong>​</li></ol><ul><li>商户入驻管理：支持周边商户信息录入及合作协议管理​</li><li>消费积分规则：设置不同商户积分兑换比例（如 10 元 = 1 积分）​</li><li>小票兑换流程：业主上传购物小票照片→系统 OCR 识别→自动发放积分​</li></ul><ol><li><strong>积分应用场景</strong>​</li></ol><ul><li>物业费抵扣：例如：100 积分 = 1 元物业费，支持部分抵扣​</li><li>积分商城：实物商品 / 服务兑换（如家政服务 / 停车券）​</li><li>积分转赠：支持业主间积分互转，转赠记录永久留存</li></ul><ol><li><strong>报事保修闭环</strong>​</li></ol><ul><li>报修类型分类：公共区域 / 户内维修，支持上传故障照片​</li><li>进度跟踪：维修派单 / 处理中 / 已完成状态实时更新​</li><li>评价体系：维修完成后业主可评分并填写反馈​</li></ul><ol><li><strong>房屋租售模块</strong>​</li></ol><ul><li>业主自主发布：填写房源信息及租售需求​</li><li>物业审核：验证房源真实性，审核通过后上线展示​</li><li>带看管理：预约看房时间，生成临时门禁权限​</li></ul><ol><li><strong>社区活动管理</strong>​</li></ol><ul><li>活动发布：支持图文 / 视频形式展示活动详情​</li><li>报名统计：自动汇总报名人数，支持名额限制设置​</li><li>活动签到：扫码签到功能，关联积分奖励</li></ul><h4 id="派单功能的核心内容（模块拆解）"><a href="#派单功能的核心内容（模块拆解）" class="headerlink" title="派单功能的核心内容（模块拆解）"></a>派单功能的核心内容（模块拆解）</h4><p>在物业 ERP 系统中，派单功能是连接 “任务需求” 与 “执行人员” 的核心枢纽，主要解决 “谁来做、做什么、怎么做、何时完成” 的问题，确保维修、保洁、安保等任务高效落地。其设计需覆盖<strong>任务全生命周期管理</strong>，并兼顾灵活性（适应不同任务类型）和可追溯性（便于考核与复盘）。</p><h5 id="1-任务创建：明确-“做什么”"><a href="#1-任务创建：明确-“做什么”" class="headerlink" title="1. 任务创建：明确 “做什么”"></a>1. 任务创建：明确 “做什么”</h5><p>任务是派单的起点，需包含足够信息让执行人员理解需求，核心要素包括：</p><ul><li><strong>基础信息</strong>：任务编号（唯一标识，便于追溯）、任务类型（如维修类：水管漏水 / 电路故障；保洁类：楼道清洁 / 垃圾清运；安保类：巡逻异常处理等）、关联对象（如具体房号、公共区域位置：3 栋 2 单元楼道 / 小区南门广场）。</li><li><strong>优先级</strong>：按紧急程度划分（如 “紧急”：水电中断需 1 小时内处理；“常规”：墙面掉漆 3 天内处理；“低优先级”：绿化修剪 1 周内处理），影响派单顺序。</li><li><strong>任务描述</strong>：详细需求（如 “3 栋 101 业主家厨房水龙头漏水，需更换阀芯”）、附件（业主上传的现场照片 / 视频，辅助执行人员判断工具 / 材料）。</li><li><strong>时间要求</strong>：期望完成时间（如 “2023-10-15 18:00 前”）、允许的执行时间段（如 “业主仅周末在家，需周六 9:00-12:00 处理”）。</li></ul><h5 id="2-任务分配：解决-“谁来做”"><a href="#2-任务分配：解决-“谁来做”" class="headerlink" title="2. 任务分配：解决 “谁来做”"></a>2. 任务分配：解决 “谁来做”</h5><p>根据任务类型、人员能力、负载情况，将任务分配给合适的执行人员，分两种模式：</p><ul><li><p><strong>自动派单</strong>（系统智能分配）：<br>  基于预设规则匹配人员，规则包括：</p><ul><li>技能匹配：如 “电路维修” 仅派给有电工证的维修员；</li><li>区域就近：优先分配给负责该楼栋 / 区域的人员（减少通勤时间）；</li><li>负载均衡：避免某人员积压过多任务（如 A 已有 5 个待处理任务，优先派给仅 2 个任务的 B）；</li><li>历史绩效：优先派给该类型任务完成率高、业主评价好的人员（如维修水管时，优先派给 “漏水维修满意度 95%” 的师傅）。</li></ul></li><li><strong>手动派单</strong>（人工干预）：<br>  适用于特殊场景（如自动派单规则不适用、紧急任务需指定资深人员），派单员（如物业管理员）可在系统中手动选择执行人员，并添加备注（如 “此业主对服务要求高，派张师傅处理”）。</li></ul><h5 id="3-任务执行跟踪：监控-“怎么做、进度如何”"><a href="#3-任务执行跟踪：监控-“怎么做、进度如何”" class="headerlink" title="3. 任务执行跟踪：监控 “怎么做、进度如何”"></a>3. 任务执行跟踪：监控 “怎么做、进度如何”</h5><p>实时记录任务状态，确保过程透明，核心功能包括：</p><ul><li><strong>状态更新</strong>：执行人员通过移动端（APP / 小程序）更新进度，状态包括：<ul><li>待接收→已接收（人员确认接单）→处理中（到达现场开始工作）→待确认（完成工作，等待验收）→已完成（验收通过）；</li><li>异常状态：无法处理（需注明原因，如 “缺少专用工具”）→转派（系统自动或手动重新分配）；暂停（如业主临时有事，约定改期）。</li></ul></li><li><strong>过程记录</strong>：执行人员需填写 “处理日志”，如 “10:30 到达现场，检查发现水龙头阀芯老化，10:45 更换新阀芯，测试无漏水”，并上传完工照片（如修好的水龙头特写）。</li><li><strong>超时预警</strong>：系统自动监控任务是否超期（如 “紧急任务 2 小时未接收”“常规任务 2 天未完成”），通过短信 / 系统消息提醒执行人员和管理员，避免拖延。</li></ul><h5 id="4-任务验收与闭环：确认-“是否做好”"><a href="#4-任务验收与闭环：确认-“是否做好”" class="headerlink" title="4. 任务验收与闭环：确认 “是否做好”"></a>4. 任务验收与闭环：确认 “是否做好”</h5><p>任务完成后需验收，确保符合标准，核心环节包括：</p><ul><li><strong>验收主体</strong>：<ul><li>业主验收：如维修、家政类任务，业主通过 APP 点击 “确认完成”，可附带评价（如 “服务及时，态度好”）；</li><li>内部验收：如公共区域保洁、设施巡检，由管理员现场检查或通过照片验收（如 “3 栋楼道清洁符合标准”）。</li></ul></li><li><strong>验收标准</strong>：系统预设各类型任务的验收项（如维修类：“问题解决 + 无二次损坏 + 现场清理干净”；保洁类：“地面无污渍 + 垃圾桶清空”），验收时逐项确认。</li><li><strong>异议处理</strong>：若验收不通过（如业主反馈 “漏水未彻底解决”），任务退回 “处理中” 状态，执行人员需重新处理，直至验收通过。</li></ul><h5 id="5-异常与兜底机制：应对-“突发情况”"><a href="#5-异常与兜底机制：应对-“突发情况”" class="headerlink" title="5. 异常与兜底机制：应对 “突发情况”"></a>5. 异常与兜底机制：应对 “突发情况”</h5><ul><li><strong>转派功能</strong>：执行人员因特殊原因（如突发疾病、工具不足）无法完成时，可申请转派，管理员审批后重新分配。</li><li><strong>抢单补充</strong>：紧急任务无人接单时，系统发布 “抢单任务”，符合条件的人员可主动抢单，抢单成功后计入绩效（如额外积分奖励）。</li><li><strong>管理员介入</strong>：超时未处理的任务自动升级至管理员待办列表，管理员可手动协调资源（如临时增派人员）。</li></ul><h5 id="6-数据统计与复盘：优化-“派单效率”"><a href="#6-数据统计与复盘：优化-“派单效率”" class="headerlink" title="6. 数据统计与复盘：优化 “派单效率”"></a>6. 数据统计与复盘：优化 “派单效率”</h5><p>通过数据沉淀分析流程问题，核心指标包括：</p><ul><li>派单效率：平均派单时长（从任务创建到人员接收的时间）、各类型任务的平均处理时长；</li><li>人员绩效：每人的任务完成率、验收通过率、业主好评率、超时次数；</li><li>任务分布：各区域 / 类型任务的数量占比（如 “3 栋维修任务占比 30%，推测管道老化需集中检修”）。<h4 id="派单功能的设计要点"><a href="#派单功能的设计要点" class="headerlink" title="派单功能的设计要点"></a>派单功能的设计要点</h4></li></ul><h5 id="1-角色分层设计（适配不同用户）"><a href="#1-角色分层设计（适配不同用户）" class="headerlink" title="1. 角色分层设计（适配不同用户）"></a>1. 角色分层设计（适配不同用户）</h5><ul><li><strong>业主端</strong>：仅需 “提交任务 + 上传附件 + 查看进度 + 验收评价”，界面极简（如小程序表单）；</li><li><strong>执行端（维修 / 保洁人员）</strong>：移动端为主，功能聚焦 “接收任务 + 导航到现场 + 更新状态 + 填写日志 + 上传照片”，支持离线操作（无信号时暂存数据，联网后同步）；</li><li><strong>管理端（物业管理员）</strong>：PC 端 + 移动端，功能包括 “创建 / 分配任务 + 监控全量任务进度 + 处理异常（转派 / 抢单）+ 查看统计报表”，需数据可视化仪表盘（如实时显示 “待派单 20 个、超时 5 个”）。</li></ul><h5 id="2-规则灵活配置（适应不同物业场景）"><a href="#2-规则灵活配置（适应不同物业场景）" class="headerlink" title="2. 规则灵活配置（适应不同物业场景）"></a>2. 规则灵活配置（适应不同物业场景）</h5><ul><li>允许管理员自定义 “优先级规则”（如 “商铺漏水” 比 “住宅漏水” 优先级高）、“派单匹配规则”（如老小区侧重 “区域就近”，高端小区侧重 “绩效优先”）；</li><li>支持不同任务类型的 “流程差异化”：如保洁任务无需业主验收（管理员抽查即可），而维修任务必须业主确认。</li></ul><h5 id="3-与其他模块联动（提升系统整体性）"><a href="#3-与其他模块联动（提升系统整体性）" class="headerlink" title="3. 与其他模块联动（提升系统整体性）"></a>3. 与其他模块联动（提升系统整体性）</h5><ul><li>与 “业主管理” 联动：自动关联任务对应的业主信息（联系方式、房屋户型），方便执行人员沟通；</li><li>与 “物料管理” 联动：维修任务需耗材时（如阀芯、水管），系统自动提示仓库是否有库存，避免 “白跑一趟”；</li><li>与 “绩效系统” 联动：将任务完成情况（数量、质量）自动计入执行人员的绩效考核，生成工资核算依据。</li></ul><h5 id="4-轻量化操作（降低一线人员使用门槛）"><a href="#4-轻量化操作（降低一线人员使用门槛）" class="headerlink" title="4. 轻量化操作（降低一线人员使用门槛）"></a>4. 轻量化操作（降低一线人员使用门槛）</h5><ul><li>执行端减少文字输入：如状态更新用 “点选”（点击 “处理中” 按钮），日志可用语音转文字（“已到达现场，开始检查”）；</li><li>位置自动获取：系统自动定位执行人员位置，与任务地点比对，生成导航路线（减少手动输入地址）。</li></ul><h4 id="商户端核心内容模块（按用户需求分层）"><a href="#商户端核心内容模块（按用户需求分层）" class="headerlink" title="商户端核心内容模块（按用户需求分层）"></a>商户端核心内容模块（按用户需求分层）</h4><h5 id="1-基础信息与资质管理（合规准入层）"><a href="#1-基础信息与资质管理（合规准入层）" class="headerlink" title="1. 基础信息与资质管理（合规准入层）"></a>1. 基础信息与资质管理（合规准入层）</h5><p>这是商户入驻的前提，确保商圈内商户合法合规，也是物业监管的基础。</p><ul><li><strong>商户档案管理</strong>：<ul><li>基础信息：商户名称、类型（餐饮 / 零售 / 维修 / 教育等）、地址（精确到门牌号，关联小区区域，如 “3 号底商”）、营业时间、联系方式（负责人电话、门店电话）、经营面积。</li><li>资质证件：营业执照、食品经营许可证（餐饮类）、从业人员健康证等，支持证件上传、有效期提醒（如 “食品许可证 30 天后到期，请更新”）。</li></ul></li><li><strong>入驻与审核</strong>：<ul><li>商户在线提交入驻申请（填写信息 + 上传证件），物业后台审核（查看材料完整性、是否符合小区商圈规划，如 “禁止高噪音商户入驻”），审核结果实时推送（通过 / 驳回及原因）。</li></ul></li></ul><h5 id="2-服务与商品管理（经营核心层）"><a href="#2-服务与商品管理（经营核心层）" class="headerlink" title="2. 服务与商品管理（经营核心层）"></a>2. 服务与商品管理（经营核心层）</h5><p>商户通过此模块向业主展示服务内容，是连接供需的核心载体。</p><ul><li><strong>服务 / 商品发布</strong>：<ul><li>分类管理：支持按 “日常服务”（如便利店商品、餐馆外卖）、“便民服务”（如家电维修、家政清洁）、“定制服务”（如儿童托管、宠物寄养）分类发布。</li><li>详情配置：包含名称、描述（如 “空调清洗含滤网更换”）、价格（固定价 / 区间价）、图片 / 视频（门店环境、服务过程）、服务范围（如 “仅限本小区内”）、预约规则（如 “需提前 2 小时预约”）。</li><li>库存管理：实体商品（如便利店零食）支持设置库存，售罄自动下架；服务类可设置每日最大接单量（如 “家政服务每日限 5 单”）。</li></ul></li><li><strong>内容更新</strong>：支持随时编辑、上下架服务，修改价格需明确生效时间（如 “明日 0 点起调价”），避免纠纷。</li></ul><h5 id="3-订单与交易管理（履约层）"><a href="#3-订单与交易管理（履约层）" class="headerlink" title="3. 订单与交易管理（履约层）"></a>3. 订单与交易管理（履约层）</h5><p>处理业主的下单、支付、履约全流程，确保服务闭环。</p><ul><li><strong>订单接收与处理</strong>：<ul><li>实时提醒：业主下单后，商户端（APP / 小程序）弹窗 + 语音提醒（如 “新订单：3 栋 101 业主预约家电维修”）。</li><li>订单操作：支持 “接单”“拒单”（需填写理由，如 “今日已约满”）、“修改时间”（与业主协商后调整上门时间）。</li></ul></li><li><strong>履约跟踪</strong>：<ul><li>服务类：记录 “已接单→上门中→服务中→服务完成” 状态，上门时可上传定位（证明已到达），完成后上传服务成果照片（如 “维修后的空调”）。</li><li>商品类：支持 “接单→备货→配送中→已送达” 状态，关联小区配送规则（如 “外卖需由物业岗亭暂存，通知业主自取”）。</li></ul></li><li><strong>支付与结算</strong>：<ul><li>对接物业统一支付通道（微信 / 支付宝），业主支付后资金暂存物业账户（避免纠纷），按约定周期（如 T+1）与商户结算（扣除约定佣金，如 3%）。</li><li>账单明细：展示每笔订单的金额、佣金、结算状态，支持导出流水（便于商户对账）。</li></ul></li></ul><h5 id="4-营销与互动管理（获客层）"><a href="#4-营销与互动管理（获客层）" class="headerlink" title="4. 营销与互动管理（获客层）"></a>4. 营销与互动管理（获客层）</h5><p>帮助商户触达业主、提升订单量，同时丰富小区商圈生态。</p><ul><li><strong>优惠活动</strong>：<ul><li>商户可自主创建活动（如 “满减券”“新客首单 8 折”“买二送一”），设置活动时间、适用范围（如 “仅限小区业主”），提交物业审核（避免虚假宣传）后发布。</li><li>活动数据跟踪：展示优惠券领取量、核销率、带动订单量，辅助商户优化策略。</li></ul></li><li><strong>业主互动</strong>：<ul><li>消息中心：接收业主咨询（如 “是否支持上门取件”），支持文字 / 图片回复；推送物业通知（如 “明日小区检修，暂停外卖配送”）。</li><li>评价管理：查看业主对服务的评分（1-5 星）和文字评价，支持回复（如 “感谢建议，已加强员工培训”），差评可申请物业介入协调。</li></ul></li></ul><h5 id="5-物业协同与监管（合规运营层）"><a href="#5-物业协同与监管（合规运营层）" class="headerlink" title="5. 物业协同与监管（合规运营层）"></a>5. 物业协同与监管（合规运营层）</h5><p>连接商户与物业的协作通道，确保商圈有序运营。</p><ul><li><strong>物业通知</strong>：接收物业发布的商圈规则（如 “节假日营业时间限制”）、安全检查通知（如 “周三消防检查，请准备”）、违规预警（如 “门口堆放杂物，需 2 小时内清理”）。</li><li><strong>资源申请</strong>：商户可在线申请使用公共资源（如 “周末促销需占用门前 5㎡区域”“申请接入小区公共 WiFi”），物业审核通过后生效。</li><li><strong>报修与服务</strong>：商户可向物业提交维修申请（如 “门店水电故障”），跟踪处理进度，与业主报修共用一套派单系统（但标记 “商户专属” 优先级）。</li></ul><h5 id="6-数据与经营分析（决策层）"><a href="#6-数据与经营分析（决策层）" class="headerlink" title="6. 数据与经营分析（决策层）"></a>6. 数据与经营分析（决策层）</h5><p>帮助商户了解经营状况，优化服务策略。</p><ul><li><strong>核心数据看板</strong>：展示今日 / 本周订单量、交易额、新老客户占比、热门服务 / 商品排行（如 “空调维修订单占比 60%”）。</li><li><strong>趋势分析</strong>：按日 / 周 / 月展示交易趋势（如 “周末订单量是工作日的 1.5 倍”）、用户画像（如 “30-40 岁业主下单占比 70%”），辅助商户调整库存或营业时间。</li></ul><h4 id="商户端设计要点（兼顾效率与体验）"><a href="#商户端设计要点（兼顾效率与体验）" class="headerlink" title="商户端设计要点（兼顾效率与体验）"></a>商户端设计要点（兼顾效率与体验）</h4><h5 id="1-多端适配，操作轻量化"><a href="#1-多端适配，操作轻量化" class="headerlink" title="1. 多端适配，操作轻量化"></a>1. 多端适配，操作轻量化</h5><ul><li><strong>商户移动端（APP / 小程序）</strong>：聚焦高频操作 —— 接单提醒、订单处理、简单营销（如发布优惠券），界面简洁（首页直接显示 “待处理订单”“今日数据”），支持语音输入（如快速回复业主咨询）。</li><li><strong>商户 PC 端（管理后台）</strong>：用于复杂操作 —— 批量上传商品、查看详细报表、编辑资质信息，支持键盘快捷操作（如 Ctrl+S 保存）。</li><li><strong>物业管理端</strong>：独立后台，功能包括商户审核、规则设置、订单监管、纠纷处理，配备数据仪表盘（如 “商圈本月总交易额”“投诉率 TOP3 商户”）。</li></ul><h5 id="2-与业主端、物业系统深度联动"><a href="#2-与业主端、物业系统深度联动" class="headerlink" title="2. 与业主端、物业系统深度联动"></a>2. 与业主端、物业系统深度联动</h5><ul><li><strong>业主端入口</strong>：业主在物业 APP 的 “商圈” 板块可直接看到商户列表（按距离 / 评分排序），点击跳转服务详情页，实现 “浏览 - 下单 - 评价” 闭环。</li><li><strong>物业系统对接</strong>：<ul><li>与 “收费系统” 联动：商户租金、物业费自动生成账单，逾期未缴时限制服务发布权限。</li><li>与 “安保系统” 联动：商户员工可申请 “小区出入证”（关联人脸识别），方便上门服务时快速通行。</li></ul></li></ul><h5 id="3-规则灵活配置，适配不同商圈场景"><a href="#3-规则灵活配置，适配不同商圈场景" class="headerlink" title="3. 规则灵活配置，适配不同商圈场景"></a>3. 规则灵活配置，适配不同商圈场景</h5><ul><li>支持物业按商圈类型（如 “社区底商”“商业综合体”）自定义规则：如社区底商禁止 22 点后营业，综合体商户可申请夜间促销；</li><li>佣金比例可按商户类型差异化设置（如餐饮类 5%，便民服务类 2%），支持阶梯佣金（交易额越高，佣金比例越低）。</li></ul><h5 id="4-风险控制与纠纷处理"><a href="#4-风险控制与纠纷处理" class="headerlink" title="4. 风险控制与纠纷处理"></a>4. 风险控制与纠纷处理</h5><ul><li><strong>交易担保</strong>：业主支付资金暂存物业账户，确认服务完成后再结算给商户，避免 “服务未达标但已收款” 纠纷；</li><li><strong>违规处理</strong>：设置商户信用分（初始 100 分，违规一次扣 10 分），低于 60 分限制接单，严重违规（如无证经营）强制下架并公示。</li></ul><p>通过以上设计，商户端既能满足商户 “便捷经营、高效获客” 的需求，也能帮助物业实现 “规范管理、提升商圈价值” 的目标，最终为业主提供 “可信、便捷、多样” 的社区服务，形成 “商户盈利 + 物业增值 + 业主满意” 的良性循环。</p><h4 id="（四）系统设置"><a href="#（四）系统设置" class="headerlink" title="（四）系统设置"></a>（四）系统设置</h4><ol><li><strong>系统设置</strong></li></ol><ul><li><p>物业可对系统进行个性化设置，包括收费项目的显示顺序、账单格式（字体、颜色、布局）、支付渠道的配置（添加或删除支付渠道）、数据备份策略（自动备份时间间隔、备份存储位置）等。例如，可根据物业的品牌形象，自定义账单的抬头和 logo，调整账单字体大小和颜色，使其更符合业主阅读习惯。系统设置界面应简洁直观，方便物业人员进行操作和管理。</p></li><li><p>可对物联网设备的对接参数、数据传输频率、预警阈值等进行配置，确保物联网功能的稳定运行。物联网设备配置可提供详细的参数说明和操作指南，帮助物业人员正确配置设备，提高设备的使用效率和稳定性。</p></li></ul><ol><li><strong>权限管理</strong></li></ol><ul><li><p>采用严格的权限管理机制，为不同岗位的物业人员分配不同的操作权限。例如，物业收费员拥有查看业主账单、收取费用、记录支付信息的权限；物业经理拥有审批账单调整、查看所有财务报表、设置系统参数的权限；财务人员拥有审核财务数据、生成财务报表、进行财务分析的权限。权限管理可根据物业的组织架构和业务流程进行灵活配置，确保系统数据的安全性和操作的规范性。</p></li><li><p>通过权限管理，确保系统数据的安全性和操作的规范性，避免因权限滥用导致的数据泄露和错误操作。系统会记录用户的操作权限变更历史，方便进行审计和追溯。<br><img src="/article/5dada370/baaaf87c93f62d90711b295e5ba2a892_compress.jpg" alt></p><h2 id="三、人力资源模块"><a href="#三、人力资源模块" class="headerlink" title="三、人力资源模块"></a>三、人力资源模块</h2></li></ul><h3 id="（一）员工档案管理"><a href="#（一）员工档案管理" class="headerlink" title="（一）员工档案管理"></a>（一）员工档案管理</h3><ol><li><strong>信息录入与维护</strong></li></ol><ul><li><p>支持批量导入员工基本信息，包括姓名、性别、年龄、联系方式、身份证号、入职时间、职位、部门、学历、专业、工作经历、培训经历等。同时，可对单个员工信息进行详细编辑，如员工职位晋升、部门调动、薪资调整等信息变更时，能及时更新系统数据。系统会对导入的员工信息进行格式校验和逻辑检查，确保信息的准确性和完整性。</p></li><li><p>员工信息可与其他模块进行关联，如薪酬管理模块、绩效管理模块等，实现数据的共享和同步。例如，当员工职位晋升时，系统会自动更新员工的薪酬信息和绩效指标。</p></li></ul><ol><li><strong>信息关联与查询</strong></li></ol><ul><li><p>对员工信息进行多维度分类，如按部门、职位层级、入职时间、学历、专业等。提供强大的查询功能，可通过关键词、筛选条件等快速定位到所需员工信息，方便企业进行人员管理和数据分析。查询结果可按不同的排序方式进行展示，如按姓名、入职时间、职位等排序。</p></li><li><p>支持对员工信息进行统计分析，如统计各部门的员工数量、各职位层级的员工比例、不同学历和专业的员工分布等。统计分析结果可通过图表和报表的形式进行展示，为企业制定人力资源规划和决策提供数据支持。</p></li></ul><ol><li><strong>合同管理</strong></li></ol><ul><li><p>记录员工劳动合同的签订、续签、变更、终止等信息，设置合同到期提醒功能，提前通知人力资源部门和员工，避免因合同管理不善带来的法律风险。合同信息可包括合同编号、合同类型、签订日期、生效日期、终止日期、合同条款等。</p></li><li><p>系统支持合同模板的管理，人力资源部门可根据不同的合同类型和岗位需求，创建和维护合同模板。在签订合同时，可直接引用合同模板，提高合同签订的效率和准确性。</p></li></ul><h3 id="（二）招聘管理"><a href="#（二）招聘管理" class="headerlink" title="（二）招聘管理"></a>（二）招聘管理</h3><ol><li><strong>职位发布</strong></li></ol><ul><li><p>可在多个招聘平台一键发布招聘职位，自定义职位描述、任职要求、薪资待遇等信息。实时跟踪职位发布状态，统计各平台的招聘效果数据，如简历投递量、浏览量、面试邀请量、录用人数等。招聘平台可包括常见的招聘网站、社交媒体平台等，系统会与各招聘平台进行数据对接，实现招聘信息的同步发布和管理。</p></li><li><p>支持对招聘职位进行分类管理，如按部门、职位类型、招聘渠道等分类。可设置不同的招聘状态，如招聘中、暂停招聘、已结束招聘等，方便企业对招聘职位进行管理和跟踪。</p></li></ul><ol><li><strong>简历筛选</strong></li></ol><ul><li><p>建立简历库，对收到的简历进行智能筛选。根据预设的关键词、学历、工作经验、技能要求等条件，自动筛选出符合要求的简历，并将其推荐给招聘负责人。支持手动筛选简历，方便招聘人员根据实际需求进行灵活处理。简历库可对简历进行分类存储和管理，如按招聘职位、投递时间、筛选状态等分类。</p></li><li><p>系统会对简历进行评分和排序，根据简历与职位要求的匹配度，对简历进行评分，并按评分高低进行排序。招聘人员可根据评分和排序结果，快速筛选出合适的简历。</p></li></ul><ol><li><strong>面试管理</strong></li></ol><ul><li><p>在线安排面试流程，包括面试时间、面试官、面试地点等。面试官可在面试过程中实时记录面试评价和意见，面试结束后，系统自动汇总面试结果，为录用决策提供参考。面试安排可与面试官和候选人的日程进行冲突检查，避免面试时间冲突。</p></li><li><p>支持面试通知的自动发送，可通过短信、邮件等方式向候选人发送面试通知。面试通知内容可包括面试时间、地点、面试官信息、面试流程等。同时，系统会记录候选人的面试反馈和录用结果，方便企业进行人才储备和后续跟踪。</p></li></ul><h3 id="（三）培训管理"><a href="#（三）培训管理" class="headerlink" title="（三）培训管理"></a>（三）培训管理</h3><ol><li><strong>培训计划制定</strong></li></ol><ul><li><p>根据企业发展战略和员工培训需求，制定年度、季度、月度培训计划。培训计划内容包括培训主题、培训时间、培训地点、培训讲师、参与人员、培训方式（线上、线下）、培训预算等信息。支持对培训计划进行灵活调整，以适应企业业务变化和员工实际需求。培训计划可按部门、职位层级、培训类型等进行分类管理，方便企业进行培训资源的分配和管理。</p></li><li><p>系统会对培训计划进行审批流程管理，培训计划需经过相关部门和领导的审批通过后才能执行。审批流程可根据企业的组织架构和管理要求进行自定义设置。</p></li></ul><ol><li><strong>培训课程管理</strong></li></ol><ul><li><p>创建和管理培训课程库，包括课程名称、课程内容、课程时长、适用对象、培训方式、培训资源等信息。可上传培训资料，如文档、视频、PPT 等，方便员工在线学习。对培训课程进行评价和反馈收集，以便不断优化课程质量。培训课程库可对课程进行分类存储和管理，如按培训类型、课程难度、适用岗位等分类。</p></li><li><p>支持培训课程的在线学习和考试功能，员工可通过系统在线学习培训课程，并参加在线考试。系统会记录员工的学习进度和考试成绩，为员工的培训效果评估提供依据。</p></li></ul><ol><li><strong>培训记录与统计</strong></li></ol><ul><li><p>自动记录员工的培训参与情况，包括培训签到、培训时长、考试成绩等信息。生成培训统计报表，展示各部门、各员工的培训完成情况，为企业评估培训效果和制定后续培训计划提供数据支持。培训统计报表可按不同的时间周期、部门、培训类型等进行细分，为企业深入分析培训效果提供数据支持。</p></li><li><p>系统会对培训效果进行评估和分析，通过员工的考试成绩、培训反馈等数据，评估培训课程的质量和培训效果。同时，系统会根据评估结果，为企业提供培训改进建议，帮助企业优化培训计划和课程内容。</p></li></ul><h3 id="（四）绩效管理"><a href="#（四）绩效管理" class="headerlink" title="（四）绩效管理"></a>（四）绩效管理</h3><ol><li><strong>绩效指标设定</strong></li></ol><ul><li><p>支持企业自定义绩效指标体系，可根据不同部门、职位设置个性化的绩效指标和权重。绩效指标类型包括定量指标（如收费额度、工作完成量、工作效率等）和定性指标（如工作态度、团队协作、沟通能力等）。绩效指标可与企业的战略目标和业务流程进行关联，确保绩效指标的合理性和有效性。</p></li><li><p>系统会对绩效指标进行审核和审批流程管理，绩效指标需经过相关部门和领导的审核和审批通过后才能生效。审批流程可根据企业的组织架构和管理要求进行自定义设置。</p></li></ul><ol><li><strong>绩效评估</strong></li></ol><ul><li><p>定期进行绩效评估，评估方式包括上级评价、同事互评、自评等多种方式。评估过程中，评估人可在线填写评估意见和评分，系统自动汇总计算员工的绩效得分。绩效评估周期可根据企业的实际情况进行设置，如月度、季度、年度等。</p></li><li><p>系统会对绩效评估结果进行公示和反馈，员工可查看自己的绩效评估详情，提出异议和申诉。同时，系统会根据绩效评估结果，为员工提供个性化的绩效改进建议，帮助员工提升工作绩效。</p></li></ul><ol><li><strong>绩效反馈与改进</strong></li></ol><ul><li><p>绩效评估结果生成后，及时反馈给员工。员工可查看自己的绩效评估详情，提出异议和申诉。同时，系统根据绩效评估结果，为员工提供个性化的绩效改进建议，帮助员工提升工作绩效。绩效反馈可通过面谈、邮件、系统站内信等方式进行，确保员工能够及时了解自己的绩效情况和改进方向。</p></li><li><p>系统会对员工的绩效改进情况进行跟踪和评估，通过对比员工的前后绩效评估结果，评估员工的绩效改进效果。同时，系统会根据评估结果，为企业提供绩效改进建议，帮助企业优化绩效管理体系。</p></li></ul><h3 id="（五）考勤管理"><a href="#（五）考勤管理" class="headerlink" title="（五）考勤管理"></a>（五）考勤管理</h3><ol><li><strong>打卡数据集成</strong></li></ol><ul><li><p>对接支持企微的考勤设备（指纹、人脸识别等），对齐企微审批流程 (请假，调休等)。系统会实时获取考勤设备的打卡数据，并与企微审批流程中的请假、调休等数据进行同步，确保考勤数据的准确性和完整性。</p></li><li><p>支持对考勤数据进行清洗和预处理，去除异常打卡数据，如重复打卡、打卡时间异常等。同时，系统会对考勤数据进行加密存储和传输，保障考勤数据的安全性。</p></li></ul><ol><li><strong>考勤规则设置</strong></li></ol><ul><li><p>自定义考勤规则（出勤时间、加班计算等）。可根据企业的实际情况，设置不同的考勤制度，如标准工时制、综合计算工时制、不定时工时制等。考勤规则可包括正常出勤时间、迟到早退判定标准、加班计算方式、请假类型和审批流程等。</p></li><li><p>自动计算加班时间和请假天数。系统会根据考勤数据和考勤规则，自动计算员工的加班时间和请假天数，并生成相应的考勤报表。考勤报表可按不同的时间周期、部门、员工等进行细分，为企业进行考勤管理和薪酬核算提供数据支持。</p></li><li><p>生成工资表和运营考核数据。系统会根据考勤数据和薪酬规则，自动生成员工的工资表。同时，系统会将考勤数据与运营考核指标进行关联，为企业进行运营考核提供数据支持。</p></li><li><p>根据打卡记录和人事审批流程 (请假，调休等) 和员工工资类型生成工资单。工资单可包括基本工资、绩效工资、加班工资、奖金、补贴、扣款等项目，详细展示员工的工资构成和计算过程。</p></li></ul><ol><li><strong>自动化考勤处理</strong></li></ol><ul><li><p>智能修正系统：支持一键修正异常打卡记录（如漏打卡）、批量修正部门考勤数据、个人原始记录追溯。智能修正系统可根据预设的规则和算法，对异常打卡记录进行自动修正，提高考勤处理的效率和准确性。</p></li><li><p>假勤联动：企业微信审批通过的请假 / 出差数据自动同步至考勤系统，免除人工核对。系统会实时获取企微审批流程中的请假、出差等数据，并自动更新考勤系统中的员工考勤状态，确保考勤数据的及时性和准确性。</p></li><li><p>异常预警：实时标记迟到、早退、旷工等异常情况，自动推送至相关人员。异常预警可通过短信、系统弹窗、邮件等方式发送给相关人员，如员工本人、部门负责人、人力资源部门等。同时，系统会记录异常考勤情况的详细信息，如异常时间、异常类型、处理结果等，方便企业进行考勤管理和分析。</p></li></ul><ol><li><strong>多维考勤分析</strong></li></ol><ul><li><p>实时仪表盘：展示部门 / 个人出勤率、异常率、加班时长排名等关键指标。实时仪表盘可通过图表和报表的形式，直观展示企业的考勤情况和员工的工作状态。关键指标可按不同的时间周期、部门、员工等进行细分，为企业进行考勤管理和决策提供数据支持。</p></li><li><p>自定义报表：支持按需生成汇总统计表（如部门月考勤汇总）和明细记录表（如个人年度出勤明细）。自定义报表可根据企业的实际需求，选择不同的统计维度和报表格式，生成个性化的考勤报表。报表可导出为 Excel、PDF 等常见格式，方便企业进行数据存档和分析。</p></li><li><p>数据归档：每月自动归档考勤数据并锁定，作为薪酬核算依据。数据归档可确保考勤数据的安全性和完整性，避免数据被篡改和误操作。同时，归档后的数据可作为历史数据进行查询和分析，为企业进行考勤管理和决策提供参考。</p></li></ul><ol><li><strong>异常考勤提醒</strong></li></ol><ul><li><p>对迟到、早退、旷工等情况自动提醒。提醒方式可包括短信、系统弹窗、邮件等，提醒内容可包括异常考勤时间、异常类型、处理要求等。</p></li><li><p>通知员工和相关管理人员及时处理。系统会将异常考勤提醒发送给员工本人和相关管理人员，如部门负责人、人力资源部门等，确保异常考勤情况能够及时得到处理。</p></li></ul><h3 id="（六）薪酬管理"><a href="#（六）薪酬管理" class="headerlink" title="（六）薪酬管理"></a>（六）薪酬管理</h3><ol><li><strong>薪酬结构设置</strong></li></ol><ul><li><p>可自定义薪酬结构，包括基本工资、绩效工资、奖金、补贴、社保公积金等项目。根据企业薪酬政策和员工实际情况，灵活设置各薪酬项目的计算方式和发放规则。薪酬结构可根据不同的部门、职位、员工级别等进行差异化设置，确保薪酬的公平性和合理性。</p></li><li><p>系统会对薪酬结构进行审批流程管理，薪酬结构需经过相关部门和领导的审批通过后才能生效。审批流程可根据企业的组织架构和管理要求进行自定义设置。</p></li></ul><ol><li><strong>薪酬计算与发放</strong></li></ol><ul><li><p>根据员工考勤、绩效等数据，自动计算员工薪酬。系统会根据薪酬结构和计算规则，结合员工的考勤数据、绩效数据等，自动计算员工的薪酬。薪酬计算过程可进行详细的展示和查询，确保薪酬计算的准确性和透明度。</p></li><li><p>支持批量生成工资条，通过邮件、短信等方式发送给员工。工资条可包括基本工资、绩效工资、奖金、补贴、扣款、社保公积金等项目，详细展示员工的工资构成和计算过程。员工可通过系统在线查看和下载工资条，方便员工进行工资核对和管理。</p></li><li><p>与银行系统对接，实现薪酬的自动发放，确保薪酬发放的准确性和及时性。系统会将员工的薪酬数据发送给银行系统，银行系统根据薪酬数据进行工资发放。薪酬发放记录可在系统中进行查询和管理，方便企业进行薪酬核算和审计。</p></li></ul><ol><li><strong>薪酬报表生成</strong></li></ol><ul><li><p>生成各类薪酬报表，如工资明细表、薪酬汇总表、社保公积金缴纳报表等。报表支持导出为 Excel、PDF 等格式，方便企业进行财务核算和数据分析。薪酬报表可按不同的时间周期、部门、员工等进行细分，为企业进行薪酬管理和决策提供数据支持。</p></li><li><p>系统会对薪酬报表进行分析和预警，通过对薪酬数据的分析，发现薪酬管理中存在的问题和风险，如薪酬过高或过低、薪酬差距过大等。同时，系统会根据分析结果，为企业提供薪酬调整建议，帮助企业优化薪酬管理体系。</p></li></ul><h3 id="（七）预算制定"><a href="#（七）预算制定" class="headerlink" title="（七）预算制定"></a>（七）预算制定</h3><ol><li><strong>预算项目设置</strong></li></ol><ul><li><p>支持自定义各管理层级（集团级、分子公司级、部门级）人力资源预算项目，包括薪酬预算、招聘预算、培训预算、福利预算等。设置每个预算项目的核定方式，如按年、按月核定，以及是否严格控制预算额度。预算项目可根据企业的实际情况进行灵活调整和扩展，确保预算项目的全面性和合理性。</p></li><li><p>系统会对预算项目进行分类管理，如按预算类型、预算周期、预算部门等分类。可设置不同的预算项目状态，如预算编制中、预算审批中、预算执行中、预算调整中等，方便企业对预算项目进行管理和跟踪。</p></li></ul><ol><li><strong>预算编制</strong></li></ol><ul><li><p>提供多种预算编制方式，可按年度设置单位年度预算、单位月度预算、部门年度预算、部门月度预算。支持手工输入预算金额，也可通过公式计算预算值，如根据历史数据、业务增长预测等自动生成预算。支持对预算数据进行校验，确保在同一个年度内，本单位的单位年度预算大于等于各部门年度预算之和；在同一个月度内，本单位的单位月度预算大于等于各部门月度预算之和；单位年度预算 / 部门年度预算大于等于本年度下各月度预算之和。预算编制过程可进行详细的记录和审计，确保预算编制的准确性和合规性。</p></li><li><p>系统会提供预算编制模板和指引，帮助企业进行预算编制。同时，系统会对预算编制进度进行跟踪和提醒，确保预算编制工作按时完成。</p></li></ul><ol><li><strong>预算执行监控</strong></li></ol><ul><li><p>实时监控预算执行情况，将实际发生的费用与预算数据进行对比。当费用接近或超出预算时，系统自动发出预警，提醒相关人员采取措施。支持按部门、预算项目等维度查看预算执行进度，生成预算执行报表，直观展示预算执行情况。预算执行报表可按不同的时间周期、部门、预算项目等进行细分，为企业进行预算管理和决策提供数据支持。</p></li><li><p>系统会对预算执行情况进行分析和预警，通过对预算执行数据的分析，发现预算执行中存在的问题和风险，如预算超支、预算执行进度缓慢等。同时，系统会根据分析结果，为企业提供预算调整建议，帮助企业优化预算管理体系。</p></li></ul><ol><li><strong>预算调整</strong></li></ol><ul><li><p>当企业业务发生变化或出现特殊情况时，可对预算进行灵活调整。调整预算需经过严格的审批流程，审批通过后，系统自动更新预算数据，并记录调整原因、调整时间、调整人等信息，以便后续追溯和审计。预算调整流程可根据企业的组织架构和管理要求进行自定义设置，确保预算调整的合规性和合理性。</p></li><li><p>系统会对预算调整情况进行跟踪和监控，及时更新预算执行报表和预警信息。同时，系统会对预算调整的效果进行评估和分析，为企业进行预算管理和决策提供参考。</p></li></ul><h4 id="（八）社保管理系统​"><a href="#（八）社保管理系统​" class="headerlink" title="（八）社保管理系统​"></a>（八）社保管理系统​</h4><ol><li><strong>社保方案配置</strong>​</li></ol><ul><li>支持多套社保方案并行（如本地员工 / 异地员工不同方案）​</li><li>自定义缴费基数上下限及比例，自动计算个人 / 单位缴纳金额​</li><li>社保方案变更历史记录，支持版本回溯查询​</li></ul><ol><li><strong>增减员管理</strong>​</li></ol><ul><li>员工入职自动触发社保新增流程，生成办理清单​</li><li>离职员工社保停缴提醒，支持批量停缴操作​</li><li>社保异动记录：记录参保人员信息变更 / 险种调整历史​</li></ul><ol><li><strong>统计分析功能</strong>​</li></ol><ul><li>社保缴纳汇总表：按部门 / 月份统计社保总支出​</li><li>合规性检查：自动校验缴费基数与工资匹配度​</li><li>社保成本预测：根据人员增减计划预估未来 6 个月社保支出​</li></ul><h4 id="（九）招聘管理系统​"><a href="#（九）招聘管理系统​" class="headerlink" title="（九）招聘管理系统​"></a>（九）招聘管理系统​</h4><ol><li><strong>人才库建设</strong>​</li></ol><ul><li>简历分类存储：按岗位 / 学历 / 工作经验等标签分类​</li><li>简历查重功能：自动识别重复投递简历并合并​</li><li>人才画像：自动提取简历关键信息生成可视化人才档案​</li></ul><ol><li><strong>面试流程管理</strong>​</li></ol><ul><li>自定义流程节点：支持设置初试 / 复试 / 终试等环节​</li><li>面试官评分系统：在线填写评分表，自动汇总平均分​</li><li>面试日程安排：自动发送面试邀请，同步至企微日历</li></ul><h2 id="五、自定义报表与数据分析"><a href="#五、自定义报表与数据分析" class="headerlink" title="五、自定义报表与数据分析"></a>五、自定义报表与数据分析</h2><ol><li><strong>数据选择与整合</strong></li></ol><ul><li><p>从系统各功能模块（员工信息管理、用人成本管理、财务费用管理、绩效管理、薪酬管理等）中自由选择需要分析的数据字段，如员工数量、招聘成本、培训满意度、绩效得分、薪酬总额等。支持对不同来源的数据进行整合，以便进行综合分析。数据选择和整合过程可通过可视化界面进行操作，方便用户进行数据筛选和处理。</p></li><li><p>系统会对数据进行清洗和预处理，去除重复数据、缺失数据、异常数据等，确保数据的准确性和完整性。同时，系统会对数据进行加密存储和传输，保障数据的安全性。</p></li></ul><ol><li><strong>图表类型选择</strong></li></ol><ul><li><p>提供丰富多样的图表类型，包括柱状图（普通柱状图、堆叠柱状图、百分比堆叠柱状图）、条形图、折线图、饼图、圆环图、漏斗图、进度图、指标卡等。用户可根据数据特点和分析目的，选择最合适的图表类型来展示数据。例如，使用柱状图对比不同部门的员工数量，用折线图展示员工绩效得分的变化趋势，用饼图分析薪酬结构中各项目的占比等。图表类型选择界面应简洁直观，方便用户进行操作和切换。</p></li><li><p>系统会根据数据特点和分析目的，提供图表类型推荐功能，帮助用户选择最合适的图表类型。同时，系统会对图表进行优化和美化，提高图表的可读性和可视化效果。</p></li></ul><ol><li><strong>图表样式自定义</strong></li></ol><ul><li><p>自定义图表的样式，包括图表标题、坐标轴标签、颜色、字体、数据标签显示方式等。可对图表进行美化，使其更符合企业的品牌形象和用户的视觉需求。同时，支持设置图表的交互功能，如鼠标悬停显示数据详情、点击图表元素进行数据下钻等，方便用户深入分析数据。图表样式自定义界面应提供丰富的设置选项和示例，方便用户进行操作和调整。</p></li><li><p>系统会保存用户自定义的图表样式，方便用户下次使用。同时，系统会提供图表样式模板，用户可直接引用模板进行图表样式设置，提高图表样式设置的效率。</p></li></ul><ol><li><strong>保存与分享图表</strong></li></ol><ul><li><p>用户可将自定义设计好的数据分析图表保存到个人图表库中，方便随时查看和使用。支持将图表以图片、PDF、链接等形式分享给其他人员，实现数据的共享和交流。在分享图表时，可同时附上图表的分析说明和解读，帮助他人更好地理解数据含义。图表保存和分享功能应简单易用，方便用户进行操作和管理。</p></li><li><p>系统会对图表的访问权限进行管理，用户可设置不同的访问权限，如公开、内部共享、私密等，确保数据的安全性和保密性。</p></li></ul><h3 id="六、物联网系统"><a href="#六、物联网系统" class="headerlink" title="六、物联网系统"></a>六、物联网系统</h3><h4 id="（一）水电智能表集成​"><a href="#（一）水电智能表集成​" class="headerlink" title="（一）水电智能表集成​"></a>（一）水电智能表集成​</h4><ol><li>自动抄表实现​</li></ol><ul><li>通信协议支持：兼容 NB-IoT/LoRa 等主流物联网协议​</li><li>抄表任务调度：可设置每日固定时段自动抄表，支持手动补抄​</li><li>数据校验规则：与历史用量偏差超 30% 自动标记异常​</li></ul><ol><li>异常处理机制​</li></ol><ul><li>表具离线告警：智能表超过 24 小时未上传数据触发提醒​</li><li>用量突增预警：单月用量较上月增长 50% 以上自动提示核查​</li><li>远程阀门控制：欠费超 60 天可远程关停水阀，缴费后自动开启​</li></ul><h4 id="（二）道闸门禁联动​"><a href="#（二）道闸门禁联动​" class="headerlink" title="（二）道闸门禁联动​"></a>（二）道闸门禁联动​</h4><ol><li>费用关联控制​</li></ol><ul><li>物业费欠费超 30 天：道闸识别车牌后显示欠费提醒，需人工确认放行​</li><li>车位费到期预警：到期前 7 天推送续费提醒，到期后限制入场​</li><li>临时车辆联动：离场时未缴停车费自动触发道闸拦截​</li></ul><ol><li>一人一卡管理​</li></ol><ul><li>实体卡与身份绑定：员工 / 业主 IC 卡关联个人信息​</li><li>权限精细化控制：设置不同区域 / 时段的门禁权限​</li><li>刷卡记录分析：异常刷卡（如非工作时段）自动预警​</li></ul><h4 id="（三）移动端物联网监控​"><a href="#（三）移动端物联网监控​" class="headerlink" title="（三）移动端物联网监控​"></a>（三）移动端物联网监控​</h4><ol><li>设备状态监控​</li></ol><ul><li>智能表具在线率实时展示，支持按区域筛选​</li><li>道闸运行状态：开关门时间 / 故障次数统计​</li><li>异常事件推送：设备故障 / 异常使用实时推送至负责人​</li></ul><ol><li>远程操作功能​</li></ol><ul><li>远程抄表：移动端发起即时抄表指令​</li><li>门禁临时授权：生成时效型开门二维码，支持分享给访客​</li><li>数据导出：移动端可下载设备运行报表（支持 Excel/PDF 格式）</li></ul><h2 id="七、非功能需求"><a href="#七、非功能需求" class="headerlink" title="七、非功能需求"></a>七、非功能需求</h2><h3 id="（一）性能需求"><a href="#（一）性能需求" class="headerlink" title="（一）性能需求"></a>（一）性能需求</h3><ol><li><strong>响应时间</strong></li></ol><ul><li><p>系统在正常负载情况下，各类操作（如数据查询、报表生成、业务流程处理等）的响应时间应控制在 3 秒以内，确保用户能够获得及时、流畅的使用体验。对于复杂的数据分析操作，响应时间最长不超过 10 秒。系统会对操作响应时间进行监控和统计，及时发现性能瓶颈和问题，并采取相应的优化措施。</p></li><li><p>系统会根据用户的使用习惯和操作频率，对常用功能和数据进行缓存，提高系统的响应速度。同时，系统会采用分布式架构和负载均衡技术，提高系统的并发处理能力和性能稳定性。</p></li></ul><ol><li><strong>吞吐量</strong></li></ol><ul><li><p>系统应具备良好的并发处理能力，能够支持至少 500 个用户同时在线操作。在高并发情况下，系统的吞吐量应满足企业日常业务处理的需求，确保数据的准确性和完整性。系统会对并发用户数和吞吐量进行监控和统计，及时发现性能瓶颈和问题，并采取相应的优化措施。</p></li><li><p>系统会采用数据库优化技术和缓存技术，提高数据的读写性能和并发处理能力。同时，系统会对业务流程进行优化和简化，减少不必要的操作和数据传输，提高系统的吞吐量。</p></li></ul><ol><li><strong>数据存储</strong></li></ol><ul><li><p>根据企业规模和数据增长预测，合理规划数据存储容量。系统应能够安全存储企业多年的人力资源管理数据，确保数据的长期保存和有效利用。同时，定期进行数据备份，防止数据丢失。数据存储应采用安全可靠的存储设备和存储架构，如磁盘阵列、分布式文件系统等。</p></li><li><p>系统会对数据存储进行管理和维护，包括数据清理、数据归档、数据恢复等。同时，系统会对数据备份进行监控和管理，确保数据备份的及时性和完整性。</p></li></ul><h3 id="（二）安全需求"><a href="#（二）安全需求" class="headerlink" title="（二）安全需求"></a>（二）安全需求</h3><ol><li><strong>用户认证与授权</strong></li></ol><ul><li><p>采用严格的用户认证机制，如用户名 / 密码、短信验证码等多种方式相结合，确保用户身份的真实性。根据用户的角色和职责，为其分配不同的操作权限，如人力资源管理员拥有人力模块的全部操作权限，普通员工仅能查看和修改自己的个人信息等。防止未经授权的访问和操作，保障系统数据的安全。用户认证和授权机制应符合国家相关安全标准和规范，确保用户身份的真实性和操作权限的合法性。</p></li><li><p>系统会对用户的登录行为进行监控和审计，如记录登录时间、登录 IP 地址、登录方式等信息。同时，系统会对异常登录行为进行预警和处理，如多次登录失败、异地登录等。</p></li></ul><ol><li><strong>数据加密</strong></li></ol><ul><li><p>对系统中的敏感数据，如员工身份证号、银行卡号、薪酬信息等，进行加密存储和传输。采用行业标准的加密算法，如 SSL/TLS 加密协议，确保数据在传输过程中的安全性；在数据库中对敏感数据字段进行加密存储，防止数据泄露。数据加密应符合国家相关安全标准和规范，确保数据的安全性和保密性。</p></li><li><p>系统会对加密密钥进行管理和维护，包括密钥的生成、存储、更新、备份等。同时，系统会对加密数据进行监控和审计，确保加密数据的完整性和可用性。</p></li></ul><ol><li><strong>操作日志与审计</strong></li></ol><ul><li><p>系统自动记录用户的所有操作日志，包括操作时间、操作人、操作内容等信息。定期对操作日志进行审计，以便及时发现潜在的安全风险和违规操作行为。同时，操作日志可作为追溯问题根源和进行合规审计的重要依据。操作日志应详细记录用户的操作行为和操作结果，确保操作日志的完整性和准确性。</p></li><li><p>系统会对操作日志进行分类管理和存储，如按操作时间、操作类型、操作人等分类。同时，系统会提供操作日志查询和统计功能，方便企业进行审计和分析。</p></li></ul><h3 id="（三）易用性需求"><a href="#（三）易用性需求" class="headerlink" title="（三）易用性需求"></a>（三）易用性需求</h3><ol><li><strong>界面设计</strong></li></ol><ul><li><p>系统界面设计应简洁明了、布局合理，符合用户的操作习惯。采用直观的图标和菜单，方便用户快速找到所需功能。对重要操作进行明确提示，避免用户误操作。同时，系统应具备良好的可访问性，支持不同分辨率的屏幕显示，方便在电脑、平板等多种设备上使用。界面设计应遵循用户体验设计原则，注重界面的美观性和易用性。</p></li><li><p>系统会对界面进行优化和改进，根据用户的反馈和使用习惯，不断调整界面布局和功能菜单，提高用户的操作效率和满意度。</p></li></ul><ol><li><strong>操作流程简化</strong></li></ol><ul><li><p>优化业务操作流程，减少不必要的操作步骤。对于复杂的业务流程，提供向导式操作指引，帮助用户顺利完成操作。例如，在薪酬计算流程中，系统自动根据预设的规则和数据进行计算，用户只需确认关键信息即可，大大提高操作效率。操作流程应简洁明了，避免繁琐的操作步骤和重复的输入。</p></li><li><p>系统会对业务流程进行持续优化和改进，根据业务需求和用户反馈，不断调整业务流程和操作步骤，提高业务处理的效率和准确性。</p></li></ul><ol><li><strong>帮助与文档</strong></li></ol><ul><li><p>提供详细的系统帮助文档和在线教程，帮助用户快速了解系统功能和使用方法。在系统界面中设置便捷的帮助入口，用户在操作过程中遇到问题时，可随时获取帮助信息。同时，定期收集用户反馈，对帮助文档进行优化和更新，确保其准确性和实用性。帮助文档和在线教程应内容丰富、通俗易懂，方便用户学习和使用。</p></li><li><p>系统会提供常见问题解答和搜索功能，用户可通过搜索关键词快速找到相关的帮助信息。同时，系统会建立用户反馈机制，及时收集用户的问题和建议，对帮助文档进行优化和更新。</p></li></ul><h2 id="八、系统业务逻辑与流程"><a href="#八、系统业务逻辑与流程" class="headerlink" title="八、系统业务逻辑与流程"></a>八、系统业务逻辑与流程</h2><h3 id="（一）财务模块业务逻辑与流程"><a href="#（一）财务模块业务逻辑与流程" class="headerlink" title="（一）财务模块业务逻辑与流程"></a>（一）财务模块业务逻辑与流程</h3><ol><li><strong>费用项目管理流程</strong></li></ol><ul><li>物业人员在系统中自定义收费项目、收费标准、计费周期和适用范围等信息。</li><li>可通过批量导入功能将大量收费项目信息导入系统，系统对导入数据进行校验和处理。</li><li>设置收费日期和滞纳金规则，系统在收费日期前自动提醒业主缴费。</li><li>支持多渠道收费，系统实时核销收款与应收款项，进行自动对账。</li><li>对欠费业主进行智能催缴，记录欠费原因和催缴情况。</li></ul><ol><li><strong>业主信息与房源关联流程</strong></li></ol><ul><li>建立业主信息库和房源信息库，为每个业主和房源生成唯一标识编码。</li><li>物业人员对业主信息和房源信息进行增删改查操作，系统自动同步关联信息。</li><li>当房源信息发生变更时，系统自动更新与之关联的收费项目信息。</li></ul><ol><li><strong>账单生成与管理流程</strong></li></ol><ul><li>系统根据预设的收费项目、标准和计费周期，自动生成账单。</li><li>当出现特殊情况需要调整账单金额时，物业人员发起调整申请，经过审批流程后，系统更新账单信息并记录调整轨迹。</li><li>业主可通过线上或线下方式缴费，系统实时更新账单状态。</li><li>系统具备重复支付识别和退款处理功能，保障资金安全。</li></ul><ol><li><strong>财务报表生成与分析流程</strong></li></ol><ul><li>系统自动生成各类财务报表，包括收费汇总报表、欠费明细报表、收支对比报表等。</li><li>物业人员可对报表进行自定义设置和导出，支持 Excel、PDF 等格式。</li><li>系统提供数据分析功能，帮助物业了解收费趋势和业主缴费习惯，为决策提供支持。</li></ul><ol><li><strong>催缴管理流程</strong></li></ol><ul><li>物业人员设置催缴提醒规则，系统根据规则自动向欠费业主发送催缴通知。</li><li>系统记录每次催缴操作的详细信息，物业人员可随时查看催缴进展情况。</li><li>针对不同欠费情况的业主，物业人员采取不同的催缴措施，直至欠费结清。</li></ul><ol><li><strong>补票管理流程</strong></li></ol><ul><li>物业人员通过 Excel 模板填写补票相关信息，导入系统进行校验和处理。</li><li>系统生成补票记录并关联至相应业主的账单中，记录操作人、操作时间和补票原因等信息。</li></ul><h3 id="（二）人力资源模块业务逻辑与流程"><a href="#（二）人力资源模块业务逻辑与流程" class="headerlink" title="（二）人力资源模块业务逻辑与流程"></a>（二）人力资源模块业务逻辑与流程</h3><ol><li><strong>员工档案管理流程</strong></li></ol><ul><li>人力资源人员通过批量导入或单个录入的方式将员工基本信息录入系统。</li><li>对员工信息进行多维度分类和关联，方便查询和统计分析。</li><li>记录员工劳动合同的签订、续签、变更、终止等信息，设置合同到期提醒。</li></ul><ol><li><strong>招聘管理流程</strong></li></ol><ul><li>招聘人员在系统中发布招聘职位，设置职位描述、任职要求、薪资待遇等信息。</li><li>系统接收简历并进行智能筛选，招聘人员可手动筛选简历。</li><li>在线安排面试流程，面试官记录面试评价和意见，系统汇总面试结果。</li><li>根据面试结果做出录用决策，办理员工入职手续。</li></ul><ol><li><strong>培训管理流程</strong></li></ol><ul><li>培训管理人员根据企业发展战略和员工培训需求制定培训计划。</li><li>创建和管理培训课程库，上传培训资料，员工可在线学习和考试。</li><li>系统记录员工的培训参与情况，生成培训统计报表，评估培训效果。</li></ul><ol><li><strong>绩效管理流程</strong></li></ol><ul><li>企业自定义绩效指标体系，为不同部门和职位设置个性化的绩效指标和权重。</li><li>定期进行绩效评估，采用多种评估方式，系统自动汇总计算员工的绩效得分。</li><li>绩效评估结果反馈给员工，员工可提出异议和申诉，系统提供绩效改进建议。</li></ul><ol><li><strong>考勤管理流程</strong></li></ol><ul><li>考勤设备与系统对接，实时获取打卡数据，对齐企微审批流程。</li><li>人力资源人员设置考勤规则，系统自动计算加班时间和请假天数。</li><li>智能修正异常打卡记录，实现假勤联动，实时标记异常考勤情况。</li><li>生成多维考勤报表，进行考勤分析和数据归档。</li></ul><ol><li><strong>薪酬管理流程</strong></li></ol><ul><li>人力资源人员自定义薪酬结构，设置各薪酬项目的计算方式和发放规则。</li><li>系统根据员工考勤、绩效等数据自动计算薪酬，批量生成工资条。</li><li>与银行系统对接，实现薪酬的自动发放，生成各类薪酬报表。</li></ul><ol><li><strong>预算制定流程</strong></li></ol><ul><li>企业自定义各管理层级的人力资源预算项目和核定方式。</li><li>采用多种预算编制方式，对预算数据进行校验和审批。</li><li>实时监控预算执行情况，当费用接近或超出预算时发出预警。</li><li>当企业业务发生变化时，可对预算进行调整，经过审批后更新预算数据。</li></ul><h3 id="（三）系统整体业务逻辑与流程"><a href="#（三）系统整体业务逻辑与流程" class="headerlink" title="（三）系统整体业务逻辑与流程"></a>（三）系统整体业务逻辑与流程</h3><ol><li><strong>数据共享与协同工作流程</strong></li></ol><ul><li>财务模块、人力资源模块等各功能模块之间实现数据共享和协同工作。例如，员工薪酬数据与财务模块的费用支出关联，考勤数据与薪酬计算关联等。</li><li>系统通过数据接口和中间件实现各模块之间的数据交互和同步，确保数据的一致性和准确性。</li></ul><ol><li><strong>用户操作与权限管理流程</strong></li></ol><ul><li>用户通过用户名和密码登录系统，系统进行身份验证和授权。</li><li>根据用户的角色和职责，分配不同的操作权限，用户只能访问和操作其权限范围内的功能和数据。</li><li>系统记录用户的操作日志，对操作行为进行监控和审计。</li></ul><ol><li><strong>系统设置与维护流程</strong></li></ol><ul><li>物业管理人员对系统进行个性化设置，包括收费项目显示顺序、账单格式、支付渠道配置等。</li><li>对物联网设备的对接参数、数据传输频率、预警阈值等进行配置和维护。</li><li>定期进行数据备份和系统更新，保障系统的安全性和稳定性。</li></ul><p>通过以上详细的功能需求和业务流程设计，物业公司 ERP 系统将为物业公司提供全面、高效、智能的管理解决方案，实现各业务模块的协同工作和数据共享，提高运营效率，降低人力成本，为物业公司的发展提供有力支持。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;  ERP系统开发功能文档，记录ERP系统开发过程中的功能需求。&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
      <category term="ERP系统" scheme="https://www.tengfei.eu.org/categories/ERP%E7%B3%BB%E7%BB%9F/"/>
    
    
      <category term="ERP系统" scheme="https://www.tengfei.eu.org/tags/ERP%E7%B3%BB%E7%BB%9F/"/>
    
      <category term="文档" scheme="https://www.tengfei.eu.org/tags/%E6%96%87%E6%A1%A3/"/>
    
  </entry>
  
  <entry>
    <title>RPA自动化办公</title>
    <link href="https://www.tengfei.eu.org/article/e102468f.html"/>
    <id>https://www.tengfei.eu.org/article/e102468f.html</id>
    <published>2025-03-25T08:12:01.000Z</published>
    <updated>2025-08-25T08:22:50.159Z</updated>
    
    <content type="html"><![CDATA[<p>RPA 自动化办公软件全解析：开启高效办公新时代</p><p><img src="https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_skill/2f098977-98da-4cc3-85af-e417cabec97f_1756108769056157621_origin\~tplv-a9rns2rl98-image-qvalue.jpeg?rk3s=6823e3d0\&amp;x-expires=1787644769\&amp;x-signature=6%2BNdJqMI9xbWcWo59DdjwS%2BnhVw%3D" alt></p><a id="more"></a><blockquote><p>在当今数字化时代，办公效率的提升成为了个人与企业追求的重要目标。RPA（Robotic Process Automation）自动化办公软件应运而生，它如同一位不知疲倦的智能助手，能够模拟人类在计算机上的操作，自动完成各种重复性、规律性的任务，为办公流程带来了革命性的变革。今天，就让我们深入了解 RPA 自动化办公软件，掌握其使用方法，开启高效办公的新时代。</p></blockquote><h2 id="一、认识-RPA-自动化办公软件"><a href="#一、认识-RPA-自动化办公软件" class="headerlink" title="一、认识 RPA 自动化办公软件"></a>一、认识 RPA 自动化办公软件</h2><h3 id="（一）什么是-RPA"><a href="#（一）什么是-RPA" class="headerlink" title="（一）什么是 RPA"></a>（一）什么是 RPA</h3><p>RPA 即机器人流程自动化，它通过软件机器人模拟人类在计算机上的操作行为，如鼠标点击、键盘输入、数据读取与写入、文件处理等。这些软件机器人能够按照预设的规则和流程，自动执行一系列任务，实现业务流程的自动化处理。例如，在日常办公中，我们可能需要每天定时从不同的系统中收集数据，整理成报表后发送给相关人员。使用 RPA，我们只需创建一个自动化流程，设置好数据来源、处理方式以及报表发送的目标对象，软件机器人就会在指定时间自动完成这些操作，无需人工干预。</p><h3 id="（二）RPA-在办公场景中的优势"><a href="#（二）RPA-在办公场景中的优势" class="headerlink" title="（二）RPA 在办公场景中的优势"></a>（二）RPA 在办公场景中的优势</h3><ol><li><p><strong>提高效率</strong>：RPA 软件能够以极快的速度执行任务，远远超过人类的操作速度。它可以同时处理多个任务，且不知疲倦，能够在短时间内完成大量重复性工作。例如，在数据录入任务中，RPA 机器人可以在几分钟内完成人工需要数小时甚至数天才能完成的工作，大大缩短了任务处理时间。</p></li><li><p><strong>减少错误</strong>：人为操作难免会出现错误，尤其是在处理大量数据或重复操作时。RPA 机器人按照预设的规则和流程执行任务，具有高度的准确性，能够有效避免因人为疏忽导致的错误。这对于财务报表制作、订单处理等对数据准确性要求极高的工作场景尤为重要。</p></li><li><p><strong>降低成本</strong>：通过使用 RPA 自动化办公软件，企业可以减少对人工的依赖，降低人力成本。同时，由于提高了工作效率和准确性，减少了因错误导致的返工和损失，进一步降低了运营成本。此外，RPA 软件的部署和维护成本相对较低，具有较高的投资回报率。</p></li><li><p><strong>提升员工满意度</strong>：将员工从繁琐、重复的工作中解放出来，让他们能够专注于更具创造性、战略性和价值的工作，不仅可以提升员工的工作满意度和成就感，还能够激发员工的工作积极性和创造力，为企业的发展注入新的活力。</p></li><li><p><strong>24/7 不间断工作</strong>：RPA 机器人可以全年无休、24 小时不间断地工作，随时响应业务需求。无论是在正常工作时间还是节假日、夜间，只要有任务需要处理，RPA 机器人都能准时执行，确保业务流程的连续性和稳定性。</p></li></ol><h2 id="二、选择适合的-RPA-软件"><a href="#二、选择适合的-RPA-软件" class="headerlink" title="二、选择适合的 RPA 软件"></a>二、选择适合的 RPA 软件</h2><p>目前市场上有众多的 RPA 软件可供选择，如 UiPath、Blue Prism、Automation Anywhere、影刀 RPA 等。在选择 RPA 软件时，我们可以从以下几个方面进行考虑：</p><ol><li><p><strong>功能需求</strong>：根据自己的办公场景和业务需求，评估软件是否具备所需的功能。例如，如果你需要进行大量的数据抓取和处理工作，那么软件应具备强大的数据采集和处理功能；如果你需要与多种办公软件和系统进行集成，那么软件应支持广泛的应用程序接口（API）。</p></li><li><p><strong>易用性</strong>：对于非技术人员来说，软件的易用性至关重要。选择操作界面简洁直观、易于上手的 RPA 软件，能够降低学习成本，加快自动化流程的开发和部署速度。一些 RPA 软件提供了图形化的流程设计器，用户可以通过拖拽、配置组件的方式创建自动化流程，无需编写复杂的代码。</p></li><li><p><strong>价格</strong>：不同的 RPA 软件定价模式不同，有的按用户数量收费，有的按使用时长收费，还有的根据功能模块进行收费。在选择软件时，要综合考虑软件的价格和功能，选择性价比高的产品。同时，要注意软件是否提供免费试用或免费版本，以便在购买前进行充分的测试和评估。</p></li><li><p><strong>可扩展性</strong>：随着业务的发展和变化，对 RPA 软件的功能需求也可能会不断增加。因此，选择具有良好可扩展性的 RPA 软件，能够方便地进行功能升级和扩展，满足未来业务发展的需要。</p></li><li><p><strong>技术支持</strong>：在使用 RPA 软件的过程中，难免会遇到各种问题。选择提供优质技术支持的软件供应商，能够及时获得技术咨询、故障排除等服务，确保软件的正常运行。</p></li></ol><h2 id="三、RPA-自动化办公软件的安装与设置"><a href="#三、RPA-自动化办公软件的安装与设置" class="headerlink" title="三、RPA 自动化办公软件的安装与设置"></a>三、RPA 自动化办公软件的安装与设置</h2><h3 id="（一）安装-RPA-软件"><a href="#（一）安装-RPA-软件" class="headerlink" title="（一）安装 RPA 软件"></a>（一）安装 RPA 软件</h3><p>以影刀 RPA 为例，安装过程通常较为简单：</p><ol><li><p>打开影刀 RPA 官方网站，在网站上找到下载链接，根据自己的操作系统（如 Windows、MacOS）选择相应的安装包进行下载。</p></li><li><p>下载完成后，双击安装包，按照安装向导的提示进行操作。在安装过程中，可能需要选择安装路径、接受许可协议等，根据自己的需求进行设置即可。</p></li><li><p>安装完成后，在桌面或开始菜单中找到影刀 RPA 的快捷方式，双击打开软件。</p></li></ol><h3 id="（二）设置-RPA-软件"><a href="#（二）设置-RPA-软件" class="headerlink" title="（二）设置 RPA 软件"></a>（二）设置 RPA 软件</h3><ol><li><p><strong>用户账号登录</strong>：首次打开 RPA 软件时，需要使用注册的账号进行登录。如果没有账号，可以在软件登录界面点击注册按钮，按照提示填写相关信息进行注册。</p></li><li><p><strong>基本设置</strong>：登录后，进入软件的设置界面，进行一些基本的设置，如语言选择、界面布局调整、通知设置等。这些设置可以根据个人的使用习惯进行调整，以提高使用体验。</p></li><li><p><strong>连接相关系统和应用程序</strong>：如果需要让 RPA 软件与其他办公软件（如 Excel、Word、Outlook）或企业内部系统（如 ERP、CRM）进行交互，需要在软件中进行相应的连接设置。不同的 RPA 软件连接方式可能略有不同，一般来说，可以通过输入账号密码、授权码或配置 API 等方式实现连接。例如，要让影刀 RPA 与 Excel 进行数据交互，需要在影刀 RPA 中配置 Excel 的安装路径或通过 COM 接口进行连接。</p></li></ol><h2 id="四、使用-RPA-软件搭建自动化办公流程"><a href="#四、使用-RPA-软件搭建自动化办公流程" class="headerlink" title="四、使用 RPA 软件搭建自动化办公流程"></a>四、使用 RPA 软件搭建自动化办公流程</h2><h3 id="（一）明确自动化需求与流程规划"><a href="#（一）明确自动化需求与流程规划" class="headerlink" title="（一）明确自动化需求与流程规划"></a>（一）明确自动化需求与流程规划</h3><p>在开始搭建自动化流程之前，首先要明确需要自动化的办公任务和流程。仔细分析任务的各个环节，确定每个步骤的操作内容、数据流向以及可能出现的异常情况。例如，我们要创建一个自动发送邮件的自动化流程，需要明确以下内容：</p><ol><li><p>邮件的数据源：邮件的收件人、主题、内容等信息从何处获取，是从 Excel 表格中读取，还是从数据库中查询。</p></li><li><p>邮件的发送规则：是否需要根据某些条件（如收件人类型、邮件主题关键词）进行分类发送，是否需要设置定时发送。</p></li><li><p>异常处理：如果邮件发送过程中出现错误（如网络故障、收件人邮箱地址错误），应如何处理，是进行重试，还是记录错误信息并通知相关人员。</p></li></ol><p>将这些需求和流程以流程图或文字描述的形式清晰地记录下来，为后续的流程搭建提供指导。</p><h3 id="（二）创建新流程"><a href="#（二）创建新流程" class="headerlink" title="（二）创建新流程"></a>（二）创建新流程</h3><p>在 RPA 软件中，一般都有创建新流程的功能入口。以影刀 RPA 为例，打开软件后，在主界面中点击 “新建流程” 按钮，即可创建一个新的自动化流程。为新流程起一个具有描述性的名称，以便于识别和管理，例如 “自动发送邮件流程”。</p><h3 id="（三）添加操作步骤"><a href="#（三）添加操作步骤" class="headerlink" title="（三）添加操作步骤"></a>（三）添加操作步骤</h3><ol><li><strong>数据读取操作</strong>：</li></ol><ul><li><p><strong>从文件读取数据</strong>：如果邮件的收件人、主题、内容等信息存储在 Excel 表格中，可以使用 RPA 软件的 “读取 Excel 数据” 组件。在属性面板中设置 Excel 文件的路径、工作表名称以及要读取的数据范围（如特定的行和列）。例如，要读取 Excel 表格中第一列的收件人邮箱地址、第二列的邮件主题和第三列的邮件内容，可以设置读取范围为 “A:B:C”。</p></li><li><p><strong>从数据库读取数据</strong>：若数据存储在数据库中，需要先配置数据库连接信息（如数据库类型、服务器地址、端口号、用户名、密码），然后使用 “数据库查询” 组件编写 SQL 查询语句，获取所需的数据。例如，使用 SQL 语句 “SELECT email, subject, content FROM emails WHERE send_status = ‘ 未发送 ‘” 从名为 “emails” 的数据库表中查询未发送邮件的相关信息。</p></li></ul><ol><li><strong>数据处理操作</strong>：</li></ol><ul><li><p><strong>数据清洗</strong>：从数据源读取的数据可能存在格式不一致、重复数据、缺失值等问题，需要进行数据清洗。例如，使用 “文本处理” 组件中的 “去除空格” 功能，去除收件人邮箱地址中的多余空格；使用 “数据去重” 组件，去除重复的收件人信息。</p></li><li><p><strong>数据转换</strong>：根据业务需求，对数据进行格式转换。比如，将日期格式从 “YYYY/MM/DD” 转换为 “MM-DD-YYYY”；将数字类型的数据转换为文本类型，以便在邮件内容中正确显示。</p></li><li><p><strong>数据计算</strong>：在某些情况下，可能需要对数据进行计算。例如，计算邮件内容中某些字段的总和、平均值或进行其他数学运算。使用 “数据计算” 组件，按照相应的计算公式进行设置即可。</p></li></ul><ol><li><strong>系统操作与模拟人类操作</strong>：</li></ol><ul><li><p><strong>打开应用程序</strong>：使用 “打开应用程序” 组件，指定要打开的办公软件或系统的路径。例如，要打开 Outlook 邮箱客户端，设置应用程序路径为 Outlook 的安装路径（一般为 “C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE”，具体路径可能因安装版本和位置而异）。</p></li><li><p><strong>鼠标点击与键盘输入</strong>：通过 “鼠标点击” 和 “键盘输入” 组件，模拟人类在应用程序中的操作。例如，在 Outlook 中，使用 “鼠标点击” 组件点击 “新建邮件” 按钮，然后使用 “键盘输入” 组件在收件人、主题和正文输入框中输入相应的内容。在设置鼠标点击位置时，可以使用坐标定位或通过元素选择器（如根据按钮的 ID、类名、文本内容等）来精准定位。</p></li><li><p><strong>下拉菜单选择与滚动操作</strong>：对于包含下拉菜单的界面，使用 “下拉菜单选择” 组件选择相应的选项。如果页面内容较多，需要进行滚动操作才能显示所需元素，可以使用 “滚动页面” 组件，设置滚动的方向和距离。</p></li></ul><ol><li><strong>流程控制操作</strong>：</li></ol><ul><li><p><strong>条件判断</strong>：使用 “条件判断” 组件，根据特定的条件决定流程的走向。例如，设置条件为 “如果邮件主题包含‘紧急’关键词，则将邮件优先级设置为‘高’，否则设置为‘普通’”。在条件判断组件的属性面板中，设置条件表达式和满足条件、不满足条件时要执行的不同操作流程。</p></li><li><p><strong>循环操作</strong>：当需要对一组数据进行重复处理时，使用 “循环操作” 组件。例如，要对从 Excel 表格中读取的每一个收件人发送邮件，就可以将邮件发送的操作步骤放在循环体中，循环次数根据读取到的数据行数自动确定。</p></li><li><p><strong>分支流程</strong>：根据不同的条件，将流程分为多个分支。例如，根据收件人的部门不同，发送不同内容的邮件。使用 “分支流程” 组件，设置多个条件分支，每个分支对应不同的操作流程。</p></li></ul><h3 id="（四）流程调试与优化"><a href="#（四）流程调试与优化" class="headerlink" title="（四）流程调试与优化"></a>（四）流程调试与优化</h3><ol><li><p><strong>流程调试</strong>：在完成流程搭建后，点击 RPA 软件中的 “调试” 按钮，对流程进行测试运行。在调试过程中，软件会逐步执行每个操作步骤，并在日志窗口中显示执行结果和相关信息。仔细观察流程的执行情况，检查是否有错误提示或异常行为。如果某个操作步骤执行失败，日志中会显示详细的错误原因，根据错误提示回到流程编辑界面，检查该步骤的设置是否正确，如数据路径是否正确、操作参数是否符合要求等，及时进行调整。</p></li><li><p><strong>流程优化</strong>：根据调试结果和实际运行情况，对流程进行优化。优化的方向包括提高流程的执行效率、增强流程的稳定性和可靠性、简化流程结构等。例如，通过减少不必要的操作步骤、合并重复的操作、优化数据读取和处理方式等方法，提高流程的执行速度；添加异常处理机制，如在网络连接失败时进行自动重试，确保流程在遇到各种异常情况时能够稳定运行；对复杂的流程结构进行梳理和简化，使其更易于理解和维护。</p></li></ol><h3 id="（五）保存与运行流程"><a href="#（五）保存与运行流程" class="headerlink" title="（五）保存与运行流程"></a>（五）保存与运行流程</h3><ol><li><p><strong>保存流程</strong>：经过调试和优化，确认流程能够正常运行后，点击 RPA 软件中的 “保存” 按钮，将搭建好的自动化流程保存到本地或指定的存储位置。建议定期保存流程，以免因意外情况导致流程数据丢失。</p></li><li><p><strong>运行流程</strong>：保存流程后，可以选择立即运行流程，也可以根据需要设置定时运行。在 RPA 软件中，一般有 “运行” 按钮或 “定时任务设置” 功能。点击 “运行” 按钮，软件将按照设置的流程自动执行任务；在 “定时任务设置” 中，可以设置流程的运行时间、运行周期（如每天、每周、每月）等参数，让 RPA 软件在指定时间自动启动并运行流程。例如，设置自动发送邮件的流程每天早上 9 点准时运行，软件会在每天 9 点自动打开 Outlook，读取数据并发送邮件。</p></li></ol><h2 id="五、RPA-自动化办公软件的常见应用场景"><a href="#五、RPA-自动化办公软件的常见应用场景" class="headerlink" title="五、RPA 自动化办公软件的常见应用场景"></a>五、RPA 自动化办公软件的常见应用场景</h2><h3 id="（一）数据处理与报表生成"><a href="#（一）数据处理与报表生成" class="headerlink" title="（一）数据处理与报表生成"></a>（一）数据处理与报表生成</h3><ol><li><p><strong>数据收集与整合</strong>：RPA 软件可以自动从多个数据源（如不同的 Excel 表格、数据库、网页等）收集数据，并将这些数据整合到一个文件或数据库表中。例如，企业的销售数据可能分散在各个区域的销售报表中，使用 RPA 机器人可以自动读取这些报表中的数据，汇总到一个总表中，方便进行后续的分析和处理。</p></li><li><p><strong>数据清洗与转换</strong>：对收集到的数据进行清洗和转换，确保数据的准确性和一致性。如去除重复数据、纠正数据格式错误、将数据进行标准化处理等。在财务数据处理中，RPA 可以自动将不同格式的金额数据统一转换为标准格式，并进行数据校验，避免因数据错误导致的财务报表不准确。</p></li><li><p><strong>报表生成</strong>：根据预设的模板和数据，RPA 软件可以自动生成各种报表，如日报、周报、月报、财务报表等。它可以将处理好的数据填充到报表模板的相应位置，生成美观、规范的报表，并自动保存或发送给相关人员。例如，每天早上自动生成前一天的销售报表，包括销售额、销售数量、客户分布等信息，发送给销售团队和管理层。</p></li></ol><h3 id="（二）文件管理与文档处理"><a href="#（二）文件管理与文档处理" class="headerlink" title="（二）文件管理与文档处理"></a>（二）文件管理与文档处理</h3><ol><li><p><strong>文件分类与归档</strong>：RPA 机器人可以根据文件的名称、内容、创建时间等属性，自动对文件进行分类和归档。例如，将收到的邮件附件按照项目名称、文件类型等进行分类存储到不同的文件夹中；定期对文档库中的文件进行整理，将过期的文件移动到归档文件夹。</p></li><li><p><strong>文档生成与编辑</strong>：根据预设的模板和数据，自动生成文档，如合同、报告、简历等。在生成文档后，还可以对文档进行编辑和格式调整，如添加页眉页脚、设置字体格式、插入图片和图表等。例如，人力资源部门使用 RPA 软件，根据员工的基本信息和绩效数据，自动生成年度绩效评估报告，并进行格式排版。</p></li><li><p><strong>文档识别与数据提取</strong>：利用 OCR（光学字符识别）技术，RPA 软件可以对扫描文档、图片中的文字进行识别，并提取出关键信息。例如，从发票图片中提取发票号码、金额、开票日期等信息，录入到财务系统中；从合同文档中提取合同编号、签订日期、双方信息等重要数据，进行合同管理。</p></li></ol><h3 id="（三）邮件处理与客户沟通"><a href="#（三）邮件处理与客户沟通" class="headerlink" title="（三）邮件处理与客户沟通"></a>（三）邮件处理与客户沟通</h3><ol><li><p><strong>邮件自动收发与分类</strong>：RPA 机器人可以定时检查邮箱，自动收取新邮件，并根据邮件的主题、发件人、内容等信息进行分类整理。例如，将客户咨询邮件放入 “客户咨询” 文件夹，将系统通知邮件放入 “系统通知” 文件夹，方便用户快速查看和处理。</p></li><li><p><strong>邮件自动回复</strong>：对于常见问题的邮件咨询，RPA 软件可以根据预设的回复模板和关键词匹配规则，自动回复邮件。例如，电商企业的客服邮箱每天会收到大量关于商品信息、物流配送等方面的咨询邮件，使用 RPA 机器人可以自动识别邮件内容，根据预设的回复模板发送相应的解答邮件，提高客户回复效率和满意度。</p></li><li><p><strong>邮件数据提取与跟进</strong>：从邮件中提取关键数据，如客户需求、订单信息等，并根据这些数据进行后续的跟进操作。例如，销售团队收到客户的采购意向邮件后，RPA 软件可以自动提取客户联系方式、需求产品信息等，创建销售机会记录，并将相关信息推送给销售人员进行跟进。</p></li></ol><h3 id="（四）系统操作与流程自动化"><a href="#（四）系统操作与流程自动化" class="headerlink" title="（四）系统操作与流程自动化"></a>（四）系统操作与流程自动化</h3><ol><li><p><strong>多系统数据同步</strong>：在企业中，不同的业务系统（如 ERP、CRM、OA 等）之间可能存在数据不一致的问题。RPA 软件可以实现不同系统之间的数据自动同步，确保数据的准确性和实时性。例如，将 CRM 系统中的客户信息更新同步到 ERP 系统中，保证两个系统中的客户数据一致，避免因数据不一致导致的业务错误。</p></li><li><p><strong>业务流程自动化</strong>：RPA 能够模拟人类操作，自动执行企业内部的各种业务流程，如审批流程、报销流程、入职离职流程等。例如，在请假审批流程中，员工提交请假申请后，RPA 机器人自动将申请信息推送给相关领导进行审批，审批通过后，自动更新员工的考勤系统数据。</p></li><li><p><strong>软件测试自动化</strong>：在软件开发过程中，RPA 可以用于自动化软件测试。它可以模拟用户的各种操作，如点击按钮、输入文本、数据提交等，自动执行测试用例，验证软件功能是否符合预期。例如，在电商平台的订单支付功能测试中，RPA 机器人可以模拟用户从选择商品、加入购物车、填写收货地址到完成支付的整个流程，自动检查每个步骤的页面显示、数据提交结果以及订单状态更新是否正确，大大减少了人工测试的工作量，提高了测试效率和覆盖率。</p></li></ol><h2 id="六、RPA-自动化办公软件使用的注意事项"><a href="#六、RPA-自动化办公软件使用的注意事项" class="headerlink" title="六、RPA 自动化办公软件使用的注意事项"></a>六、RPA 自动化办公软件使用的注意事项</h2><h3 id="（一）数据安全与隐私保护"><a href="#（一）数据安全与隐私保护" class="headerlink" title="（一）数据安全与隐私保护"></a>（一）数据安全与隐私保护</h3><p>在使用 RPA 软件处理办公数据时，数据安全与隐私保护是重中之重。许多办公数据涉及企业机密信息（如财务数据、客户信息、商业计划等）和个人隐私信息（如员工身份证号、联系方式等），一旦泄露或被滥用，将给企业和个人带来严重的损失。</p><ol><li><p><strong>权限控制</strong>：为 RPA 软件设置严格的权限管理机制，根据用户的角色和职责分配相应的操作权限。例如，普通员工只能使用已创建好的自动化流程，无法修改流程设置或查看敏感数据；管理员拥有更高的权限，可以进行流程的创建、修改、删除以及用户权限的管理。同时，定期审查和更新用户权限，确保权限设置与实际工作需求相符，避免因权限过高或权限过期导致的数据安全风险。</p></li><li><p><strong>数据加密</strong>：对 RPA 软件处理过程中的数据进行加密处理，包括数据在传输过程中的加密和数据存储时的加密。在数据传输方面，采用 HTTPS、SSL 等加密协议，确保数据在从一个系统传输到另一个系统（如从 Excel 表格传输到邮件系统）时不被窃取或篡改；在数据存储方面，对存储在 RPA 软件数据库或本地文件中的敏感数据进行加密存储，设置复杂的加密密钥，并定期更换密钥，提高数据的安全性。</p></li><li><p><strong>操作日志记录</strong>：开启 RPA 软件的操作日志记录功能，详细记录用户对 RPA 软件的所有操作，如流程的创建、修改、运行、删除，数据的读取、写入、传输等。操作日志应包含操作时间、操作人、操作内容、操作结果等信息，以便在发生数据安全事件时，能够及时追溯到相关责任人，查明事件原因，并采取相应的补救措施。同时，定期对操作日志进行备份和审查，及时发现异常操作行为，防范数据安全风险。</p></li></ol><h3 id="（二）流程稳定性与异常处理"><a href="#（二）流程稳定性与异常处理" class="headerlink" title="（二）流程稳定性与异常处理"></a>（二）流程稳定性与异常处理</h3><p>虽然 RPA 软件能够自动执行任务，但在实际使用过程中，可能会受到各种因素的影响，导致流程运行不稳定或出现异常情况。因此，必须重视流程的稳定性和异常处理能力。</p><ol><li><p><strong>定期维护流程</strong>：随着办公软件版本的更新、系统环境的变化以及业务流程的调整，原有的 RPA 自动化流程可能会出现不兼容或无法正常运行的情况。因此，需要定期对已创建的自动化流程进行维护和检查，查看流程是否能够正常运行，操作步骤是否需要调整，数据交互是否正常等。例如，当 Excel 软件进行版本更新后，可能会导致 RPA 软件中的 “读取 Excel 数据” 组件无法正常工作，此时需要及时更新 RPA 软件的组件或调整流程中的相关设置，确保流程的稳定性。</p></li><li><p><strong>完善异常处理机制</strong>：在搭建自动化流程时，要充分考虑各种可能出现的异常情况，并设置相应的异常处理机制。常见的异常情况包括网络连接失败、应用程序未响应、数据缺失或格式错误、操作超时等。针对不同的异常情况，制定不同的处理策略：</p></li></ol><ul><li><p><strong>重试机制</strong>：对于因网络波动、应用程序临时故障等原因导致的操作失败，可以设置重试机制。例如，在发送邮件时，如果出现网络连接失败的错误，RPA 机器人可以自动等待一段时间后重新尝试发送，重试次数可以根据实际情况进行设置（如 3 次），如果多次重试后仍无法成功，则执行其他处理步骤。</p></li><li><p><strong>错误提示与记录</strong>：当流程运行出现异常时，RPA 软件应及时向用户发送错误提示信息，告知用户异常发生的位置、原因等。同时，将异常信息详细记录到日志中，包括异常发生时间、异常类型、异常描述、相关数据等，以便用户后续分析和处理异常问题。</p></li><li><p><strong>流程跳转与降级处理</strong>：在某些情况下，当某个操作步骤出现异常且无法通过重试解决时，可以设置流程跳转到备用流程或执行降级处理操作。例如，在自动生成报表时，如果无法从数据库中读取数据，可以设置流程跳转到从备用 Excel 文件中读取数据的步骤，确保报表能够正常生成，避免整个流程因一个步骤的异常而完全停滞。</p></li></ul><h3 id="（三）避免过度自动化"><a href="#（三）避免过度自动化" class="headerlink" title="（三）避免过度自动化"></a>（三）避免过度自动化</h3><p>虽然 RPA 自动化办公软件能够带来诸多优势，但并非所有办公任务都适合进行自动化处理。过度自动化可能会导致资源浪费、流程僵化等问题，因此在使用 RPA 软件时，要合理评估自动化的范围和程度，避免过度自动化。</p><ol><li><p><strong>任务适用性评估</strong>：在决定是否对某个办公任务进行自动化处理时，要对任务的性质、复杂度、频率、稳定性等因素进行全面评估。适合进行自动化处理的任务通常具有以下特点：重复性高、规则明确、操作流程稳定、数据格式规范、人工操作耗时较长。而对于那些需要人类主观判断、创造力、灵活性以及频繁变化的任务（如战略规划制定、复杂问题分析、客户情感沟通等），则不适合进行自动化处理，应保留人工操作。</p></li><li><p><strong>平衡自动化与人工干预</strong>：即使是适合自动化处理的任务，也并非所有环节都需要完全自动化，在某些关键环节或容易出现异常的环节，保留适当的人工干预是必要的。例如，在自动处理财务报销流程时，RPA 机器人可以自动完成报销单数据录入、发票信息验证、报销金额计算等操作，但对于报销单的合理性审核（如报销项目是否符合公司规定、发票真伪的最终确认等），则需要人工进行审核确认，确保报销流程的准确性和合规性。通过平衡自动化与人工干预，既能够充分发挥 RPA 软件的优势，又能够避免因完全自动化而导致的风险。</p></li></ol><h2 id="七、RPA-自动化办公软件的未来发展趋势"><a href="#七、RPA-自动化办公软件的未来发展趋势" class="headerlink" title="七、RPA 自动化办公软件的未来发展趋势"></a>七、RPA 自动化办公软件的未来发展趋势</h2><p>随着人工智能、大数据、云计算等技术的不断发展和融合，RPA 自动化办公软件也在不断升级和进化，未来将呈现出以下几个主要发展趋势：</p><h3 id="（一）RPA-与-AI-深度融合（AI-RPA）"><a href="#（一）RPA-与-AI-深度融合（AI-RPA）" class="headerlink" title="（一）RPA 与 AI 深度融合（AI+RPA）"></a>（一）RPA 与 AI 深度融合（AI+RPA）</h3><p>传统的 RPA 软件主要依靠预设的规则和流程执行任务，缺乏自主学习和灵活应变的能力，对于一些复杂的、非结构化的任务（如自然语言处理、图像识别、情感分析等）难以处理。而人工智能技术（如机器学习、深度学习、自然语言处理、计算机视觉等）具有强大的数据分析、模式识别和自主学习能力。未来，RPA 将与 AI 技术深度融合，形成 “AI+RPA” 的新模式，使 RPA 软件具备更高级的智能能力。</p><p>例如，在客户服务场景中，“AI+RPA” 机器人可以通过自然语言处理技术理解客户的咨询内容，利用机器学习技术分析客户的需求和情感，然后自动执行相应的操作（如查询客户信息、处理订单、发送解决方案等），甚至可以根据客户的历史交互数据进行个性化推荐和服务。在文档处理场景中，结合计算机视觉和 OCR 技术，RPA 机器人可以更准确地识别和提取非结构化文档（如扫描件、图片式合同、手写发票等）中的关键信息，并进行自动分类、整理和分析，大大扩展了 RPA 软件的应用范围。</p><h3 id="（二）云原生-RPA-的普及"><a href="#（二）云原生-RPA-的普及" class="headerlink" title="（二）云原生 RPA 的普及"></a>（二）云原生 RPA 的普及</h3><p>目前，大多数 RPA 软件都是部署在本地计算机或服务器上，存在部署成本高、维护难度大、资源利用率低、跨平台协作困难等问题。随着云计算技术的不断成熟和普及，云原生 RPA 将成为未来的重要发展方向。</p><p>云原生 RPA 软件部署在云端，用户可以通过浏览器或移动终端随时随地访问和使用，无需在本地安装和维护软件，大大降低了部署成本和维护难度。同时，云原生 RPA 可以充分利用云端的弹性计算资源，根据业务需求灵活调整计算资源的分配，提高资源利用率。此外，云原生 RPA 还支持多用户协同工作，不同地区、不同部门的用户可以共享自动化流程和数据，实现跨平台、跨团队的高效协作。例如，企业的销售团队和财务团队可以通过云原生 RPA 软件共享客户订单数据，销售团队实时更新订单信息，财务团队则通过 RPA 机器人自动根据订单信息生成发票和进行财务核算，实现业务流程的无缝衔接和高效协同。</p><h3 id="（三）低代码-无代码-RPA-平台的发展"><a href="#（三）低代码-无代码-RPA-平台的发展" class="headerlink" title="（三）低代码 / 无代码 RPA 平台的发展"></a>（三）低代码 / 无代码 RPA 平台的发展</h3><p>传统的 RPA 软件虽然已经具备一定的易用性，但对于一些非技术背景的用户来说，仍然需要一定的学习成本和技术能力才能创建和维护自动化流程。为了进一步降低 RPA 的使用门槛，让更多的普通用户能够轻松使用 RPA 软件，低代码 / 无代码 RPA 平台将成为未来的发展热点。</p><p>低代码 / 无代码 RPA 平台提供了更加直观、简洁的图形化操作界面，用户无需编写复杂的代码，只需通过拖拽组件、配置参数、可视化流程设计等方式，就可以快速创建自动化流程。平台还会提供丰富的预制组件和模板（如数据处理组件、邮件处理模板、报表生成模板等），用户可以直接调用这些组件和模板，进一步缩短流程创建时间。例如，人力资源部门的普通员工，即使没有编程基础，也可以通过低代码 / 无代码 RPA 平台，拖拽 “读取员工信息 Excel”“发送入职通知邮件”“更新考勤系统” 等组件，快速创建员工入职流程的自动化程序，大大提高工作效率。</p><h3 id="（四）行业化、场景化-RPA-解决方案的涌现"><a href="#（四）行业化、场景化-RPA-解决方案的涌现" class="headerlink" title="（四）行业化、场景化 RPA 解决方案的涌现"></a>（四）行业化、场景化 RPA 解决方案的涌现</h3><p>随着 RPA 技术在各个行业的广泛应用，不同行业、不同业务场景对 RPA 软件的功能需求也越来越多样化和专业化。未来，RPA 软件供应商将不再局限于提供通用的 RPA 平台，而是会针对特定行业（如金融、医疗、零售、制造、物流等）和特定业务场景（如银行的信贷审批、医院的病历管理、零售企业的库存盘点、制造企业的生产数据统计、物流企业的订单跟踪等），开发更加专业、高效的行业化、场景化 RPA 解决方案。</p><p>这些行业化、场景化的 RPA 解决方案将深度结合行业特点和业务需求，预置行业专属的流程模板、数据模型和合规规则，能够快速适配行业业务系统，满足行业特定的功能需求和合规要求。例如，针对金融行业的信贷审批场景，RPA 解决方案可以预置信贷申请数据采集、信用评分查询、抵押物评估、审批流程推送等功能模块，并符合金融行业的数据安全和合规监管要求，帮助金融企业快速实现信贷审批流程的自动化，提高审批效率和合规性。</p><h2 id="八、学习-RPA-自动化办公软件的资源推荐"><a href="#八、学习-RPA-自动化办公软件的资源推荐" class="headerlink" title="八、学习 RPA 自动化办公软件的资源推荐"></a>八、学习 RPA 自动化办公软件的资源推荐</h2><p>对于想要学习和掌握 RPA 自动化办公软件的用户，以下提供一些优质的学习资源，帮助大家快速入门和提升技能：</p><h3 id="（一）官方学习平台"><a href="#（一）官方学习平台" class="headerlink" title="（一）官方学习平台"></a>（一）官方学习平台</h3><ol><li><p><strong>UiPath Academy</strong>：UiPath 官方推出的免费在线学习平台，提供了从基础到高级的完整 RPA 课程体系，包括视频教程、实践项目、认证考试等。课程内容涵盖 UiPath Studio 的使用、自动化流程设计、数据处理、AI 集成等方面，适合不同层次的学习者。学习者可以根据自己的需求选择相应的课程，完成学习后还可以参加官方认证考试，获取 UiPath 认证证书，提升个人职业竞争力。</p></li><li><p><strong>影刀 RPA 学院</strong>：影刀 RPA 官方学习平台，针对国内用户的需求，提供了丰富的中文学习资源，包括入门教程、实战案例、直播课程、社区问答等。平台的课程内容贴近国内办公场景，如 Excel 数据处理、微信消息自动发送、电商平台操作自动化等，实用性强。同时，影刀 RPA 学院还会定期举办线上线下培训活动，帮助学习者快速掌握影刀 RPA 的使用技巧。</p></li><li><p><strong>Automation Anywhere University</strong>：Automation Anywhere 官方学习平台，提供了系统化的 RPA 学习课程，包括基础操作、高级功能、行业解决方案等。平台还提供了虚拟实验室，学习者可以在虚拟环境中进行实践操作，加深对知识的理解和掌握。此外，平台还提供了专业的认证体系，通过认证可以证明自己的 RPA 技能水平。</p></li></ol><h3 id="（二）在线课程平台"><a href="#（二）在线课程平台" class="headerlink" title="（二）在线课程平台"></a>（二）在线课程平台</h3><ol><li><p><strong>Coursera</strong>：全球知名的在线教育平台，上面有许多来自世界顶尖大学和机构的 RPA 相关课程，如 “RPA Fundamentals”“Robotic Process Automation with UiPath” 等。这些课程通常由专业的讲师授课，内容全面、系统，适合想要深入学习 RPA 理论和技术的学习者。部分课程需要付费，但也有一些免费课程可供选择，学习者可以根据自己的情况进行选择。</p></li><li><p><strong>Udemy</strong>：另一个 popular 的在线课程平台，上面有大量的 RPA 实战课程，课程内容涵盖 UiPath、Blue Prism、影刀 RPA 等主流 RPA 软件的使用。这些课程大多由行业资深从业者授课，注重实践操作，通过大量的案例演示和实战项目，帮助学习者快速掌握 RPA 软件的使用技巧。课程价格相对较为亲民，且经常会有折扣活动。</p></li><li><p><strong>网易云课堂、腾讯课堂</strong>：国内知名的在线课程平台，上面有许多针对国内用户的 RPA 学习课程，课程内容以实战为主，适合零基础的学习者入门。例如，网易云课堂上的 “影刀 RPA 从入门到精通” 课程，通过详细的视频讲解和实战案例，帮助学习者快速掌握影刀 RPA 的基本操作和流程设计方法；腾讯课堂上的 “UiPath 自动化办公实战课程”，则针对办公场景中的常见任务（如数据录入、报表生成、邮件处理等），教授如何使用 UiPath 进行自动化处理。</p></li></ol><h3 id="（三）社区与论坛"><a href="#（三）社区与论坛" class="headerlink" title="（三）社区与论坛"></a>（三）社区与论坛</h3><ol><li><p><strong>UiPath Community</strong>：UiPath 官方社区，是全球最大的 RPA 社区之一，拥有来自世界各地的 RPA 爱好者、从业者和专家。在社区中，学习者可以提问交流、分享经验、获取最新的 RPA 资讯和资源，还可以参与社区举办的黑客马拉松、案例竞赛等活动，与其他 RPA 从业者共同学习和成长。社区提供了丰富的文档资料、视频教程、实战案例等，是学习 UiPath 的重要资源平台。</p></li><li><p><strong>影刀 RPA 社区</strong>：影刀 RPA 官方社区，主要面向国内用户，社区氛围活跃，用户可以在社区中分享自己的自动化流程案例、提问解答、交流学习心得。社区还会定期发布 RPA 行业动态、实战技巧、用户案例等内容，帮助学习者了解 RPA 的实际应用场景和最新发展趋势。此外，社区还提供了 “流程市场” 功能，用户可以下载其他用户分享的自动化流程模板，快速应用到自己的工作中。</p></li><li><p><strong>RPA 中国论坛</strong>：国内专注于 RPA 领域的专业论坛，汇聚了大量的 RPA 行业资讯、技术文章、实战案例、招聘信息等。论坛设有不同的板块（如 RPA 技术讨论、行业应用案例、资源共享、求职招聘等），学习者可以根据自己的需求在相应板块中获取信息和交流互动。论坛还会邀请行业专家进行线上分享和答疑，为学习者提供专业的指导和帮助。</p></li></ol><h3 id="（四）实战项目练习"><a href="#（四）实战项目练习" class="headerlink" title="（四）实战项目练习"></a>（四）实战项目练习</h3><ol><li><p><strong>个人办公场景实战</strong>：从自己日常办公中遇到的重复性任务入手，尝试使用 RPA 软件进行自动化处理。例如，设计一个自动整理邮箱附件的流程（将不同类型的附件分类保存到指定文件夹）、自动生成月度工作报表的流程（从多个 Excel 表格中读取数据并汇总生成报表）、自动发送会议通知的流程（从 Excel 表格中读取参会人员信息并发送会议邮件）等。通过实际操作，不仅可以加深对 RPA 软件的理解和掌握，还可以切实提高自己的办公效率。</p></li><li><p><strong>企业级案例模拟</strong>：在学习了一定的 RPA 知识和技能后，可以尝试模拟企业中的实际业务场景进行项目练习。例如，模拟电商企业的订单处理流程（从订单系统中读取订单信息、进行库存检查、生成发货单、更新订单状态）、模拟财务部门的费用报销流程（读取报销单数据、验证发票信息、计算报销金额、推送审批流程）等。可以通过查找企业 RPA 应用案例、参考行业解决方案等方式，了解企业级 RPA 项目的需求分析、流程设计、开发实施等环节，提升自己的项目实战能力。</p></li><li><p><strong>参加 RPA 竞赛</strong>：国内外许多机构和企业会举办 RPA 竞赛（如 UiPath 全球黑客马拉松、影刀 RPA 自动化大赛等），竞赛通常会提供具体的业务场景和需求，要求参赛者在规定时间内使用指定的 RPA 软件设计并实现自动化解决方案。参加 RPA 竞赛是提升实战能力的有效途径，通过竞赛可以锻炼自己的问题分析能力、流程设计能力、技术应用能力，还可以与其他优秀的 RPA 从业者交流学习，拓展人脉资源。</p></li></ol><h2 id="九、总结"><a href="#九、总结" class="headerlink" title="九、总结"></a>九、总结</h2><p>RPA 自动化办公软件作为数字化时代提升办公效率的重要工具，正在逐步改变传统的办公模式，为个人和企业带来了巨大的价值。通过本文的介绍，我们了解了 RPA 的基本概念、优势、选择方法、安装设置、流程搭建、应用场景、注意事项、未来发展趋势以及学习资源。</p><p>无论是数据处理、文件管理、邮件处理，还是系统操作与流程自动化，RPA 都能够发挥重要作用，帮助我们从繁琐的重复性工作中解放出来，专注于更具创造性和价值的工作。当然，在使用 RPA 的过程中，我们也要重视数据安全与隐私保护、流程稳定性与异常处理，避免过度自动化，确保 RPA 的合理、有效应用。</p><p>随着 RPA 与 AI、云计算等技术的深度融合，以及低代码 / 无代码平台的发展和行业化解决方案的涌现，RPA 的应用前景将更加广阔。对于想要学习 RPA 的人来说，只要选择合适的学习资源，结合实际项目进行练习，不断积累经验，就能够快速掌握 RPA 技能，在数字化办公浪潮中占据先机，开启高效办公的新时代。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;RPA 自动化办公软件全解析：开启高效办公新时代&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-flow-imagex-sign.byteimg.com/ocean-cloud-tos/image_skill/2f098977-98da-4cc3-85af-e417cabec97f_1756108769056157621_origin\~tplv-a9rns2rl98-image-qvalue.jpeg?rk3s=6823e3d0\&amp;amp;x-expires=1787644769\&amp;amp;x-signature=6%2BNdJqMI9xbWcWo59DdjwS%2BnhVw%3D&quot; alt&gt;&lt;/p&gt;
    
    </summary>
    
    
    
      <category term="RPA" scheme="https://www.tengfei.eu.org/tags/RPA/"/>
    
      <category term="工具" scheme="https://www.tengfei.eu.org/tags/%E5%B7%A5%E5%85%B7/"/>
    
  </entry>
  
  <entry>
    <title>Docker模块化工具&#39;&#39;&#39;</title>
    <link href="https://www.tengfei.eu.org/article/4e502d86.html"/>
    <id>https://www.tengfei.eu.org/article/4e502d86.html</id>
    <published>2025-01-20T09:30:55.000Z</published>
    <updated>2025-08-25T08:22:41.854Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Docker 模块化工具</p></blockquote><a id="more"></a><h1 id="Docker-从入门到实战：安装、部署、挂载与常用命令详解"><a href="#Docker-从入门到实战：安装、部署、挂载与常用命令详解" class="headerlink" title="Docker 从入门到实战：安装、部署、挂载与常用命令详解"></a>Docker 从入门到实战：安装、部署、挂载与常用命令详解</h1><p>在当今的软件开发与运维领域，Docker 已经成为不可或缺的工具。它通过容器化技术，实现了应用程序的快速部署、移植和扩展，极大地提高了开发和运维效率。本文将从 Docker 的基础简介开始，详细介绍其安装步骤、核心命令、数据挂载与端口映射，以及实际应用部署的全过程，帮助初学者快速上手并深入运用 Docker。</p><h2 id="一、Docker-简介"><a href="#一、Docker-简介" class="headerlink" title="一、Docker 简介"></a>一、Docker 简介</h2><p>Docker 是一个开源的容器化平台，允许开发者将应用程序及其依赖打包成轻量级、可移植的容器，实现快速部署和跨环境运行。相比传统虚拟机，Docker 容器更加高效、轻量，启动速度更快。传统虚拟机需要模拟完整的操作系统，而容器则共享宿主机的内核，仅包含应用程序及其所需的依赖库，因此在资源占用、启动速度和移植性上具有显著优势。</p><h2 id="二、Docker-安装指南"><a href="#二、Docker-安装指南" class="headerlink" title="二、Docker 安装指南"></a>二、Docker 安装指南</h2><p>Docker 支持多种操作系统，包括 Windows、macOS 和 Linux。不同系统的安装方式略有差异，下面分别介绍。</p><h3 id="1-Linux-系统安装（以-Ubuntu-Debian-为例）"><a href="#1-Linux-系统安装（以-Ubuntu-Debian-为例）" class="headerlink" title="1. Linux 系统安装（以 Ubuntu/Debian 为例）"></a>1. Linux 系统安装（以 Ubuntu/Debian 为例）</h3><p>Linux 系统对 Docker 的支持最为原生，安装步骤如下：</p><ul><li><p><strong>更新软件包索引</strong>：<code>sudo apt update</code></p></li><li><p><strong>安装依赖</strong>：<code>sudo apt install -y apt-transport-https ca-certificates curl software-properties-common</code></p></li><li><p><strong>添加 Docker 官方 GPG 密钥</strong>：<code>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg</code></p></li><li><p><strong>添加 Docker 仓库</strong>：<code>echo &quot;deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot; | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null</code></p></li><li><p><strong>安装 Docker</strong>：</p></li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update</span><br><span class="line"></span><br><span class="line">sudo apt install -y docker-ce docker-ce-cli containerd.io</span><br></pre></td></tr></table></figure><ul><li><p><strong>启动 Docker 并设置开机自启</strong>：<code>sudo systemctl enable --now docker</code></p></li><li><p><strong>验证安装</strong>：</p></li></ul><pre><code>*   查看 Docker 版本：`docker --version`*   运行测试镜像：`sudo docker run hello-world`，如果出现 “Hello from Docker!” 的消息，则表示安装成功。</code></pre><h3 id="2-Windows-系统安装"><a href="#2-Windows-系统安装" class="headerlink" title="2. Windows 系统安装"></a>2. Windows 系统安装</h3><p>Windows 系统安装 Docker 需要满足一定的条件，首先你的 Windows 版本需要是 Windows 10 专业版、企业版或教育版（Build 16299 或更高），因为需要开启 Hyper-V 功能。如果是家庭版用户，可以安装 Docker Desktop for Windows（基于 WSL 2）。</p><p>安装步骤如下：</p><ul><li><p>访问 Docker 官网（<a href="https://www.docker.com/products/docker-desktop" target="_blank" rel="noopener">https://www.docker.com/products/docker-desktop</a>），下载 Docker Desktop for Windows 安装包。</p></li><li><p>双击安装包，按照提示进行安装，勾选 “使用 WSL 2 而不是 Hyper-V”（如果是家庭版用户）。</p></li><li><p>安装完成后，启动 Docker，在任务栏中会出现 Docker 图标，当图标显示为绿色时，表示 Docker 启动成功。</p></li></ul><h3 id="3-macOS-系统安装"><a href="#3-macOS-系统安装" class="headerlink" title="3. macOS 系统安装"></a>3. macOS 系统安装</h3><p>macOS 系统安装 Docker 相对简单，可通过以下方式：</p><ul><li><p><strong>下载安装包安装</strong>：前往 Docker 官网下载 Docker Desktop for Mac 安装包，将下载的.dmg 文件打开，把 Docker 图标拖拽到 Applications 文件夹，从应用程序中启动 Docker，首次启动可能需要输入密码授权，启动成功后在菜单栏会出现 Docker 图标。</p></li><li><p><strong>使用 Homebrew 安装</strong>：<code>brew install --cask docker</code></p></li></ul><h2 id="三、Docker-核心命令"><a href="#三、Docker-核心命令" class="headerlink" title="三、Docker 核心命令"></a>三、Docker 核心命令</h2><p>掌握 Docker 的核心命令是使用 Docker 的基础，下面详细介绍一些常用的命令。</p><h3 id="1-镜像管理"><a href="#1-镜像管理" class="headerlink" title="1. 镜像管理"></a>1. 镜像管理</h3><div class="table-container"><table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>docker pull &lt;镜像名&gt;</code></td><td>拉取镜像（如 <code>docker pull nginx</code>），如果不指定标签，默认拉取 latest 标签的镜像</td></tr><tr><td><code>docker images</code></td><td>查看本地镜像，包括镜像名称、标签、ID、创建时间和大小</td></tr><tr><td><code>docker rmi &lt;镜像ID或镜像名称:标签&gt;</code></td><td>删除镜像，例如删除 Ubuntu 18.04 镜像：<code>docker rmi ubuntu:18.04</code>。如果镜像正在被容器使用，需要先删除容器才能删除镜像</td></tr><tr><td><code>docker search &lt;镜像名&gt;</code></td><td>搜索 Docker Hub 上的镜像，会显示镜像的名称、描述、星级等信息</td></tr><tr><td><code>docker build -t &lt;镜像名&gt; .</code></td><td>构建镜像（需 Dockerfile），其中 <code>-t</code> 选项用于指定镜像的名称和标签，<code>.</code> 表示当前目录</td></tr></tbody></table></div><h3 id="2-容器管理"><a href="#2-容器管理" class="headerlink" title="2. 容器管理"></a>2. 容器管理</h3><div class="table-container"><table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>docker run [选项] &lt;镜像名称&gt;:&lt;标签&gt; [命令]</code></td><td>创建并启动容器，常用选项包括 <code>-d</code>（后台运行）、<code>-i</code>（交互模式）、<code>-t</code>（伪终端）、<code>-p</code>（端口映射）、<code>-v</code>（挂载卷）等。例如后台运行容器：<code>docker run -d --name &lt;容器名&gt; &lt;镜像&gt;</code></td></tr><tr><td><code>docker ps</code></td><td>查看运行中的容器</td></tr><tr><td><code>docker ps -a</code></td><td>查看所有容器（包括已停止的）</td></tr><tr><td><code>docker stop &lt;容器ID或容器名称&gt;</code></td><td>停止容器，例如停止某个容器：<code>docker stop 123456</code></td></tr><tr><td><code>docker start &lt;容器ID或容器名称&gt;</code></td><td>启动已停止的容器</td></tr><tr><td><code>docker restart &lt;容器ID或容器名称&gt;</code></td><td>重启容器</td></tr><tr><td><code>docker rm &lt;容器ID或容器名称&gt;</code></td><td>删除容器，删除容器前需要先停止容器。如果要强制删除正在运行的容器，可以使用 <code>-f</code> 选项：<code>docker rm -f &lt;容器ID或容器名称&gt;</code></td></tr><tr><td><code>docker logs &lt;容器ID或容器名称&gt;</code></td><td>查看容器的运行日志，使用 <code>-f</code> 选项可以实时查看日志：<code>docker logs -f &lt;容器ID或容器名称&gt;</code></td></tr><tr><td><code>docker exec -it &lt;容器ID或容器名称&gt; /bin/bash</code></td><td>进入容器终端，与容器进行交互</td></tr></tbody></table></div><h3 id="3-其他常用命令"><a href="#3-其他常用命令" class="headerlink" title="3. 其他常用命令"></a>3. 其他常用命令</h3><ul><li><p><strong>查看 Docker 信息</strong>：<code>docker info</code>，该命令会显示 Docker 的详细信息，包括容器数量、镜像数量、存储驱动等。</p></li><li><p><strong>查看容器详细信息</strong>：<code>docker inspect &lt;容器ID或容器名称&gt;</code>，该命令会返回容器的详细配置信息，包括网络设置、挂载点等。</p></li><li><p><strong>查看 Docker 网络</strong>：<code>docker network ls</code></p></li><li><p><strong>创建自定义网络</strong>：<code>docker network create my-net</code></p></li><li><p><strong>使用 docker-compose.yml 启动服务</strong>：<code>docker-compose up -d</code></p></li><li><p><strong>查看容器资源占用</strong>：<code>docker stats</code></p></li><li><p><strong>清理无用镜像、容器、网络</strong>：<code>docker system prune</code>，该命令会删除所有停止的容器、未被使用的网络、悬空镜像等，释放磁盘空间。</p></li></ul><h2 id="四、数据挂载与端口映射"><a href="#四、数据挂载与端口映射" class="headerlink" title="四、数据挂载与端口映射"></a>四、数据挂载与端口映射</h2><h3 id="1-数据挂载"><a href="#1-数据挂载" class="headerlink" title="1. 数据挂载"></a>1. 数据挂载</h3><p>为了防止容器被删除后数据丢失，需要对容器的数据进行持久化。Docker 提供了数据卷挂载（Volume）等方式。</p><p><strong>数据卷挂载（Volume）</strong>：将主机目录挂载到容器，实现数据持久化。</p><p>命令格式：<code>docker run -d --name &lt;容器名&gt; -v /宿主机目录:/容器目录 &lt;镜像&gt;</code></p><p>示例：<code>docker run -d --name my-nginx -v /home/user/nginx:/usr/share/nginx/html nginx</code>，其中 <code>/home/user/nginx</code> 是宿主机目录，<code>/usr/share/nginx/html</code> 是容器目录，宿主机目录的文件会同步到容器目录，容器内的修改也会反映到宿主机。</p><p>除了绑定宿主机目录，还可以使用 Docker 管理的卷（Volume），卷是 Docker 管理的宿主机文件系统的一部分，位于 <code>/var/lib/docker/volumes/</code> 目录下。创建卷的命令：<code>docker volume create myvolume</code>，使用卷的命令：<code>docker run -d -v myvolume:/data --name mycontainer ubuntu:18.04</code>。</p><h3 id="2-端口映射"><a href="#2-端口映射" class="headerlink" title="2. 端口映射"></a>2. 端口映射</h3><p>端口映射用于将容器内的服务端口暴露到宿主机，实现外部网络访问。</p><p>命令格式：<code>docker run -d --name &lt;容器名&gt; -p &lt;宿主机端口&gt;:&lt;容器端口&gt; &lt;镜像&gt;</code></p><p>示例：<code>docker run -d --name nginx -p 8080:80 nginx</code>，其中 8080 是宿主机端口，80 是容器端口，通过访问宿主机的 8080 端口即可访问容器内 80 端口提供的服务。</p><h2 id="五、Docker-部署实战"><a href="#五、Docker-部署实战" class="headerlink" title="五、Docker 部署实战"></a>五、Docker 部署实战</h2><h3 id="1-部署-Nginx"><a href="#1-部署-Nginx" class="headerlink" title="1. 部署 Nginx"></a>1. 部署 Nginx</h3><ul><li><p><strong>拉取镜像</strong>：<code>docker pull nginx</code></p></li><li><p><strong>运行容器（挂载本地目录 + 端口映射）</strong>：<code>docker run -d --name my-nginx -p 80:80 -v /home/user/nginx:/usr/share/nginx/html nginx</code></p></li><li><p><strong>验证部署</strong>：访问 <code>http://localhost</code> 即可看到 Nginx 默认页面，如果宿主机目录 <code>/home/user/nginx</code> 中有自定义的 index.html 文件，会显示该文件内容。</p></li></ul><h3 id="2-部署-MySQL"><a href="#2-部署-MySQL" class="headerlink" title="2. 部署 MySQL"></a>2. 部署 MySQL</h3><ul><li><p><strong>拉取 MySQL 镜像</strong>：<code>docker pull mysql:8.0</code></p></li><li><p><strong>运行 MySQL 容器（设置密码 + 数据持久化）</strong>：<code>docker run -d --name mysql-db -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -v /home/user/mysql:/var/lib/mysql mysql:8.0</code></p></li></ul><pre><code>*   `MYSQL_ROOT_PASSWORD=123456` 用于设置 root 密码。*   `/home/user/mysql` 是宿主机存储 MySQL 数据的目录，确保数据在容器删除后不丢失。</code></pre><ul><li><strong>验证部署</strong>：可以使用 MySQL 客户端工具连接宿主机的 3306 端口，输入用户名 root 和密码 123456，如果连接成功，则表示部署成功。</li></ul><h3 id="3-部署-Node-js-应用"><a href="#3-部署-Node-js-应用" class="headerlink" title="3. 部署 Node.js 应用"></a>3. 部署 Node.js 应用</h3><ul><li><strong>准备应用代码</strong>：创建一个简单的 Node.js 应用，文件名为 app.js：</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">const http = require(&apos;http&apos;);</span><br><span class="line"></span><br><span class="line">const server = http.createServer((req, res) =&gt; &#123;</span><br><span class="line"></span><br><span class="line">&amp;#x20; res.statusCode = 200;</span><br><span class="line"></span><br><span class="line">&amp;#x20; res.setHeader(&apos;Content-Type&apos;, &apos;text/plain&apos;);</span><br><span class="line"></span><br><span class="line">&amp;#x20; res.end(&apos;Hello, Docker!\n&apos;);</span><br><span class="line"></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">const port = 3000;</span><br><span class="line"></span><br><span class="line">server.listen(port, () =&gt; &#123;</span><br><span class="line"></span><br><span class="line">&amp;#x20; console.log(\`Server running at http://localhost:\$&#123;port&#125;/\`);</span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ul><li><strong>编写 Dockerfile</strong>：</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">FROM node:14</span><br><span class="line"></span><br><span class="line">WORKDIR /app</span><br><span class="line"></span><br><span class="line">COPY package\*.json ./</span><br><span class="line"></span><br><span class="line">RUN npm install</span><br><span class="line"></span><br><span class="line">COPY . .</span><br><span class="line"></span><br><span class="line">EXPOSE 3000</span><br><span class="line"></span><br><span class="line">CMD \[&quot;node&quot;, &quot;app.js&quot;]</span><br></pre></td></tr></table></figure><ul><li><p><strong>构建镜像</strong>：在 Dockerfile 所在目录执行 <code>docker build -t mynodeapp:v1 .</code> 命令。</p></li><li><p><strong>运行容器</strong>：<code>docker run -d -p 3000:3000 --name mynodecontainer mynodeapp:v1</code></p></li><li><p><strong>验证部署</strong>：在浏览器中访问 <code>http://localhost:3000</code>，如果看到 “Hello, Docker!” 的消息，则表示应用部署成功。</p></li></ul><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><h3 id="Docker-优势"><a href="#Docker-优势" class="headerlink" title="Docker 优势"></a>Docker 优势</h3><ul><li><p>轻量：容器共享宿主机内核，资源占用少。</p></li><li><p>快速：启动速度快，相比虚拟机可节省大量时间。</p></li><li><p>跨平台：可在不同操作系统上运行，保证应用环境一致性。</p></li><li><p>易于部署：通过镜像打包应用及依赖，一键部署。</p></li></ul><h3 id="核心功能"><a href="#核心功能" class="headerlink" title="核心功能"></a>核心功能</h3><ul><li><p>镜像管理：<code>docker pull</code>、<code>docker images</code> 等命令用于镜像的获取、查看和删除。</p></li><li><p>容器管理：<code>docker run</code>、<code>docker ps</code>、<code>docker exec</code> 等命令用于容器的创建、查看、进入等操作。</p></li><li><p>数据持久化：通过 <code>-v</code> 挂载数据卷，确保容器数据不丢失。</p></li><li><p>端口映射：通过 <code>-p</code> 映射端口，实现外部对容器服务的访问。</p></li></ul><h3 id="实战应用"><a href="#实战应用" class="headerlink" title="实战应用"></a>实战应用</h3><p>通过本文介绍的方法，可快速部署 Nginx、MySQL、Node.js 等服务，大大简化了应用的部署流程。</p><p>掌握这些知识后，你可以轻松使用 Docker 进行开发、测试和生产部署！ 🚀</p><h2 id="延伸阅读"><a href="#延伸阅读" class="headerlink" title="延伸阅读"></a>延伸阅读</h2><ul><li><p><a href="https://docs.docker.com/" target="_blank" rel="noopener">Docker 官方文档</a></p></li><li><p><a href="https://docs.docker.com/compose/" target="_blank" rel="noopener">Docker Compose 文档</a></p></li><li><p><a href="https://hub.docker.com/" target="_blank" rel="noopener">Docker Hub（官方镜像仓库）</a></p></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;Docker 模块化工具&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
    
      <category term="Docker" scheme="https://www.tengfei.eu.org/tags/Docker/"/>
    
      <category term="运维" scheme="https://www.tengfei.eu.org/tags/%E8%BF%90%E7%BB%B4/"/>
    
  </entry>
  
  <entry>
    <title>Zabbix 监控系统全面指南</title>
    <link href="https://www.tengfei.eu.org/article/57dc9716.html"/>
    <id>https://www.tengfei.eu.org/article/57dc9716.html</id>
    <published>2025-01-15T02:00:00.000Z</published>
    <updated>2025-08-20T05:10:20.610Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Zabbix 是一个功能强大的开源监控工具，适用于各种规模的企业。本文将详细介绍 Zabbix 的核心功能和使用方法。</p></blockquote><a id="more"></a><h1 id="Zabbix-监控系统全面指南"><a href="#Zabbix-监控系统全面指南" class="headerlink" title="Zabbix 监控系统全面指南"></a>Zabbix 监控系统全面指南</h1><p>Zabbix 是一个企业级开源监控解决方案，能够监控网络和服务的众多参数，从硬件指标到应用性能，从单机环境到分布式集群，都能提供全面的监控支持。本文将详细介绍 Zabbix 的核心功能和使用方法，助力运维人员构建稳定可靠的监控体系。</p><h2 id="1-Zabbix-简介"><a href="#1-Zabbix-简介" class="headerlink" title="1. Zabbix 简介"></a>1. Zabbix 简介</h2><p>Zabbix 是一个高度集成的网络监控解决方案，自诞生以来，凭借其强大的功能和开源免费的特性，被广泛应用于各类企业的 IT 基础设施监控中。它具有以下显著特点：</p><ul><li><p><strong>数据收集功能强大</strong>：支持多种数据采集方式，包括 Agent 代理、SNMP 协议、JMX、IPMI 等，可监控 CPU、内存、磁盘、网络、进程、日志等各类指标。</p></li><li><p><strong>灵活的告警机制</strong>：能根据自定义的阈值触发告警，并通过邮件、短信、企业微信、钉钉等多种渠道通知相关人员，还可设置告警升级策略。</p></li><li><p><strong>可视化展示丰富</strong>：提供折线图、饼图、柱状图等多种图表类型，支持自定义仪表盘，可直观展示监控数据和系统状态。</p></li><li><p><strong>支持分布式监控</strong>：通过 Zabbix Proxy 可实现大规模、跨地域的分布式监控，减轻中心服务器压力，提高监控效率。</p></li><li><p><strong>提供官方 API 接口</strong>：方便与其他系统集成，可通过 API 实现自动化配置、数据查询等操作，满足个性化需求。</p></li></ul><h2 id="2-核心组件"><a href="#2-核心组件" class="headerlink" title="2. 核心组件"></a>2. 核心组件</h2><p>Zabbix 监控系统由多个核心组件协同工作，共同完成数据采集、处理、存储、展示和告警等功能。</p><h3 id="2-1-Zabbix-Server"><a href="#2-1-Zabbix-Server" class="headerlink" title="2.1 Zabbix Server"></a>2.1 Zabbix Server</h3><p>Zabbix Server 是整个监控系统的核心，负责统筹协调各项工作：</p><ul><li><p><strong>数据收集</strong>：接收来自 Zabbix Agent、Proxy、SNMP 设备等的监控数据。</p></li><li><p><strong>触发器处理</strong>：对收集到的数据进行分析，判断是否满足触发器定义的条件，若满足则触发告警。</p></li><li><p><strong>告警发送</strong>：当触发器被触发时，按照预设的告警动作，通过指定的媒介发送告警信息。</p></li><li><p><strong>数据存储</strong>：将监控数据存储到数据库中，以便后续查询和分析。</p></li></ul><h3 id="2-2-数据库存储"><a href="#2-2-数据库存储" class="headerlink" title="2.2 数据库存储"></a>2.2 数据库存储</h3><p>Zabbix 需要数据库来存储大量的监控数据、配置信息等。它支持多种数据库后端：</p><ul><li><p>MySQL：最常用的数据库之一，部署简单，性能稳定，适合中小规模监控场景。</p></li><li><p>PostgreSQL：开源的关系型数据库，支持复杂的数据类型和查询，适合大规模监控。</p></li><li><p>Oracle：商业数据库，功能强大，性能优异，适合对数据库有高要求的企业级环境。</p></li><li><p>SQLite：轻量级嵌入式数据库，无需单独部署服务器，适合简单的测试环境或小规模监控。</p></li></ul><h3 id="2-3-Web-前端"><a href="#2-3-Web-前端" class="headerlink" title="2.3 Web 前端"></a>2.3 Web 前端</h3><p>Zabbix 提供了直观易用的 Web 界面，用户可通过该界面完成各种操作：</p><ul><li><p>配置监控项、触发器、告警媒介等监控相关参数。</p></li><li><p>查看实时和历史监控数据，通过图表直观了解系统运行状态。</p></li><li><p>管理用户和用户组，分配不同的权限，确保系统安全。</p></li><li><p>生成各类报表，如可用性报表、性能报表等，为系统优化提供依据。</p></li></ul><h2 id="3-安装部署"><a href="#3-安装部署" class="headerlink" title="3. 安装部署"></a>3. 安装部署</h2><p>以下以 CentOS 7 系统为例，介绍 Zabbix 的安装部署过程，采用 “Zabbix Server + MySQL + Apache + PHP” 架构。</p><h3 id="3-1-环境准备"><a href="#3-1-环境准备" class="headerlink" title="3.1 环境准备"></a>3.1 环境准备</h3><p>首先安装必要的软件包，包括 Web 服务器、数据库、PHP 及相关模块：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">\# 安装必要的软件包</span><br><span class="line"></span><br><span class="line">yum install -y httpd mariadb-server mariadb php php-mysql php-gd php-xml php-bcmath php-mbstring</span><br><span class="line"></span><br><span class="line">\# 启动服务并设置开机自启</span><br><span class="line"></span><br><span class="line">systemctl start httpd mariadb</span><br><span class="line"></span><br><span class="line">systemctl enable httpd mariadb</span><br></pre></td></tr></table></figure><h3 id="3-2-安装-Zabbix"><a href="#3-2-安装-Zabbix" class="headerlink" title="3.2 安装 Zabbix"></a>3.2 安装 Zabbix</h3><p>添加 Zabbix 官方仓库并安装 Zabbix 相关组件：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">\# 安装 Zabbix 仓库（以 Zabbix 5.0 版本为例）</span><br><span class="line"></span><br><span class="line">rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/x86\_64/zabbix-release-5.0-1.el7.noarch.rpm</span><br><span class="line"></span><br><span class="line">\# 安装 Zabbix Server、前端及相关配置</span><br><span class="line"></span><br><span class="line">yum install zabbix-server-mysql zabbix-web-mysql zabbix-apache-conf</span><br></pre></td></tr></table></figure><h3 id="3-3-数据库配置"><a href="#3-3-数据库配置" class="headerlink" title="3.3 数据库配置"></a>3.3 数据库配置</h3><p>登录数据库，创建 Zabbix 专用数据库和用户，并授予相应权限：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">\# 登录数据库</span><br><span class="line"></span><br><span class="line">mysql -u root -p</span><br><span class="line"></span><br><span class="line">\# 创建数据库（字符集设置为 utf8，排序规则为 utf8\_bin）</span><br><span class="line"></span><br><span class="line">CREATE DATABASE zabbix CHARACTER SET utf8 COLLATE utf8\_bin;</span><br><span class="line"></span><br><span class="line">\# 创建用户并设置密码（用户名 zabbix，密码可自定义，此处以 password 为例）</span><br><span class="line"></span><br><span class="line">CREATE USER &apos;zabbix&apos;@&apos;localhost&apos; IDENTIFIED BY &apos;password&apos;;</span><br><span class="line"></span><br><span class="line">\# 授予用户对 Zabbix 数据库的所有权限</span><br><span class="line"></span><br><span class="line">GRANT ALL PRIVILEGES ON zabbix.\* TO &apos;zabbix&apos;@&apos;localhost&apos;;</span><br><span class="line"></span><br><span class="line">\# 刷新权限</span><br><span class="line"></span><br><span class="line">FLUSH PRIVILEGES;</span><br><span class="line"></span><br><span class="line">\# 退出数据库</span><br><span class="line"></span><br><span class="line">exit</span><br></pre></td></tr></table></figure><h3 id="3-4-导入初始数据库"><a href="#3-4-导入初始数据库" class="headerlink" title="3.4 导入初始数据库"></a>3.4 导入初始数据库</h3><p>Zabbix 提供了默认的数据库表结构和初始数据，需导入到创建的数据库中：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">\# 导入数据（输入前面设置的 zabbix 用户密码）</span><br><span class="line"></span><br><span class="line">zcat /usr/share/doc/zabbix-server-mysql\*/create.sql.gz | mysql -u zabbix -p zabbix</span><br></pre></td></tr></table></figure><h3 id="3-5-配置-Zabbix-Server"><a href="#3-5-配置-Zabbix-Server" class="headerlink" title="3.5 配置 Zabbix Server"></a>3.5 配置 Zabbix Server</h3><p>修改 Zabbix Server 配置文件，关联数据库信息：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">\# 编辑配置文件</span><br><span class="line"></span><br><span class="line">vim /etc/zabbix/zabbix\_server.conf</span><br><span class="line"></span><br><span class="line">\# 找到以下参数，修改为实际数据库信息</span><br><span class="line"></span><br><span class="line">DBName=zabbix</span><br><span class="line"></span><br><span class="line">DBUser=zabbix</span><br><span class="line"></span><br><span class="line">DBPassword=password  # 前面设置的 zabbix 用户密码</span><br></pre></td></tr></table></figure><h3 id="3-6-配置-PHP"><a href="#3-6-配置-PHP" class="headerlink" title="3.6 配置 PHP"></a>3.6 配置 PHP</h3><p>调整 PHP 时区配置，确保与系统时区一致：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">\# 编辑 PHP 配置文件</span><br><span class="line"></span><br><span class="line">vim /etc/php.ini</span><br><span class="line"></span><br><span class="line">\# 找到 date.timezone，修改为 Asia/Shanghai</span><br><span class="line"></span><br><span class="line">date.timezone = Asia/Shanghai</span><br></pre></td></tr></table></figure><h3 id="3-7-启动服务"><a href="#3-7-启动服务" class="headerlink" title="3.7 启动服务"></a>3.7 启动服务</h3><p>启动 Zabbix Server、Apache 服务，并设置开机自启：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">\# 启动服务</span><br><span class="line"></span><br><span class="line">systemctl start zabbix-server httpd</span><br><span class="line"></span><br><span class="line">\# 设置开机自启</span><br><span class="line"></span><br><span class="line">systemctl enable zabbix-server httpd</span><br><span class="line"></span><br><span class="line">\# 检查服务状态（确保状态为 active (running)）</span><br><span class="line"></span><br><span class="line">systemctl status zabbix-server httpd</span><br></pre></td></tr></table></figure><h2 id="4-基础配置"><a href="#4-基础配置" class="headerlink" title="4. 基础配置"></a>4. 基础配置</h2><p>完成安装部署后，需要进行基础配置，以实现对目标主机的监控。</p><h3 id="4-1-访问-Zabbix-前端并登录"><a href="#4-1-访问-Zabbix-前端并登录" class="headerlink" title="4.1 访问 Zabbix 前端并登录"></a>4.1 访问 Zabbix 前端并登录</h3><p>在浏览器中输入服务器 IP 地址（如 <code>http://192.168.1.100/zabbix</code>），进入 Zabbix 初始化页面，按照提示完成配置。初始化完成后，使用默认用户名 <code>Admin</code> 和密码 <code>zabbix</code> 登录。登录后建议立即修改密码，点击右上角用户名 → “Profile” → “Password” 进行修改。</p><h3 id="4-2-添加主机"><a href="#4-2-添加主机" class="headerlink" title="4.2 添加主机"></a>4.2 添加主机</h3><p>添加需要监控的主机，步骤如下：</p><ol><li><p>登录 Zabbix Web 界面，进入 “配置” -&gt; “主机”。</p></li><li><p>点击右上角 “创建主机”。</p></li><li><p>填写主机信息：</p></li></ol><ul><li><p>主机名称：自定义，如 “Web-Server-01”。</p></li><li><p>可见名称：同主机名称或其他易识别的名称。</p></li><li><p>群组：点击 “Select”，勾选合适的主机组（如 “Linux servers”），若没有合适的可自定义创建。</p></li><li><p>接口：点击 “Add”，类型选择 “Agent”，IP 地址填写被监控主机的 IP 地址，端口默认 10050。</p></li></ul><ol><li>点击页面底部 “Add” 保存。</li></ol><h3 id="4-3-配置监控项"><a href="#4-3-配置监控项" class="headerlink" title="4.3 配置监控项"></a>4.3 配置监控项</h3><p>监控项是 Zabbix 收集数据的基本单位，每个监控项对应一个具体的监控指标。例如配置 CPU 使用率监控项：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">\# 示例：CPU 使用率监控项</span><br><span class="line"></span><br><span class="line">名称: CPU 使用率</span><br><span class="line"></span><br><span class="line">类型: Zabbix agent</span><br><span class="line"></span><br><span class="line">键值: system.cpu.util</span><br><span class="line"></span><br><span class="line">数据类型: 浮点数</span><br><span class="line"></span><br><span class="line">单位: %</span><br><span class="line"></span><br><span class="line">更新间隔: 60s  # 每 60 秒收集一次数据</span><br></pre></td></tr></table></figure><p>添加监控项的步骤：在主机配置页面切换到 “监控项” 标签，点击 “创建监控项”，按照上述示例填写相关信息，点击 “Add” 保存。</p><h3 id="4-4-设置触发器"><a href="#4-4-设置触发器" class="headerlink" title="4.4 设置触发器"></a>4.4 设置触发器</h3><p>触发器用于定义问题阈值，当监控项的取值满足触发器条件时，会触发告警。例如设置 CPU 使用率过高的触发器：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">\# 示例：CPU 使用率触发器</span><br><span class="line"></span><br><span class="line">名称: CPU 使用率过高</span><br><span class="line"></span><br><span class="line">表达式: &#123;host:system.cpu.util.last()&#125;&gt;80  # 当 CPU 使用率大于 80% 时触发</span><br><span class="line"></span><br><span class="line">严重性: 警告  # 可根据严重程度选择“信息”“警告”“平均”“高”“灾难”等</span><br></pre></td></tr></table></figure><p>添加触发器的步骤：在主机配置页面切换到 “触发器” 标签，点击 “创建触发器”，填写上述信息，点击 “Add” 保存。</p><h2 id="5-高级功能"><a href="#5-高级功能" class="headerlink" title="5. 高级功能"></a>5. 高级功能</h2><p>Zabbix 提供了许多高级功能，可满足复杂的监控需求。</p><h3 id="5-1-自动发现"><a href="#5-1-自动发现" class="headerlink" title="5.1 自动发现"></a>5.1 自动发现</h3><p>Zabbix 支持网络发现和低级别发现，提高监控的自动化程度：</p><ul><li><p><strong>网络发现</strong>：通过配置发现规则，自动发现网络中的设备（如服务器、交换机等），并根据预设的动作自动添加到监控系统中。</p></li><li><p><strong>低级别发现</strong>：自动发现主机上的具体资源，如文件系统、网络接口、进程等，并为这些资源自动创建监控项和触发器，无需手动配置。</p></li></ul><h3 id="5-2-分布式监控"><a href="#5-2-分布式监控" class="headerlink" title="5.2 分布式监控"></a>5.2 分布式监控</h3><p>当监控规模较大或跨地域时，可通过 Zabbix Proxy 实现分布式监控。Zabbix Proxy 可部署在远程区域，负责收集该区域内主机的监控数据，然后定期将数据发送给 Zabbix Server，减轻中心服务器的压力。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">\# Proxy 配置示例（zabbix\_proxy.conf）</span><br><span class="line"></span><br><span class="line">ProxyMode: 0  # 0 表示主动模式，Proxy 主动向 Server 发送数据</span><br><span class="line"></span><br><span class="line">Server: zabbix-server-ip  # Zabbix Server 的 IP 地址</span><br><span class="line"></span><br><span class="line">Hostname: proxy-name  # Proxy 的名称</span><br><span class="line"></span><br><span class="line">DBHost: localhost  # 数据库主机</span><br><span class="line"></span><br><span class="line">DBName: zabbix\_proxy  # Proxy 专用数据库名称</span><br><span class="line"></span><br><span class="line">DBUser: zabbix  # 数据库用户</span><br><span class="line"></span><br><span class="line">DBPassword: password  # 数据库密码</span><br></pre></td></tr></table></figure><h3 id="5-3-模板管理"><a href="#5-3-模板管理" class="headerlink" title="5.3 模板管理"></a>5.3 模板管理</h3><p>模板是监控配置的集合，包含监控项、触发器、图形等配置信息。通过模板可以实现监控配置的复用，提高配置效率：</p><ul><li><p><strong>创建模板</strong>：根据特定的监控需求，创建包含相关监控项、触发器等的模板。</p></li><li><p><strong>链接模板到主机</strong>：将模板链接到需要监控的主机，主机将继承模板中的所有监控配置。</p></li><li><p><strong>模板嵌套</strong>：一个模板可以链接到另一个模板，实现配置的层级复用。</p></li></ul><h2 id="6-告警配置"><a href="#6-告警配置" class="headerlink" title="6. 告警配置"></a>6. 告警配置</h2><p>及时的告警是监控系统的重要功能，Zabbix 提供了灵活的告警配置方式。</p><h3 id="6-1-配置告警媒介"><a href="#6-1-配置告警媒介" class="headerlink" title="6.1 配置告警媒介"></a>6.1 配置告警媒介</h3><p>Zabbix 支持多种告警媒介，可根据实际需求选择：</p><ul><li><p><strong>Email</strong>：通过邮件发送告警信息，需配置 SMTP 服务器信息。</p></li><li><p><strong>SMS</strong>：通过短信网关发送告警，适合紧急情况。</p></li><li><p><strong>Slack</strong>：集成 Slack 工具，在 Slack 频道中发送告警。</p></li><li><p><strong>微信</strong>：通过企业微信机器人发送告警信息。</p></li><li><p><strong>钉钉</strong>：通过钉钉机器人发送告警到钉钉群。</p></li></ul><p>以配置 QQ 邮箱告警媒介为例：</p><ol><li><p>进入 “Administration” → “Media types” → 点击 “Create media type”。</p></li><li><p>配置 SMTP 信息：</p></li></ol><ul><li><p>Name：自定义，如 “QQ-Email”。</p></li><li><p>Type：SMTP。</p></li><li><p>SMTP server：<a href="https://smtp.qq.com" target="_blank" rel="noopener">smtp.qq.com</a>。</p></li><li><p>SMTP server port：587（或 465，需对应加密方式）。</p></li><li><p>SMTP helo：<a href="https://qq.com" target="_blank" rel="noopener">qq.com</a>。</p></li><li><p>SMTP email：你的 QQ 邮箱（如 123456@qq.com）。</p></li><li><p>Authentication：勾选 “Username and password”。</p></li><li><p>Username：你的 QQ 邮箱。</p></li><li><p>Password：QQ 邮箱的授权码（不是邮箱密码，需在邮箱设置中获取）。</p></li><li><p>Connection security：STARTTLS（对应 587 端口）。</p></li></ul><ol><li><p>点击 “Test” 测试发送（输入接收邮箱，点击 “Test”，若收到测试邮件则配置正确）。</p></li><li><p>点击 “Add” 保存。</p></li></ol><h3 id="6-2-配置用户接收邮箱"><a href="#6-2-配置用户接收邮箱" class="headerlink" title="6.2 配置用户接收邮箱"></a>6.2 配置用户接收邮箱</h3><p>为用户配置接收告警的邮箱和媒介：</p><ol><li><p>进入 “Administration” → “Users” → 点击目标用户（如 “Admin”）。</p></li><li><p>切换到 “Media” 标签 → 点击 “Add”：</p></li></ol><ul><li><p>Type：选择前面创建的告警媒介（如 “QQ-Email”）。</p></li><li><p>Send to：填写接收告警的邮箱（如 admin@example.com）。</p></li><li><p>When active：1-7,00:00-24:00（全天生效）。</p></li><li><p>Severity：勾选需要通知的级别（如 “警告”“高”“灾难”）。</p></li></ul><ol><li>点击 “Add” 保存。</li></ol><h3 id="6-3-设置告警动作"><a href="#6-3-设置告警动作" class="headerlink" title="6.3 设置告警动作"></a>6.3 设置告警动作</h3><p>告警动作定义了在触发器被触发时要执行的操作：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">\# 示例：CPU 告警动作</span><br><span class="line"></span><br><span class="line">名称: CPU 使用率告警</span><br><span class="line"></span><br><span class="line">条件: 触发器严重性 = 警告  # 当触发器严重性为警告及以上时执行动作</span><br><span class="line"></span><br><span class="line">操作:</span><br><span class="line"></span><br><span class="line">&amp;#x20; \- 发送通知：通过配置的告警媒介向指定用户发送告警信息。</span><br><span class="line"></span><br><span class="line">&amp;#x20; \- 执行远程命令：如重启服务、清理磁盘等（需谨慎使用）。</span><br></pre></td></tr></table></figure><p>添加告警动作的步骤：进入 “Configuration” → “Actions” → 点击 “Create action”，配置动作名称、条件和操作，点击 “Add” 保存。</p><h2 id="7-可视化展示"><a href="#7-可视化展示" class="headerlink" title="7. 可视化展示"></a>7. 可视化展示</h2><p>Zabbix 提供了丰富的可视化展示功能，帮助用户直观了解系统运行状态。</p><h3 id="7-1-创建图形"><a href="#7-1-创建图形" class="headerlink" title="7.1 创建图形"></a>7.1 创建图形</h3><p>通过图形可以直观展示监控数据的变化趋势，支持多种图形类型：</p><ul><li><p>折线图：适合展示数据随时间的变化趋势。</p></li><li><p>饼图：适合展示各部分占比情况，如磁盘分区使用率占比。</p></li><li><p>柱状图：适合比较不同时间段或不同指标的数据。</p></li><li><p>离散图：适合展示离散型数据。</p></li></ul><p>创建图形的步骤：进入 “Configuration” → “Hosts”，选择目标主机，切换到 “Graphs” 标签，点击 “Create graph”，设置图形名称、添加监控项、选择图形类型等，点击 “Add” 保存。</p><h3 id="7-2-配置仪表盘"><a href="#7-2-配置仪表盘" class="headerlink" title="7.2 配置仪表盘"></a>7.2 配置仪表盘</h3><p>仪表盘可以集中展示多个监控指标和系统状态，方便用户快速了解整体情况。仪表盘可包含：</p><ul><li><p>监控数据图表：展示关键指标的实时数据。</p></li><li><p>系统状态汇总：如主机在线率、告警数量等。</p></li><li><p>告警信息：显示最新的告警内容。</p></li><li><p>拓扑图：展示网络设备之间的连接关系。</p></li></ul><p>配置仪表盘的步骤：进入 “Monitoring” → “Dashboard”，点击 “Edit dashboard”，添加需要展示的组件（如图形、触发器、地图等），调整布局和参数，点击 “Update” 保存。</p><h2 id="8-性能优化"><a href="#8-性能优化" class="headerlink" title="8. 性能优化"></a>8. 性能优化</h2><p>随着监控规模的扩大，Zabbix 可能会出现性能问题，需要进行优化。</p><h3 id="8-1-数据库优化"><a href="#8-1-数据库优化" class="headerlink" title="8.1 数据库优化"></a>8.1 数据库优化</h3><p>数据库是 Zabbix 性能的关键，可通过以下方式优化：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">\-- 为常用表创建索引，提高查询速度</span><br><span class="line"></span><br><span class="line">CREATE INDEX idx\_history ON history(itemid, clock);</span><br><span class="line"></span><br><span class="line">CREATE INDEX idx\_trends ON trends(itemid, clock);</span><br><span class="line"></span><br><span class="line">\-- 定期清理历史数据，避免数据量过大</span><br><span class="line"></span><br><span class="line">\-- 可通过 Zabbix 前端的“Administration”→“General”→“Housekeeping”配置数据保留时间</span><br></pre></td></tr></table></figure><h3 id="8-2-Zabbix-Server-配置优化"><a href="#8-2-Zabbix-Server-配置优化" class="headerlink" title="8.2 Zabbix Server 配置优化"></a>8.2 Zabbix Server 配置优化</h3><p>调整 Zabbix Server 配置文件中的参数，提高性能：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">\# zabbix\_server.conf</span><br><span class="line"></span><br><span class="line">StartPollers=20  # 启动的 poller 进程数量，根据监控主机数量调整</span><br><span class="line"></span><br><span class="line">StartTrappers=10  # 启动的 trapper 进程数量，处理被动接收的数据</span><br><span class="line"></span><br><span class="line">StartPingers=5  # 启动的 pinger 进程数量，用于 ICMP 监控</span><br><span class="line"></span><br><span class="line">CacheSize=256M  # 共享内存缓存大小，用于存储主机和监控项信息</span><br><span class="line"></span><br><span class="line">HistoryCacheSize=128M  # 历史数据缓存大小</span><br><span class="line"></span><br><span class="line">TrendCacheSize=64M  # 趋势数据缓存大小</span><br></pre></td></tr></table></figure><h2 id="9-常见问题解决"><a href="#9-常见问题解决" class="headerlink" title="9. 常见问题解决"></a>9. 常见问题解决</h2><p>在使用 Zabbix 过程中，可能会遇到一些常见问题，以下是解决方法：</p><h3 id="9-1-Zabbix-Agent-连接失败"><a href="#9-1-Zabbix-Agent-连接失败" class="headerlink" title="9.1 Zabbix Agent 连接失败"></a>9.1 Zabbix Agent 连接失败</h3><h1 id="Zabbix-监控系统全面指南-1"><a href="#Zabbix-监控系统全面指南-1" class="headerlink" title="Zabbix 监控系统全面指南"></a>Zabbix 监控系统全面指南</h1><ul><li>检查被监控主机的防火墙设置，确保 10050 端口（Zabbix Agent 默认端口）处于开放状态。可通过以下命令开放端口：</li></ul><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">\# 开放 10050 端口</span><br><span class="line"></span><br><span class="line">firewall-cmd --zone=public --add-port=10050/tcp --permanent</span><br><span class="line"></span><br><span class="line">\# 重新加载防火墙规则</span><br><span class="line"></span><br><span class="line">firewall-cmd --reload</span><br></pre></td></tr></table></figure><ul><li><p>验证 Zabbix Agent 配置文件是否正确，确保 <code>Server</code> 或 <code>ServerActive</code> 参数指向 Zabbix Server 的 IP 地址。配置文件路径通常为 <code>/etc/zabbix/zabbix_agentd.conf</code>。</p></li><li><p>确认 Zabbix Agent 服务是否正常运行，可通过 <code>systemctl status zabbix-agent</code> 命令查看，若未运行则执行 <code>systemctl start zabbix-agent</code> 启动服务。</p></li><li><p>检查网络连通性，在 Zabbix Server 上使用 <code>telnet 被监控主机IP 10050</code> 命令测试能否连接到被监控主机的 10050 端口，若无法连接则需排查网络问题。</p></li></ul><h3 id="9-2-数据库性能问题"><a href="#9-2-数据库性能问题" class="headerlink" title="9.2 数据库性能问题"></a>9.2 数据库性能问题</h3><ul><li><p>优化数据库查询语句，避免复杂的多表关联查询，减少数据库的负担。</p></li><li><p>调整数据库参数，如增大 <code>innodb_buffer_pool_size</code>（MySQL）、<code>shared_buffers</code>（PostgreSQL）等缓存参数，提高数据库的读写性能。</p></li><li><p>定期清理历史数据，根据业务需求合理设置数据保留时间，可通过 Zabbix 前端的 “Administration”→“General”→“Housekeeping” 进行配置。</p></li><li><p>考虑对数据库进行分区，将历史数据按时间分区存储，提高查询效率。</p></li></ul><h3 id="9-3-告警延迟或丢失"><a href="#9-3-告警延迟或丢失" class="headerlink" title="9.3 告警延迟或丢失"></a>9.3 告警延迟或丢失</h3><ul><li><p>检查 Zabbix Server 性能，若 Server 负载过高可能导致告警处理延迟，可通过优化 Server 配置（如增加进程数量、扩大缓存大小）提高性能。</p></li><li><p>确认告警媒介配置是否正确，如 SMTP 服务器地址、端口、账号密码等，可通过测试功能验证告警媒介是否能正常发送信息。</p></li><li><p>检查告警动作的配置，确保动作的条件设置合理，操作步骤正确，如是否指定了正确的接收用户和告警级别。</p></li><li><p>查看 Zabbix Server 日志（通常位于 <code>/var/log/zabbix/zabbix_server.log</code>），查找是否有与告警相关的错误信息，根据日志提示进行排查。</p></li></ul><h2 id="10-最佳实践"><a href="#10-最佳实践" class="headerlink" title="10. 最佳实践"></a>10. 最佳实践</h2><ol><li><p><strong>合理规划监控架构</strong>：根据监控规模和业务需求，选择合适的监控架构（单机或分布式）。对于大规模监控，建议采用分布式架构，通过 Zabbix Proxy 分担压力。</p></li><li><p><strong>使用模板统一管理</strong>：创建标准化的模板，包含常用的监控项、触发器、图形等配置，将模板链接到主机，实现监控配置的统一管理和复用，减少重复劳动。</p></li><li><p><strong>设置合适的监控间隔</strong>：根据监控指标的重要性和变化频率，设置合理的监控间隔。对于关键指标，可缩短监控间隔，确保及时发现问题；对于非关键指标，可适当延长监控间隔，减少资源消耗。</p></li><li><p><strong>定期维护和优化</strong>：定期检查监控系统的运行状态，包括 Zabbix Server、数据库、Agent 等组件的性能；清理无用的监控项、触发器和历史数据；根据业务变化调整监控策略。</p></li><li><p><strong>建立完善的告警机制</strong>：制定清晰的告警策略，包括告警级别、接收人员、通知方式等；设置告警升级机制，确保重要告警能及时送达相关人员；定期测试告警机制的有效性。</p></li></ol><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Zabbix 作为一款功能强大的企业级开源监控解决方案，为 IT 基础设施监控提供了全面的支持。从数据收集、处理、存储到可视化展示和告警通知，Zabbix 都表现出优异的性能和灵活性。</p><p>通过本文的介绍，相信你对 Zabbix 的核心功能、安装部署、配置使用、高级特性、性能优化及常见问题解决等方面有了全面的了解。在实际应用中，需根据具体的业务需求和环境特点，合理配置和使用 Zabbix，充分发挥其优势，构建稳定、高效、可靠的监控体系，及时发现和解决系统问题，保障 IT 系统的稳定运行。</p><p>如果你在使用 Zabbix 的过程中遇到其他问题或有好的经验分享，欢迎在评论区留言讨论，共同学习和进步。</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;Zabbix 是一个功能强大的开源监控工具，适用于各种规模的企业。本文将详细介绍 Zabbix 的核心功能和使用方法。&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
      <category term="技术教程" scheme="https://www.tengfei.eu.org/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="运维" scheme="https://www.tengfei.eu.org/tags/%E8%BF%90%E7%BB%B4/"/>
    
      <category term="监控" scheme="https://www.tengfei.eu.org/tags/%E7%9B%91%E6%8E%A7/"/>
    
      <category term="Zabbix" scheme="https://www.tengfei.eu.org/tags/Zabbix/"/>
    
  </entry>
  
  <entry>
    <title>微信小程序中的客服功能详解</title>
    <link href="https://www.tengfei.eu.org/article/2726645f.html"/>
    <id>https://www.tengfei.eu.org/article/2726645f.html</id>
    <published>2024-09-16T09:10:00.000Z</published>
    <updated>2025-07-08T01:55:58.118Z</updated>
    
    <content type="html"><![CDATA[<p>🔔 💬 👩‍💼</p><a id="more"></a><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在电商、服务预订等类型的微信小程序中，客服功能是连接用户与商家的重要桥梁。它不仅能够提升用户体验，还能有效提高转化率和客户满意度。本文将详细解析如何在微信小程序中实现客服功能，并探讨其优势和使用方法。</p><h2 id="小程序客服功能概述"><a href="#小程序客服功能概述" class="headerlink" title="小程序客服功能概述"></a>小程序客服功能概述</h2><p>微信小程序提供了便捷的客服接口，允许开发者为小程序接入客服系统。通过这个功能，用户可以在小程序内直接与客服人员进行沟通，解决问题或提供咨询服务。</p><h2 id="接入客服功能"><a href="#接入客服功能" class="headerlink" title="接入客服功能"></a>接入客服功能</h2><p>要使用小程序的客服功能，首先需要在微信公众平台进行配置。</p><h3 id="1-开通客服功能"><a href="#1-开通客服功能" class="headerlink" title="1. 开通客服功能"></a>1. 开通客服功能</h3><p>登录微信公众平台，进入“小程序管理后台”，在“功能”中找到“客服”，启用“客服消息”和“小程序客服消息”接口，按照指引开通客服功能并设置接待人员。</p><h3 id="2-小程序客服"><a href="#2-小程序客服" class="headerlink" title="2. 小程序客服"></a>2. 小程序客服</h3><blockquote><p><a href="https://mpkf.weixin.qq.com/" target="_blank" rel="noopener">小程序客服功能控制台</a></p></blockquote><p>在小程序页面中，你可以直接使用官方提供的 <code>open-type=&quot;contact&quot;</code> 组件来快速实现一个客服入口。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">open-type</span>=<span class="string">"contact"</span> <span class="attr">bindcontact</span>=<span class="string">"handleContact"</span> <span class="attr">session-from</span>=<span class="string">"sessionFrom"</span>&gt;</span><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="3-微信客服"><a href="#3-微信客服" class="headerlink" title="3. 微信客服"></a>3. 微信客服</h3><blockquote><p><a href="[开放接口 / 微信客服 / wx.openCustomerServiceChat (qq.com">微信客服接口手册</a>](<a href="https://developers.weixin.qq.com/miniprogram/dev/api/open-api/service-chat/wx.openCustomerServiceChat.html" target="_blank" rel="noopener">https://developers.weixin.qq.com/miniprogram/dev/api/open-api/service-chat/wx.openCustomerServiceChat.html</a>))</p></blockquote><p>如果你希望有更个性化的客服入口，可以自定义按钮样式，并通过编写代码触发客服会话。</p><ul><li>可关联企业微信使用客服功能；</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//客服服务</span></span><br><span class="line">bindCustomerServiceChat: <span class="keyword">async</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">  <span class="comment">// console.log('联系企业微信客服');</span></span><br><span class="line">  wx.openCustomerServiceChat(&#123;</span><br><span class="line">    extInfo: &#123;</span><br><span class="line">      url: <span class="string">''</span>,<span class="comment">//客服链接</span></span><br><span class="line">  &#125;,</span><br><span class="line">    corpId: <span class="string">''</span>,<span class="comment">//企业号</span></span><br><span class="line">    showMessageCard: <span class="literal">true</span>,</span><br><span class="line">    sendMessageTitle: <span class="string">'每日签到获取积分'</span>,</span><br><span class="line">    sendMessagePath: <span class="string">'projects/house/pages/my/points/my_points.html'</span>,</span><br><span class="line">    sendMessageImg: <span class="string">'projects/house/images/my/get_points.png'</span>,</span><br><span class="line">    success(res) &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'成功打开客服聊天界面'</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    fail(err) &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'打开客服聊天界面失败'</span>, err);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="优化客服体验"><a href="#优化客服体验" class="headerlink" title="优化客服体验"></a>优化客服体验</h2><p>为了提升用户的咨询体验，你可以采取以下措施：</p><ul><li><strong>快速响应</strong>：确保客服人员能够及时回复用户的消息。</li><li><strong>自动回复</strong>：设置常见问题的自动回复，提高回复效率。</li><li><strong>消息通知</strong>：合理设置接收消息通知，避免错过用户咨询。</li><li><strong>数据统计</strong>：利用客服接口提供的数据统计功能，分析用户咨询情况，不断优化服务。</li></ul><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>微信小程序的客服功能为商家和用户之间的沟通提供了便捷的方式。通过上述步骤，你可以在小程序中轻松实现专业的客服服务。记得定期评估和优化客服流程，确保用户能够得到满意的服务体验。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;🔔 💬 👩‍💼&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术教程" scheme="https://www.tengfei.eu.org/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="云开发" scheme="https://www.tengfei.eu.org/tags/%E4%BA%91%E5%BC%80%E5%8F%91/"/>
    
      <category term="低代码" scheme="https://www.tengfei.eu.org/tags/%E4%BD%8E%E4%BB%A3%E7%A0%81/"/>
    
      <category term="小程序" scheme="https://www.tengfei.eu.org/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>微信云开发如何共享使用云环境</title>
    <link href="https://www.tengfei.eu.org/article/409cf66f.html"/>
    <id>https://www.tengfei.eu.org/article/409cf66f.html</id>
    <published>2024-08-29T11:53:00.000Z</published>
    <updated>2025-07-08T01:55:39.523Z</updated>
    
    <content type="html"><![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在数字化时代，云计算已成为软件开发的重要工具。微信云开发作为腾讯云的一种服务，为开发者提供了便捷的云端开发环境。本文将详细介绍如何在个人博客中共享使用微信云环境。</p><a id="more"></a><h2 id="使用前提"><a href="#使用前提" class="headerlink" title="使用前提"></a>使用前提</h2><p><strong>需小程序公共库 2.13.0 或以上）（需 wx-server-sdk 版本大于或等于 2.3.0）</strong></p><ul><li><p>被共享方无法进行函数更新只能调用，只有共享方可以进行云函数更新上传；</p></li><li><p>云开发后台的管理权限是可以操作新建/删除函数的；</p></li><li>确保要共享资源的两个小程序在<strong>同个主体</strong>下面；</li></ul><h2 id="相关文档"><a href="#相关文档" class="headerlink" title="相关文档"></a>相关文档</h2><blockquote><p><a href="https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/resource-sharing/#%E4%BB%A3%E7%A0%81%E4%BD%BF%E7%94%A8" target="_blank" rel="noopener">共享代码要参考文档进行cloud的初始化和调用</a> </p></blockquote><h2 id="操作步骤"><a href="#操作步骤" class="headerlink" title="操作步骤"></a>操作步骤</h2><h3 id="一、开通资源共享"><a href="#一、开通资源共享" class="headerlink" title="一、开通资源共享"></a>一、开通资源共享</h3><p><img src="/article/409cf66f/image-20240314094850119.png" alt="image-20240314094850119"></p><h3 id="二、共享方添加cloudbase-auth-云函数，用于鉴权调用方的身份并制定相关权限。"><a href="#二、共享方添加cloudbase-auth-云函数，用于鉴权调用方的身份并制定相关权限。" class="headerlink" title="二、共享方添加cloudbase_auth 云函数，用于鉴权调用方的身份并制定相关权限。"></a>二、共享方添加<code>cloudbase_auth</code> 云函数，用于鉴权调用方的身份并制定相关权限。</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cloud = <span class="built_in">require</span>(<span class="string">'wx-server-sdk'</span>)</span><br><span class="line">cloud.init(&#123;</span><br><span class="line">  env: cloud.DYNAMIC_CURRENT_ENV</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 云函数入口函数</span></span><br><span class="line">exports.main = <span class="keyword">async</span> (event, context) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> wxContext = cloud.getWXContext()</span><br><span class="line"></span><br><span class="line">  <span class="built_in">console</span>.log(event)</span><br><span class="line">  <span class="built_in">console</span>.log(wxContext)</span><br><span class="line">  <span class="comment">// 跨账号调用时，由此拿到来源方小程序/公众号 AppID</span></span><br><span class="line">  <span class="built_in">console</span>.log(wxContext.FROM_APPID)</span><br><span class="line">  <span class="comment">// 跨账号调用时，由此拿到来源方小程序/公众号的用户 OpenID</span></span><br><span class="line">  <span class="built_in">console</span>.log(wxContext.FROM_OPENID)</span><br><span class="line">  <span class="comment">// 跨账号调用、且满足 unionid 获取条件时，由此拿到同主体下的用户 UnionID</span></span><br><span class="line">  <span class="built_in">console</span>.log(wxContext.FROM_UNIONID)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    errCode: <span class="number">0</span>,</span><br><span class="line">    errMsg: <span class="string">''</span>,</span><br><span class="line">    auth: <span class="built_in">JSON</span>.stringify(&#123;</span><br><span class="line">      <span class="comment">// 自定义安全规则</span></span><br><span class="line">      <span class="comment">// 在前端访问资源方数据库、云函数等资源时，资源方可以通过</span></span><br><span class="line">      <span class="comment">// 安全规则的 `auth.custom` 字段获取此对象的内容做校验，</span></span><br><span class="line">      <span class="comment">// 像这个示例就是资源方可以在安全规则中通过 `auth.custom.x` 获取</span></span><br><span class="line">      x: <span class="number">1</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="三、被共享方调用操作"><a href="#三、被共享方调用操作" class="headerlink" title="三、被共享方调用操作"></a>三、被共享方调用操作</h3><p><strong>被共享方小程序端调用共享方云函数：</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 声明新的 cloud 实例</span></span><br><span class="line"><span class="keyword">var</span> c1 = <span class="keyword">new</span> wx.cloud.Cloud(&#123;</span><br><span class="line">  <span class="comment">// 资源方 AppID</span></span><br><span class="line">  resourceAppid: <span class="string">'wxe0e2656d74f0bff3'</span>,</span><br><span class="line">  <span class="comment">// 资源方环境 ID</span></span><br><span class="line">  resourceEnv: <span class="string">'test-f96b31'</span>,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 跨账号调用，必须等待 init 完成</span></span><br><span class="line"><span class="comment">// init 过程中，资源方小程序对应环境下的 cloudbase_auth 函数会被调用，并需返回协议字段（见下）来确认允许访问、并可自定义安全规则</span></span><br><span class="line"><span class="keyword">await</span> c1.init()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 完成后正常使用资源方的已授权的云资源</span></span><br><span class="line"><span class="keyword">await</span> c1.callFunction(&#123;</span><br><span class="line">  name: <span class="string">'函数名'</span>,</span><br><span class="line">  data: &#123;&#125;,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><strong>被共享方云函数端调用共享方云函数：（共享方有云开发环境的情况下）</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cloud = <span class="built_in">require</span>(<span class="string">'wx-server-sdk'</span>)</span><br><span class="line"></span><br><span class="line">exports.main = <span class="keyword">async</span> (event) =&gt; &#123;</span><br><span class="line">  <span class="comment">// 声明新的 cloud 实例</span></span><br><span class="line">  <span class="keyword">var</span> c1 = <span class="keyword">new</span> cloud.Cloud(&#123;</span><br><span class="line">    <span class="comment">// 资源方 AppID</span></span><br><span class="line">    resourceAppid: <span class="string">'wxe0e2656d74f0bff3'</span>,</span><br><span class="line">    <span class="comment">// 资源方环境 ID</span></span><br><span class="line">    resourceEnv: <span class="string">'test-f96b31'</span>,</span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 跨账号调用，必须等待 init 完成</span></span><br><span class="line">  <span class="comment">// init 过程中，资源方小程序对应环境下的 cloudbase_auth 函数会被调用，并需返回协议字段（见下）来确认允许访问、并可自定义安全规则</span></span><br><span class="line">  <span class="keyword">await</span> c1.init()</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 完成后正常使用资源方的已授权的云资源</span></span><br><span class="line">  <span class="keyword">return</span> c1.callFunction(&#123;</span><br><span class="line">    name: <span class="string">'函数名'</span>,</span><br><span class="line">    data: &#123;&#125;,</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul><li>确保cloud 实例创建成功；</li><li>c1.init()方法必须使用<strong>异步执行</strong>，确保该方法执行完以后再进行其他操作；</li><li><p>所有调用cloud的方法，都需要改为c1的方法；</p></li><li><p>图片资源暂时不支持被共享方直接调用cloud://的path。因此需要转成https格式；</p></li></ul>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;引言&quot;&gt;&lt;a href=&quot;#引言&quot; class=&quot;headerlink&quot; title=&quot;引言&quot;&gt;&lt;/a&gt;引言&lt;/h2&gt;&lt;p&gt;在数字化时代，云计算已成为软件开发的重要工具。微信云开发作为腾讯云的一种服务，为开发者提供了便捷的云端开发环境。本文将详细介绍如何在个人博客中共享使用微信云环境。&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术教程" scheme="https://www.tengfei.eu.org/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="云开发" scheme="https://www.tengfei.eu.org/tags/%E4%BA%91%E5%BC%80%E5%8F%91/"/>
    
      <category term="低代码" scheme="https://www.tengfei.eu.org/tags/%E4%BD%8E%E4%BB%A3%E7%A0%81/"/>
    
      <category term="小程序" scheme="https://www.tengfei.eu.org/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>微信小程序使用订阅消息</title>
    <link href="https://www.tengfei.eu.org/article/c6e653ce.html"/>
    <id>https://www.tengfei.eu.org/article/c6e653ce.html</id>
    <published>2024-08-10T01:43:00.000Z</published>
    <updated>2025-07-08T01:55:07.085Z</updated>
    
    <content type="html"><![CDATA[<p>🔔 💬 📣<br><a id="more"></a></p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>微信小程序提供了订阅消息功能，允许用户在特定的场景下收到小程序主动发送的消息。它可以帮助开发者与用户进行实时的交互和沟通。通过使用微信小程序的消息推送功能，开发者可以向用户发送通知、提醒或者重要的信息。这对于提高用户体验和增强用户粘性非常有帮助。下面将介绍如何在微信小程序中实现订阅消息功能。</p><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>要使用订阅消息功能，需要先在微信公众平台开通模板消息功能，并获取到appid和模板消息的ID。</p><ul><li><strong>微信小程序的公共模板库内容主要是根据小程序的服务类目来分配的</strong>。</li><li><a href="https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/sendMessage.html" target="_blank" rel="noopener">订阅消息 相关文档</a></li></ul><p><img src="/article/c6e653ce/image-20240322145621524.png" alt="image-20240322145621524"></p><h3 id="长期订阅模版和一次性订阅模版区别："><a href="#长期订阅模版和一次性订阅模版区别：" class="headerlink" title="长期订阅模版和一次性订阅模版区别："></a>长期订阅模版和一次性订阅模版区别：</h3><p>在微信小程序中，长期订阅和一次性订阅模板是微信为满足不同服务场景需求而设立的两种消息推送机制。具体区别如下：</p><ul><li><strong>一次性订阅</strong>：指用户对小程序进行一次订阅后，开发者可以对该用户的OpenId推送一条模板消息，且无法再次对该用户推送消息，除非用户重新进行订阅操作。一次性订阅适用于那些只需要单次通知或交易的场景。</li><li><strong>长期订阅</strong>：允许开发者对已订阅的用户多次推送模板消息。长期订阅通常面向提供持续性服务的小程序，如政务民生、医疗、交通、金融、教育等线下公共服务类别，这些服务需要多次与用户互动或提供状态更新。</li></ul><p>总的来说，一次性订阅更适用于那些不需要持续跟踪用户状态的小程序，而长期订阅则适合需要与用户保持频繁联系的服务类小程序。开发者在选择使用哪种订阅方式时，应考虑自身的业务需求和用户的体验感受。</p><h2 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h2><h3 id="1-获取用户授权"><a href="#1-获取用户授权" class="headerlink" title="1. 获取用户授权"></a>1. 获取用户授权</h3><p>在使用订阅消息之前，需要先获取用户的授权。可以通过以下代码实现：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wx.requestSubscribeMessage(&#123;</span><br><span class="line">  tmplIds: [<span class="string">'template_id'</span>], <span class="comment">// 替换为实际的模板消息ID</span></span><br><span class="line">  success(res) &#123;</span><br><span class="line">    <span class="keyword">if</span> (res.errMsg === <span class="string">'requestSubscribeMessage:ok'</span>) &#123;</span><br><span class="line">      <span class="comment">// 用户同意订阅消息</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// 用户拒绝订阅消息</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  fail(err) &#123;</span><br><span class="line">    <span class="built_in">console</span>.error(<span class="string">'请求订阅消息失败'</span>, err);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="2-发送订阅消息"><a href="#2-发送订阅消息" class="headerlink" title="2. 发送订阅消息"></a>2. 发送订阅消息</h3><p>获取到订阅消息的token后，就可以向指定的用户发送订阅消息了。可以使用以下代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> cloud = <span class="built_in">require</span>(<span class="string">'wx-server-sdk'</span>)</span><br><span class="line">cloud.init(&#123;</span><br><span class="line">  env: cloud.DYNAMIC_CURRENT_ENV,</span><br><span class="line">&#125;)</span><br><span class="line">exports.main = <span class="keyword">async</span> (event, context) =&gt; &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> cloud.openapi.subscribeMessage.send(&#123;</span><br><span class="line">        <span class="string">"touser"</span>: <span class="string">'OPENID'</span>, <span class="comment">// 替换为实际的用户openid</span></span><br><span class="line">        <span class="string">"page"</span>: <span class="string">'pages/index/index'</span>,<span class="comment">// 替换为实际的页面路径</span></span><br><span class="line">        <span class="string">"lang"</span>: <span class="string">'zh_CN'</span>,</span><br><span class="line">        <span class="string">"data"</span>: &#123;</span><br><span class="line">          <span class="string">"number01"</span>: &#123;</span><br><span class="line">            <span class="string">"value"</span>: <span class="string">'339208499'</span></span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">"date01"</span>: &#123;</span><br><span class="line">            <span class="string">"value"</span>: <span class="string">'2015年01月05日'</span></span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">"site01"</span>: &#123;</span><br><span class="line">            <span class="string">"value"</span>: <span class="string">'TIT创意园'</span></span><br><span class="line">          &#125;,</span><br><span class="line">          <span class="string">"site02"</span>: &#123;</span><br><span class="line">            <span class="string">"value"</span>: <span class="string">'广州市新港中路397号'</span></span><br><span class="line">          &#125;</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="string">"templateId"</span>: <span class="string">'TEMPLATE_ID'</span>,<span class="comment">// 替换为实际的模板消息ID</span></span><br><span class="line">        <span class="string">"miniprogramState"</span>: <span class="string">'developer'</span><span class="comment">//developer为开发版；trial为体验版；formal为正式版；默认为正式版</span></span><br><span class="line">      &#125;)</span><br><span class="line">    <span class="keyword">return</span> result</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">return</span> err</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>以上是关于如何在微信小程序中使用订阅消息功能的简单介绍。通过获取用户授权、获取订阅消息的templateId以及发送订阅消息等步骤，可以实现小程序主动向用户推送消息的功能。希望对你有所帮助！</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;🔔 💬 📣&lt;br&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术教程" scheme="https://www.tengfei.eu.org/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="云开发" scheme="https://www.tengfei.eu.org/tags/%E4%BA%91%E5%BC%80%E5%8F%91/"/>
    
      <category term="低代码" scheme="https://www.tengfei.eu.org/tags/%E4%BD%8E%E4%BB%A3%E7%A0%81/"/>
    
      <category term="小程序" scheme="https://www.tengfei.eu.org/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>静态网站H5与微信小程序交互</title>
    <link href="https://www.tengfei.eu.org/article/5572efc6.html"/>
    <id>https://www.tengfei.eu.org/article/5572efc6.html</id>
    <published>2024-06-29T07:30:00.000Z</published>
    <updated>2025-07-08T01:52:38.006Z</updated>
    
    <content type="html"><![CDATA[<p>📱💬🖥️</p><a id="more"></a><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p><strong>什么是微信小程序？</strong></p><p>微信小程序是一种不需要下载安装即可使用的应用，它实现了应用“触手可及”的梦想，用户扫一扫或搜一下即可打开应用。同时，它也具备了传统APP的功能，如消息通知、离线缓存等。</p><p><strong>为什么需要从H5跳转到微信小程序？</strong></p><p>在移动互联网时代，小程序的使用场景越来越广泛，而H5页面作为传统的网页形式，其功能和体验相较于小程序有所不足。因此，从H5页面跳转到微信小程序，可以为用户提供更好的服务和体验。</p><h2 id="一、静态H5网站跳转到微信小程序"><a href="#一、静态H5网站跳转到微信小程序" class="headerlink" title="一、静态H5网站跳转到微信小程序"></a>一、静态H5网站跳转到微信小程序</h2><h3 id="H5跳转到微信小程序"><a href="#H5跳转到微信小程序" class="headerlink" title="H5跳转到微信小程序"></a>H5跳转到微信小程序</h3><ol><li>首先，我们需要在H5页面中添加一个跳转按钮，当用户点击该按钮时，触发跳转事件。</li></ol><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">"jumpToMiniProgram"</span>&gt;</span>跳转到微信小程序<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><ol><li>接下来，我们需要编写JavaScript代码，监听按钮的点击事件，并在点击事件中实现跳转逻辑。</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">document</span>.getElementById(<span class="string">'jumpToMiniProgram'</span>).addEventListener(<span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 跳转到微信小程序的逻辑</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><ol><li>在跳转逻辑中，我们需要使用微信小程序的scheme协议来实现跳转。具体实现如下：</li></ol><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">jumpToMiniProgram</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> appId = <span class="string">'wx1234567890abcdef'</span>; <span class="comment">// 替换为你的小程序appId</span></span><br><span class="line">  <span class="keyword">var</span> path = <span class="string">'pages/index/index'</span>; <span class="comment">// 替换为你的小程序页面路径</span></span><br><span class="line">  <span class="keyword">var</span> extraData = &#123;</span><br><span class="line">    foo: <span class="string">'bar'</span> <span class="comment">// 携带的参数</span></span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">var</span> envVersion = <span class="string">'release'</span>; <span class="comment">// 可选值：develop（开发版）、trial（体验版）、release（正式版）</span></span><br><span class="line">  <span class="keyword">var</span> href = <span class="string">`weixin://dl/<span class="subst">$&#123;appId&#125;</span>?type=view&amp;env_version=<span class="subst">$&#123;envVersion&#125;</span>&amp;page=<span class="subst">$&#123;path&#125;</span>&amp;extra_data=<span class="subst">$&#123;<span class="built_in">JSON</span>.stringify(extraData)&#125;</span>`</span>;</span><br><span class="line">  <span class="built_in">window</span>.location.href = href;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>最后，将上述代码添加到H5页面中，即可实现从H5页面跳转到微信小程序并携带参数的功能。</li></ol><p>总结：通过以上步骤，我们可以实现从静态H5网站跳转到微信小程序并携带参数的功能。这不仅可以提高用户体验，还可以为用户提供更便捷的服务。希望本文对您有所帮助！</p><h2 id="二、静态H5网站调用微信云函数"><a href="#二、静态H5网站调用微信云函数" class="headerlink" title="二、静态H5网站调用微信云函数"></a>二、静态H5网站调用微信云函数</h2><p>在现代Web开发中，云函数已经成为了一种非常流行的服务，它允许开发者在云端运行代码，而无需关心底层基础设施。微信云函数是腾讯云提供的一种云函数服务，它允许开发者在微信环境中运行代码。本文将介绍如何在静态H5网站上调用微信云函数。</p><h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><p>首先，你需要有一个微信公众号，并开通微信云开发环境。然后，你需要安装微信开发者工具，用于创建使用你的云函数。</p><h3 id="创建云函数"><a href="#创建云函数" class="headerlink" title="创建云函数"></a>创建云函数</h3><p>在微信开发者工具中，选择“云开发”选项卡，然后点击“新建云函数”。输入函数名，例如“helloWorld”，并选择运行环境（如Node.js）。接下来，编写一个简单的云函数，如下所示：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 云函数入口文件</span></span><br><span class="line"><span class="keyword">const</span> cloud = <span class="built_in">require</span>(<span class="string">'wx-server-sdk'</span>);</span><br><span class="line"></span><br><span class="line">cloud.init(&#123; <span class="attr">env</span>: <span class="string">''</span> &#125;); <span class="comment">// 使用当前云环境</span></span><br><span class="line"><span class="comment">//初始化云函数</span></span><br><span class="line"><span class="keyword">const</span> db = cloud.database();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 云函数入口函数</span></span><br><span class="line">exports.main = <span class="keyword">async</span> (event, context) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> wxContext = cloud.getWXContext();</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    message: <span class="string">'Hello World'</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="部署云函数"><a href="#部署云函数" class="headerlink" title="部署云函数"></a>部署云函数</h3><p>保存云函数后，点击“上传并部署”按钮，等待部署完成。</p><h3 id="在H5网站中调用云函数"><a href="#在H5网站中调用云函数" class="headerlink" title="在H5网站中调用云函数"></a>在H5网站中调用云函数</h3><p>在你的H5网站中，你可以使用wx.cloud.callFunction方法来调用云函数。首先，确保你已经引入了微信JS-SDK。然后，编写如下代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">&lt;script src=<span class="string">"https://web-9gikcbug35bad3a8-1304825656.tcloudbaseapp.com/sdk/1.3.0/cloud.js"</span>&gt;<span class="xml"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 声明新的 cloud 实例</span></span><br><span class="line"><span class="keyword">var</span> c1 = <span class="keyword">new</span> <span class="keyword">this</span>.cloud.Cloud(&#123;</span><br><span class="line">    <span class="comment">// 必填，表示是未登录模式</span></span><br><span class="line">    identityless: <span class="literal">true</span>,</span><br><span class="line">    <span class="comment">// 资源方 AppID</span></span><br><span class="line">    resourceAppid: <span class="string">''</span>,</span><br><span class="line">    <span class="comment">// 资源方环境 ID</span></span><br><span class="line">    resourceEnv: <span class="string">''</span>,</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">//初始化</span></span><br><span class="line"><span class="keyword">await</span> c1.init();</span><br><span class="line"><span class="comment">// 初始化执行完再调用</span></span><br><span class="line"><span class="comment">// 完成后正常使用资源方的已授权的云资源</span></span><br><span class="line">c1.callFunction(&#123;</span><br><span class="line">    name: <span class="string">'helloWorld'</span>,</span><br><span class="line">    success: <span class="function"><span class="params">res</span> =&gt;</span> &#123;</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'云函数调用成功'</span>, res)</span><br><span class="line">    &#125;,</span><br><span class="line">    fail: <span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.error(<span class="string">'云函数调用失败'</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="开通云函数和数据库权限"><a href="#开通云函数和数据库权限" class="headerlink" title="开通云函数和数据库权限"></a>开通云函数和数据库权限</h3><p><img src="/article/5572efc6/image-20240219090349614.png" alt="image-20240219090349614"></p><p><img src="/article/5572efc6/image-20240219090207689.png" alt="image-20240219090207689"></p><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><p>现在，你可以在你的H5网站获取云函数信息了。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>通过以上步骤，你可以在静态H5网站上调用微信云函数。这种方法可以让你在不修改服务器端代码的情况下，实现一些复杂的业务逻辑。希望本文能帮助你更好地理解和使用微信云函数。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;📱💬🖥️&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术教程" scheme="https://www.tengfei.eu.org/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="云开发" scheme="https://www.tengfei.eu.org/tags/%E4%BA%91%E5%BC%80%E5%8F%91/"/>
    
      <category term="低代码" scheme="https://www.tengfei.eu.org/tags/%E4%BD%8E%E4%BB%A3%E7%A0%81/"/>
    
      <category term="小程序" scheme="https://www.tengfei.eu.org/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>云开发低代码平台后台管理页面设置</title>
    <link href="https://www.tengfei.eu.org/article/c84f7eec.html"/>
    <id>https://www.tengfei.eu.org/article/c84f7eec.html</id>
    <published>2024-06-20T07:30:00.000Z</published>
    <updated>2025-07-07T09:08:35.676Z</updated>
    
    <content type="html"><![CDATA[<a id="more"></a><h2 id="引言："><a href="#引言：" class="headerlink" title="引言："></a>引言：</h2><p>随着云开发的普及，越来越多的开发者开始关注低代码平台的应用。低代码平台通过可视化的方式，帮助开发者快速搭建应用，无需编写代码，大大提高了开发效率。然而，在使用低代码平台时，开发者通常需要对后台管理页面进行一些自定义设置，以满足特定的业务需求。本文将介绍如何在云开发低代码平台上设置后台管理页面，并提供具体的代码示例。</p><h2 id="步骤一：创建低代码应用"><a href="#步骤一：创建低代码应用" class="headerlink" title="步骤一：创建低代码应用"></a>步骤一：创建低代码应用</h2><p>首先，在云开发低代码平台上创建一个新的应用。应用创建完成后，你将获得一个应用的唯一标识符（AppID），用于后续的配置。</p><h2 id="步骤二：导入页面组件"><a href="#步骤二：导入页面组件" class="headerlink" title="步骤二：导入页面组件"></a>步骤二：导入页面组件</h2><p>在创建的云开发低代码应用中，导入页面组件。页面组件是低代码平台用于构建页面的 fundamental building block。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;a id=&quot;more&quot;&gt;&lt;/a&gt;
&lt;h2 id=&quot;引言：&quot;&gt;&lt;a href=&quot;#引言：&quot; class=&quot;headerlink&quot; title=&quot;引言：&quot;&gt;&lt;/a&gt;引言：&lt;/h2&gt;&lt;p&gt;随着云开发的普及，越来越多的开发者开始关注低代码平台的应用。低代码平台通过可视化的方式，帮助开发
      
    
    </summary>
    
    
      <category term="技术教程" scheme="https://www.tengfei.eu.org/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="云开发" scheme="https://www.tengfei.eu.org/tags/%E4%BA%91%E5%BC%80%E5%8F%91/"/>
    
      <category term="低代码" scheme="https://www.tengfei.eu.org/tags/%E4%BD%8E%E4%BB%A3%E7%A0%81/"/>
    
      <category term="小程序" scheme="https://www.tengfei.eu.org/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>小程序使用体验评价组件</title>
    <link href="https://www.tengfei.eu.org/article/560e2ad9.html"/>
    <id>https://www.tengfei.eu.org/article/560e2ad9.html</id>
    <published>2024-05-18T09:10:00.000Z</published>
    <updated>2025-07-07T10:28:36.815Z</updated>
    
    <content type="html"><![CDATA[<p>📝🌟👍</p><a id="more"></a><h1 id="微信小程序使用体验评价组件开发指南"><a href="#微信小程序使用体验评价组件开发指南" class="headerlink" title="微信小程序使用体验评价组件开发指南"></a>微信小程序使用体验评价组件开发指南</h1><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在小程序生态中，用户体验是决定产品成功的关键因素之一。为了更好地了解用户需求和改进服务，开发者通常会在小程序中加入使用体验评价组件。本文将指导你如何设计并实现一个微信小程序的使用体验评价组件，以收集用户反馈，提升产品质量。</p><h2 id="评价组件"><a href="#评价组件" class="headerlink" title="评价组件"></a>评价组件</h2><p>评价组件的设计简洁直观，让用户能够轻松地完成评价操作。</p><blockquote><p><a href="[行业能力 / 小程序评价 / 组件接口 (qq.com">体验评价组件</a>](<a href="https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/comment-plugin.html" target="_blank" rel="noopener">https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/guarantee/comment-plugin.html</a>))</p></blockquote><h3 id="1-添加组件"><a href="#1-添加组件" class="headerlink" title="1. 添加组件"></a>1. 添加组件</h3><p>在公众平台-设置-第三方设置-插件管理中添加评价组件</p><p><img src="/article/560e2ad9/Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20240216144124549.png" alt="image-20240216144124549"></p><ul><li><strong>星级评价</strong>：通常采用1-5星的评分体系，用户可以通过点击星星来进行打分。</li><li><strong>文本输入</strong>：提供一个文本框供用户填写具体的反馈意见。</li><li><strong>提交按钮</strong>：用户填写完毕后，点击提交按钮将反馈信息发送给开发者。</li><li><strong>动态效果</strong>：为星级评价添加点击动画，提高用户体验。</li><li><strong>即时反馈</strong>：在用户提交评价后，显示感谢语或提示信息。</li><li><strong>数据上报</strong>：将用户的评价数据上报到服务器，供开发者分析。</li></ul><h3 id="2-引用组件"><a href="#2-引用组件" class="headerlink" title="2. 引用组件"></a>2. 引用组件</h3><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">"plugins": &#123;</span><br><span class="line">  "wxacommentplugin": &#123;</span><br><span class="line">    "version": "latest",</span><br><span class="line">    "provider": "wx82e6ae1175f264fa"</span><br><span class="line">  &#125;</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><h3 id="3-功能实现"><a href="#3-功能实现" class="headerlink" title="3. 功能实现"></a>3. 功能实现</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用户反馈，体验评价</span></span><br><span class="line">bindUserFeedback: <span class="keyword">async</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">  <span class="keyword">var</span> plugin = requirePlugin(<span class="string">"wxacommentplugin"</span>);</span><br><span class="line">  </span><br><span class="line">  plugin.openComment(&#123;</span><br><span class="line">    <span class="comment">// wx_pay_id: '4200001729202306024807578', // 交易评价类账号选填</span></span><br><span class="line">    success: <span class="function">(<span class="params">res</span>)=&gt;</span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'plugin.openComment success'</span>, res)</span><br><span class="line">    &#125;,</span><br><span class="line">    fail: <span class="function">(<span class="params">res</span>) =&gt;</span>&#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'plugin.openComment fail'</span>, res)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><h2 id="自定义组件"><a href="#自定义组件" class="headerlink" title="自定义组件"></a>自定义组件</h2><h3 id="使用-lt-rate-gt-组件"><a href="#使用-lt-rate-gt-组件" class="headerlink" title="使用 &lt;rate&gt; 组件"></a>使用 <code>&lt;rate&gt;</code> 组件</h3><p>微信小程序提供了 <code>&lt;rate&gt;</code> 组件来实现星级评价功能。你可以在页面的 WXML 文件中这样使用它：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">rate</span> <span class="attr">disabled</span>=<span class="string">"&#123;&#123;false&#125;&#125;"</span> <span class="attr">max</span>=<span class="string">"&#123;&#123;5&#125;&#125;"</span> <span class="attr">value</span>=<span class="string">"&#123;&#123;3&#125;&#125;"</span> <span class="attr">bindchange</span>=<span class="string">"onRateChange"</span>&gt;</span><span class="tag">&lt;/<span class="name">rate</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在对应的 Page JS 文件中，你可以定义 <code>onRateChange</code> 函数来处理用户的评分：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Page(&#123;</span><br><span class="line">  onRateChange: <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'评分为：'</span>, e.detail.value);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="自定义文本输入与提交按钮"><a href="#自定义文本输入与提交按钮" class="headerlink" title="自定义文本输入与提交按钮"></a>自定义文本输入与提交按钮</h3><p>对于文本输入和提交按钮，你可以使用 <code>&lt;textarea&gt;</code> 和 <code>&lt;button&gt;</code> 组件来实现：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">textarea</span> <span class="attr">placeholder</span>=<span class="string">"请输入您的建议"</span> <span class="attr">bindinput</span>=<span class="string">"onTextInput"</span>&gt;</span><span class="tag">&lt;/<span class="name">textarea</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">form-type</span>=<span class="string">"submit"</span>&gt;</span>提交<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在 Page JS 文件中，定义 <code>onTextInput</code> 函数来获取用户的输入内容：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Page(&#123;</span><br><span class="line">  onTextInput: <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'用户输入：'</span>, e.detail.value);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="数据上报与本地存储"><a href="#数据上报与本地存储" class="headerlink" title="数据上报与本地存储"></a>数据上报与本地存储</h3><p>为了在无网络情况下也能收集用户反馈，可以使用 <code>wx.setStorage</code> 方法将数据存储在本地：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wx.setStorage(&#123;</span><br><span class="line">  key: <span class="string">'feedback'</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    rating: e.detail.value,</span><br><span class="line">    text: e.detail.value</span><br><span class="line">  &#125;,</span><br><span class="line">  success: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    wx.showToast(&#123;</span><br><span class="line">      title: <span class="string">'评价已保存'</span>,</span><br><span class="line">      icon: <span class="string">'success'</span></span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>在有网络的情况下，可以使用 <code>wx.request</code> 方法将数据上报到服务器：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">wx.request(&#123;</span><br><span class="line">  url: <span class="string">'https://yourserver.com/feedback'</span>,</span><br><span class="line">  method: <span class="string">'POST'</span>,</span><br><span class="line">  data: &#123;</span><br><span class="line">    rating: e.detail.value,</span><br><span class="line">    text: e.detail.value</span><br><span class="line">  &#125;,</span><br><span class="line">  success: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    wx.showToast(&#123;</span><br><span class="line">      title: <span class="string">'评价已提交'</span>,</span><br><span class="line">      icon: <span class="string">'success'</span></span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过上述步骤，你可以在微信小程序中实现一个使用体验评价组件，帮助收集用户的反馈，从而不断优化产品。记得在设计组件时，要注重用户体验，确保操作简单直观。同时，合理利用本地存储和数据上报功能，即使在离线状态下也能保证用户反馈不丢失，并为后续的产品改进提供宝贵数据。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;📝🌟👍&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术教程" scheme="https://www.tengfei.eu.org/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="云开发" scheme="https://www.tengfei.eu.org/tags/%E4%BA%91%E5%BC%80%E5%8F%91/"/>
    
      <category term="低代码" scheme="https://www.tengfei.eu.org/tags/%E4%BD%8E%E4%BB%A3%E7%A0%81/"/>
    
      <category term="小程序" scheme="https://www.tengfei.eu.org/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>小程序位置接口功能</title>
    <link href="https://www.tengfei.eu.org/article/700a87e1.html"/>
    <id>https://www.tengfei.eu.org/article/700a87e1.html</id>
    <published>2024-05-02T09:10:00.000Z</published>
    <updated>2025-07-07T10:27:15.425Z</updated>
    
    <content type="html"><![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在微信小程序的开发过程中，位置信息是一个常用的功能模块。它允许用户与现实世界的地理位置进行交互，无论是展示地图、查找周边服务还是实现定位功能，位置接口都扮演着至关重要的角色。本文将详细介绍如何在微信小程序中使用位置相关接口。</p><a id="more"></a><h2 id="一、官方文档"><a href="#一、官方文档" class="headerlink" title="一、官方文档"></a>一、官方文档</h2><blockquote><p>[微信小程序位置接口]: <a href="https://developers.weixin.qq.com/miniprogram/dev/api/location/wx.getFuzzyLocation.html" target="_blank" rel="noopener">https://developers.weixin.qq.com/miniprogram/dev/api/location/wx.getFuzzyLocation.html</a></p></blockquote><h2 id="二、接口调用"><a href="#二、接口调用" class="headerlink" title="二、接口调用"></a>二、接口调用</h2><ol><li>接口调用之前需要在公众平台提交申请，申请的接口审核通过后，才可以使用。</li></ol><p><img src="/article/700a87e1/Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20240216105746576.png" alt="image-20240216105746576"></p><h3 id="1-获取位置信息"><a href="#1-获取位置信息" class="headerlink" title="1. 获取位置信息"></a>1. 获取位置信息</h3><p>微信提供了丰富的 API 来获取用户的位置信息，最常用的是 <code>wx.getLocation</code> 方法和<code>wx.getFuzzyLocation</code>方法。</p><h4 id="wx-getLocation"><a href="#wx-getLocation" class="headerlink" title="wx.getLocation"></a>wx.getLocation</h4><p>使用 <code>wx.getLocation</code> 可以获取用户的当前地理位置信息。该方法返回一个包含经度、纬度等详细信息的对象。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wx.getLocation(&#123;</span><br><span class="line">  type: <span class="string">'gcj02'</span>, <span class="comment">// 坐标类型，默认为 wgs84</span></span><br><span class="line">  success: <span class="function"><span class="keyword">function</span>(<span class="params">res</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> latitude = res.latitude</span><br><span class="line">    <span class="keyword">var</span> longitude = res.longitude</span><br><span class="line">    <span class="keyword">var</span> speed = res.speed</span><br><span class="line">    <span class="keyword">var</span> accuracy = res.accuracy</span><br><span class="line">    <span class="comment">// 处理位置信息</span></span><br><span class="line">  &#125;,</span><br><span class="line">  fail: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 处理错误情况</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h4 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h4><ul><li>确保用户已授权小程序获取位置信息。</li><li><code>wx.getLocation</code> 需要在页面加载时调用，且不能在后台调用。</li><li>参数 <code>type</code> 可指定返回的坐标类型，’gcj02’ 为可以用于wx.openLocation使用的坐标类型。</li></ul><h3 id="2-展示地图"><a href="#2-展示地图" class="headerlink" title="2. 展示地图"></a>2. 展示地图</h3><p>要展示地图，你可以使用微信小程序提供的地图组件 <code>&lt;map&gt;</code>。</p><h4 id="使用-lt-map-gt-组件"><a href="#使用-lt-map-gt-组件" class="headerlink" title="使用&lt;map&gt;组件"></a>使用<code>&lt;map&gt;</code>组件</h4><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">map</span> <span class="attr">longitude</span>=<span class="string">"113.264700"</span> <span class="attr">latitude</span>=<span class="string">"23.099900"</span> <span class="attr">scale</span>=<span class="string">"14"</span>&gt;</span><span class="tag">&lt;/<span class="name">map</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="属性说明"><a href="#属性说明" class="headerlink" title="属性说明"></a>属性说明</h4><ul><li><code>longitude</code>：中心点的经度。</li><li><code>latitude</code>：中心点的纬度。</li><li><code>scale</code>：缩放级别，数值为5～18。</li><li>其他属性还包括 <code>markers</code>（标记点）、<code>style</code>（样式）等。</li></ul><h4 id="示例代码"><a href="#示例代码" class="headerlink" title="示例代码"></a>示例代码</h4><p>在页面 WXML 文件中加入 <code>&lt;map&gt;</code> 组件，并绑定事件和设置属性。</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">map</span> <span class="attr">id</span>=<span class="string">"myMap"</span> <span class="attr">longitude</span>=<span class="string">"113.264700"</span> <span class="attr">latitude</span>=<span class="string">"23.099900"</span> <span class="attr">scale</span>=<span class="string">"14"</span> <span class="attr">bindmarkertap</span>=<span class="string">"markerTap"</span>&gt;</span><span class="tag">&lt;/<span class="name">map</span>&gt;</span></span><br></pre></td></tr></table></figure><p>对应的 JavaScript 文件：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Page(&#123;</span><br><span class="line">  markerTap: <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(e.markerId)</span><br><span class="line">    <span class="built_in">console</span>.log(e.latLng)</span><br><span class="line">    <span class="comment">// 处理点击事件</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="3-地理编码与反地理编码"><a href="#3-地理编码与反地理编码" class="headerlink" title="3. 地理编码与反地理编码"></a>3. 地理编码与反地理编码</h3><p>有时我们可能需要将经纬度与具体的地址信息相互转换，这时就需要用到地理编码和反地理编码接口。</p><h4 id="wx-chooseLocation"><a href="#wx-chooseLocation" class="headerlink" title="wx.chooseLocation"></a>wx.chooseLocation</h4><p>用于让用户选择地点，返回具体地址信息。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">wx.chooseLocation(&#123;</span><br><span class="line">  success: <span class="function"><span class="keyword">function</span>(<span class="params">res</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> address = res.name <span class="comment">// 街道名字</span></span><br><span class="line">    <span class="keyword">var</span> latitude = res.latitude</span><br><span class="line">    <span class="keyword">var</span> longitude = res.longitude</span><br><span class="line">    <span class="comment">// 处理选择的位置信息</span></span><br><span class="line">  &#125;,</span><br><span class="line">  fail: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 处理错误情况</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h4 id="wx-geocoder"><a href="#wx-geocoder" class="headerlink" title="wx.geocoder"></a>wx.geocoder</h4><p>用于将经纬度转换为地址描述。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">wx.geocoder(&#123;</span><br><span class="line">  type: <span class="string">'wgs84'</span>,</span><br><span class="line">  location: &#123;</span><br><span class="line">    latitude: <span class="number">23.099900</span>,</span><br><span class="line">    longitude: <span class="number">113.264700</span></span><br><span class="line">  &#125;,</span><br><span class="line">  success: <span class="function"><span class="keyword">function</span>(<span class="params">res</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> country = res.country</span><br><span class="line">    <span class="keyword">var</span> province = res.provinceName</span><br><span class="line">    <span class="keyword">var</span> city = res.cityName</span><br><span class="line">    <span class="keyword">var</span> district = res.districtName</span><br><span class="line">    <span class="keyword">var</span> street = res.streetName</span><br><span class="line">    <span class="keyword">var</span> building = res.buildingName</span><br><span class="line">    <span class="comment">// 处理地址信息</span></span><br><span class="line">  &#125;,</span><br><span class="line">  fail: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 处理错误情况</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h4 id="wx-geocoder-translate"><a href="#wx-geocoder-translate" class="headerlink" title="wx.geocoder.translate"></a>wx.geocoder.translate</h4><p>用于将地址描述转换为经纬度。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">wx.geocoder.translate(&#123;</span><br><span class="line">  type: <span class="string">'wgs84'</span>,</span><br><span class="line">  location: <span class="string">'广东省深圳市南山区科技园南区高新南一道1000号'</span>,</span><br><span class="line">  success: <span class="function"><span class="keyword">function</span>(<span class="params">res</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> latitude = res.latitude</span><br><span class="line">    <span class="keyword">var</span> longitude = res.longitude</span><br><span class="line">    <span class="comment">// 处理经纬度信息</span></span><br><span class="line">  &#125;,</span><br><span class="line">  fail: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 处理错误情况</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="4-用户签到，判断当前位置距离活动地点距离"><a href="#4-用户签到，判断当前位置距离活动地点距离" class="headerlink" title="4. 用户签到，判断当前位置距离活动地点距离"></a>4. 用户签到，判断当前位置距离活动地点距离</h3><h4 id="使用getLocation接口获取用户当前位置"><a href="#使用getLocation接口获取用户当前位置" class="headerlink" title="使用getLocation接口获取用户当前位置"></a>使用getLocation接口获取用户当前位置</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">wx.getLocation(&#123;</span><br><span class="line">  type: <span class="string">'gcj02'</span>, <span class="comment">// 坐标类型，默认为 wgs84</span></span><br><span class="line">  success: <span class="function"><span class="keyword">function</span>(<span class="params">res</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> userLatitude = res.latitude</span><br><span class="line">    <span class="keyword">var</span> userLongitude = res.longitude</span><br><span class="line">    <span class="keyword">var</span> speed = res.speed</span><br><span class="line">    <span class="keyword">var</span> accuracy = res.accuracy</span><br><span class="line">    <span class="comment">// 处理位置信息</span></span><br><span class="line">  &#125;,</span><br><span class="line">  fail: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 处理错误情况</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h4 id="计算用户当前位置和活动位置距离"><a href="#计算用户当前位置和活动位置距离" class="headerlink" title="计算用户当前位置和活动位置距离"></a>计算用户当前位置和活动位置距离</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 计算两个经纬度之间的距离（单位：米）</span></span><br><span class="line"><span class="keyword">static</span> getDistance(lat1, lon1, lat2, lon2) &#123;</span><br><span class="line">  <span class="keyword">const</span> R = <span class="number">6371e3</span>; <span class="comment">// 地球半径（单位：米）</span></span><br><span class="line">  <span class="keyword">const</span> radLat1 = (lat1 * <span class="built_in">Math</span>.PI) / <span class="number">180</span>;</span><br><span class="line">  <span class="keyword">const</span> radLat2 = (lat2 * <span class="built_in">Math</span>.PI) / <span class="number">180</span>;</span><br><span class="line">  <span class="keyword">const</span> a = radLat1 - radLat2;</span><br><span class="line">  <span class="keyword">const</span> b = ((lon1 * <span class="built_in">Math</span>.PI) / <span class="number">180</span>) - ((lon2 * <span class="built_in">Math</span>.PI) / <span class="number">180</span>);</span><br><span class="line">  <span class="keyword">const</span> distance =</span><br><span class="line">    <span class="number">2</span> *</span><br><span class="line">    R *</span><br><span class="line">    <span class="built_in">Math</span>.asin(</span><br><span class="line">      <span class="built_in">Math</span>.sqrt(<span class="built_in">Math</span>.pow(<span class="built_in">Math</span>.sin(a / <span class="number">2</span>), <span class="number">2</span>) + <span class="built_in">Math</span>.cos(radLat1) * <span class="built_in">Math</span>.cos(radLat2) * <span class="built_in">Math</span>.pow(<span class="built_in">Math</span>.sin(b / <span class="number">2</span>), <span class="number">2</span>))</span><br><span class="line">    );</span><br><span class="line">  <span class="keyword">return</span> distance;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="判断位置距离是否超过活动范围"><a href="#判断位置距离是否超过活动范围" class="headerlink" title="判断位置距离是否超过活动范围"></a>判断位置距离是否超过活动范围</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (distance &lt;= <span class="keyword">this</span>.data.radius) &#123;</span><br><span class="line">  <span class="comment">// 在范围内，打开地图并显示活动地点</span></span><br><span class="line">  pageHelper.showSuccToast(<span class="string">'签到成功！'</span>,<span class="number">2500</span>,callback);</span><br><span class="line">&#125;<span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 不在范围内，提示用户距离过远</span></span><br><span class="line">  <span class="keyword">let</span> callback = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    wx.reLaunch(&#123;</span><br><span class="line">      url: <span class="string">'../../default/index/default_index'</span></span><br><span class="line">    &#125;)</span><br><span class="line">    wx.openLocation(&#123;</span><br><span class="line">      latitude: activityLatitude,</span><br><span class="line">      longitude: activityLongitude,</span><br><span class="line">      name: <span class="string">'活动地点'</span>,</span><br><span class="line">      scale: <span class="number">15</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  pageHelper.showNoneToast(<span class="string">'您距离活动地点较远，请前往活动附近区域！'</span>, <span class="number">2500</span>, callback);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="5-用户授权"><a href="#5-用户授权" class="headerlink" title="5. 用户授权"></a>5. 用户授权</h3><p>在app.json中添加，声明小程序需要获取的权限，这些权限需要在用户授权后才能使用。</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">"permission": &#123;</span><br><span class="line">"scope.userLocation": &#123;</span><br><span class="line">"desc": "获取您当前位置信息用于小程序位置接口的效果展示"</span><br><span class="line">       &#125;,</span><br><span class="line">       "scope.userFuzzyLocation":&#123;</span><br><span class="line">         "desc":"获取您当前位置信息用于小程序位置接口的效果展示"</span><br><span class="line">       &#125;</span><br><span class="line">&#125;,</span><br><span class="line"> "requiredPrivateInfos": ["chooseLocation","getLocation"],</span><br></pre></td></tr></table></figure><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>微信小程序提供了一系列强大的位置接口，使得开发者可以轻松地实现位置相关的功能。从获取用户当前位置到展示地图，再到复杂的地理编码转换，这些接口都是构建位置感知应用不可或缺的工具。记得在开发时遵守用户隐私和相关法律法规，合理使用这些位置数据。</p><p>​    </p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;引言&quot;&gt;&lt;a href=&quot;#引言&quot; class=&quot;headerlink&quot; title=&quot;引言&quot;&gt;&lt;/a&gt;引言&lt;/h2&gt;&lt;p&gt;在微信小程序的开发过程中，位置信息是一个常用的功能模块。它允许用户与现实世界的地理位置进行交互，无论是展示地图、查找周边服务还是实现定位功能，位置接口都扮演着至关重要的角色。本文将详细介绍如何在微信小程序中使用位置相关接口。&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术教程" scheme="https://www.tengfei.eu.org/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="云开发" scheme="https://www.tengfei.eu.org/tags/%E4%BA%91%E5%BC%80%E5%8F%91/"/>
    
      <category term="低代码" scheme="https://www.tengfei.eu.org/tags/%E4%BD%8E%E4%BB%A3%E7%A0%81/"/>
    
      <category term="小程序" scheme="https://www.tengfei.eu.org/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>小程序实现首页轮播图</title>
    <link href="https://www.tengfei.eu.org/article/b2c91cf7.html"/>
    <id>https://www.tengfei.eu.org/article/b2c91cf7.html</id>
    <published>2024-04-26T03:37:00.000Z</published>
    <updated>2025-07-08T01:50:21.709Z</updated>
    
    <content type="html"><![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>在小程序中，图片轮播是一个常见的功能，用于展示商品图片、广告宣传图等。微信官方提供的 <code>&lt;swiper&gt;</code> 和 <code>&lt;swiper-item&gt;</code> 组件可以轻松实现轮播图效果。本文将带你详细了解如何在微信小程序中使用轮播图组件。</p><a id="more"></a><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>在开始之前，确保你的小程序已经获取了所需的图片资源。这些图片可以存放在远程服务器或者云开发存储中。</p><h3 id="一、官方文档"><a href="#一、官方文档" class="headerlink" title="一、官方文档"></a>一、官方文档</h3><blockquote><p><a href="(https://developers.weixin.qq.com/miniprogram/dev/component/swiper.html">微信官方文档，视图组件swiper</a>)</p></blockquote><h3 id="二、使用-lt-swiper-gt-组件"><a href="#二、使用-lt-swiper-gt-组件" class="headerlink" title="二、使用 &lt;swiper&gt; 组件"></a>二、使用 <code>&lt;swiper&gt;</code> 组件</h3><p><code>&lt;swiper&gt;</code> 组件是轮播图的容器，而 <code>&lt;swiper-item&gt;</code> 组件则代表每一张轮播图。</p><h4 id="1-基本用法"><a href="#1-基本用法" class="headerlink" title="1. 基本用法"></a>1. 基本用法</h4><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">swiper</span> <span class="attr">indicator-dots</span>=<span class="string">"true"</span> <span class="attr">autoplay</span>=<span class="string">"false"</span> <span class="attr">interval</span>=<span class="string">"5000"</span> <span class="attr">duration</span>=<span class="string">"1000"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">swiper-item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">image</span> <span class="attr">src</span>=<span class="string">"path/to/image1.jpg"</span> <span class="attr">class</span>=<span class="string">"slide-image"</span>&gt;</span><span class="tag">&lt;/<span class="name">image</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">swiper-item</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">swiper-item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">image</span> <span class="attr">src</span>=<span class="string">"path/to/image2.jpg"</span> <span class="attr">class</span>=<span class="string">"slide-image"</span>&gt;</span><span class="tag">&lt;/<span class="name">image</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">swiper-item</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">swiper-item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">image</span> <span class="attr">src</span>=<span class="string">"path/to/image3.jpg"</span> <span class="attr">class</span>=<span class="string">"slide-image"</span>&gt;</span><span class="tag">&lt;/<span class="name">image</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">swiper-item</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">swiper</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2-属性说明"><a href="#2-属性说明" class="headerlink" title="2. 属性说明"></a>2. 属性说明</h4><ul><li><code>indicator-dots</code>：是否显示面板指示点，布尔值。</li><li><code>autoplay</code>：是否自动切换，布尔值。</li><li><code>interval</code>：自动切换时间间隔，单位毫秒，默认为 5000。</li><li><code>duration</code>：滑动动画时长，单位毫秒，默认为 500。</li></ul><div class="table-container"><table><thead><tr><th style="text-align:left">属性</th><th style="text-align:left">类型</th><th style="text-align:left">默认值</th><th style="text-align:left">必填</th><th style="text-align:left">说明</th></tr></thead><tbody><tr><td style="text-align:left">indicator-dots</td><td style="text-align:left">boolean</td><td style="text-align:left">false</td><td style="text-align:left">否</td><td style="text-align:left">是否显示面板指示点</td></tr><tr><td style="text-align:left">indicator-color</td><td style="text-align:left">color</td><td style="text-align:left">rgba(0, 0, 0, .3)</td><td style="text-align:left">否</td><td style="text-align:left">指示点颜色</td></tr><tr><td style="text-align:left">indicator-active-color</td><td style="text-align:left">color</td><td style="text-align:left">#000000</td><td style="text-align:left">否</td><td style="text-align:left">当前选中的指示点颜色</td></tr><tr><td style="text-align:left">autoplay</td><td style="text-align:left">boolean</td><td style="text-align:left">false</td><td style="text-align:left">否</td><td style="text-align:left">是否自动切换</td></tr><tr><td style="text-align:left">current</td><td style="text-align:left">number</td><td style="text-align:left">0</td><td style="text-align:left">否</td><td style="text-align:left">当前所在滑块的 index</td></tr><tr><td style="text-align:left">interval</td><td style="text-align:left">number</td><td style="text-align:left">5000</td><td style="text-align:left">否</td><td style="text-align:left">自动切换时间间隔</td></tr><tr><td style="text-align:left">duration</td><td style="text-align:left">number</td><td style="text-align:left">500</td><td style="text-align:left">否</td><td style="text-align:left">滑动动画时长</td></tr><tr><td style="text-align:left">circular</td><td style="text-align:left">boolean</td><td style="text-align:left">false</td><td style="text-align:left">否</td><td style="text-align:left">是否采用</td></tr></tbody></table></div><h3 id="三、代码模块"><a href="#三、代码模块" class="headerlink" title="三、代码模块"></a>三、代码模块</h3><ul><li>实现首页轮播图片</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&lt;swiper autoplay=<span class="string">"&#123;&#123;true&#125;&#125;"</span> circular=<span class="string">"&#123;&#123;true&#125;&#125;"</span> indicator-dots=<span class="string">"&#123;&#123;true&#125;&#125;"</span> indicator-active-color=<span class="string">"#0061AE"</span> style=<span class="string">"height:400rpx;"</span> &gt;</span><br><span class="line">  &lt;block wx:<span class="keyword">for</span>=<span class="string">"&#123;&#123;coverImgList&#125;&#125;"</span> wx:key=<span class="string">"*this"</span>&gt;</span><br><span class="line">    &lt;swiper-item&gt;</span><br><span class="line">      &lt;image src=<span class="string">"&#123;&#123;item&#125;&#125;"</span> mode=<span class="string">"aspectFill"</span>&gt;<span class="xml"><span class="tag">&lt;/<span class="name">image</span>&gt;</span></span></span><br><span class="line">    &lt;<span class="regexp">/swiper-item&gt;</span></span><br><span class="line"><span class="regexp">  &lt;/</span>block&gt;</span><br><span class="line">&lt;<span class="regexp">/swiper&gt;</span></span><br></pre></td></tr></table></figure><ul><li>确保轮播图片集合为空时，有图片展示，设置默认图片</li></ul><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&lt;view <span class="class"><span class="keyword">class</span></span>=<span class="string">"up"</span>&gt;</span><br><span class="line">    &lt;swiper wx:<span class="keyword">if</span>=<span class="string">"&#123;&#123;coverImgList.length &gt; 0&#125;&#125;"</span> autoplay=<span class="string">"&#123;&#123;true&#125;&#125;"</span> circular=<span class="string">"&#123;&#123;true&#125;&#125;"</span> indicator-dots=<span class="string">"&#123;&#123;true&#125;&#125;"</span>indicator-active-color=<span class="string">"#0061AE"</span> style=<span class="string">"height:400rpx;"</span> &gt;</span><br><span class="line">      &lt;block wx:<span class="keyword">for</span>=<span class="string">"&#123;&#123;coverImgList&#125;&#125;"</span> wx:key=<span class="string">"*this"</span>&gt;</span><br><span class="line">        &lt;swiper-item&gt;</span><br><span class="line">          &lt;image src=<span class="string">"&#123;&#123;item&#125;&#125;"</span> mode=<span class="string">"aspectFill"</span>&gt;<span class="xml"><span class="tag">&lt;/<span class="name">image</span>&gt;</span></span></span><br><span class="line">        &lt;<span class="regexp">/swiper-item&gt;</span></span><br><span class="line"><span class="regexp">      &lt;/</span>block&gt;</span><br><span class="line">    &lt;<span class="regexp">/swiper&gt;</span></span><br><span class="line"><span class="regexp">    &lt;image wx:else mode="aspectFill" src="../</span>../../images/home/homePicture.png<span class="string">" /&gt;</span></span><br><span class="line"><span class="string">&lt;/view&gt;</span></span><br></pre></td></tr></table></figure><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>通过 <code>&lt;swiper&gt;</code> 和 <code>&lt;swiper-item&gt;</code> 组件，你可以在微信小程序中轻松实现图片轮播功能。记得合理利用组件提供的属性和事件，以提升用户体验。无论是简单的图片展示还是复杂的交互设计，轮播图都是一个不可多得的界面元素。</p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;引言&quot;&gt;&lt;a href=&quot;#引言&quot; class=&quot;headerlink&quot; title=&quot;引言&quot;&gt;&lt;/a&gt;引言&lt;/h2&gt;&lt;p&gt;在小程序中，图片轮播是一个常见的功能，用于展示商品图片、广告宣传图等。微信官方提供的 &lt;code&gt;&amp;lt;swiper&amp;gt;&lt;/code&gt; 和 &lt;code&gt;&amp;lt;swiper-item&amp;gt;&lt;/code&gt; 组件可以轻松实现轮播图效果。本文将带你详细了解如何在微信小程序中使用轮播图组件。&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术教程" scheme="https://www.tengfei.eu.org/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="云开发" scheme="https://www.tengfei.eu.org/tags/%E4%BA%91%E5%BC%80%E5%8F%91/"/>
    
      <category term="低代码" scheme="https://www.tengfei.eu.org/tags/%E4%BD%8E%E4%BB%A3%E7%A0%81/"/>
    
      <category term="小程序" scheme="https://www.tengfei.eu.org/tags/%E5%B0%8F%E7%A8%8B%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title>vue项目引入flexslider插件实现时间轴轮播</title>
    <link href="https://www.tengfei.eu.org/article/5d58bf88.html"/>
    <id>https://www.tengfei.eu.org/article/5d58bf88.html</id>
    <published>2024-03-18T08:19:00.000Z</published>
    <updated>2025-07-07T10:25:31.237Z</updated>
    
    <content type="html"><![CDATA[<h2 id="引言："><a href="#引言：" class="headerlink" title="引言："></a>引言：</h2><p>在Vue项目中，为了提升用户体验，我们经常需要在项目中集成各种第三方库。FlexSlider是一个非常流行的轮播图插件，它可以帮助开发者轻松实现响应式的轮播效果。本文将介绍如何在Vue项目中引入并使用<code>flexslider</code>插件。</p><a id="more"></a><h2 id="1-安装依赖"><a href="#1-安装依赖" class="headerlink" title="1. 安装依赖"></a>1. 安装依赖</h2><p>首先，我们需要安装<code>flexslider</code>插件。在项目根目录下运行以下命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install flexslider --save</span><br></pre></td></tr></table></figure><h2 id="2-引入插件"><a href="#2-引入插件" class="headerlink" title="2. 引入插件"></a>2. 引入插件</h2><p>在项目的<code>main.js</code>文件中引入<code>flexslider</code>插件：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">'flexslider'</span>;</span><br></pre></td></tr></table></figure><h2 id="3-创建HTML结构"><a href="#3-创建HTML结构" class="headerlink" title="3. 创建HTML结构"></a>3. 创建HTML结构</h2><p>在需要添加轮播的组件中，创建一个包含轮播图片的<code>div</code>容器：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">template</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"flexslider"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">ul</span> <span class="attr">class</span>=<span class="string">"slides"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">"path/to/image1.jpg"</span> <span class="attr">alt</span>=<span class="string">"Image 1"</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">"path/to/image2.jpg"</span> <span class="attr">alt</span>=<span class="string">"Image 2"</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">"path/to/image3.jpg"</span> <span class="attr">alt</span>=<span class="string">"Image 3"</span>&gt;</span><span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">template</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="4-初始化插件"><a href="#4-初始化插件" class="headerlink" title="4. 初始化插件"></a>4. 初始化插件</h2><p>在组件的<code>mounted</code>生命周期钩子中，初始化<code>flexslider</code>插件：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">mounted() &#123;</span><br><span class="line">  $(<span class="string">".flexslider"</span>).flexslider(&#123;</span><br><span class="line">    animation: <span class="string">"slide"</span>,</span><br><span class="line">    controlNav: <span class="literal">true</span>,</span><br><span class="line">    animationLoop: <span class="literal">false</span>,</span><br><span class="line">    slideshow: <span class="literal">false</span>,</span><br><span class="line">    itemWidth: <span class="number">1140</span>,</span><br><span class="line">    itemMargin: <span class="number">5</span>,</span><br><span class="line">    pausePlay: <span class="literal">false</span>,</span><br><span class="line">    start: <span class="function"><span class="keyword">function</span>(<span class="params">slider</span>) </span>&#123;</span><br><span class="line">      slider.find(<span class="string">".slide_description"</span>).fadeIn();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="5-样式调整"><a href="#5-样式调整" class="headerlink" title="5. 样式调整"></a>5. 样式调整</h2><p>根据需要可以对轮播组件进行样式调整。例如，可以在组件的<code>&lt;style&gt;</code>标签中添加以下CSS样式：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.flexslider</span> &#123;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.flexslider</span> <span class="selector-class">.slides</span> &gt; <span class="selector-tag">li</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: none;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.flexslider</span> <span class="selector-class">.slides</span> <span class="selector-tag">img</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">display</span>: block;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至此，我们已经成功在Vue项目中引入了<code>flexslider</code>插件，并实现了时间轴轮播效果。</p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;引言：&quot;&gt;&lt;a href=&quot;#引言：&quot; class=&quot;headerlink&quot; title=&quot;引言：&quot;&gt;&lt;/a&gt;引言：&lt;/h2&gt;&lt;p&gt;在Vue项目中，为了提升用户体验，我们经常需要在项目中集成各种第三方库。FlexSlider是一个非常流行的轮播图插件，它可以帮助开发者轻松实现响应式的轮播效果。本文将介绍如何在Vue项目中引入并使用&lt;code&gt;flexslider&lt;/code&gt;插件。&lt;/p&gt;
    
    </summary>
    
    
      <category term="技术教程" scheme="https://www.tengfei.eu.org/categories/%E6%8A%80%E6%9C%AF%E6%95%99%E7%A8%8B/"/>
    
    
      <category term="vue" scheme="https://www.tengfei.eu.org/tags/vue/"/>
    
      <category term="插件" scheme="https://www.tengfei.eu.org/tags/%E6%8F%92%E4%BB%B6/"/>
    
      <category term="样式" scheme="https://www.tengfei.eu.org/tags/%E6%A0%B7%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>Telegraph-Image搭建免费图床</title>
    <link href="https://www.tengfei.eu.org/article/53c28e1d.html"/>
    <id>https://www.tengfei.eu.org/article/53c28e1d.html</id>
    <published>2023-10-07T12:28:37.000Z</published>
    <updated>2023-10-23T07:21:47.093Z</updated>
    
    <content type="html"><![CDATA[<p>Telegraph-Image<br>是一款免费开源的免费图片托管解决方案，Flickr/imgur替代品。使用Cloudflare Pages和Telegraph。</p><a id="more"></a><p>Github仓库地址：<a href="https://github.com/cf-pages/Telegraph-Image" target="_blank" rel="noopener">https://github.com/cf-pages/Telegraph-Image</a></p><p>本次发布的更新则是之前呼声最高的后台图片管理功能</p><h3 id="如何部署"><a href="#如何部署" class="headerlink" title="如何部署"></a>如何部署</h3><h4 id="提前准备"><a href="#提前准备" class="headerlink" title="提前准备"></a>提前准备</h4><p>你唯一需要提前准备的就是一个Cloudflare账户</p><h4 id="手把手教程"><a href="#手把手教程" class="headerlink" title="手把手教程"></a>手把手教程</h4><p>简单3步，即可部署本项目，拥有自己的图床</p><p>1.下载或Fork本仓库 (注意：目前请使用fork，在使用下载<a href="https://www.nodeseek.com/jump?to=https%3A%2F%2Fgithub.com%2Fcf-pages%2FTelegraph-Image%2Fissues%2F14" target="_blank" rel="noopener">#14</a>部署存在问题)</p><p>2.打开Cloudflare Dashboard，进入Pages管理页面，选择创建项目，如果在第一步中选择的是fork本仓库，则选择<code>连接到 Git 提供程序</code>，如果第一步中选择的是下载本仓库则选择<code>直接上传</code><br><img src="https://www.xiaoyaoyou.eu.org/file/0315762e4beb9a08b53a2.png" alt="img"></p><ol><li>按照页面提示输入项目名称，选择需要连接的git仓库（第一步选择的是fork）或是上传刚刚下载的仓库文件（第一步选择的是下载本仓库），点击<code>部署站点</code>即可完成部署</li></ol><h3 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h3><p>1.无限图片储存数量，你可以上传不限数量的图片</p><p>2.无需购买服务器，托管于Cloudflare的网络上，当使用量不超过Cloudflare的免费额度时，完全免费</p><p>3.无需购买域名，可以使用Cloudflare Pages提供的<code>*.pages.dev</code>的免费二级域名，同时也支持绑定自定义域名</p><p>4.支持图片审查API，可根据需要开启，开启后不良图片将自动屏蔽，不再加载</p><p>5.支持后台图片管理，可以对上传的图片进行在线预览，添加白名单，黑名单等操作</p><h4 id="绑定自定义域名"><a href="#绑定自定义域名" class="headerlink" title="绑定自定义域名"></a>绑定自定义域名</h4><p>在pages的自定义域里面，绑定cloudflare中存在的域名，在cloudflare托管的域名，自动会修改dns记录<br><img src="https://www.xiaoyaoyou.eu.org/file/97ce68da9421b208be4a1.png" alt="img"></p><h4 id="开启图片审查"><a href="#开启图片审查" class="headerlink" title="开启图片审查"></a>开启图片审查</h4><p>1.请前往<a href="https://moderatecontent.com/" target="_blank" rel="noopener">https://moderatecontent.com/</a> 注册并获得一个免费的用于审查图像内容的API key</p><p>2.打开Cloudflare Pages的管理页面，依次点击<code>设置</code>，<code>环境变量</code>，<code>添加环境变量</code></p><p>3.添加一个<code>变量名称</code>为<code>ModerateContentApiKey</code>，<code>值</code>为你刚刚第一步获得的<code>API key</code>，点击<code>保存</code>即可</p><p>注意：由于所做的更改将在下次部署时生效，你或许还需要进入<code>部署</code>页面，重新部署一下该本项目</p><p>开启图片审查后，因为审查需要时间，首次的图片加载将会变得缓慢，之后的图片加载由于存在缓存，并不会受到影响<br><img src="https://www.xiaoyaoyou.eu.org/file/1b39f71558fb62d30a0ef.png" alt="img"></p><h4 id="限制"><a href="#限制" class="headerlink" title="限制"></a>限制</h4><p>1.由于图片文件实际存储于Telegraph，Telegraph限制上传的图片大小最大为5MB</p><p>2.由于使用Cloudflare的网络，图片的加载速度在某些地区可能得不到保证</p><p>3.Cloudflare Function免费版每日限制100,000个请求（即上传或是加载图片的总次数不能超过100,000次）如超过可能需要选择购买Cloudflare Function的付费套餐，如开启图片管理功能还会存在KV操作数量的限制，如超过需购买付费套餐</p><h4 id="感谢"><a href="#感谢" class="headerlink" title="感谢"></a>感谢</h4><p>Hostloc @feixiang和@乌拉擦 提供的思路和代码</p><h3 id="更新日志"><a href="#更新日志" class="headerlink" title="更新日志"></a>更新日志</h3><p>2023年1月18日—图片管理功能更新</p><p>1、支持图片管理功能，默认是关闭的，如需开启请部署完成后前往后台依次点击<code>设置</code>-&gt;<code>函数</code>-&gt;<code>KV 命名空间绑定</code>-&gt;<code>编辑绑定</code>-&gt;<code>变量名称</code>填写：<code>img_url</code> <code>KV 命名空间</code> 选择你提前创建好的KV储存空间，开启后访问http(s)?/你的域名/admin 即可打开后台管理页面</p><div class="table-container"><table><thead><tr><th>变量名称</th><th>KV 命名空间</th></tr></thead><tbody><tr><td>img_url</td><td>选择提前创建好的KV储存空间</td></tr></tbody></table></div><p><img src="https://www.xiaoyaoyou.eu.org/file/8fcf012a68b1e5dc356fb.png" alt="img"><br><img src="https://www.xiaoyaoyou.eu.org/file/c48dda49d9736a6fc6861.png" alt="img"></p><p>2、后台管理页面新增登录验证功能，默认也是关闭的，如需开启请部署完成后前往后台依次点击<code>设置</code>-&gt;<code>环境变量</code>-&gt;<code>为生产环境定义变量</code>-&gt;<code>编辑变量</code> 添加如下表格所示的变量即可开启登录验证</p><div class="table-container"><table><thead><tr><th>变量名称</th><th>值</th></tr></thead><tbody><tr><td>BASIC_USER =</td><td>&lt;后台管理页面登录用户名称&gt;</td></tr><tr><td>BASIC_PASS =</td><td>&lt;后台管理页面登录用户密码&gt;</td></tr></tbody></table></div><p><img src="https://www.xiaoyaoyou.eu.org/file/f992ef91bcd6f0982bb69.png" alt="img"></p><p>当然你也可以不设置这两个值，这样访问后台管理页面时将无需验证，直接跳过登录步骤，这一设计使得你可以结合Cloudflare Access进行使用，实现支持邮件验证码登录，Microsoft账户登录，Github账户登录等功能，能够与你域名上原有的登录方式所集成，无需再次记忆多一组后台的账号密码，添加Cloudflare Access的方式请参考官方文档，注意需要保护路径包括/admin 以及 /api/manage/*</p><p>3、新增图片总数量统计<br>当开启图片管理功能后，可在后台顶部查看记录中的图片数量</p><p><img src="https://www.xiaoyaoyou.eu.org/file/c8851530f9faa15bbccbf.png" alt="img"></p><p>4、新增图片文件名搜索<br>当开启图片管理功能后，可在后台搜索框使用图片文件名称，快速搜索定位需要管理的图片</p><p><img src="https://www.xiaoyaoyou.eu.org/file/5484e8af85003e25e614f.png" alt="img"></p><p>5、新增图片状态显示<br>当开启图片管理功能后，可在后台查看图片当前的状态{ “ListType”: “None”, “TimeStamp”: 1673984678274 }<br>ListType代表图片当前是否在黑白名单当中，None则表示既不在黑名单中也不在白名单中，White表示在在白名单中，Block表示在黑名单中，TimeStamp为图片首次加载的时间戳，如开启的图片审查API，则这里还会显示图片审查的结果用Label标识</p><p>6、新增黑名单功能<br>当开启图片管理功能后，可在后台手动为图片加入黑名单，加入黑名单的图片将无法正常加载</p><p>7、新增白名单功能<br>当开启图片管理功能后，可在后台手动为图片加入白名单，加入白名单的图片无论如何都会正常加载，可绕过图片审查API的结果</p><p>8、新增记录删除功能<br>当开启图片管理功能后，可在后台手动删除图片记录，即不再后台显示该图片，除非有人再次上传并加载该图片，注意由于图片储存在telegraph的服务器上，我们无法删除上传的原始图片，只能通过上述第6点的黑名单功能屏蔽图片的加载</p><p>9、新增程序运行模式：白名单模式<br>当开启图片管理功能后，除了默认模式外，这次更新还新增了一项新的运行模式，在该模式下，只有被添加进白名单的图片才会被加载，上传的图片需要审核通过后才能展示，最大程度的防止不良图片的加载，如需开启请设置环境变量：WhiteList_Mode==”true”</p><p>10、新增后台图片预览功能<br>当开启图片管理功能后，可在后台预览通过你的域名加载过的图片，点击图片可以进行放大，缩小，旋转等操作</p><h3 id="已经部署了的，如何更新？"><a href="#已经部署了的，如何更新？" class="headerlink" title="已经部署了的，如何更新？"></a>已经部署了的，如何更新？</h3><p>其实更新非常简单，只需要参照上面的更新内容，先进入到Cloudflare Pages后台，把需要使用的环境变量提前设置好并绑定上KV命名空间，然后去到Github你之前fork过的仓库依次选择<code>Sync fork</code>-&gt;<code>Update branch</code>即可，稍等一会，Cloudflare Pages那边检测到你的仓库更新了之后就会自动部署最新的代码了</p><h3 id="一些限制："><a href="#一些限制：" class="headerlink" title="一些限制："></a>一些限制：</h3><p>Cloudflare KV每天只有1000次的免费写入额度，每有一张新的图片加载都会占用该写入额度，如果超过该额度，图片管理后台将无法记录新加载的图片</p><p>每天最多 100,000 次免费读取操作，图片每加载一次都会占用该额度（在没有缓存的情况下，如果你的域名在Cloudflare开启了缓存，当缓存未命中时才会占用该额度），超过黑白名单等功能可能会失效</p><p>每天最多 1,000 次免费删除操作，每有一条图片记录都会占用该额度，超过将无法删除图片记录</p><p>每天最多 1,000 次免费列出操作，每打开或刷新一次后台/admin都会占用该额度，超过将进行后台图片管理</p><p>绝大多数情况下，该免费额度都基本够用，并且可以稍微超出一点，不是已超出就立马停用，且每项额度单独计算，某项操作超出免费额度后只会停用该项操作，不影响其他的功能，即即便我的免费写入额度用完了，我的读写功能不受影响，图片能够正常加载，只是不能在图片管理后台看到新的图片了。</p><p>如果你的免费额度不够用，可以自行向Cloudflare购买Cloudflare Workers的付费版本，每月$5起步，按量收费，没有上述额度限制</p><p>另外针对环境变量所做的更改将在下次部署时生效，如更改了<code>环境变量</code>，针对某项功能进行了开启或关闭，请记得重新部署。</p><p><img src="https://www.xiaoyaoyou.eu.org/file/2969eb29d7d5ccc5b2525.png" alt="img"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Telegraph-Image&lt;br&gt;是一款免费开源的免费图片托管解决方案，Flickr/imgur替代品。使用Cloudflare Pages和Telegraph。&lt;/p&gt;
    
    </summary>
    
    
    
      <category term="Cloudflare" scheme="https://www.tengfei.eu.org/tags/Cloudflare/"/>
    
      <category term="图床" scheme="https://www.tengfei.eu.org/tags/%E5%9B%BE%E5%BA%8A/"/>
    
  </entry>
  
  <entry>
    <title>Flowable工作流</title>
    <link href="https://www.tengfei.eu.org/article/cb91b1d5.html"/>
    <id>https://www.tengfei.eu.org/article/cb91b1d5.html</id>
    <published>2023-04-08T14:24:12.000Z</published>
    <updated>2023-10-16T04:54:09.739Z</updated>
    
    <content type="html"><![CDATA[<p>Flowable工作流学习笔记</p><a id="more"></a><h1 id="一、Flowable介绍"><a href="#一、Flowable介绍" class="headerlink" title="一、Flowable介绍"></a>一、Flowable介绍</h1><p>&emsp;&emsp;Flowable是BPMN的一个基于java的软件实现，不过Flowable不仅仅包括BPMN，还有DMN决策表和CMMN Case管理引擎，并且有自己的用户管理、微服务API等一系列功能，是一个服务平台。</p><p><img src="/article/cb91b1d5/image-20220317101115398.png" alt="image-20220317101115398"></p><h1 id="二、Flowable基础"><a href="#二、Flowable基础" class="headerlink" title="二、Flowable基础"></a>二、Flowable基础</h1><p>官方手册：<a href="https://tkjohn.github.io/flowable-userguide/#_introduction" target="_blank" rel="noopener">https://tkjohn.github.io/flowable-userguide/#_introduction</a></p><h2 id="1-创建ProcessEngine"><a href="#1-创建ProcessEngine" class="headerlink" title="1.创建ProcessEngine"></a>1.创建ProcessEngine</h2><p>&emsp;&emsp;创建一个基本的maven工程，可以是Eclipse也可以是其他IDEA。然后添加两个依赖</p><ul><li>Flowable流程引擎。使我们可以创建一个ProcessEngine流程引擎对象，并访问Flowable API。</li><li>一个是MySQL的数据库驱动</li></ul><p>在<em>pom.xml</em>文件中添加下列行：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.flowable<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>flowable-engine<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>6.3.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>mysql<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>mysql-connector-java<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>8.0.21<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>&emsp;&emsp;然后创建一个普通的Java类，添加对应的main方法，首先要做的是初始化<strong>ProcessEngine</strong>流程引擎实例。这是一个线程安全的对象，因此通常只需要在一个应用中初始化一次。 <em>ProcessEngine</em>由<strong>ProcessEngineConfiguration</strong>实例创建。该实例可以配置与调整流程引擎的设置。 通常使用一个配置XML文件创建<em>ProcessEngineConfiguration</em>，但是（像在这里做的一样）也可以编程方式创建它。 <em>ProcessEngineConfiguration</em>所需的最小配置，是数据库JDBC连接：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">    ProcessEngineConfiguration cfg = <span class="keyword">new</span> StandaloneProcessEngineConfiguration()</span><br><span class="line">        .setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn?serverTimezone=UTC"</span>)</span><br><span class="line">        .setJdbcUsername(<span class="string">"root"</span>)</span><br><span class="line">        .setJdbcPassword(<span class="string">"123456"</span>)</span><br><span class="line">        .setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>)</span><br><span class="line">        .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line">    ProcessEngine processEngine = cfg.buildProcessEngine();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;注意在mysql8.0中执行可能出现如下的错误</p><p><img src="/article/cb91b1d5/image-20220316093416477.png" alt="image-20220316093416477"></p><p>&emsp;&emsp;出现这种情况只需要在mysql的连接字符串中添加上nullCatalogMeansCurrent=true，设置为只查当前连接的schema库即可。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">    ProcessEngineConfiguration cfg = <span class="keyword">new</span> StandaloneProcessEngineConfiguration()</span><br><span class="line">            .setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn1?serverTimezone=UTC&amp;nullCatalogMeansCurrent=true"</span>)</span><br><span class="line">            .setJdbcUsername(<span class="string">"root"</span>)</span><br><span class="line">            .setJdbcPassword(<span class="string">"123456"</span>)</span><br><span class="line">            .setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>)</span><br><span class="line">            .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line">    ProcessEngine processEngine = cfg.buildProcessEngine();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;然后应用运行没有问题，但也没有在控制台提供有用的信息，只有一条消息提示日志没有正确配置。Flowable使用<a href="http://www.slf4j.org/" target="_blank" rel="noopener">SLF4J</a>作为内部日志框架。在这个例子中，我们使用log4j作为SLF4J的实现。因此在pom.xml文件中添加下列依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.slf4j<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>slf4j-api<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.7.21<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.slf4j<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>slf4j-log4j12<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.7.21<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>&emsp;&emsp;Log4j需要一个配置文件。在<em>src/main/resources</em>文件夹下添加<em>log4j.properties</em>文件，并写入下列内容：</p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">log4j.rootLogger</span>=<span class="string">DEBUG, CA</span></span><br><span class="line"></span><br><span class="line"><span class="meta">log4j.appender.CA</span>=<span class="string">org.apache.log4j.ConsoleAppender</span></span><br><span class="line"><span class="meta">log4j.appender.CA.layout</span>=<span class="string">org.apache.log4j.PatternLayout</span></span><br><span class="line"><span class="meta">log4j.appender.CA.layout.ConversionPattern</span>= <span class="string">%d&#123;hh:mm:ss,SSS&#125; [%t] %-5p %c %x - %m%n</span></span><br></pre></td></tr></table></figure><p>&emsp;&emsp;重新运行应用。应该可以看到关于引擎启动与创建数据库表结构的提示日志：</p><p><img src="/article/cb91b1d5/image-20220316093922199.png" alt="image-20220316093922199"></p><p>&emsp;&emsp;同时可以看到创建了相关的表结构在数据库中</p><p><img src="/article/cb91b1d5/image-20220316093957662.png" alt="image-20220316093957662"></p><p>&emsp;&emsp;这样就得到了一个启动可用的流程引擎。接下来为它提供一个流程！</p><h2 id="2-部署流程定义"><a href="#2-部署流程定义" class="headerlink" title="2.部署流程定义"></a>2.部署流程定义</h2><p>&emsp;&emsp;接下来我们构建一个非常简单的请假流程，Flowable引擎需要流程定义为BPMN 2.0格式，这是一个业界广泛接受的XML标准。 在Flowable术语中，我们将其称为一个<strong>流程定义(process definition)</strong>。一个<em>流程定义</em>可以启动多个<strong>流程实例(process instance)</strong>。<em>流程定义</em>可以看做是重复执行流程的蓝图。 在这个例子中，<em>流程定义</em>定义了请假的各个步骤，而一个<em>流程实例</em>对应某个雇员提出的一个请假申请。</p><p>&emsp;&emsp;BPMN 2.0存储为XML，并包含可视化的部分：使用标准方式定义了每个步骤类型（人工任务，自动服务调用，等等）如何呈现，以及如何互相连接。这样BPMN 2.0标准使技术人员与业务人员能用双方都能理解的方式交流业务流程。</p><p>&emsp;&emsp;我们要使用的流程定义为：</p><p><img src="/article/cb91b1d5/getting.started.bpmn.process.png" alt="getting.started.bpmn.process"></p><p>&emsp;&emsp;流程定义说明：</p><ul><li>我们假定启动流程需要提供一些信息，例如雇员名字、请假时长以及说明。当然，这些可以单独建模为流程中的第一步。 但是如果将它们作为流程的“输入信息”，就能保证只有在实际请求时才会建立一个流程实例。否则（将提交作为流程的第一步），用户可能在提交之前改变主意并取消，但流程实例已经创建了。 在某些场景中，就可能影响重要的指标（例如启动了多少申请，但还未完成），取决于业务目标。</li><li>左侧的圆圈叫做<strong>启动事件(start event)</strong>。这是一个流程实例的起点。</li><li>第一个矩形是一个<strong>用户任务(user task)</strong>。这是流程中用户操作的步骤。在这个例子中，经理需要批准或驳回申请</li><li>取决于经理的决定，<strong>排他网关(exclusive gateway)</strong> (带叉的菱形)会将流程实例路由至批准或驳回路径</li><li>如果批准，则需要将申请注册至某个外部系统，并跟着另一个用户任务，将经理的决定通知给申请人。当然也可以改为发送邮件。</li><li>如果驳回，则为雇员发送一封邮件通知他。</li></ul><p>&emsp;&emsp;一般来说，这样的<em>流程定义</em>使用可视化建模工具建立，如Flowable Designer(Eclipse)或Flowable Web Modeler(Web应用)。但在这里我们直接撰写XML，以熟悉BPMN 2.0及其概念。</p><p>&emsp;&emsp;与上面展示的流程图对应的BPMN 2.0 XML在下面显示。请注意这只包含了“流程部分”。如果使用图形化建模工具，实际的XML文件还将包含“可视化部分”，用于描述图形信息，如流程定义中各个元素的坐标（所有的图形化信息包含在XML的<em>BPMNDiagram</em>标签中，作为<em>definitions</em>标签的子元素）。</p><p>&emsp;&emsp;将下面的XML保存在<em>src/main/resources</em>文件夹下名为<em>holiday-request.bpmn20.xml</em>的文件中。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">definitions</span> <span class="attr">xmlns</span>=<span class="string">"http://www.omg.org/spec/BPMN/20100524/MODEL"</span></span></span><br><span class="line"><span class="tag">             <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">             <span class="attr">xmlns:xsd</span>=<span class="string">"http://www.w3.org/2001/XMLSchema"</span></span></span><br><span class="line"><span class="tag">             <span class="attr">xmlns:bpmndi</span>=<span class="string">"http://www.omg.org/spec/BPMN/20100524/DI"</span></span></span><br><span class="line"><span class="tag">             <span class="attr">xmlns:omgdc</span>=<span class="string">"http://www.omg.org/spec/DD/20100524/DC"</span></span></span><br><span class="line"><span class="tag">             <span class="attr">xmlns:omgdi</span>=<span class="string">"http://www.omg.org/spec/DD/20100524/DI"</span></span></span><br><span class="line"><span class="tag">             <span class="attr">xmlns:flowable</span>=<span class="string">"http://flowable.org/bpmn"</span></span></span><br><span class="line"><span class="tag">             <span class="attr">typeLanguage</span>=<span class="string">"http://www.w3.org/2001/XMLSchema"</span></span></span><br><span class="line"><span class="tag">             <span class="attr">expressionLanguage</span>=<span class="string">"http://www.w3.org/1999/XPath"</span></span></span><br><span class="line"><span class="tag">             <span class="attr">targetNamespace</span>=<span class="string">"http://www.flowable.org/processdef"</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">process</span> <span class="attr">id</span>=<span class="string">"holidayRequest"</span> <span class="attr">name</span>=<span class="string">"Holiday Request"</span> <span class="attr">isExecutable</span>=<span class="string">"true"</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">startEvent</span> <span class="attr">id</span>=<span class="string">"startEvent"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">sequenceFlow</span> <span class="attr">sourceRef</span>=<span class="string">"startEvent"</span> <span class="attr">targetRef</span>=<span class="string">"approveTask"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">userTask</span> <span class="attr">id</span>=<span class="string">"approveTask"</span> <span class="attr">name</span>=<span class="string">"Approve or reject request"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">sequenceFlow</span> <span class="attr">sourceRef</span>=<span class="string">"approveTask"</span> <span class="attr">targetRef</span>=<span class="string">"decision"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">exclusiveGateway</span> <span class="attr">id</span>=<span class="string">"decision"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">sequenceFlow</span> <span class="attr">sourceRef</span>=<span class="string">"decision"</span> <span class="attr">targetRef</span>=<span class="string">"externalSystemCall"</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">conditionExpression</span> <span class="attr">xsi:type</span>=<span class="string">"tFormalExpression"</span>&gt;</span></span><br><span class="line">                &lt;![CDATA[</span><br><span class="line">          $&#123;approved&#125;</span><br><span class="line">        ]]&gt;</span><br><span class="line">            <span class="tag">&lt;/<span class="name">conditionExpression</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">sequenceFlow</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">sequenceFlow</span>  <span class="attr">sourceRef</span>=<span class="string">"decision"</span> <span class="attr">targetRef</span>=<span class="string">"sendRejectionMail"</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">conditionExpression</span> <span class="attr">xsi:type</span>=<span class="string">"tFormalExpression"</span>&gt;</span></span><br><span class="line">                &lt;![CDATA[</span><br><span class="line">          $&#123;!approved&#125;</span><br><span class="line">        ]]&gt;</span><br><span class="line">            <span class="tag">&lt;/<span class="name">conditionExpression</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">sequenceFlow</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">serviceTask</span> <span class="attr">id</span>=<span class="string">"externalSystemCall"</span> <span class="attr">name</span>=<span class="string">"Enter holidays in external system"</span></span></span><br><span class="line"><span class="tag">                     <span class="attr">flowable:class</span>=<span class="string">"org.flowable.CallExternalSystemDelegate"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">sequenceFlow</span> <span class="attr">sourceRef</span>=<span class="string">"externalSystemCall"</span> <span class="attr">targetRef</span>=<span class="string">"holidayApprovedTask"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">userTask</span> <span class="attr">id</span>=<span class="string">"holidayApprovedTask"</span> <span class="attr">name</span>=<span class="string">"Holiday approved"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">sequenceFlow</span> <span class="attr">sourceRef</span>=<span class="string">"holidayApprovedTask"</span> <span class="attr">targetRef</span>=<span class="string">"approveEnd"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">serviceTask</span> <span class="attr">id</span>=<span class="string">"sendRejectionMail"</span> <span class="attr">name</span>=<span class="string">"Send out rejection email"</span></span></span><br><span class="line"><span class="tag">                     <span class="attr">flowable:class</span>=<span class="string">"org.flowable.SendRejectionMail"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">sequenceFlow</span> <span class="attr">sourceRef</span>=<span class="string">"sendRejectionMail"</span> <span class="attr">targetRef</span>=<span class="string">"rejectEnd"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">endEvent</span> <span class="attr">id</span>=<span class="string">"approveEnd"</span>/&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">endEvent</span> <span class="attr">id</span>=<span class="string">"rejectEnd"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">process</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">definitions</span>&gt;</span></span><br></pre></td></tr></table></figure><p>&emsp;&emsp;现在我们已经有了流程BPMN 2.0 XML文件，下来需要将它<strong><em>部署(deploy)\</em></strong>到引擎中。<em>部署</em>一个流程定义意味着：</p><ul><li>流程引擎会将XML文件存储在数据库中，这样可以在需要的时候获取它</li><li>流程定义转换为内部的、可执行的对象模型，这样使用它就可以启动<em>流程实例</em>。</li></ul><p><img src="/article/cb91b1d5/image-20220317110902636.png" alt="image-20220317110902636"></p><p>&emsp;&emsp;将流程定义<em>部署</em>至Flowable引擎，需要使用<em>RepositoryService</em>，其可以从<em>ProcessEngine</em>对象获取。使用<em>RepositoryService</em>，可以通过XML文件的路径创建一个新的<em>部署(Deployment)</em>，并调用<em>deploy()</em>方法实际执行：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 部署流程</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDeploy</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 配置数据库相关信息 获取 ProcessEngineConfiguration</span></span><br><span class="line">    ProcessEngineConfiguration cfg = <span class="keyword">new</span> StandaloneProcessEngineConfiguration()</span><br><span class="line">            .setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&amp;nullCatalogMeansCurrent=true"</span>)</span><br><span class="line">            .setJdbcUsername(<span class="string">"root"</span>)</span><br><span class="line">            .setJdbcPassword(<span class="string">"123456"</span>)</span><br><span class="line">            .setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>)</span><br><span class="line">            .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line">    <span class="comment">// 获取流程引擎对象</span></span><br><span class="line">    ProcessEngine processEngine = cfg.buildProcessEngine();</span><br><span class="line">    <span class="comment">// 部署流程 获取RepositoryService对象</span></span><br><span class="line">    RepositoryService repositoryService = processEngine.getRepositoryService();</span><br><span class="line">    Deployment deployment = repositoryService.createDeployment()<span class="comment">// 创建Deployment对象</span></span><br><span class="line">            .addClasspathResource(<span class="string">"holiday-request.bpmn20.xml"</span>) <span class="comment">// 添加流程部署文件</span></span><br><span class="line">            .name(<span class="string">"请求流程"</span>) <span class="comment">// 设置部署流程的名称</span></span><br><span class="line">            .deploy(); <span class="comment">// 执行部署操作</span></span><br><span class="line">    System.out.println(<span class="string">"deployment.getId() = "</span> + deployment.getId());</span><br><span class="line">    System.out.println(<span class="string">"deployment.getName() = "</span> + deployment.getName());</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;然后执行该方法日志操作成功：</p><p><img src="/article/cb91b1d5/image-20220316100439048.png" alt="image-20220316100439048"></p><p>&emsp;&emsp;在后台表结构也可以看到相关的信息</p><p>act_re_deployment: 流程定义部署表，每部署一次就增加一条记录</p><p><img src="/article/cb91b1d5/image-20220316100532725.png" alt="image-20220316100532725"></p><p>act_re_procdef ：流程定义表，部署每个新的流程定义都会在这张表中增加一条记录</p><p><img src="/article/cb91b1d5/image-20220316100611004.png" alt="image-20220316100611004"></p><p>act_ge_bytearray ：流程资源表，流程部署的 bpmn文件和png图片会保存在该表中</p><p><img src="/article/cb91b1d5/image-20220316100648362.png" alt="image-20220316100648362"></p><p>&emsp;&emsp;我们现在可以通过API查询验证流程定义已经部署在引擎中（并学习一些API）。通过<em>RepositoryService</em>创建的<em>ProcessDefinitionQuery</em>对象实现。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查看流程定义</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDeployQuery</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">// 配置数据库相关信息 获取 ProcessEngineConfiguration</span></span><br><span class="line">        ProcessEngineConfiguration cfg = <span class="keyword">new</span> StandaloneProcessEngineConfiguration()</span><br><span class="line">                .setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&amp;nullCatalogMeansCurrent=true"</span>)</span><br><span class="line">                .setJdbcUsername(<span class="string">"root"</span>)</span><br><span class="line">                .setJdbcPassword(<span class="string">"123456"</span>)</span><br><span class="line">                .setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>)</span><br><span class="line">                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line">        <span class="comment">// 获取流程引擎对象</span></span><br><span class="line">        ProcessEngine processEngine = cfg.buildProcessEngine();</span><br><span class="line">        <span class="comment">// 部署流程 获取RepositoryService对象</span></span><br><span class="line">        RepositoryService repositoryService = processEngine.getRepositoryService();</span><br><span class="line">        <span class="comment">// 获取流程定义对象</span></span><br><span class="line">        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()</span><br><span class="line">                .deploymentId(<span class="string">"2501"</span>)</span><br><span class="line">                .singleResult();</span><br><span class="line">        System.out.println(<span class="string">"processDefinition.getId() = "</span> + processDefinition.getId());</span><br><span class="line">        System.out.println(<span class="string">"processDefinition.getName() = "</span> + processDefinition.getName());</span><br><span class="line">        System.out.println(<span class="string">"processDefinition.getDeploymentId() = "</span> + processDefinition.getDeploymentId());</span><br><span class="line">        System.out.println(<span class="string">"processDefinition.getDescription() = "</span> + processDefinition.getDescription());</span><br><span class="line"></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>输出结果为：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">processDefinition.getId() = holidayRequest:2:2503</span><br><span class="line">processDefinition.getName() = Holiday Request</span><br><span class="line">processDefinition.getDeploymentId() = 2501</span><br><span class="line">processDefinition.getDescription() = null</span><br></pre></td></tr></table></figure><h2 id="3-启动流程实例"><a href="#3-启动流程实例" class="headerlink" title="3.启动流程实例"></a>3.启动流程实例</h2><p>&emsp;&emsp;现在已经在流程引擎中<em>部署</em>了流程定义，因此可以使用这个<em>流程定义</em>作为“模板”启动<em>流程实例</em>。</p><p><img src="/article/cb91b1d5/image-20220316102638015.png" alt="image-20220316102638015"></p><p>&emsp;&emsp;要启动流程实例，需要提供一些初始化<em>流程变量</em>。一般来说，可以通过呈现给用户的表单，或者在流程由其他系统自动触发时通过REST API，来获取这些变量。在这个例子里，我们简化直接在代码中定义了，我们使用<em>RuntimeService</em>启动一个<em>流程实例</em>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 启动流程实例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRunProcess</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 配置数据库相关信息 获取 ProcessEngineConfiguration</span></span><br><span class="line">    ProcessEngineConfiguration cfg = <span class="keyword">new</span> StandaloneProcessEngineConfiguration()</span><br><span class="line">            .setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&amp;nullCatalogMeansCurrent=true"</span>)</span><br><span class="line">            .setJdbcUsername(<span class="string">"root"</span>)</span><br><span class="line">            .setJdbcPassword(<span class="string">"123456"</span>)</span><br><span class="line">            .setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>)</span><br><span class="line">            .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line">    <span class="comment">// 获取流程引擎对象</span></span><br><span class="line">    ProcessEngine processEngine = cfg.buildProcessEngine();</span><br><span class="line">    <span class="comment">// 启动流程实例通过 RuntimeService 对象</span></span><br><span class="line">    RuntimeService runtimeService = processEngine.getRuntimeService();</span><br><span class="line">    <span class="comment">// 构建流程变量</span></span><br><span class="line">    Map&lt;String,Object&gt; variables = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">    variables.put(<span class="string">"employee"</span>,<span class="string">"张三"</span>) ;<span class="comment">// 谁申请请假</span></span><br><span class="line">    variables.put(<span class="string">"nrOfHolidays"</span>,<span class="number">3</span>); <span class="comment">// 请几天假</span></span><br><span class="line">    variables.put(<span class="string">"description"</span>,<span class="string">"工作累了，想出去玩玩"</span>); <span class="comment">// 请假的原因</span></span><br><span class="line">    <span class="comment">// 启动流程实例，第一个参数是流程定义的id</span></span><br><span class="line">    ProcessInstance processInstance = runtimeService</span><br><span class="line">            .startProcessInstanceByKey(<span class="string">"holidayRequest"</span>, variables);<span class="comment">// 启动流程实例</span></span><br><span class="line">    <span class="comment">// 输出相关的流程实例信息</span></span><br><span class="line">    System.out.println(<span class="string">"流程定义的ID："</span> + processInstance.getProcessDefinitionId());</span><br><span class="line">    System.out.println(<span class="string">"流程实例的ID："</span> + processInstance.getId());</span><br><span class="line">    System.out.println(<span class="string">"当前活动的ID："</span> + processInstance.getActivityId());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>启动成功，输出结果如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">流程定义的ID：holidayRequest:2:2503</span><br><span class="line">流程实例的ID：5001</span><br><span class="line">当前活动的ID：null</span><br></pre></td></tr></table></figure><p>对应的流程实例ID为：5001</p><p>启动流程实例涉及到的表结构：</p><ul><li>act_hi_actinst 流程实例执行历史</li><li>act_hi_identitylink 流程的参与用户的历史信息</li><li>act_hi_procinst 流程实例历史信息</li><li>act_hi_taskinst 流程任务历史信息</li><li>act_ru_execution 流程执行信息</li><li>act_ru_identitylink 流程的参与用户信息</li><li>act_ru_task 任务信息</li></ul><h2 id="4-查看任务"><a href="#4-查看任务" class="headerlink" title="4.查看任务"></a>4.查看任务</h2><p>&emsp;&emsp;上面员工发起了一个请假流程，接下来就会流转到总经理这儿来处理，之前我们没有指定经理这的处理人，我们可以加一个</p><p><img src="/article/cb91b1d5/image-20220316110406801.png" alt="image-20220316110406801"></p><p>&emsp;&emsp;然后我们来查看下lisi的任务</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 查看任务</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testQueryTask</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 配置数据库相关信息 获取 ProcessEngineConfiguration</span></span><br><span class="line">    ProcessEngineConfiguration cfg = <span class="keyword">new</span> StandaloneProcessEngineConfiguration()</span><br><span class="line">            .setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&amp;nullCatalogMeansCurrent=true"</span>)</span><br><span class="line">            .setJdbcUsername(<span class="string">"root"</span>)</span><br><span class="line">            .setJdbcPassword(<span class="string">"123456"</span>)</span><br><span class="line">            .setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>)</span><br><span class="line">            .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line">    <span class="comment">// 获取流程引擎对象</span></span><br><span class="line">    ProcessEngine processEngine = cfg.buildProcessEngine();</span><br><span class="line">    TaskService taskService = processEngine.getTaskService();</span><br><span class="line">    List&lt;Task&gt; list = taskService.createTaskQuery()</span><br><span class="line">            .processDefinitionKey(<span class="string">"holidayRequestNew"</span>)</span><br><span class="line">            .taskAssignee(<span class="string">"lisi"</span>)</span><br><span class="line">            .list();</span><br><span class="line">    <span class="keyword">for</span> (Task task : list) &#123;</span><br><span class="line">        System.out.println(<span class="string">"task.getProcessDefinitionId() = "</span> + task.getProcessDefinitionId());</span><br><span class="line">        System.out.println(<span class="string">"task.getId() = "</span> + task.getId());</span><br><span class="line">        System.out.println(<span class="string">"task.getAssignee() = "</span> + task.getAssignee());</span><br><span class="line">        System.out.println(<span class="string">"task.getName() = "</span> + task.getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出结果为：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">task.getProcessDefinitionId() = holidayRequestNew:1:10003</span><br><span class="line">task.getId() = 12508</span><br><span class="line">task.getAssignee() = lisi</span><br><span class="line">task.getName() = Approve or reject request</span><br></pre></td></tr></table></figure><h2 id="5-完成任务"><a href="#5-完成任务" class="headerlink" title="5.完成任务"></a>5.完成任务</h2><p>&emsp;&emsp;现在李四这个角色可以来完成当前的任务了</p><p><img src="/article/cb91b1d5/image-20220316111124019.png" alt="image-20220316111124019"></p><p>&emsp;&emsp;在此处我们直接解决掉这个请假，然后会走发送拒绝邮件的流程，这块我们需要用到JavaDelegate来触发。</p><p><img src="/article/cb91b1d5/image-20220316111253702.png" alt="image-20220316111253702"></p><p>我们定义这样一个Java类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SendRejectionMail</span> <span class="keyword">implements</span> <span class="title">JavaDelegate</span> </span>&#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 触发发送邮件的操作</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> delegateExecution</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(DelegateExecution delegateExecution)</span> </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">"请假被拒绝,,,安心工作吧"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后来完成任务</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 完成任务</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testCompleteTask</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 配置数据库相关信息 获取 ProcessEngineConfiguration</span></span><br><span class="line">    ProcessEngineConfiguration cfg = <span class="keyword">new</span> StandaloneProcessEngineConfiguration()</span><br><span class="line">            .setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&amp;nullCatalogMeansCurrent=true"</span>)</span><br><span class="line">            .setJdbcUsername(<span class="string">"root"</span>)</span><br><span class="line">            .setJdbcPassword(<span class="string">"123456"</span>)</span><br><span class="line">            .setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>)</span><br><span class="line">            .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line">    <span class="comment">// 获取流程引擎对象</span></span><br><span class="line">    ProcessEngine processEngine = cfg.buildProcessEngine();</span><br><span class="line">    TaskService taskService = processEngine.getTaskService();</span><br><span class="line">    Task task = taskService.createTaskQuery()</span><br><span class="line">            .processDefinitionKey(<span class="string">"holidayRequestNew"</span>)</span><br><span class="line">            .taskAssignee(<span class="string">"lisi"</span>)</span><br><span class="line">            .singleResult();</span><br><span class="line">    <span class="comment">// 添加流程变量</span></span><br><span class="line">    Map&lt;String,Object&gt; variables = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">    variables.put(<span class="string">"approved"</span>,<span class="keyword">false</span>); <span class="comment">// 拒绝请假</span></span><br><span class="line">    <span class="comment">// 完成任务</span></span><br><span class="line">    taskService.complete(task.getId(),variables);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后可以看到JavaDelegate触发了</p><p><img src="/article/cb91b1d5/image-20220316111913933.png" alt="image-20220316111913933"></p><h2 id="6-流程的删除"><a href="#6-流程的删除" class="headerlink" title="6.流程的删除"></a>6.流程的删除</h2><p>&emsp;&emsp;有些流程已经没有用了，我们需要删除掉，其实也非常简单</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 删除流程</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDeleteProcess</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 配置数据库相关信息 获取 ProcessEngineConfiguration</span></span><br><span class="line">    ProcessEngineConfiguration cfg = <span class="keyword">new</span> StandaloneProcessEngineConfiguration()</span><br><span class="line">            .setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&amp;nullCatalogMeansCurrent=true"</span>)</span><br><span class="line">            .setJdbcUsername(<span class="string">"root"</span>)</span><br><span class="line">            .setJdbcPassword(<span class="string">"123456"</span>)</span><br><span class="line">            .setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>)</span><br><span class="line">            .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line">    <span class="comment">// 获取流程引擎对象</span></span><br><span class="line">    ProcessEngine processEngine = cfg.buildProcessEngine();</span><br><span class="line">    RepositoryService repositoryService = processEngine.getRepositoryService();</span><br><span class="line">    <span class="comment">// 删除流程定义，如果该流程定义已经有了流程实例启动则删除时报错</span></span><br><span class="line">    <span class="comment">// repositoryService.deleteDeployment("1");</span></span><br><span class="line">    <span class="comment">// 设置为TRUE 级联删除流程定义，及时流程有实例启动，也可以删除，设置为false 非级联删除操作。</span></span><br><span class="line">    repositoryService.deleteDeployment(<span class="string">"2501"</span>,<span class="keyword">true</span>);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="7-查看历史信息"><a href="#7-查看历史信息" class="headerlink" title="7.查看历史信息"></a>7.查看历史信息</h2><p>&emsp;&emsp;选择使用Flowable这样的流程引擎的原因之一，是它可以自动存储所有流程实例的<strong>审计数据</strong>或<strong>历史数据</strong>。这些数据可以用于创建报告，深入展现组织运行的情况，瓶颈在哪里，等等。</p><p>&emsp;&emsp;例如，如果希望显示流程实例已经执行的时间，就可以从<em>ProcessEngine</em>获取<em>HistoryService</em>，并创建<em>历史活动(historical activities)</em>的查询。在下面的代码片段中，可以看到我们添加了一些额外的过滤条件：</p><ul><li>只选择一个特定流程实例的活动</li><li>只选择已完成的活动</li></ul><p>&emsp;&emsp;结果按照结束时间排序，代表其执行顺序。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 查看历史</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testQueryHistory</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">// 配置数据库相关信息 获取 ProcessEngineConfiguration</span></span><br><span class="line">        ProcessEngineConfiguration cfg = <span class="keyword">new</span> StandaloneProcessEngineConfiguration()</span><br><span class="line">                .setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&amp;nullCatalogMeansCurrent=true"</span>)</span><br><span class="line">                .setJdbcUsername(<span class="string">"root"</span>)</span><br><span class="line">                .setJdbcPassword(<span class="string">"123456"</span>)</span><br><span class="line">                .setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>)</span><br><span class="line">                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line">        <span class="comment">// 获取流程引擎对象</span></span><br><span class="line">        ProcessEngine processEngine = cfg.buildProcessEngine();</span><br><span class="line">        HistoryService historyService = processEngine.getHistoryService();</span><br><span class="line">        List&lt;HistoricActivityInstance&gt; list = historyService.createHistoricActivityInstanceQuery()</span><br><span class="line">                .processDefinitionId(<span class="string">"holidayRequestNew:1:10003"</span>)</span><br><span class="line">                .finished()</span><br><span class="line">                .orderByHistoricActivityInstanceEndTime().asc()</span><br><span class="line">                .list();</span><br><span class="line">        <span class="keyword">for</span> (HistoricActivityInstance historicActivityInstance : list) &#123;</span><br><span class="line">            System.out.println(historicActivityInstance.getActivityId() + <span class="string">" took "</span></span><br><span class="line">                    + historicActivityInstance.getDurationInMillis() + <span class="string">" milliseconds"</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>输出结果</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">startEvent took 1 milliseconds</span><br><span class="line">approveTask took 837735 milliseconds</span><br><span class="line">decision took 13 milliseconds</span><br><span class="line">sendRejectionMail took 2 milliseconds</span><br><span class="line">rejectEnd took 1 milliseconds</span><br></pre></td></tr></table></figure><p>好了~flowable的基本应用我们就先介绍到这里了。</p><h1 id="三、Flowable流程设计器"><a href="#三、Flowable流程设计器" class="headerlink" title="三、Flowable流程设计器"></a>三、Flowable流程设计器</h1><h2 id="1-Eclipse-Designer"><a href="#1-Eclipse-Designer" class="headerlink" title="1.Eclipse Designer"></a>1.Eclipse Designer</h2><p>&emsp;&emsp;Flowable提供了名为Flowable Eclipse Designer的Eclipse插件，可以用于图形化地建模、测试与部署BPMN 2.0流程。</p><h3 id="1-1-下载安装Eclipse"><a href="#1-1-下载安装Eclipse" class="headerlink" title="1.1 下载安装Eclipse"></a>1.1 下载安装Eclipse</h3><p>&emsp;&emsp;去Eclipse官网下载即可：<a href="https://www.eclipse.org/downloads/packages/release" target="_blank" rel="noopener">https://www.eclipse.org/downloads/packages/release</a> 注意2020-09后的版本不再支持jdk8</p><p><img src="/article/cb91b1d5/image-20220316202904261.png" alt="image-20220316202904261"></p><p>&emsp;解压缩就可以了，然后进入解压缩的目录</p><p><img src="/article/cb91b1d5/image-20220316203036602.png" alt="image-20220316203036602"></p><p>&emsp;&emsp;直接启动即可</p><p><img src="/article/cb91b1d5/image-20220316203111301.png" alt="image-20220316203111301"></p><p><img src="/article/cb91b1d5/image-20220316203237316.png" alt="image-20220316203237316"></p><h3 id="1-2-安装Flowable插件"><a href="#1-2-安装Flowable插件" class="headerlink" title="1.2 安装Flowable插件"></a>1.2 安装Flowable插件</h3><p>&emsp;&emsp;然后我们再安装下Flowable的插件，选择<strong>Help → Install New Software</strong>。在下图面板中，点击<em>Add</em>按钮，并填写下列字段</p><ul><li><strong>Name:</strong> Flowable BPMN 2.0 designer</li><li><strong>Location:</strong> <a href="http://www.flowable.org/designer/update/" target="_blank" rel="noopener">http://www.flowable.org/designer/update/</a></li></ul><p><img src="/article/cb91b1d5/image-20220316203454281.png" alt="image-20220316203454281"></p><p>&emsp;&emsp;这种在线更新的方式已经被官网移除了，操作不了</p><p><img src="/article/cb91b1d5/image-20220316211319931.png" alt="image-20220316211319931"></p><p>&emsp;&emsp;这时我们就只能通过离线安装的方式来实现了，下载对应的离线文件</p><p><img src="/article/cb91b1d5/image-20220316211405001.png" alt="image-20220316211405001"></p><p>&emsp;&emsp;安装步骤来操作，</p><p><img src="/article/cb91b1d5/image-20220316211518520.png" alt="image-20220316211518520"></p><p>然后继续：选择<strong>Help → Install New Software</strong></p><p><img src="/article/cb91b1d5/image-20220316211622115.png" alt="image-20220316211622115"></p><p><img src="/article/cb91b1d5/image-20220316211651195.png" alt="image-20220316211651195"></p><p>下一步</p><p><img src="/article/cb91b1d5/image-20220316211729581.png" alt="image-20220316211729581"></p><p>再下一步</p><p><img src="/article/cb91b1d5/image-20220316211802548.png" alt="image-20220316211802548"></p><p>然后finish。弹出如下窗口</p><p><img src="/article/cb91b1d5/image-20220316212008477.png" alt="image-20220316212008477"></p><p>重启即可</p><h3 id="1-3-创建项目"><a href="#1-3-创建项目" class="headerlink" title="1.3 创建项目"></a>1.3 创建项目</h3><p>&emsp;&emsp;然后我们就可以创建一个Flowable Project了</p><p><img src="/article/cb91b1d5/image-20220316212418899.png" alt="image-20220316212418899"></p><p>&emsp;&emsp;然后我们可以在src/mian/resources/ 的目录下创建对应的流程图了</p><p><img src="/article/cb91b1d5/image-20220316212605146.png" alt="image-20220316212605146"></p><p>&emsp;&emsp;看到如下的界面说明插件安装成功了</p><p><img src="/article/cb91b1d5/image-20220316212720767.png" alt="image-20220316212720767"></p><h3 id="1-4-创建流程图"><a href="#1-4-创建流程图" class="headerlink" title="1.4 创建流程图"></a>1.4 创建流程图</h3><p>使用滑板来绘制流程，通过从右侧把图标拖拽到左侧的面板，最终的效果</p><p><img src="/article/cb91b1d5/image-20220316214339678.png" alt="image-20220316214339678"></p><p>指定流程的主键</p><p><img src="/article/cb91b1d5/image-20220316214430644.png" alt="image-20220316214430644"></p><p>指定任务的负责人</p><p>在Properties视图中指定每个任务节点的负责人</p><p><img src="/article/cb91b1d5/image-20220316214700887.png" alt="image-20220316214700887"></p><p>创建请假单：zhangsan</p><p>审批请假单：lisi</p><p>当我们设置完成后保存文件，会同时生成png图片</p><p><img src="/article/cb91b1d5/image-20220316214808400.png" alt="image-20220316214808400"></p><p>注意：生成图片需要如下配置</p><p><img src="/article/cb91b1d5/image-20220316214256235.png" alt="image-20220316214256235"></p><h3 id="1-5-部署流程"><a href="#1-5-部署流程" class="headerlink" title="1.5 部署流程"></a>1.5 部署流程</h3><p>&emsp;&emsp;首先在Eclipse中生成bar文件，选中项目然后鼠标右击</p><p><img src="/article/cb91b1d5/image-20220316215805503.png" alt="image-20220316215805503"></p><p>然后会发现在项目的根目录下多了一个deployment文件夹，里面多了一个MyProcess.bar文件</p><p><img src="/article/cb91b1d5/image-20220316215900052.png" alt="image-20220316215900052"></p><p>然后我们就可以把这个bar文件拷贝到IDEA中，继续部署的流程</p><p><img src="/article/cb91b1d5/image-20220316215945236.png" alt="image-20220316215945236"></p><p>而部署的代码和前面没啥区别</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDeploy</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 1.获取 ProcessEngine 对象</span></span><br><span class="line">    ProcessEngine processEngine = configuration.buildProcessEngine();</span><br><span class="line">    <span class="comment">// 2.获取RepositoryService</span></span><br><span class="line">    RepositoryService repositoryService = processEngine.getRepositoryService();</span><br><span class="line">    InputStream in = <span class="keyword">this</span>.getClass().getClassLoader().getResourceAsStream(<span class="string">"MyHoliday.bar"</span>);</span><br><span class="line">    ZipInputStream zipInputStream = <span class="keyword">new</span> ZipInputStream(in);</span><br><span class="line">    <span class="comment">// 3.完成流程的部署操作 ZIP 或者 Bar文件</span></span><br><span class="line">    Deployment deploy = repositoryService.createDeployment()</span><br><span class="line">           <span class="comment">// .addClasspathResource("MyHoliday.bar")// 关联要部署的流程文件</span></span><br><span class="line">            .addZipInputStream(zipInputStream)</span><br><span class="line">            .name(<span class="string">"XXX公司请求流程"</span>)</span><br><span class="line">            .deploy() ;<span class="comment">// 部署流程</span></span><br><span class="line">    System.out.println(<span class="string">"deploy.getId() = "</span> + deploy.getId());</span><br><span class="line">    System.out.println(<span class="string">"deploy.getName() = "</span> + deploy.getName());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行后查看表结构，相关的信息就进去了</p><p><img src="/article/cb91b1d5/image-20220316220121734.png" alt="image-20220316220121734"></p><p>完整的案例代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.bobo.flowable.test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.flowable.engine.*;</span><br><span class="line"><span class="keyword">import</span> org.flowable.engine.history.HistoricActivityInstance;</span><br><span class="line"><span class="keyword">import</span> org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration;</span><br><span class="line"><span class="keyword">import</span> org.flowable.engine.repository.Deployment;</span><br><span class="line"><span class="keyword">import</span> org.flowable.engine.runtime.ProcessInstance;</span><br><span class="line"><span class="keyword">import</span> org.flowable.task.api.Task;</span><br><span class="line"><span class="keyword">import</span> org.junit.Before;</span><br><span class="line"><span class="keyword">import</span> org.junit.Test;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.InputStream;</span><br><span class="line"><span class="keyword">import</span> java.util.HashMap;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"><span class="keyword">import</span> java.util.zip.ZipInputStream;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test02</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    ProcessEngineConfiguration configuration = <span class="keyword">null</span>;</span><br><span class="line">    <span class="meta">@Before</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">before</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">// 获取  ProcessEngineConfiguration 对象</span></span><br><span class="line">        configuration = <span class="keyword">new</span> StandaloneProcessEngineConfiguration();</span><br><span class="line">        <span class="comment">// 配置 相关的数据库的连接信息</span></span><br><span class="line">        configuration.setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>);</span><br><span class="line">        configuration.setJdbcUsername(<span class="string">"root"</span>);</span><br><span class="line">        configuration.setJdbcPassword(<span class="string">"123456"</span>);</span><br><span class="line">        configuration.setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn?serverTimezone=UTC&amp;nullCatalogMeansCurrent=true"</span>);</span><br><span class="line">        <span class="comment">// 如果数据库中的表结构不存在就新建</span></span><br><span class="line">        configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 部署流程</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testDeploy</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">// 1.获取 ProcessEngine 对象</span></span><br><span class="line">        ProcessEngine processEngine = configuration.buildProcessEngine();</span><br><span class="line">        <span class="comment">// 2.获取RepositoryService</span></span><br><span class="line">        RepositoryService repositoryService = processEngine.getRepositoryService();</span><br><span class="line">        InputStream in = <span class="keyword">this</span>.getClass().getClassLoader().getResourceAsStream(<span class="string">"MyHoliday.bar"</span>);</span><br><span class="line">        ZipInputStream zipInputStream = <span class="keyword">new</span> ZipInputStream(in);</span><br><span class="line">        <span class="comment">// 3.完成流程的部署操作 ZIP 或者 Bar文件</span></span><br><span class="line">        Deployment deploy = repositoryService.createDeployment()</span><br><span class="line">               <span class="comment">// .addClasspathResource("MyHoliday.bar")// 关联要部署的流程文件</span></span><br><span class="line">                .addZipInputStream(zipInputStream)</span><br><span class="line">                .name(<span class="string">"XXX公司请求流程"</span>)</span><br><span class="line">                .deploy() ;<span class="comment">// 部署流程</span></span><br><span class="line">        System.out.println(<span class="string">"deploy.getId() = "</span> + deploy.getId());</span><br><span class="line">        System.out.println(<span class="string">"deploy.getName() = "</span> + deploy.getName());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 启动流程实例</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRunProcess</span><span class="params">()</span></span>&#123;</span><br><span class="line">        ProcessEngine processEngine = configuration.buildProcessEngine();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 我们需要通过RuntimeService来启动流程实例</span></span><br><span class="line">        RuntimeService runtimeService = processEngine.getRuntimeService();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 启动流程实例</span></span><br><span class="line">        ProcessInstance holidayRequest = runtimeService.startProcessInstanceById(<span class="string">"myProcess:1:25004"</span>);</span><br><span class="line">        System.out.println(<span class="string">"holidayRequest.getProcessDefinitionId() = "</span> + holidayRequest.getProcessDefinitionId());</span><br><span class="line">        System.out.println(<span class="string">"holidayRequest.getActivityId() = "</span> + holidayRequest.getActivityId());</span><br><span class="line">        System.out.println(<span class="string">"holidayRequest.getId() = "</span> + holidayRequest.getId());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 测试任务查询</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testQueryTask</span><span class="params">()</span></span>&#123;</span><br><span class="line">        ProcessEngine processEngine = configuration.buildProcessEngine();</span><br><span class="line">        TaskService taskService = processEngine.getTaskService();</span><br><span class="line">        List&lt;Task&gt; list = taskService.createTaskQuery()</span><br><span class="line">                .processDefinitionKey(<span class="string">"myProcess"</span>) <span class="comment">// 指定查询的流程编程</span></span><br><span class="line">                .taskAssignee(<span class="string">"zhangsan"</span>) <span class="comment">// 查询这个任务的处理人</span></span><br><span class="line">                .list();</span><br><span class="line">        <span class="keyword">for</span> (Task task : list) &#123;</span><br><span class="line">            System.out.println(<span class="string">"task.getProcessDefinitionId() = "</span> + task.getProcessDefinitionId());</span><br><span class="line">            System.out.println(<span class="string">"task.getName() = "</span> + task.getName());</span><br><span class="line">            System.out.println(<span class="string">"task.getAssignee() = "</span> + task.getAssignee());</span><br><span class="line">            System.out.println(<span class="string">"task.getDescription() = "</span> + task.getDescription());</span><br><span class="line">            System.out.println(<span class="string">"task.getId() = "</span> + task.getId());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 完成当前任务</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testCompleteTask</span><span class="params">()</span></span>&#123;</span><br><span class="line">        ProcessEngine processEngine = configuration.buildProcessEngine();</span><br><span class="line">        TaskService taskService = processEngine.getTaskService();</span><br><span class="line">        Task task = taskService.createTaskQuery()</span><br><span class="line">                .processDefinitionKey(<span class="string">"myProcess"</span>)</span><br><span class="line">                .taskAssignee(<span class="string">"lisi"</span>)</span><br><span class="line">                .singleResult();</span><br><span class="line">        <span class="comment">// 创建流程变量</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(task != <span class="keyword">null</span>)&#123;</span><br><span class="line">            <span class="comment">// 完成任务</span></span><br><span class="line">            taskService.complete(task.getId());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取流程任务的历史数据</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testHistory</span><span class="params">()</span></span>&#123;</span><br><span class="line">        ProcessEngine processEngine = configuration.buildProcessEngine();</span><br><span class="line">        HistoryService historyService = processEngine.getHistoryService();</span><br><span class="line">        List&lt;HistoricActivityInstance&gt; list = historyService.createHistoricActivityInstanceQuery()</span><br><span class="line">                .processDefinitionId(<span class="string">"myProcess:1:25004"</span>)</span><br><span class="line">                .finished() <span class="comment">// 查询的历史记录的状态是已经完成</span></span><br><span class="line">                .orderByHistoricActivityInstanceEndTime().asc() <span class="comment">// 指定排序的字段和顺序</span></span><br><span class="line">                .list();</span><br><span class="line">        <span class="keyword">for</span> (HistoricActivityInstance history : list) &#123;</span><br><span class="line">            System.out.println(history.getActivityName()+<span class="string">":"</span>+history.getAssignee()+<span class="string">"--"</span></span><br><span class="line">                    +history.getActivityId()+<span class="string">":"</span> + history.getDurationInMillis()+<span class="string">"毫秒"</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2-Flowable-UI应用"><a href="#2-Flowable-UI应用" class="headerlink" title="2.Flowable UI应用"></a>2.Flowable UI应用</h2><p>&emsp;&emsp;Flowable提供了几个web应用，用于演示及介绍Flowable项目提供的功能：</p><ul><li>Flowable IDM: 身份管理应用。为所有Flowable UI应用提供单点登录认证功能，并且为拥有IDM管理员权限的用户提供了管理用户、组与权限的功能。</li><li>Flowable Modeler: 让具有建模权限的用户可以创建流程模型、表单、选择表与应用定义。</li><li>Flowable Task: 运行时任务应用。提供了启动流程实例、编辑任务表单、完成任务，以及查询流程实例与任务的功能。</li><li>Flowable Admin: 管理应用。让具有管理员权限的用户可以查询BPMN、DMN、Form及Content引擎，并提供了许多选项用于修改流程实例、任务、作业等。管理应用通过REST API连接至引擎，并与Flowable Task应用及Flowable REST应用一同部署。</li></ul><p>&emsp;&emsp;所有其他的应用都需要Flowable IDM提供认证。每个应用的WAR文件可以部署在相同的servlet容器（如Apache Tomcat）中，也可以部署在不同的容器中。由于每个应用使用相同的cookie进行认证，因此应用需要运行在相同的域名下。</p><h3 id="2-1-安装部署"><a href="#2-1-安装部署" class="headerlink" title="2.1 安装部署"></a>2.1 安装部署</h3><p>下载Tomcat：<a href="https://tomcat.apache.org/download-80.cgi" target="_blank" rel="noopener">https://tomcat.apache.org/download-80.cgi</a> 官网下载后解压缩到非中文目录即可，然后是下载FlowableUI的文件，在Flowable6.6之后把FlowableUI中提供的四个功能合并到了一起。</p><p><img src="/article/cb91b1d5/image-20220318102128672.png" alt="image-20220318102128672"></p><p>然后把解压缩后的两个war包拷贝到Tomcat的解压缩的webapps目录下</p><p><img src="/article/cb91b1d5/image-20220318102224330.png" alt="image-20220318102224330"></p><p>Tomcat目录：</p><p><img src="/article/cb91b1d5/image-20220318102255188.png" alt="image-20220318102255188"></p><h3 id="2-2-启动服务"><a href="#2-2-启动服务" class="headerlink" title="2.2 启动服务"></a>2.2 启动服务</h3><p>&emsp;&emsp;启动Tomcat服务，执行startup.bat文件</p><p><img src="/article/cb91b1d5/image-20220318102325924.png" alt="image-20220318102325924"></p><p>如果启动中出现乱码修改Tomcat的conf目录下的 logging.properties 文件中的编码</p><p><img src="/article/cb91b1d5/image-20220318102446699.png" alt="image-20220318102446699"></p><p>如果一闪而过则检查jdk的环境变量配置。启动成功后，在浏览器中访问 <a href="http://localhost:8080/flowable-ui" target="_blank" rel="noopener">http://localhost:8080/flowable-ui</a>, 默认的账号密码是 admin/test</p><p><img src="/article/cb91b1d5/image-20220318102609807.png" alt="image-20220318102609807"></p><h3 id="2-3-用户管理"><a href="#2-3-用户管理" class="headerlink" title="2.3 用户管理"></a>2.3 用户管理</h3><p>&emsp;&emsp;我们先在 <code>身份管理应用程序</code> 中创建用户并授权</p><p><img src="/article/cb91b1d5/image-20220318102707368.png" alt="image-20220318102707368"></p><p>创建用户</p><p><img src="/article/cb91b1d5/image-20220318102734238.png" alt="image-20220318102734238"></p><p>填写详细信息</p><p><img src="/article/cb91b1d5/image-20220318102817782.png" alt="image-20220318102817782"></p><p>授权管理</p><p><img src="/article/cb91b1d5/image-20220318102859814.png" alt="image-20220318102859814"></p><h3 id="2-4-绘制流程"><a href="#2-4-绘制流程" class="headerlink" title="2.4 绘制流程"></a>2.4 绘制流程</h3><p>创建新的流程</p><p><img src="/article/cb91b1d5/image-20220318101239742.png" alt="image-20220318101239742"></p><p>流程图界面</p><p><img src="/article/cb91b1d5/image-20220318101346692.png" alt="image-20220318101346692"></p><p>创建流程，分配处理人</p><p><img src="/article/cb91b1d5/image-20220318101639424.png" alt="image-20220318101639424"></p><p><img src="/article/cb91b1d5/image-20220318101611564.png" alt="image-20220318101611564"></p><p>继续完成流程图的创建</p><p><img src="/article/cb91b1d5/image-20220318101810272.png" alt="image-20220318101810272"></p><h3 id="2-5-部署流程"><a href="#2-5-部署流程" class="headerlink" title="2.5 部署流程"></a>2.5 部署流程</h3><p>&emsp;&emsp;绘制好的流程图，我们只需要一键导出即可</p><p><img src="/article/cb91b1d5/image-20220318103413285.png" alt="image-20220318103413285"></p><p>下载下来后拷贝到项目的resource目录下即可</p><p><img src="/article/cb91b1d5/image-20220318103518807.png" alt="image-20220318103518807"></p><p>然后就是正常的操作流程了</p><h3 id="2-6-FlowableUI-演示"><a href="#2-6-FlowableUI-演示" class="headerlink" title="2.6 FlowableUI 演示"></a>2.6 FlowableUI 演示</h3><h4 id="2-6-1-部署流程"><a href="#2-6-1-部署流程" class="headerlink" title="2.6.1 部署流程"></a>2.6.1 部署流程</h4><p>&emsp;&emsp;在FlowableUI中提供了演示程序</p><p><img src="/article/cb91b1d5/image-20220318104517967.png" alt="image-20220318104517967"></p><p>创建一个新的应用程序，并指定相关的信息</p><p><img src="/article/cb91b1d5/image-20220318104614784.png" alt="image-20220318104614784"></p><p>创建应用后需要指定对应的流程图</p><p><img src="/article/cb91b1d5/image-20220318104703306.png" alt="image-20220318104703306"></p><p><img src="/article/cb91b1d5/image-20220318104735714.png" alt="image-20220318104735714"></p><p><img src="/article/cb91b1d5/image-20220318104811812.png" alt="image-20220318104811812"></p><p>发布应用程序</p><p><img src="/article/cb91b1d5/image-20220318105045345.png" alt="image-20220318105045345"></p><h4 id="2-6-2-启动流程"><a href="#2-6-2-启动流程" class="headerlink" title="2.6.2 启动流程"></a>2.6.2 启动流程</h4><p>&emsp;&emsp;发布了应用程序后我们就可以来启动流程了</p><p><img src="/article/cb91b1d5/image-20220318105258331.png" alt="image-20220318105258331"></p><p><img src="/article/cb91b1d5/image-20220318105315908.png" alt="image-20220318105315908"></p><p><img src="/article/cb91b1d5/image-20220318105336107.png" alt="image-20220318105336107"></p><p><img src="/article/cb91b1d5/image-20220318105420011.png" alt="image-20220318105420011"></p><p>点击显示图：</p><p><img src="/article/cb91b1d5/image-20220318105444672.png" alt="image-20220318105444672"></p><p>也就是可以看到当前是user1来处理，user1登录后可以看到要处理的流程，user2登录是看不到的。</p><p><img src="/article/cb91b1d5/image-20220318105646273.png" alt="image-20220318105646273"></p><p>点击完成后流程就向下一步流转了</p><p><img src="/article/cb91b1d5/image-20220318105727192.png" alt="image-20220318105727192"></p><p>这时再通过user2登录，就可以看到对应的代办的信息</p><p><img src="/article/cb91b1d5/image-20220318105808830.png" alt="image-20220318105808830"></p><p><img src="/article/cb91b1d5/image-20220318105848185.png" alt="image-20220318105848185"></p><p>然后点击完成，那么整个流程就介绍了</p><h1 id="四、Flowable基础表结构"><a href="#四、Flowable基础表结构" class="headerlink" title="四、Flowable基础表结构"></a>四、Flowable基础表结构</h1><h2 id="1-表结构讲解"><a href="#1-表结构讲解" class="headerlink" title="1.表结构讲解"></a>1.表结构讲解</h2><p>&emsp;&emsp;工作流程的相关操作都是操作存储在对应的表结构中，为了能更好的弄清楚Flowable的实现原理和细节，我们有必要先弄清楚Flowable的相关表结构及其作用。在Flowable中的表结构在初始化的时候会创建五类表结构，具体如下：</p><ul><li><strong>ACT_RE</strong> ：’RE’表示 repository。 这个前缀的表包含了流程定义和流程静态资源 （图片，规则，等等）。</li><li><strong>ACT_RU</strong>：’RU’表示 runtime。 这些运行时的表，包含流程实例，任务，变量，异步任务，等运行中的数据。 Flowable只在流程实例执行过程中保存这些数据， 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。</li><li><strong>ACT_HI</strong>：’HI’表示 history。 这些表包含历史数据，比如历史流程实例， 变量，任务等等。</li><li><strong>ACT_GE</strong>： GE 表示 general。 通用数据， 用于不同场景下 </li><li><strong>ACT_ID:</strong>   ’ID’表示identity(组织机构)。这些表包含标识的信息，如用户，用户组，等等。</li></ul><p>具体的表结构的含义:</p><div class="table-container"><table><thead><tr><th><strong>表分类</strong></th><th><strong>表名</strong></th><th><strong>解释</strong></th></tr></thead><tbody><tr><td>一般数据</td><td></td><td></td></tr><tr><td></td><td>[ACT_GE_BYTEARRAY]</td><td>通用的流程定义和流程资源</td></tr><tr><td></td><td>[ACT_GE_PROPERTY]</td><td>系统相关属性</td></tr><tr><td>流程历史记录</td><td></td><td></td></tr><tr><td></td><td>[ACT_HI_ACTINST]</td><td>历史的流程实例</td></tr><tr><td></td><td>[ACT_HI_ATTACHMENT]</td><td>历史的流程附件</td></tr><tr><td></td><td>[ACT_HI_COMMENT]</td><td>历史的说明性信息</td></tr><tr><td></td><td>[ACT_HI_DETAIL]</td><td>历史的流程运行中的细节信息</td></tr><tr><td></td><td>[ACT_HI_IDENTITYLINK]</td><td>历史的流程运行过程中用户关系</td></tr><tr><td></td><td>[ACT_HI_PROCINST]</td><td>历史的流程实例</td></tr><tr><td></td><td>[ACT_HI_TASKINST]</td><td>历史的任务实例</td></tr><tr><td></td><td>[ACT_HI_VARINST]</td><td>历史的流程运行中的变量信息</td></tr><tr><td>流程定义表</td><td></td><td></td></tr><tr><td></td><td>[ACT_RE_DEPLOYMENT]</td><td>部署单元信息</td></tr><tr><td></td><td>[ACT_RE_MODEL]</td><td>模型信息</td></tr><tr><td></td><td>[ACT_RE_PROCDEF]</td><td>已部署的流程定义</td></tr><tr><td>运行实例表</td><td></td><td></td></tr><tr><td></td><td>[ACT_RU_EVENT_SUBSCR]</td><td>运行时事件</td></tr><tr><td></td><td>[ACT_RU_EXECUTION]</td><td>运行时流程执行实例</td></tr><tr><td></td><td>[ACT_RU_IDENTITYLINK]</td><td>运行时用户关系信息，存储任务节点与参与者的相关信息</td></tr><tr><td></td><td>[ACT_RU_JOB]</td><td>运行时作业</td></tr><tr><td></td><td>[ACT_RU_TASK]</td><td>运行时任务</td></tr><tr><td></td><td>[ACT_RU_VARIABLE]</td><td>运行时变量表</td></tr><tr><td>用户用户组表</td><td></td><td></td></tr><tr><td></td><td>[ACT_ID_BYTEARRAY]</td><td>二进制数据表</td></tr><tr><td></td><td>[ACT_ID_GROUP]</td><td>用户组信息表</td></tr><tr><td></td><td>[ACT_ID_INFO]</td><td>用户信息详情表</td></tr><tr><td></td><td>[ACT_ID_MEMBERSHIP]</td><td>人与组关系表</td></tr><tr><td></td><td>[ACT_ID_PRIV]</td><td>权限表</td></tr><tr><td></td><td>[ACT_ID_PRIV_MAPPING]</td><td>用户或组权限关系表</td></tr><tr><td></td><td>[ACT_ID_PROPERTY]</td><td>属性表</td></tr><tr><td></td><td>[ACT_ID_TOKEN]</td><td>记录用户的token信息</td></tr><tr><td></td><td>[ACT_ID_USER]</td><td>用户表</td></tr></tbody></table></div><h2 id="2-ProcessEngine讲解"><a href="#2-ProcessEngine讲解" class="headerlink" title="2.ProcessEngine讲解"></a>2.ProcessEngine讲解</h2><h3 id="2-1-硬编码的方式"><a href="#2-1-硬编码的方式" class="headerlink" title="2.1 硬编码的方式"></a>2.1 硬编码的方式</h3><p>&emsp;&emsp;我们前面讲解案例的时候是通过ProcessEngineConfiguration这个配置类来加载的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 配置数据库相关信息 获取 ProcessEngineConfiguration</span></span><br><span class="line">ProcessEngineConfiguration cfg = <span class="keyword">new</span> StandaloneProcessEngineConfiguration()</span><br><span class="line">    .setJdbcUrl(<span class="string">"jdbc:mysql://localhost:3306/flowable-learn2?serverTimezone=UTC&amp;nullCatalogMeansCurrent=true"</span>)</span><br><span class="line">    .setJdbcUsername(<span class="string">"root"</span>)</span><br><span class="line">    .setJdbcPassword(<span class="string">"123456"</span>)</span><br><span class="line">    .setJdbcDriver(<span class="string">"com.mysql.cj.jdbc.Driver"</span>)</span><br><span class="line">    .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);</span><br><span class="line"><span class="comment">// 获取流程引擎对象</span></span><br><span class="line">ProcessEngine processEngine = cfg.buildProcessEngine();</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;这种方式会调用buildProcessEngine()方法，里面的核心代码为：</p><p><img src="/article/cb91b1d5/image-20220319113106848.png" alt="image-20220319113106848"></p><p><img src="/article/cb91b1d5/image-20220319113139646.png" alt="image-20220319113139646"></p><h3 id="2-2-配置文件"><a href="#2-2-配置文件" class="headerlink" title="2.2 配置文件"></a>2.2 配置文件</h3><p>&emsp;&emsp;除了上面的硬编码的方式外，我们还可以在resources目录下创建一个<code>flowable.cfg.xml</code>文件，注意这个名称是固定的哦。内容如下：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"processEngineConfiguration"</span></span></span><br><span class="line"><span class="tag">          <span class="attr">class</span>=<span class="string">"org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"jdbcUrl"</span> <span class="attr">value</span>=<span class="string">"jdbc:mysql://localhost:3306/flow1?allowMultiQueries=true&amp;amp;useUnicode=true&amp;amp;characterEncoding=UTF-8&amp;amp;useSSL=false&amp;amp;serverTimezone=UTC&amp;amp;nullCatalogMeansCurrent=true"</span> /&gt;</span><span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"jdbcDriver"</span> <span class="attr">value</span>=<span class="string">"com.mysql.cj.jdbc.Driver"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"jdbcUsername"</span> <span class="attr">value</span>=<span class="string">"root"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"jdbcPassword"</span> <span class="attr">value</span>=<span class="string">"123456"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"databaseSchemaUpdate"</span> <span class="attr">value</span>=<span class="string">"true"</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">"asyncExecutorActivate"</span> <span class="attr">value</span>=<span class="string">"false"</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure><p>&emsp;&emsp;在上面的配置文件中配置相关的信息。我们在Java代码中就可以简化为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test01</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">// 获取流程引擎对象</span></span><br><span class="line">    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();</span><br><span class="line">    System.out.println(<span class="string">"processEngine = "</span> + processEngine);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;可以看下getDefaultProcessEngine的源码，在里面最终还是执行了和硬编码一样的代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ProcessEngine <span class="title">getProcessEngine</span><span class="params">(String processEngineName)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!isInitialized()) &#123;</span><br><span class="line">        init(); <span class="comment">// 完成初始化操作</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> processEngines.get(processEngineName);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;进入init方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!isInitialized()) &#123;</span><br><span class="line">        <span class="keyword">if</span> (processEngines == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="comment">// Create new map to store process-engines if current map is null</span></span><br><span class="line">            processEngines = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        &#125;</span><br><span class="line">        ClassLoader classLoader = ReflectUtil.getClassLoader();</span><br><span class="line">        Enumeration&lt;URL&gt; resources = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            resources = classLoader.getResources(<span class="string">"flowable.cfg.xml"</span>); <span class="comment">// 加载flowable.cfg.xml配置文件</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> FlowableIllegalArgumentException(<span class="string">"problem retrieving flowable.cfg.xml resources on the classpath: "</span> + System.getProperty(<span class="string">"java.class.path"</span>), e);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Remove duplicated configuration URL's using set. Some</span></span><br><span class="line">        <span class="comment">// classloaders may return identical URL's twice, causing duplicate</span></span><br><span class="line">        <span class="comment">// startups</span></span><br><span class="line">        Set&lt;URL&gt; configUrls = <span class="keyword">new</span> HashSet&lt;&gt;();</span><br><span class="line">        <span class="keyword">while</span> (resources.hasMoreElements()) &#123;</span><br><span class="line">            configUrls.add(resources.nextElement());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (Iterator&lt;URL&gt; iterator = configUrls.iterator(); iterator.hasNext();) &#123;</span><br><span class="line">            URL resource = iterator.next();</span><br><span class="line">            LOGGER.info(<span class="string">"Initializing process engine using configuration '&#123;&#125;'"</span>, resource.toString());</span><br><span class="line">            initProcessEngineFromResource(resource); <span class="comment">// 初始化ProcessEngine</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            resources = classLoader.getResources(<span class="string">"flowable-context.xml"</span>); <span class="comment">// 在整合Spring的情况下加载该文件</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> FlowableIllegalArgumentException(<span class="string">"problem retrieving flowable-context.xml resources on the classpath: "</span> + System.getProperty(<span class="string">"java.class.path"</span>), e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">while</span> (resources.hasMoreElements()) &#123;</span><br><span class="line">            URL resource = resources.nextElement();</span><br><span class="line">            LOGGER.info(<span class="string">"Initializing process engine using Spring configuration '&#123;&#125;'"</span>, resource.toString());</span><br><span class="line">            initProcessEngineFromSpringResource(resource); <span class="comment">// 从Spring的资源文件中完成ProcessEngine的初始化</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        setInitialized(<span class="keyword">true</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        LOGGER.info(<span class="string">"Process engines already initialized"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;在源码中提供了单独使用好整合Spring的配置加载方式。再进入到initProcessEngineFromResource(resource)方法中：</p><p><img src="/article/cb91b1d5/image-20220319114011806.png" alt="image-20220319114011806"></p><p><img src="/article/cb91b1d5/image-20220319114053252.png" alt="image-20220319114053252"></p><p><img src="/article/cb91b1d5/image-20220319114210012.png" alt="image-20220319114210012"></p><p>而且我们也可以看到ProcessEngine最终的实现是 ProcessEngineImpl对象。</p><h3 id="2-3-自定义配置文件"><a href="#2-3-自定义配置文件" class="headerlink" title="2.3 自定义配置文件"></a>2.3 自定义配置文件</h3><p>&emsp;&emsp;最后我们如果要加载自定义名称的配置文件可以通过ProcessEngineConfiguration中的对应构造方法来实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test2</span><span class="params">()</span> <span class="keyword">throws</span> Exception</span>&#123;</span><br><span class="line">    ProcessEngineConfiguration configuration = ProcessEngineConfiguration</span><br><span class="line">            .createProcessEngineConfigurationFromResource(<span class="string">"flowable.cfg.xml"</span>);</span><br><span class="line">    ProcessEngine processEngine = configuration.buildProcessEngine();</span><br><span class="line">    System.out.println(<span class="string">"processEngine = "</span> + processEngine);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-Servcie服务接口"><a href="#3-Servcie服务接口" class="headerlink" title="3. Servcie服务接口"></a>3. Servcie服务接口</h2><p>Service是工作流引擎提供用于进行工作流部署、执行、管理的服务接口，我们使用这些接口可以就是操作服务对应的数据表</p><p><img src="/article/cb91b1d5/image-20220319223019186.png" alt="image-20220319223019186"></p><h3 id="3-1-Service创建方式"><a href="#3-1-Service创建方式" class="headerlink" title="3.1 Service创建方式"></a>3.1 Service创建方式</h3><p>通过ProcessEngine创建Service</p><p>方式如下：</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">RuntimeService runtimeService = processEngine.getRuntimeService();</span><br><span class="line">RepositoryService repositoryService = processEngine.getRepositoryService();</span><br><span class="line">TaskService taskService = processEngine.getTaskService();</span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure><h3 id="3-2-Service总览"><a href="#3-2-Service总览" class="headerlink" title="3.2 Service总览"></a>3.2 Service总览</h3><div class="table-container"><table><thead><tr><th>service名称</th><th>service作用</th></tr></thead><tbody><tr><td>RepositoryService</td><td>Flowable的资源管理类</td></tr><tr><td>RuntimeService</td><td>Flowable的流程运行管理类</td></tr><tr><td>TaskService</td><td>Flowable的任务管理类</td></tr><tr><td>HistoryService</td><td>Flowable的历史管理类</td></tr><tr><td>ManagerService</td><td>Flowable的引擎管理类</td></tr></tbody></table></div><p> 简单介绍：</p><p><strong>RepositoryService</strong></p><p>是activiti的资源管理类，提供了管理和控制流程发布包和流程定义的操作。使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。</p><p>除了部署流程定义以外还可以：查询引擎中的发布包和流程定义。</p><p>暂停或激活发布包，对应全部和特定流程定义。 暂停意味着它们不能再执行任何操作了，激活是对应的反向操作。获得多种资源，像是包含在发布包里的文件， 或引擎自动生成的流程图。</p><p>获得流程定义的pojo版本， 可以用来通过java解析流程，而不必通过xml。</p><p><strong>RuntimeService</strong></p><p>Activiti的流程运行管理类。可以从这个服务类中获取很多关于流程执行相关的信息</p><p><strong>TaskService</strong></p><p>Activiti的任务管理类。可以从这个类中获取任务的信息。</p><p><strong>HistoryService</strong></p><p>Flowable的历史管理类，可以查询历史信息，执行流程时，引擎会保存很多数据（根据配置），比如流程实例启动时间，任务的参与者， 完成任务的时间，每个流程实例的执行路径，等等。 这个服务主要通过查询功能来获得这些数据。</p><p><strong>ManagementService</strong></p><p>Activiti的引擎管理类，提供了对Flowable 流程引擎的管理和维护功能，这些功能不在工作流驱动的应用程序中使用，主要用于 Flowable 系统的日常维护。</p><h2 id="4-图标介绍"><a href="#4-图标介绍" class="headerlink" title="4.图标介绍"></a>4.图标介绍</h2><p>&emsp;&emsp;BPMN 2.0是业务流程建模符号2.0的缩写。它由Business Process Management Initiative这个非营利协会创建并不断发展。作为一种标识，BPMN 2.0是使用一些<strong>符号</strong>来明确业务流程设计流程图的一整套符号规范，它能增进业务建模时的沟通效率。目前BPMN2.0是最新的版本，它用于在BPM上下文中进行布局和可视化的沟通。接下来我们先来了解在流程设计中常见的 符号。</p><p>BPMN2.0的<strong>基本符合</strong>主要包含：</p><h3 id="4-1-事件图标"><a href="#4-1-事件图标" class="headerlink" title="4.1 事件图标"></a>4.1 事件图标</h3><p>&emsp;&emsp;在Flowable中的事件图标启动事件，边界事件,中间事件和结束事件.</p><p><img src="/article/cb91b1d5/image-20220320103803308.png" alt="image-20220320103803308"></p><h3 id="4-2-活动-任务-图标"><a href="#4-2-活动-任务-图标" class="headerlink" title="4.2 活动(任务)图标"></a>4.2 活动(任务)图标</h3><p>&emsp;&emsp;活动是工作或任务的一个通用术语。一个活动可以是一个任务，还可以是一个当前流程的子处理流程； 其次，你还可以为活动指定不同的类型。常见活动如下:</p><p><img src="/article/cb91b1d5/image-20220320103929543.png" alt="image-20220320103929543"></p><h3 id="4-3-结构图标"><a href="#4-3-结构图标" class="headerlink" title="4.3 结构图标"></a>4.3 结构图标</h3><p>&emsp;&emsp;结构图标可以看做是整个流程活动的结构，一个流程中可以包括子流程。常见的结构有：</p><p><img src="/article/cb91b1d5/Users\dpb\AppData\Roaming\Typora\typora-user-images\image-20220320104025737.png" alt="image-20220320104025737"></p><h3 id="4-4-网关图标"><a href="#4-4-网关图标" class="headerlink" title="4.4 网关图标"></a>4.4 网关图标</h3><p>&emsp;&emsp;网关用来处理决策，有几种常用网关需要了解：</p><p><img src="/article/cb91b1d5/image-20220320104157816.png" alt="image-20220320104157816"></p><h2 id="5-流程部署详解"><a href="#5-流程部署详解" class="headerlink" title="5.流程部署详解"></a>5.流程部署详解</h2><h3 id="5-1-部署实现"><a href="#5-1-部署实现" class="headerlink" title="5.1 部署实现"></a>5.1 部署实现</h3><p>&emsp;&emsp;我们先来看下流程部署的具体过程。代码实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 部署流程</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test3</span><span class="params">()</span></span>&#123;</span><br><span class="line">        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();</span><br><span class="line">        RepositoryService repositoryService = processEngine.getRepositoryService();</span><br><span class="line">        Deployment deploy = repositoryService.createDeployment()</span><br><span class="line">                .addClasspathResource(<span class="string">"holiday-request-new.bpmn20.xml"</span>)</span><br><span class="line">                .name(<span class="string">"请假流程..."</span>)</span><br><span class="line">                .category(<span class="string">"请假"</span>) <span class="comment">// 分类</span></span><br><span class="line">                .tenantId(<span class="string">"dpb"</span>) <span class="comment">// 租户id</span></span><br><span class="line">                .deploy();</span><br><span class="line">        System.out.println(<span class="string">"deploy.getId() = "</span> + deploy.getId());</span><br><span class="line">        System.out.println(<span class="string">"deploy.getName() = "</span> + deploy.getName());</span><br><span class="line">        System.out.println(<span class="string">"deploy.getCategory() = "</span> + deploy.getCategory());</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="5-2-部署涉及表结构"><a href="#5-2-部署涉及表结构" class="headerlink" title="5.2 部署涉及表结构"></a>5.2 部署涉及表结构</h3><p>涉及到的三张表：</p><p>部署资源表：act_ge_bytearray</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>REV_</td><td>版本号</td><td></td></tr><tr><td>NAME_</td><td>名称</td><td>部署的文件名称，如：holiday-request-new.bpmn20.xml、holiday-request-new.bpmn20.png</td></tr><tr><td>DEPLOYMENT<em>ID</em></td><td>部署ID</td><td></td></tr><tr><td>BYTES_</td><td>字节（二进制数据）</td><td></td></tr><tr><td>GENERATED_</td><td>是否系统生成</td><td>0为用户上传，<br>1为系统自动生成， 比如系统会 自动根据xml生 成png</td></tr></tbody></table></div><p>部署ID表：act_re_deployment</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>NAME_</td><td>名称</td><td></td></tr><tr><td>CATEGORY_</td><td>分类</td><td></td></tr><tr><td>TENANT<em>ID</em></td><td>租户ID</td><td></td></tr><tr><td>DEPLOY<em>TIME</em></td><td>部署时间</td><td></td></tr><tr><td>DERIVED<em>FROM</em></td><td>来源于</td><td></td></tr><tr><td>DERIVED<em>FROM_ROOT</em></td><td>来源于</td><td></td></tr><tr><td>ENGINE<em>VERSION</em></td><td>流程引擎的版本</td></tr></tbody></table></div><p>流程表：act_re_procdef</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>REV_</td><td>版本号</td><td></td></tr><tr><td>CATEGORY_</td><td>分类</td><td>流程定义的Namespace就是类别</td></tr><tr><td>NAME_</td><td>名称</td><td></td></tr><tr><td>KEY_</td><td>标识</td><td></td></tr><tr><td>VERSION_</td><td>版本</td><td></td></tr><tr><td>DEPLOYMENT<em>ID</em></td><td>部署ID</td><td></td></tr><tr><td>RESOURCE<em>NAME</em></td><td>资源名称</td><td>流程bpmn文件名称</td></tr><tr><td>DGRM<em>RESOURCE_NAME</em></td><td>图片资源名称</td><td></td></tr><tr><td>DESCRIPTION_</td><td>描述</td><td></td></tr><tr><td>HAS<em>START_FORM_KEY</em></td><td>拥有开始表单标识</td><td>start节点是否存在formKey  0否 1是</td></tr><tr><td>HAS<em>GRAPHICAL_NOTATION</em></td><td>拥有图形信息</td><td></td></tr><tr><td>SUSPENSION<em>STATE</em></td><td>挂起状态</td><td>暂停状态 1激活 2暂停</td></tr><tr><td>TENANT<em>ID</em></td><td>租户ID</td><td></td></tr><tr><td></td><td></td></tr></tbody></table></div><p>注意：</p><p>业务流程定义数据表。此表和ACT<em>RE_DEPLOYMENT是多对一的关系，即，一个部署的bar包里可能包含多个流程定义文件，每个流程定义文件都会有一条记录在ACT_REPROCDEF表内，每个流程定义的数据，都会对于ACT_GE_BYTEARRAY表内的一个资源文件和PNG图片文件。和ACT_GE_BYTEARRAY的关联是通过程序用ACT_GE_BYTEARRAY.NAME与ACT_RE_PROCDEF.NAME</em>完成的</p><h3 id="5-3-挂起和激活"><a href="#5-3-挂起和激活" class="headerlink" title="5.3 挂起和激活"></a>5.3 挂起和激活</h3><p>&emsp;&emsp;部署的流程默认的状态为激活，如果我们暂时不想使用该定义的流程，那么可以挂起该流程。当然该流程定义下边所有的流程实例全部暂停。</p><p>流程定义为挂起状态，该流程定义将不允许启动新的流程实例，同时该流程定义下的所有的流程实例都将全部挂起暂停执行。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 挂起流程</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test05</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">// 获取流程引擎对象</span></span><br><span class="line">        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();</span><br><span class="line">        RepositoryService repositoryService = processEngine.getRepositoryService();</span><br><span class="line">        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()</span><br><span class="line">                .processDefinitionId(<span class="string">"holiday:1:4"</span>)</span><br><span class="line">                .singleResult();</span><br><span class="line">        <span class="comment">// 获取流程定义的状态</span></span><br><span class="line">        <span class="keyword">boolean</span> suspended = processDefinition.isSuspended();</span><br><span class="line">        System.out.println(<span class="string">"suspended = "</span> + suspended);</span><br><span class="line">        <span class="keyword">if</span>(suspended)&#123;</span><br><span class="line">            <span class="comment">// 表示被挂起</span></span><br><span class="line">            System.out.println(<span class="string">"激活流程定义"</span>);</span><br><span class="line">            repositoryService.activateProcessDefinitionById(<span class="string">"holiday:1:4"</span>,<span class="keyword">true</span>,<span class="keyword">null</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="comment">// 表示激活状态</span></span><br><span class="line">            System.out.println(<span class="string">"挂起流程"</span>);</span><br><span class="line">            repositoryService.suspendProcessDefinitionById(<span class="string">"holiday:1:4"</span>,<span class="keyword">true</span>,<span class="keyword">null</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>具体的实现其实就是更新了流程定义表中的字段</p><p><img src="/article/cb91b1d5/image-20220321210010518.png" alt="image-20220321210010518"></p><p>而且通过REV_字段来控制数据安全，也是一种乐观锁的体现了，如果要启动一个已经挂起的流程就会出现如下的错误</p><p><img src="/article/cb91b1d5/image-20220321211858122.png" alt="image-20220321211858122"></p><h2 id="6-启动流程实例"><a href="#6-启动流程实例" class="headerlink" title="6.启动流程实例"></a>6.启动流程实例</h2><p>&emsp;&emsp;然后我们来看看启动流程实例的过程。实现代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 启动流程实例</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testRunProcess</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">// 获取流程引擎对象</span></span><br><span class="line">        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();</span><br><span class="line">        <span class="comment">// 启动流程实例通过 RuntimeService 对象</span></span><br><span class="line">        RuntimeService runtimeService = processEngine.getRuntimeService();</span><br><span class="line">        <span class="comment">// 构建流程变量</span></span><br><span class="line">        Map&lt;String,Object&gt; variables = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        variables.put(<span class="string">"employee"</span>,<span class="string">"张三"</span>) ;<span class="comment">// 谁申请请假</span></span><br><span class="line">        variables.put(<span class="string">"nrOfHolidays"</span>,<span class="number">3</span>); <span class="comment">// 请几天假</span></span><br><span class="line">        variables.put(<span class="string">"description"</span>,<span class="string">"工作累了，想出去玩玩"</span>); <span class="comment">// 请假的原因</span></span><br><span class="line">        <span class="comment">// 启动流程实例，第一个参数是流程定义的id</span></span><br><span class="line">        ProcessInstance processInstance = runtimeService</span><br><span class="line">                .startProcessInstanceById(<span class="string">"holiday:1:4"</span>, variables);<span class="comment">// 启动流程实例</span></span><br><span class="line">        <span class="comment">// 输出相关的流程实例信息</span></span><br><span class="line">        System.out.println(<span class="string">"流程定义的ID："</span> + processInstance.getProcessDefinitionId());</span><br><span class="line">        System.out.println(<span class="string">"流程实例的ID："</span> + processInstance.getId());</span><br><span class="line">        System.out.println(<span class="string">"当前活动的ID："</span> + processInstance.getActivityId());</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;当我们启动了一个流程实例后，会在ACT<em>RU</em>*对应的表结构中操作,运行时实例涉及的表结构共10张：</p><ul><li>ACT_RU_DEADLETTER_JOB  正在运行的任务表 </li><li>ACT_RU_EVENT_SUBSCR 运行时事件 </li><li>ACT_RU_EXECUTION 运行时流程执行实例 </li><li>ACT_RU_HISTORY_JOB  历史作业表 </li><li>ACT_RU_IDENTITYLINK 运行时用户关系信息 </li><li>ACT_RU_JOB 运行时作业表 </li><li>ACT_RU_SUSPENDED_JOB 暂停作业表 </li><li>ACT_RU_TASK  运行时任务表 </li><li>ACT_RU_TIMER_JOB 定时作业表 </li><li>ACT_RU_VARIABLE  运行时变量表</li></ul><p>&emsp;&emsp;启动一个流程实例的时候涉及到的表有</p><ul><li>ACT_RU_EXECUTION 运行时流程执行实例 </li><li>ACT_RU_IDENTITYLINK 运行时用户关系信息 </li><li>ACT_RU_TASK  运行时任务表 </li><li>ACT_RU_VARIABLE  运行时变量表</li></ul><p>ACT_RU_EXECUTION表结构</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>REV_</td><td>版本号</td><td></td></tr><tr><td>PROC<em>INST_ID</em></td><td>流程实例ID</td><td></td></tr><tr><td>BUSINESS<em>KEY</em></td><td>业务主键ID</td><td></td></tr><tr><td>PARENT<em>ID</em></td><td>父执行流的ID</td><td></td></tr><tr><td>PROC<em>DEF_ID</em></td><td>流程定义的数据ID</td><td></td></tr><tr><td>SUPER<em>EXEC</em></td><td></td><td></td></tr><tr><td>ROOT<em>PROC_INST_ID</em></td><td>流程实例的root流程id</td><td></td></tr><tr><td>ACT<em>ID</em></td><td>节点实例ID</td><td></td></tr><tr><td>IS<em>ACTIVE</em></td><td>是否存活</td><td></td></tr><tr><td>IS<em>CONCURRENT</em></td><td>执行流是否正在并行</td><td></td></tr><tr><td>IS<em>SCOPE</em></td><td></td><td></td></tr><tr><td>IS<em>EVENT_SCOPE</em></td><td></td><td></td></tr><tr><td>IS<em>MI_ROOT</em></td><td></td><td></td></tr><tr><td>SUSPENSION<em>STATE</em></td><td>流程终端状态</td><td></td></tr><tr><td>CACHED<em>ENT_STATE</em></td><td></td><td></td></tr><tr><td>TENANT<em>ID</em></td><td>租户编号</td><td></td></tr><tr><td>NAME_</td><td></td><td></td></tr><tr><td>START<em>TIME</em></td><td>开始时间</td><td></td></tr><tr><td>START<em>USER_ID</em></td><td>开始的用户编号</td><td></td></tr><tr><td>LOCK<em>TIME</em></td><td>锁定时间</td><td></td></tr><tr><td>IS<em>COUNT_ENABLED</em></td><td></td><td></td></tr><tr><td>EVT<em>SUBSCR_COUNT</em></td><td></td><td></td></tr><tr><td>TASK<em>COUNT</em></td><td></td><td></td></tr><tr><td>JOB<em>COUNT</em></td><td></td><td></td></tr><tr><td>TIMER<em>JOB_COUNT</em></td><td></td><td></td></tr><tr><td>SUSP<em>JOB_COUNT</em></td><td></td><td></td></tr><tr><td>DEADLETTER<em>JOB_COUNT</em></td><td></td><td></td></tr><tr><td>VAR<em>COUNT</em></td><td></td><td></td></tr><tr><td>ID<em>LINK_COUNT</em></td><td></td></tr></tbody></table></div><p>创建流程实例后对应的表结构的数据</p><p><img src="/article/cb91b1d5/image-20220322133108405.png" alt="image-20220322133108405"></p><p><img src="/article/cb91b1d5/image-20220322133219534.png" alt="image-20220322133219534"></p><p>ACT_RU_TASK  运行时任务表</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>REV_</td><td>版本号</td><td></td></tr><tr><td>EXECUTION<em>ID</em></td><td>任务所在的执行流ID</td><td></td></tr><tr><td>PROC<em>INST_ID</em></td><td>流程实例ID</td><td></td></tr><tr><td>PROC<em>DEF_ID</em></td><td>流程定义数据ID</td><td></td></tr><tr><td>NAME_</td><td>任务名称</td><td></td></tr><tr><td>PARENT<em>TASK_ID</em></td><td>父任务ID</td><td></td></tr><tr><td>DESCRIPTION_</td><td>说明</td><td></td></tr><tr><td>TASK<em>DEF_KEY</em></td><td>任务定义的ID值</td><td></td></tr><tr><td>OWNER_</td><td>任务拥有人</td><td></td></tr><tr><td>ASSIGNEE_</td><td>被指派执行该任务的人</td><td></td></tr><tr><td>DELEGATION_</td><td>委托人</td><td></td></tr><tr><td>PRIORITY_</td><td>优先级</td><td></td></tr><tr><td>CREATE<em>TIME</em></td><td>创建时间</td><td></td></tr><tr><td>DUE<em>DATE</em></td><td>耗时</td><td></td></tr><tr><td>CATEGORY_</td><td>类别</td><td></td></tr><tr><td>SUSPENSION<em>STATE</em></td><td>是否挂起</td><td>1代表激活 2代表挂起</td></tr><tr><td>TENANT<em>ID</em></td><td>租户编号</td><td></td></tr><tr><td>FORM<em>KEY</em></td><td></td><td></td></tr><tr><td>CLAIM<em>TIME</em></td><td>拾取时间</td></tr></tbody></table></div><p>创建流程实例后对应的表结构的数据</p><p><img src="/article/cb91b1d5/image-20220322133307195.png" alt="image-20220322133307195"></p><p><img src="/article/cb91b1d5/image-20220322133335326.png" alt="image-20220322133335326"></p><p>ACT_RU_VARIABLE  运行时变量表</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>REV_</td><td>版本号</td><td></td></tr><tr><td>TYPE_</td><td>参数类型</td><td>可以是基本的类型，也可以用户自行扩展</td></tr><tr><td>NAME_</td><td>参数名称</td><td></td></tr><tr><td>EXECUTION<em>ID</em></td><td>参数执行ID</td><td></td></tr><tr><td>PROC<em>INST_ID</em></td><td>流程实例ID</td><td></td></tr><tr><td>TASK<em>ID</em></td><td>任务ID</td><td></td></tr><tr><td>BYTEARRAY<em>ID</em></td><td>资源ID</td><td></td></tr><tr><td>DOUBLE_</td><td>参数为double，则保存在该字段中</td><td></td></tr><tr><td>LONG_</td><td>参数为long，则保存在该字段中</td><td></td></tr><tr><td>TEXT_</td><td>用户保存文本类型的参数值</td><td></td></tr><tr><td>TEXT2_</td><td>用户保存文本类型的参数值</td></tr></tbody></table></div><p>创建流程实例后对应的表结构的数据</p><p><img src="/article/cb91b1d5/image-20220322133406398.png" alt="image-20220322133406398"></p><p><img src="/article/cb91b1d5/image-20220322133439827.png" alt="image-20220322133439827"></p><p>ACT_RU_IDENTITYLINK 运行时用户关系信息 </p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>REV_</td><td>版本号</td><td></td></tr><tr><td>GROUP<em>ID</em></td><td>用户组ID</td><td></td></tr><tr><td>TYPE_</td><td>关系数据类型</td><td>assignee支配人(组)、candidate候选人(组)、owner拥有人,participant参与者</td></tr><tr><td>USER<em>ID</em></td><td>用户ID</td><td></td></tr><tr><td>TASK<em>ID</em></td><td>任务ID</td><td></td></tr><tr><td>PROC<em>INST_ID</em></td><td>流程定义ID</td><td></td></tr><tr><td>PROC<em>DEF_ID</em></td><td>属性ID</td></tr></tbody></table></div><p>创建流程实例后对应的表结构的数据:</p><p><img src="/article/cb91b1d5/image-20220322133501720.png" alt="image-20220322133501720"></p><h2 id="7-处理流程"><a href="#7-处理流程" class="headerlink" title="7.处理流程"></a>7.处理流程</h2><p>&emsp;&emsp;上面的流程已经流转到了zhangsan这个用户这里，然后可以开始审批了</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取流程引擎对象</span></span><br><span class="line">        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();</span><br><span class="line">        TaskService taskService = processEngine.getTaskService();</span><br><span class="line">        Task task = taskService.createTaskQuery()</span><br><span class="line">                .processDefinitionId(<span class="string">"holiday:1:4"</span>)</span><br><span class="line">                .taskAssignee(<span class="string">"zhangsan"</span>)</span><br><span class="line">                .singleResult();</span><br><span class="line">        <span class="comment">// 添加流程变量</span></span><br><span class="line">        Map&lt;String,Object&gt; variables = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        variables.put(<span class="string">"approved"</span>,<span class="keyword">false</span>); <span class="comment">// 拒绝请假</span></span><br><span class="line">        <span class="comment">// 完成任务</span></span><br><span class="line">        taskService.complete(task.getId(),variables);</span><br></pre></td></tr></table></figure><p>&emsp;&emsp;在正常处理流程中涉及到的表结构</p><ul><li>ACT_RU_EXECUTION 运行时流程执行实例 </li><li>ACT_RU_IDENTITYLINK 运行时用户关系信息 </li><li>ACT_RU_TASK  运行时任务表 </li><li>ACT_RU_VARIABLE  运行时变量表</li></ul><p>ACT_RU_TASK  运行时任务表 :会新生成一条记录</p><p><img src="/article/cb91b1d5/image-20220322135040119.png" alt="image-20220322135040119"></p><p><img src="/article/cb91b1d5/image-20220322135125703.png" alt="image-20220322135125703"></p><p>ACT_RU_VARIABLE  运行时变量表:会记录新的流程变量</p><p><img src="/article/cb91b1d5/image-20220322135204021.png" alt="image-20220322135204021"></p><p>当然流程实例也可以挂起</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1.获取ProcessEngine对象</span></span><br><span class="line">ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();</span><br><span class="line"><span class="comment">// 2.获取RuntimeService</span></span><br><span class="line">RuntimeService runtimeService = engine.getRuntimeService();</span><br><span class="line"><span class="comment">// 3.获取流程实例对象</span></span><br><span class="line">ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()</span><br><span class="line">    .processInstanceId(<span class="string">"25001"</span>)</span><br><span class="line">    .singleResult();</span><br><span class="line"><span class="comment">// 4.获取相关的状态操作</span></span><br><span class="line"><span class="keyword">boolean</span> suspended = processInstance.isSuspended();</span><br><span class="line">String id = processInstance.getId();</span><br><span class="line"><span class="keyword">if</span>(suspended)&#123;</span><br><span class="line">    <span class="comment">// 挂起--》激活</span></span><br><span class="line">    runtimeService.activateProcessInstanceById(id);</span><br><span class="line">    System.out.println(<span class="string">"流程定义："</span> + id + <span class="string">"，已激活"</span>);</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">    <span class="comment">// 激活--》挂起</span></span><br><span class="line">    runtimeService.suspendProcessInstanceById(id);</span><br><span class="line">    System.out.println(<span class="string">"流程定义："</span> + id + <span class="string">"，已挂起"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>启动第二个流程实例后再查看相关的表结构时，对他们的关系理解会更加的清楚一些</p><p>启动一个新的流程实例对应的就会产生两条记录</p><p><img src="/article/cb91b1d5/image-20220322135605252.png" alt="image-20220322135605252"></p><p>IDENTITYLINK中会记录每次流程操作的信息</p><p><img src="/article/cb91b1d5/image-20220322135636841.png" alt="image-20220322135636841"></p><p><img src="/article/cb91b1d5/image-20220322135659671.png" alt="image-20220322135659671"></p><p>流程变量数据，及时key 相同，但是属于不同的流程实例相互间也是隔离的</p><p><img src="/article/cb91b1d5/image-20220322135719104.png" alt="image-20220322135719104"></p><h2 id="8-完成一个流程"><a href="#8-完成一个流程" class="headerlink" title="8.完成一个流程"></a>8.完成一个流程</h2><p>&emsp;&emsp;然后我们把第一个流程处理完成</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">ProcessEngine processEngine = cfg.buildProcessEngine();</span><br><span class="line">TaskService taskService = processEngine.getTaskService();</span><br><span class="line">Task task = taskService.createTaskQuery()</span><br><span class="line">    .processDefinitionId(<span class="string">"holiday:1:4"</span>)</span><br><span class="line">    .taskAssignee(<span class="string">"lisi"</span>)</span><br><span class="line">    .singleResult();</span><br><span class="line"><span class="comment">// 添加流程变量</span></span><br><span class="line">Map&lt;String,Object&gt; variables = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">variables.put(<span class="string">"approved"</span>,<span class="keyword">false</span>); <span class="comment">// 拒绝请假</span></span><br><span class="line"><span class="comment">// 完成任务</span></span><br><span class="line">taskService.complete(task.getId(),variables);</span><br></pre></td></tr></table></figure><p>处理完了一个工作流程后，我们来看看相关的表结构信息</p><p>首先我们会发现</p><ul><li>ACT_RU_EXECUTION 运行时流程执行实例 </li><li>ACT_RU_IDENTITYLINK 运行时用户关系信息 </li><li>ACT_RU_TASK  运行时任务表 </li><li>ACT_RU_VARIABLE  运行时变量表</li></ul><p>这四张表中对应的数据都没有了，也就是这个流程已经不是运行中的流程了。然后在对应的历史表中我们可以看到相关的信息</p><ul><li><p>ACT_HI_ACTINST  历史的流程实例</p></li><li><p>ACT_HI_ATTACHMENT 历史的流程附件</p></li><li>ACT_HI_COMMENT  历史的说明性信息</li><li>ACT_HI_DETAIL 历史的流程运行中的细节信息</li><li>ACT_HI_IDENTITYLINK 历史的流程运行过程中用户关系</li><li>ACT_HI_PROCINST  历史的流程实例</li><li>ACT_HI_TASKINST  历史的任务实例</li><li>ACT_HI_VARINST  历史的流程运行中的变量信息</li></ul><p>在我们上面的处理流程的过程中设计到的历史表有</p><p>ACT_HI_ACTINST  历史的流程实例</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>PROC<em>DEF_ID</em></td><td>流程定义ID</td><td></td></tr><tr><td>PROC<em>INST_ID</em></td><td>流程实例ID</td><td></td></tr><tr><td>EXECUTION<em>ID</em></td><td>执行ID</td><td></td></tr><tr><td>ACT<em>ID</em></td><td>节点实例ID</td><td></td></tr><tr><td>TASK<em>ID</em></td><td>任务ID</td><td></td></tr><tr><td>CALL<em>PROC_INST_ID</em></td><td>调用外部的流程实例ID</td><td></td></tr><tr><td>ACT<em>NAME</em></td><td>节点名称</td><td></td></tr><tr><td>ACT<em>TYPE</em></td><td>节点类型</td><td></td></tr><tr><td>ASSIGNEE_</td><td>处理人</td><td></td></tr><tr><td>START<em>TIME</em></td><td>开始时间</td><td></td></tr><tr><td>END<em>TIME</em></td><td>结束时间</td><td></td></tr><tr><td>DURATION_</td><td>耗时</td><td></td></tr><tr><td>DELETE<em>REASON</em></td><td>删除原因</td><td></td></tr><tr><td>TENANT<em>ID</em></td><td>租户编号</td></tr></tbody></table></div><p><img src="/article/cb91b1d5/image-20220322141800554.png" alt="image-20220322141800554"></p><p><img src="/article/cb91b1d5/image-20220322141825065.png" alt="image-20220322141825065"></p><p>ACT_HI_IDENTITYLINK 历史的流程运行过程中用户关系</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>GROUP<em>ID</em></td><td>组编号</td><td></td></tr><tr><td>TYPE_</td><td>类型</td><td></td></tr><tr><td>USER<em>ID</em></td><td>用户编号</td><td></td></tr><tr><td>TASK<em>ID</em></td><td>任务编号</td><td></td></tr><tr><td>CREATE<em>TIME</em></td><td>创建时间</td><td></td></tr><tr><td>PROC<em>INST_ID</em></td><td>流程实例编号</td><td></td></tr><tr><td>SCOPE<em>ID</em></td><td></td><td></td></tr><tr><td>SCOPE<em>TYPE</em></td><td></td><td></td></tr><tr><td>SCOPE<em>DEFINITION_ID</em></td><td></td><td></td></tr><tr><td></td><td></td></tr></tbody></table></div><p><img src="/article/cb91b1d5/image-20220322141717826.png" alt="image-20220322141717826"></p><p>ACT_HI_PROCINST  历史的流程实例</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>PROC<em>INST_ID</em></td><td>流程实例ID</td><td></td></tr><tr><td>BUSINESS<em>KEY</em></td><td>业务主键</td><td></td></tr><tr><td>PROC<em>DEF_ID</em></td><td>属性ID</td><td></td></tr><tr><td>START<em>TIME</em></td><td>开始时间</td><td></td></tr><tr><td>END<em>TIME</em></td><td>结束时间</td><td></td></tr><tr><td>DURATION_</td><td>耗时</td><td></td></tr><tr><td>START<em>USER_ID</em></td><td>起始人</td><td></td></tr><tr><td>START<em>ACT_ID</em></td><td>起始节点</td><td></td></tr><tr><td>END<em>ACT_ID</em></td><td>结束节点</td><td></td></tr><tr><td>SUPER<em>PROCESS_INSTANCE_ID</em></td><td>父流程实例ID</td><td></td></tr><tr><td>DELETE<em>REASON</em></td><td>删除原因</td><td></td></tr><tr><td>TENANT<em>ID</em></td><td>租户编号</td><td></td></tr><tr><td>NAME_</td><td>名称</td></tr></tbody></table></div><p><img src="/article/cb91b1d5/image-20220322141855401.png" alt="image-20220322141855401"></p><p><img src="/article/cb91b1d5/image-20220322141912602.png" alt="image-20220322141912602"></p><p>ACT_HI_TASKINST  历史的任务实例</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>PROC<em>DEF_ID</em></td><td>流程定义ID</td><td></td></tr><tr><td>TASK<em>DEF_KEY</em></td><td>任务定义的ID值</td><td></td></tr><tr><td>PROC<em>INST_ID</em></td><td>流程实例ID</td><td></td></tr><tr><td>EXECUTION<em>ID</em></td><td>执行ID</td><td></td></tr><tr><td>PARENT<em>TASK_ID</em></td><td>父任务ID</td><td></td></tr><tr><td>NAME_</td><td>名称</td><td></td></tr><tr><td>DESCRIPTION_</td><td>说明</td><td></td></tr><tr><td>OWNER_</td><td>实际签收人 任务的拥有者</td><td>签收人（默认为空，只有在委托时才有值）</td></tr><tr><td>ASSIGNEE_</td><td>被指派执行该任务的人</td><td></td></tr><tr><td>START<em>TIME</em></td><td>开始时间</td><td></td></tr><tr><td>CLAIM<em>TIME</em></td><td>任务拾取时间</td><td></td></tr><tr><td>END<em>TIME</em></td><td>结束时间</td><td></td></tr><tr><td>DURATION_</td><td>耗时</td><td></td></tr><tr><td>DELETE<em>REASON</em></td><td>删除原因</td><td></td></tr><tr><td>PRIORITY_</td><td>优先级别</td><td></td></tr><tr><td>DUE<em>DATE</em></td><td>过期时间</td><td></td></tr><tr><td>FORM<em>KEY</em></td><td>节点定义的formkey</td><td></td></tr><tr><td>CATEGORY_</td><td>类别</td><td></td></tr><tr><td>TENANT<em>ID</em></td><td>租户</td></tr></tbody></table></div><p><img src="/article/cb91b1d5/image-20220322142609163.png" alt="image-20220322142609163"></p><p><img src="/article/cb91b1d5/image-20220322142650699.png" alt="image-20220322142650699"></p><p>ACT_HI_VARINST  历史的流程运行中的变量信息：流程变量虽然在任务完成后在流程实例表中会删除，但是在历史表中还是会记录的</p><div class="table-container"><table><thead><tr><th>字段</th><th>名称</th><th>备注</th></tr></thead><tbody><tr><td>ID_</td><td>主键</td><td></td></tr><tr><td>PROC<em>INST_ID</em></td><td>流程实例ID</td><td></td></tr><tr><td>EXECUTION<em>ID</em></td><td>指定ID</td><td></td></tr><tr><td>TASK<em>ID</em></td><td>任务ID</td><td></td></tr><tr><td>NAME_</td><td>名称</td><td></td></tr><tr><td>VAR<em>TYPE</em></td><td>参数类型</td><td></td></tr><tr><td>REV_</td><td>数据版本</td><td></td></tr><tr><td>BYTEARRAY<em>ID</em></td><td>字节表ID</td><td></td></tr><tr><td>DOUBLE_</td><td>存储double类型数据</td><td></td></tr><tr><td>LONG_</td><td>存储long类型数据</td><td></td></tr><tr><td>…..</td><td></td></tr></tbody></table></div><p><img src="/article/cb91b1d5/image-20220322142756867.png" alt="image-20220322142756867"></p><p>好了一个相对简单的流程处理涉及到的相关表结构内容就介绍完了</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Flowable工作流学习笔记&lt;/p&gt;
    
    </summary>
    
    
    
      <category term="Flowable" scheme="https://www.tengfei.eu.org/tags/Flowable/"/>
    
      <category term="工作流" scheme="https://www.tengfei.eu.org/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
    
  </entry>
  
  <entry>
    <title>SpringCloud使用笔记</title>
    <link href="https://www.tengfei.eu.org/article/def94604.html"/>
    <id>https://www.tengfei.eu.org/article/def94604.html</id>
    <published>2023-01-27T06:38:19.000Z</published>
    <updated>2023-10-16T04:51:27.143Z</updated>
    
    <content type="html"><![CDATA[<p>SpringCloud学习笔记</p><a id="more"></a><h1 id="SpringCloud和SpringBoot关系"><a href="#SpringCloud和SpringBoot关系" class="headerlink" title="SpringCloud和SpringBoot关系"></a>SpringCloud和SpringBoot关系</h1><ol><li>SpringBoot专注于 快速方便的开发单个个体微服务。</li><li>SpringCloud是关注全局的微服务协调整理治理框架，它将SpringBoot开发的一个个单体微服务整合并管理起分布式会话等等集成服务。</li><li>SpringBoot可以离开SpringClooud独立使用， 开发项目，但是SpringCloud离不开SpringBoot, 属于依赖关系</li><li>SpringBoot专注于快速、方便的开发单个个体微服务，SpringCloud关注全局的服务治理框架</li></ol><h1 id="创建springCloud"><a href="#创建springCloud" class="headerlink" title="创建springCloud"></a>创建springCloud</h1><h3 id="Rest环境搭建"><a href="#Rest环境搭建" class="headerlink" title="Rest环境搭建"></a>Rest环境搭建</h3><h4 id="在父模块导入jar包"><a href="#在父模块导入jar包" class="headerlink" title="在父模块导入jar包"></a>在父模块导入jar包</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line">&lt;properties&gt;</span><br><span class="line">    &lt;maven.compiler.source&gt;15&lt;/maven.compiler.source&gt;</span><br><span class="line">    &lt;maven.compiler.target&gt;15&lt;/maven.compiler.target&gt;</span><br><span class="line">    &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;</span><br><span class="line">    &lt;junit.version&gt;4.13.1&lt;/junit.version&gt;</span><br><span class="line">    &lt;lombok.version&gt;1.18.18&lt;/lombok.version&gt;</span><br><span class="line">    &lt;log4j.version&gt;1.2.17&lt;/log4j.version&gt;</span><br><span class="line">    &lt;logback.version&gt;1.2.3&lt;/logback.version&gt;</span><br><span class="line">&lt;/properties&gt;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&lt;dependencyManagement&gt;</span><br><span class="line">        &lt;dependencies&gt;</span><br><span class="line">            &lt;!-- spring-cloud-dependencies --&gt;</span><br><span class="line">            &lt;dependency&gt;</span><br><span class="line">                &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">                &lt;artifactId&gt;spring-cloud-dependencies&lt;/artifactId&gt;</span><br><span class="line">                &lt;version&gt;2020.0.1&lt;/version&gt;</span><br><span class="line">                &lt;scope&gt;import&lt;/scope&gt;</span><br><span class="line">                &lt;type&gt;pom&lt;/type&gt;</span><br><span class="line">            &lt;/dependency&gt;</span><br><span class="line">            &lt;!-- spring-boot --&gt;</span><br><span class="line">            &lt;dependency&gt;</span><br><span class="line">                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">                &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt;</span><br><span class="line">                &lt;type&gt;pom&lt;/type&gt;</span><br><span class="line">                &lt;scope&gt;import&lt;/scope&gt;</span><br><span class="line">                &lt;version&gt;2.4.3&lt;/version&gt;</span><br><span class="line">            &lt;/dependency&gt;</span><br><span class="line">            &lt;!-- mysql --&gt;</span><br><span class="line">            &lt;dependency&gt;</span><br><span class="line">                &lt;groupId&gt;mysql&lt;/groupId&gt;</span><br><span class="line">                &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;</span><br><span class="line">                &lt;version&gt;8.0.23&lt;/version&gt;</span><br><span class="line">            &lt;/dependency&gt;</span><br><span class="line">            &lt;!-- druid 数据源 --&gt;</span><br><span class="line">            &lt;dependency&gt;</span><br><span class="line">                &lt;groupId&gt;com.alibaba&lt;/groupId&gt;</span><br><span class="line">                &lt;artifactId&gt;druid&lt;/artifactId&gt;</span><br><span class="line">                &lt;version&gt;1.2.4&lt;/version&gt;</span><br><span class="line">            &lt;/dependency&gt;</span><br><span class="line">            &lt;!-- springboot 启动器 --&gt;</span><br><span class="line">            &lt;dependency&gt;</span><br><span class="line">                &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;</span><br><span class="line">                &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;</span><br><span class="line">                &lt;version&gt;2.1.3&lt;/version&gt;</span><br><span class="line">            &lt;/dependency&gt;</span><br><span class="line">            &lt;!-- junit --&gt;</span><br><span class="line">            &lt;dependency&gt;</span><br><span class="line">                &lt;groupId&gt;junit&lt;/groupId&gt;</span><br><span class="line">                &lt;artifactId&gt;junit&lt;/artifactId&gt;</span><br><span class="line">                &lt;version&gt;$&#123;junit.version&#125;&lt;/version&gt;</span><br><span class="line">                &lt;scope&gt;test&lt;/scope&gt;</span><br><span class="line">            &lt;/dependency&gt;</span><br><span class="line">            &lt;!-- lombok --&gt;</span><br><span class="line">            &lt;dependency&gt;</span><br><span class="line">                &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;</span><br><span class="line">                &lt;artifactId&gt;lombok&lt;/artifactId&gt;</span><br><span class="line">                &lt;version&gt;$&#123;lombok.version&#125;&lt;/version&gt;</span><br><span class="line">            &lt;/dependency&gt;</span><br><span class="line">            &lt;!-- log4j --&gt;</span><br><span class="line">            &lt;dependency&gt;</span><br><span class="line">                &lt;groupId&gt;log4j&lt;/groupId&gt;</span><br><span class="line">                &lt;artifactId&gt;log4j&lt;/artifactId&gt;</span><br><span class="line">                &lt;version&gt;$&#123;log4j.version&#125;&lt;/version&gt;</span><br><span class="line">            &lt;/dependency&gt;</span><br><span class="line">            &lt;!-- logback 日志测试--&gt;</span><br><span class="line">            &lt;dependency&gt;</span><br><span class="line">                &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;</span><br><span class="line">                &lt;artifactId&gt;logback-classic&lt;/artifactId&gt;</span><br><span class="line">                &lt;version&gt;$&#123;logback.version&#125;&lt;/version&gt;</span><br><span class="line">                &lt;scope&gt;test&lt;/scope&gt;</span><br><span class="line">            &lt;/dependency&gt;</span><br><span class="line">        &lt;/dependencies&gt;</span><br><span class="line">    &lt;/dependencyManagement&gt;</span><br></pre></td></tr></table></figure><p>子模板可以有Api，生产者(提供)，消费者,Eurekazhuce中心</p><h4 id="服务提供者"><a href="#服务提供者" class="headerlink" title="服务提供者"></a>服务提供者</h4><p>如果有同一个服务提供者的集群，只需要保持==spring.application.name==相同即可，如：</p><p><img src="/article/def94604/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210328145009680.png" alt="image-20210328145009680"></p><p>创建完后，在子模板的pom文件添加该子模板需要的jar包</p><p><img src="/article/def94604/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210321213159489.png" alt="image-20210321213159489"></p><p>如果子模板需要另一个子模板的pojo，也可以在pom文件中导入其他子模板 </p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependencies&gt;</span><br><span class="line">        &lt;!-- 需要拿到实体类,所以需要将包含实体类的<span class="keyword">module</span>配置一下 --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.example&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;springcloud-api&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;junit&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;junit&lt;/artifactId&gt;</span><br><span class="line">            &lt;scope&gt;test&lt;/scope&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;mysql&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;com.alibaba&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;druid&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;logback-core&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;!-- jetty --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-jetty&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;!-- 热部署 --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">    &lt;/dependencies&gt;</span><br></pre></td></tr></table></figure><h5 id="按需求配置yaml文件"><a href="#按需求配置yaml文件" class="headerlink" title="按需求配置yaml文件"></a>按需求配置yaml文件</h5><p>如果api模板或者其他模板不需要就可以不配置，依各模板情况编写</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: 8081<span class="comment">#端口</span></span><br><span class="line"></span><br><span class="line">mybatis:</span><br><span class="line">  apper-locations: classpath:mybatis/mapper/*.xml</span><br><span class="line">  <span class="built_in">type</span>-aliases-package: com.flf.springcloud.pojo</span><br><span class="line">  <span class="comment">#config-location: classpath:mybatis/mybatis-config.xml</span></span><br><span class="line"></span><br><span class="line">spring:</span><br><span class="line">  application:</span><br><span class="line">    name: springcloud-provider-dept</span><br><span class="line">  datasource:</span><br><span class="line">    <span class="built_in">type</span>: com.alibaba.druid.pool.DruidDataSource</span><br><span class="line">    driver-class-name: com.mysql.cj.jdbc.Driver</span><br><span class="line">    url: jdbc:mysql://localhost:3306/xiaoxi?useUnicode=<span class="literal">true</span>&amp;characterEncoding=UTF-8</span><br><span class="line">    username: root</span><br><span class="line">    password: 123456</span><br></pre></td></tr></table></figure><h5 id="编写dao，service等"><a href="#编写dao，service等" class="headerlink" title="编写dao，service等"></a>编写dao，service等</h5><p>mapper(dao)层，service，serviceImpl，controller，mapper.xml,启动类</p><p>例子：</p><blockquote><p>dao</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="meta">@Repository</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">DeptDao</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Boolean <span class="title">addDept</span><span class="params">(Dept dept)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Dept <span class="title">queryById</span><span class="params">(Long id)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;Dept&gt; <span class="title">queryAll</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>service</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">DeptService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Boolean <span class="title">addDept</span><span class="params">(Dept dept)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Dept <span class="title">queryById</span><span class="params">(Long id)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;Dept&gt; <span class="title">queryAll</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>serviceImpl</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeptServiceImpl</span> <span class="keyword">implements</span> <span class="title">DeptService</span></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    DeptDao deptDao;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Boolean <span class="title">addDept</span><span class="params">(Dept dept)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> deptDao.addDept(dept);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Dept <span class="title">queryById</span><span class="params">(Long id)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> deptDao.queryById(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;Dept&gt; <span class="title">queryAll</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> deptDao.queryAll();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>controller</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//提供restful服务</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeptController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> DeptService deptService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping</span>(<span class="string">"/dept/add"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Boolean <span class="title">addDept</span><span class="params">(Dept dept)</span></span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> deptService.addDept(dept);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/dept/get/&#123;id&#125;"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Dept <span class="title">queryById</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id)</span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> deptService.queryById(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/dept/list"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;Dept&gt; <span class="title">queryAll</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> deptService.queryAll();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//获取注册在注册中心的实例</span></span><br><span class="line">    <span class="meta">@GetMappering</span>(<span class="string">"/dept/discovery"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">discovery</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">//获取所有实例</span></span><br><span class="line">        List&lt;String&gt; services = client.getServices();</span><br><span class="line">        System.out.println(<span class="string">"discovery-&gt;services:"</span> +services);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//获取一个具体的微服务信息  参数是 application，即yaml文件中spring.application.name的值</span></span><br><span class="line">        List&lt;ServiceInstance&gt; instances = client.getInstances(<span class="string">"SPRINGCLOUD-PROVIDER-DEPT"</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (ServiceInstance instance : instances) &#123;</span><br><span class="line">            System.out.println(</span><br><span class="line">                    instance.getInstanceId() + <span class="string">"\t"</span>+</span><br><span class="line">                    instance.getHost() + <span class="string">"\t"</span>+</span><br><span class="line">                    instance.getPort() + <span class="string">"\t"</span>+</span><br><span class="line">                    instance.getUri()</span><br><span class="line">            );</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.client;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>mapper.xml</p></blockquote><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE mapper</span></span><br><span class="line"><span class="meta">        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"</span></span><br><span class="line"><span class="meta">        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">"com.flf.dao.DeptDao"</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">"addDept"</span> <span class="attr">parameterType</span>=<span class="string">"Dept"</span>&gt;</span></span><br><span class="line">        insert into dept(deptName,db_source)</span><br><span class="line">        values (#&#123;deptName&#125;,DATABASE())</span><br><span class="line">    <span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">"queryById"</span> <span class="attr">parameterType</span>=<span class="string">"Long"</span> <span class="attr">resultType</span>=<span class="string">"Dept"</span>&gt;</span></span><br><span class="line">        select * from dept where deptNo = #&#123;deptNo&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">"queryAll"</span> <span class="attr">resultType</span>=<span class="string">"Dept"</span>&gt;</span></span><br><span class="line">        select * from dept</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><blockquote><p>启动类</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//启动类</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeptProviderStart_8081</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        SpringApplication.run(DeptProviderStart_8081<span class="class">.<span class="keyword">class</span>,<span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="服务消费者"><a href="#服务消费者" class="headerlink" title="服务消费者"></a>服务消费者</h4><blockquote><p>导入jar包</p></blockquote><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"> &lt;!-- 一般消费者只需要实体类和web --&gt;</span><br><span class="line">&lt;dependencies&gt;</span><br><span class="line"></span><br><span class="line">     &lt;dependency&gt;</span><br><span class="line">         &lt;groupId&gt;org.example&lt;/groupId&gt;</span><br><span class="line">         &lt;artifactId&gt;springcloud-api&lt;/artifactId&gt;</span><br><span class="line">         &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;</span><br><span class="line">     &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">     &lt;dependency&gt;</span><br><span class="line">         &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">         &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;</span><br><span class="line">     &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">     &lt;dependency&gt;</span><br><span class="line">         &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">         &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line">&lt;/dependencies&gt;</span><br></pre></td></tr></table></figure><blockquote><p>配置yaml 文件</p></blockquote><p>只需要配置端口号即可，一般默认为 80</p><blockquote><p>配置RestTemplate</p></blockquote><p>建立一个 config文件夹，在文件夹里编写</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConfigBean</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span>   <span class="comment">//需要什么就把什么当做返回值</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> RestTemplate <span class="title">getRestTemplate</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();<span class="comment">//默认配置</span></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><blockquote><p>编写controller层</p></blockquote><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> *</span><br><span class="line"> * 消费者，不需要service业务层</span><br><span class="line"> * 自定义RestTemplate，注册到spring中</span><br><span class="line"> * 通过 RestTemplate 来获取提供者的方法</span><br><span class="line"> * 参数 ：String url(路径),  Object request(实体，通常用Map,也可以使用实体), Class&lt;T&gt; responseType(返回的参数类型)</span><br><span class="line"> */</span><br><span class="line">@Resource//自己配置的RestTemplate</span><br><span class="line">private RestTemplate restTemplate;  //提供多种便捷访问远程http服务的方法，简单的Restful服务模板</span><br><span class="line"></span><br><span class="line">private static final String REST_URL_PREFIX = &quot;http://localhost:8081&quot;;</span><br><span class="line"></span><br><span class="line">@RequestMapping(&quot;/consumer/dept/add&quot;)</span><br><span class="line">public Boolean add(Dept dept)&#123;</span><br><span class="line">    //restTemplate之后 用post 还是 get 取决于提供者的controller</span><br><span class="line">    return restTemplate.postForObject(REST_URL_PREFIX+&quot;/dept/add&quot;,dept,Boolean.class);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">@RequestMapping(&quot;/consumer/dept/get/&#123;id&#125;&quot;)</span><br><span class="line">public Dept getDept(@PathVariable(&quot;id&quot;) Long id)&#123;</span><br><span class="line"></span><br><span class="line">    return restTemplate.getForObject(REST_URL_PREFIX+&quot;/dept/get/&quot;+id,Dept.class);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">@RequestMapping(&quot;/consumer/dept/list&quot;)</span><br><span class="line">public List&lt;Dept&gt; list()&#123;</span><br><span class="line"></span><br><span class="line">    return restTemplate.getForObject(REST_URL_PREFIX+&quot;/dept/list&quot;,List.class);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="Eureka"><a href="#Eureka" class="headerlink" title="Eureka"></a>Eureka</h1><h4 id="什么是Eureka"><a href="#什么是Eureka" class="headerlink" title="什么是Eureka"></a>什么是Eureka</h4><ul><li>Eureka遵循AP原则</li><li>Eureka是Netflix的一个子模块，也是核心模块之一。Eureka是一个基于REST的服务，用于定位服务，以实现云端中间层发现和故障转移，服务注册与发现对微服务很重要，有了服务发现注册，只需要使用服务的标识符，就可以访问到服务器，而不需要修改服务调用的配置文件了，功能类似于Dubbo的注册中心，如：zookeeper</li></ul><h4 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h4><blockquote><p>Eureka的基本架构</p></blockquote><ul><li>SpringCloud 封装了NetFlix公司开发的Eureka模块来实现服务注册和发现(对比Zookeeper)。Eureka采用 了C-S的架构设计，EurekaServer 作为服务注册功能的服务器,他是服务注册中心</li><li><p>而系统中的其他微服务。使用Eureka的客户 端连接到EurekaServer并维持心跳连接。这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行，SpringCloud的一些其他模块(比如Zuul)就可以通过EurekaServer来发现系统中的其他微服务 ，并执行相关的逻辑;</p></li><li><p>Eureka 包含两个组件: Eureka Server和Eureka Client 。</p></li><li>Eureka Server提供服务注册服务，各个节点启动后，会在EurekaServer中进行注册， 这样Eureka Server中的服务注册表中将会村粗所有可用服务节点的信息，服务节点的信息可以在界面中直观的看到。</li><li>Eureka Client是一个Java客户端， 用于简化EurekaServer的交互， 客户端同时也具备一个内置的，使用轮询负载算法的负载均衡器。在应用启动后，将会向EurekaServer发送心跳(默认周期为30秒)。如果</li><li>Eureka Server在多个心跳周期内没有接收到某个节点的心跳，EurekaServer将 会从服务注册表中把这个服务节点移除掉(默认周期为90秒)</li></ul><blockquote><p>三大角色</p></blockquote><ul><li>Eureka Server:提供服务的注册于发现。</li><li>Service Provider:将自身服务注册到Eureka中，从而使消费方能够找到。</li><li>Service Consumer:服务消费方从Eureka中获取注册服务列表，从而找到消费服务。</li></ul><h4 id="配置Eureka"><a href="#配置Eureka" class="headerlink" title="配置Eureka"></a>配置Eureka</h4><h5 id="1-导入jar包"><a href="#1-导入jar包" class="headerlink" title="1.导入jar包"></a>1.导入jar包</h5><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependencies&gt;</span><br><span class="line">       &lt;!-- spring-cloud-starter-netflix-eureka-server --&gt;</span><br><span class="line">       &lt;dependency&gt;</span><br><span class="line">           &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">           &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-server&lt;/artifactId&gt;</span><br><span class="line">           &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">       &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">       &lt;dependency&gt;</span><br><span class="line">           &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">           &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;</span><br><span class="line">       &lt;/dependency&gt;</span><br><span class="line">   &lt;/dependencies&gt;</span><br></pre></td></tr></table></figure><h5 id="2-配置yaml文件"><a href="#2-配置yaml文件" class="headerlink" title="2.配置yaml文件"></a>2.配置yaml文件</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: <span class="number">7001</span></span><br><span class="line"></span><br><span class="line">eureka:</span><br><span class="line">  instance:</span><br><span class="line">    hostname: localhost   #Eureka服务端的实例名</span><br><span class="line">  client:</span><br><span class="line">    register-with-eureka: false   #表示是否向Eureka注册中心注册自己</span><br><span class="line">    fetch-registry: false     #fetch-registry 如果为false，标明自己为注册中心</span><br><span class="line">    service-url:    #监控页面</span><br><span class="line">      defaultZone: http:<span class="comment">//$&#123;eureka.instance.hostname&#125;:$&#123;server.port&#125;/eureka/</span></span><br></pre></td></tr></table></figure><h5 id="3-加注解"><a href="#3-加注解" class="headerlink" title="3.加注解"></a>3.加注解</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaServer</span>     <span class="comment">//服务端的启动类,可以接受别人注册服务</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Eureka_7001_Start</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(Eureka_7001_Start<span class="class">.<span class="keyword">class</span>,<span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="错误"><a href="#错误" class="headerlink" title="错误"></a>错误</h5><p>进入 <code>localhost:7001</code> 后 可能会出现 404，原因可能是 springboot和springCloud版本不兼容</p><p>==推荐版本:springBoot:2.3.3.RELEASE   springCloud:Hoxton.SR8==</p><p>版本对应查询地址 <code>https://spring.io/projects/spring-boot#learn</code></p><h4 id="服务注册"><a href="#服务注册" class="headerlink" title="服务注册"></a>服务注册</h4><h5 id="1-在服务提供者的pom文件加入jar包"><a href="#1-在服务提供者的pom文件加入jar包" class="headerlink" title="1.在服务提供者的pom文件加入jar包"></a>1.在服务提供者的pom文件加入jar包</h5><p>在之前Rest环境搭建中的服务提供者的基础上额外加入该jar包</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"> &lt;!--spring-cloud-starter-netflix-eureka-client --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">     &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">     &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-client&lt;/artifactId&gt;</span><br><span class="line">     &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">//如果没加该jar包，在Eureka注册中心页面，点击状态会跳转404网页</span></span><br><span class="line"><span class="comment">//导入之后需要咋子 yaml文件配置信息</span></span><br><span class="line">&lt;!-- 完善监控信息 --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">     &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">     &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><h5 id="2-在yaml文件加入-Eureka配置"><a href="#2-在yaml文件加入-Eureka配置" class="headerlink" title="2.在yaml文件加入 Eureka配置"></a>2.在yaml文件加入 Eureka配置</h5><p>该配置service-url 需要和==注册中心==的地址一致</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">#Eureka</span><br><span class="line">eureka:</span><br><span class="line">  client:</span><br><span class="line">    service-url:</span><br><span class="line">      defaultZone: http:<span class="comment">//localhost:7001/eureka</span></span><br><span class="line">  instance:</span><br><span class="line">    instance-id: spring-cloud-provider-dept-8081  #修改状态名</span><br><span class="line"></span><br><span class="line">#info信息,导入actuator之后配置,这个用于查看注册中心中，点击该服务，弹出提供该服务的信息</span><br><span class="line">info:#json格式即可，可任意配置</span><br><span class="line">  app.name: flf</span><br><span class="line">  company.name: zzz</span><br></pre></td></tr></table></figure><p>点击此处</p><p><img src="/article/def94604/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210327150355570.png" alt="image-20210327150355570"></p><p>然后调出页面，页面显示的为 info 配置的信息</p><p><img src="/article/def94604/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210327150432828.png" alt="image-20210327150432828"></p><h5 id="3-在启动类加注解"><a href="#3-在启动类加注解" class="headerlink" title="3.在启动类加注解"></a>3.在启动类加注解</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaClient</span><span class="comment">//服务提供者,服务启动后，自动注册到Eureka中</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeptProviderStart_8081</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(DeptProviderStart_8081<span class="class">.<span class="keyword">class</span>,<span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="自我保护机制"><a href="#自我保护机制" class="headerlink" title="自我保护机制"></a>自我保护机制</h4><h5 id="一句话总结-某时刻某一个微服务不可以用了，-eureka不会-立刻清理，依旧会对该微服务的信息进行保存"><a href="#一句话总结-某时刻某一个微服务不可以用了，-eureka不会-立刻清理，依旧会对该微服务的信息进行保存" class="headerlink" title="一句话总结:某时刻某一个微服务不可以用了， eureka不会 立刻清理，依旧会对该微服务的信息进行保存"></a>一句话总结:某时刻某一个微服务不可以用了， eureka不会 立刻清理，依旧会对该微服务的信息进行保存</h5><ul><li>默认情况下，如果EurekaServer在一 定时间内没有接收到某个微服务实例的心跳，EurekaServer将会注销该实例(==默认90秒==)。但是当网络分区故障发生时，微服务与Eureka之间无法正常通行，以上行为可能变得非常危险了-因为微服务本身其实是健康的，此时本不应该注销这个服务。Eureka通过 自我保护机制来解决这个问题—当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障)，那么这个节点就会进入==自我保护模式==。一旦进入该模式（==当实例少于85%时，会进入该模式==），EurekaServer就会保护服务注册表中的信息，不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后，该EurekaServer 节点会自动退出自我保护模式。</li><li>在自我保护模式中，EurekaServer会保护服务注册表中的信息，不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时，该EurekaServer 节点就会自动退出自我保护模式。宁可保留错误的服务注册信息，也不盲目注销任何可能健康的服务实例。</li><li>综上，自我保护模式是一种应对网络异常的安全保护措施。 它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)，也不盲目注销任何健康的微服务。使用自我保护模式，可以让Eureka集群更加的健壮和稳定</li><li>在SpringCloud中, 可以使用eureka. server. enable-self-preservation = false 禁用自我保护模式[不推荐关闭自我保护机制]</li></ul><h4 id="Eureka集群"><a href="#Eureka集群" class="headerlink" title="Eureka集群"></a>Eureka集群</h4><h5 id="1-配置注册中心yaml"><a href="#1-配置注册中心yaml" class="headerlink" title="1.配置注册中心yaml"></a>1.配置注册中心yaml</h5><p>需要几个注册中心，就新建几个木块</p><p>或者在之前的注册中心的 yaml文件 用 <code>---</code> 分隔，做不同的配置，然后不同配置启动同一个模块</p><blockquote><p>yaml文件配置</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: <span class="number">7001</span></span><br><span class="line"></span><br><span class="line">eureka:</span><br><span class="line">  instance:</span><br><span class="line">    hostname: 7001  #Eureka服务端的实例名</span><br><span class="line">  client:</span><br><span class="line">    register-with-eureka: false   #表示是否向Eureka注册中心注册自己</span><br><span class="line">    fetch-registry: false     #fetch-registry 如果为false，标明自己为注册中心</span><br><span class="line">    service-url:    #监控页面</span><br><span class="line">      #这个是单机时候用的</span><br><span class="line">      #defaultZone: http://$&#123;eureka.instance.hostname&#125;:$&#123;server.port&#125;/eureka/</span><br><span class="line">      #集群时，互相关联//这个地方需要与另外的注册中心关联，其中localhost是其他注册中心的ip地址，这里是主机ip</span><br><span class="line">      defaultZone: http:<span class="comment">//localhost:7002/eureka/,http://localhost:7003/eureka/</span></span><br></pre></td></tr></table></figure><p>其他的yaml文件除了 hostname ，port和defaultZone 不同之外，其余都一样</p><h5 id="2-配置服务提供者yaml"><a href="#2-配置服务提供者yaml" class="headerlink" title="2.配置服务提供者yaml"></a>2.配置服务提供者yaml</h5><p>只需要添加其他注册中心的注册地址即可</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#Eureka</span><br><span class="line">eureka:</span><br><span class="line">  client:</span><br><span class="line">    service-url:</span><br><span class="line">      defaultZone: http:<span class="comment">//localhost:7001/eureka,http://localhost:7002/eureka,http://localhost:7003/eureka</span></span><br><span class="line">  instance:</span><br><span class="line">    instance-id: spring-cloud-provider-dept-8081  #修改状态名</span><br></pre></td></tr></table></figure><h5 id="3-依次启动"><a href="#3-依次启动" class="headerlink" title="3.依次启动"></a>3.依次启动</h5><p>启动三个注册中心，然后启动服务提供者就可以</p><p>每个注册中心都会注册服务提供者的服务</p><p><img src="/article/def94604/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210327170720194.png" alt="image-20210327170720194"></p><p>当一个节点挂掉之后，不会影响服务</p><h4 id="CAP原则"><a href="#CAP原则" class="headerlink" title="CAP原则"></a>CAP原则</h4><p>REDMS(MySQL,Oracle,sqlServer) —&gt; ACID</p><p>NoSQL(redis,mongdb) —&gt; CAP</p><h5 id="ACID"><a href="#ACID" class="headerlink" title="ACID"></a>ACID</h5><ul><li>A (Atomicity)  原子性</li><li>C (Consistency)  一致性</li><li>I (Isolation)  隔离性</li><li>D (Durability)  持久性</li></ul><h5 id="CAP"><a href="#CAP" class="headerlink" title="CAP"></a>CAP</h5><ul><li>C (Consistency)  强一致性</li><li>A (Availability)  可用性</li><li>P (Partition tolerance)  分区容错性</li></ul><p>CAP的三进二：CA，AP，CP</p><h5 id="CAP核心理论"><a href="#CAP核心理论" class="headerlink" title="CAP核心理论"></a>CAP核心理论</h5><p>1.一个分布式系统不可能同时很好地满足一致性，可用性，分区容错性三个需求</p><p>2.根据CAP原理，将NoSQL数据库分成了满足CA原则，AP原则，CP原则三大类</p><ul><li>CA:单点集群，满足一致性，可用性的系统，通常可拓展性较差</li><li>CP:满足一致性，分区容错性，通常性能不是特别高</li><li>AP:满足可用性，分区容错性，可能对一致性要求稍低</li></ul><p>由于在分布式系统中，分区容错性p 是必要的，因此只能在A，和C之间权衡</p><ul><li>Zookeeper保证的是CP</li><li>Eureka保证的是AP</li></ul><h5 id="Zookeeper：CP"><a href="#Zookeeper：CP" class="headerlink" title="Zookeeper：CP"></a>Zookeeper：CP</h5><p>当向注册中心查询服务列表时，我们可以容忍注册中心返回的是几分钟以前的注册信息，但不能接受服务直接down掉不可用。也就是说，服务注册功能对可用性的要求要高于一致性。 但是zk会出现这样一种情况， 当master节点因为网络故障与其他节点失去联系时，剩余节点会重新进行leader选举。问题在于，选举leader的时间太长，30-120s，且选举期间整个zk集群 都是不可用的，这就导致在选举期间注册服务瘫痪。在云部署的环境下，因为网络问题使得zk集群失去master节点是较大概率会发生的事件，虽然服务最终能够恢复，但是漫长的选举时间导致的注册长期不可用是不能容忍的。</p><h5 id="Eureka：AP"><a href="#Eureka：AP" class="headerlink" title="Eureka：AP"></a>Eureka：AP</h5><p>Eureka各个节点都是平等的， 几个节点挂掉不会影响正常节点的工作，剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时， 如果发现连接失败，则会自动切换至其他节点，只要有一台Eureka还在，就能保住注册服务的可用性，只不过查到的信息可能不是最新的，除此之外，Eureka还有一种自我保护机制，如果在15分钟内超过15%的节点都没有正常的心跳，那么Eureka就认为客户端与注册中心出现了网络故障，此时会出现以下几种情况:</p><ol><li>Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务</li><li>Eureka仍然能够接受新服务的注册和查询请求，但是不会被同步到其他节点上(即保证当前节点依然可用)</li><li>当网络稳定时，当前实例新的注册信息会被同步到其他节点中</li></ol><p>因此，Eureka可以很好的应对因网络故障导致部分节点失去联系的情况，而不会像Zookeeper那样使整个注册服务瘫痪</p><h1 id="Ribbon"><a href="#Ribbon" class="headerlink" title="Ribbon"></a>Ribbon</h1><h4 id="Ribbon简介"><a href="#Ribbon简介" class="headerlink" title="Ribbon简介"></a>Ribbon简介</h4><ul><li>Spring Cloud Ribbon是基于Netflix Ribbon实现的一套==客户端负载均衡==的工具。</li><li>简单的说，Ribbon是Netflix发 布的开源项目，主要功能是提供客户端的软件负载均衡算法，将NetFlix的中间层服务连接在一起。 Ribbon的客户端组件提供一 系列完整的配置项如: 连接超时、重试等等。简单的说，就是在配置文件中列出LoadBalancer (简称LB: 负载均衡)后面所有的机器，Ribbon会 自动的帮助你基于某种规则(如简单轮询，随机连接等等)去连接这些机器，也很容易使用Ribbon实现自定义的负载均衡算法</li></ul><h4 id="Ribbon作用"><a href="#Ribbon作用" class="headerlink" title="Ribbon作用"></a>Ribbon作用</h4><ol><li><p>LB,即负载均衡(Load Balance) ，在微服务或分布式集群中经常用的一一种应用。</p></li><li><p>负载均衡简单的说就是将用户的请求平摊的分配到多个服务上，从而达到系统的HA (高可用)。</p></li><li><p>常见的负载均衡软件有Nginx, Lvs 等等</p></li><li><p>dubbo. SpringCloud中均给我们提供 了负载均衡，SpringCloud的负载均衡算法可以自定义</p></li><li><p>负载均衡简单分类:</p><ul><li><p>集中式LB</p><ul><li>即在服务的消费方和提供方之间使用独立的LB设施，如Nginx(反向代理服务器),油该设施负责把访问请求通过某种策略转发至服务的提供方!</li></ul></li><li><p>进程式LB</p><ul><li><p>将LB逻辑集成到消费方，消费方从服务注册中心获知有哪些地址可用，然后自己再从这些地址中选出-个合适的服务器。</p></li><li><p>==Ribon属于进程内LB==. 它只是一个类库， 集成于消费方进程，消费方通过它来获取到服务提供方的地址!</p></li></ul></li></ul></li></ol><h4 id="springCloud加入Ribbon"><a href="#springCloud加入Ribbon" class="headerlink" title="springCloud加入Ribbon"></a>springCloud加入Ribbon</h4><h5 id="1-在消费者中加入Jar包"><a href="#1-在消费者中加入Jar包" class="headerlink" title="1.在消费者中加入Jar包"></a>1.在消费者中加入Jar包</h5><p>加入ribbon以及Eureka的jar包</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- spring-cloud-starter-netflix-ribbon --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-netflix-ribbon&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line"> &lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-client&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><h5 id="2-配置yaml"><a href="#2-配置yaml" class="headerlink" title="2.配置yaml"></a>2.配置yaml</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">#Eureka</span><br><span class="line">eureka:</span><br><span class="line">  client:</span><br><span class="line">    register-with-eureka: <span class="keyword">false</span></span><br><span class="line">    service-url:</span><br><span class="line">      defaultZone: http:<span class="comment">//localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/</span></span><br></pre></td></tr></table></figure><h5 id="3-加注解-1"><a href="#3-加注解-1" class="headerlink" title="3.加注解"></a>3.加注解</h5><p>在启动类上加上 @EnableEurekaClient</p><h5 id="4-配置config"><a href="#4-配置config" class="headerlink" title="4.配置config"></a>4.配置config</h5><p>配置文件</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConfigBean</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Bean</span>   <span class="comment">//需要什么就把什么当做返回值</span></span><br><span class="line">    <span class="meta">@LoadBalanced</span>   <span class="comment">//负载均衡</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> RestTemplate <span class="title">getRestTemplate</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();<span class="comment">//默认配置</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="5-更改controller"><a href="#5-更改controller" class="headerlink" title="5.更改controller"></a>5.更改controller</h5><p>由于做了负载均衡，所以访问地址是根据负载均衡算法选择的，而不是单机时固定的路径，所以要改变访问路径</p><p><img src="/article/def94604/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210328134044383.png" alt="image-20210328134044383"></p><h5 id="6-新建多个模块-多个配置文件"><a href="#6-新建多个模块-多个配置文件" class="headerlink" title="6.新建多个模块/多个配置文件"></a>6.新建多个模块/多个配置文件</h5><p>新建多个模块或者通过不同配置文件启动同一个模块,只需要保持==spring.application.name==相同</p><p><img src="/article/def94604/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210328145009680.png" alt="image-20210328145009680"></p><h5 id="7-分别启动"><a href="#7-分别启动" class="headerlink" title="7.分别启动"></a>7.分别启动</h5><p>分别启动消费者，注册中心，多个提供者，然后通过消费者调用提供者(查看结果，会发现数据来自不同数据库，均衡模式默认是轮询)</p><h3 id="Ribbon下的springCloud例子"><a href="#Ribbon下的springCloud例子" class="headerlink" title="Ribbon下的springCloud例子"></a>Ribbon下的springCloud例子</h3><p>负载均衡有ribbon和feign，ribbon更具有restful风格，而feign更像是Service和Controller层之间的调用(面向接口)，==feign默认集成了ribbon==，ribbon的完整例子：</p><h4 id="API接口模块"><a href="#API接口模块" class="headerlink" title="API接口模块"></a>API接口模块</h4><p>api模块只有一个pojo类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span>       <span class="comment">//get.set.toString</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span>  <span class="comment">//无参构造</span></span><br><span class="line"><span class="meta">@Accessors</span>(chain = <span class="keyword">true</span>)    <span class="comment">//支持链式写法</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Dept</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>&#123; <span class="comment">//实体类序列化</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Long deptNo;</span><br><span class="line">    <span class="keyword">private</span> String deptName;</span><br><span class="line">    <span class="comment">//该字段表示此数据存在于哪一个数据库，微服务，一个服务可能对应一个数据库，同一个信息可能存在不同的数据库</span></span><br><span class="line">    <span class="keyword">private</span> String db_source;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Dept</span><span class="params">(String deptName)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.deptName = deptName;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    * 链式写法，如：</span></span><br><span class="line"><span class="comment">    * Dept dept = new Dept();</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * dept.setDeptNo(11).setDeptName("zz").setDb_source("001");</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * */</span></span><br></pre></td></tr></table></figure><h4 id="Eureka注册中心"><a href="#Eureka注册中心" class="headerlink" title="Eureka注册中心"></a>Eureka注册中心</h4><h5 id="jar包"><a href="#jar包" class="headerlink" title="jar包"></a>jar包</h5><p>eureka以及热部署</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- spring-cloud-starter-netflix-eureka-server --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-server&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">    &lt;/dependencies&gt;</span><br></pre></td></tr></table></figure><h5 id="yaml配置"><a href="#yaml配置" class="headerlink" title="yaml配置"></a>yaml配置</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: <span class="number">7001</span></span><br><span class="line"></span><br><span class="line">eureka:</span><br><span class="line">  instance:</span><br><span class="line">    hostname: 7001  #Eureka服务端的实例名</span><br><span class="line">  client:</span><br><span class="line">    register-with-eureka: false   #表示是否向Eureka注册中心注册自己</span><br><span class="line">    fetch-registry: false     #fetch-registry 如果为false，标明自己为注册中心</span><br><span class="line">    service-url:    #监控页面</span><br><span class="line">      #这个是单机时候用的</span><br><span class="line">      #defaultZone: http://$&#123;eureka.instance.hostname&#125;:$&#123;server.port&#125;/eureka/</span><br><span class="line">      #集群时，互相关联</span><br><span class="line">      defaultZone: http:<span class="comment">//localhost:7002/eureka/,http://localhost:7003/eureka/</span></span><br></pre></td></tr></table></figure><h5 id="主启动类"><a href="#主启动类" class="headerlink" title="主启动类"></a>主启动类</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaServer</span><span class="comment">//注册中心用EnableEurekaServer，提供者用EnableEurekaClient</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Eureka_7001_start</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(Eureka_7001_start<span class="class">.<span class="keyword">class</span>,<span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="提供者"><a href="#提供者" class="headerlink" title="提供者"></a>提供者</h4><h5 id="jar包-1"><a href="#jar包-1" class="headerlink" title="jar包"></a>jar包</h5><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependencies&gt;</span><br><span class="line">        &lt;!-- 需要拿到实体类,所以需要将包含实体类的<span class="keyword">module</span>配置一下 --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.example&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;springcloud-api&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;!--spring-cloud-starter-netflix-eureka-client --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-client&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;!-- 完善监控信息,这个jar包用于Eureka注册中心页面查看实例信息 --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;2.3.3.RELEASE&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;junit&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;junit&lt;/artifactId&gt;</span><br><span class="line">            &lt;scope&gt;test&lt;/scope&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;mysql&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">    &lt;!-- 数据源 --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;com.alibaba&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;druid&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;logback-core&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;!-- jetty --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-jetty&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;!-- 热部署 --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">    &lt;/dependencies&gt;</span><br></pre></td></tr></table></figure><h5 id="yml配置"><a href="#yml配置" class="headerlink" title="yml配置"></a>yml配置</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: <span class="number">8081</span></span><br><span class="line"></span><br><span class="line">mybatis:</span><br><span class="line">  mapper-locations: classpath:mybatis.mapper<span class="comment">/*.xml</span></span><br><span class="line"><span class="comment">  type-aliases-package: com.flf.springcloud.pojo</span></span><br><span class="line"><span class="comment">  #config-location: classpath:mybatis/mybatis-config.xml</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">spring:</span></span><br><span class="line"><span class="comment">  application:</span></span><br><span class="line"><span class="comment">    name: springcloud-provider-dept</span></span><br><span class="line"><span class="comment">  datasource:</span></span><br><span class="line"><span class="comment">    type: com.alibaba.druid.pool.DruidDataSource</span></span><br><span class="line"><span class="comment">    driver-class-name: com.mysql.cj.jdbc.Driver</span></span><br><span class="line"><span class="comment">    url: jdbc:mysql://localhost:3306/xiaoxi?useUnicode=true&amp;characterEncoding=UTF-8</span></span><br><span class="line"><span class="comment">    username: root</span></span><br><span class="line"><span class="comment">    password: 123456</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">#Eureka</span></span><br><span class="line"><span class="comment">eureka:</span></span><br><span class="line"><span class="comment">  client:</span></span><br><span class="line"><span class="comment">    service-url:</span></span><br><span class="line"><span class="comment">      defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka,http://localhost:7003/eureka</span></span><br><span class="line"><span class="comment">  instance:</span></span><br><span class="line"><span class="comment">    instance-id: spring-cloud-provider-dept-8081  #修改状态名</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">#info</span></span><br><span class="line"><span class="comment">info:</span></span><br><span class="line"><span class="comment">  app.name: flf</span></span><br><span class="line"><span class="comment">  company.name: zzz</span></span><br></pre></td></tr></table></figure><h5 id="省略Dao，Service，Impl-Mapper-xml"><a href="#省略Dao，Service，Impl-Mapper-xml" class="headerlink" title="省略Dao，Service，Impl,Mapper.xml"></a>省略Dao，Service，Impl,Mapper.xml</h5><h5 id="Controller"><a href="#Controller" class="headerlink" title="Controller"></a>Controller</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeptController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> DeptService deptService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> DiscoveryClient client;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostMapping</span>(<span class="string">"/dept/add"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Boolean <span class="title">addDept</span><span class="params">(Dept dept)</span></span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> deptService.addDept(dept);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/dept/get/&#123;id&#125;"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Dept <span class="title">queryById</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id)</span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> deptService.queryById(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span>(<span class="string">"/dept/list"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;Dept&gt; <span class="title">queryAll</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> deptService.queryAll();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//获取注册在注册中心的实例</span></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"dept/discovery"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">discovery</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">//获取所有实例</span></span><br><span class="line">        List&lt;String&gt; services = client.getServices();</span><br><span class="line">        System.out.println(<span class="string">"discovery-&gt;services:"</span> +services);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//获取一个具体的微服务信息  参数是 application，即yaml文件中spring.application.name的值</span></span><br><span class="line">        List&lt;ServiceInstance&gt; instances = client.getInstances(<span class="string">"SPRINGCLOUD-PROVIDER-DEPT"</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (ServiceInstance instance : instances) &#123;</span><br><span class="line">            System.out.println(</span><br><span class="line">                    instance.getInstanceId() + <span class="string">"\t\t"</span>+</span><br><span class="line">                    instance.getHost() + <span class="string">"\t\t"</span>+</span><br><span class="line">                    instance.getPort() + <span class="string">"\t"</span>+</span><br><span class="line">                    instance.getUri()</span><br><span class="line">            );</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.client;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="启动类"><a href="#启动类" class="headerlink" title="启动类"></a>启动类</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaClient</span><span class="comment">//除了注册中心是EnableEurekaServer，无论是provider和consumer都是EnableEurekaClient</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeptProviderStart_8081</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(DeptProviderStart_8081<span class="class">.<span class="keyword">class</span>,<span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="消费者"><a href="#消费者" class="headerlink" title="消费者"></a>消费者</h4><h5 id="jar包-2"><a href="#jar包-2" class="headerlink" title="jar包"></a>jar包</h5><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependencies&gt;</span><br><span class="line"></span><br><span class="line">        &lt;!-- spring-cloud-starter-netflix-ribbon --&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-cloud-starter-netflix-ribbon&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-client&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.example&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;springcloud-api&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">    </span><br><span class="line">&lt;/dependencies&gt;</span><br></pre></td></tr></table></figure><h5 id="yaml文件"><a href="#yaml文件" class="headerlink" title="yaml文件"></a>yaml文件</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: <span class="number">80</span></span><br><span class="line"></span><br><span class="line">#Eureka</span><br><span class="line">eureka:</span><br><span class="line">  client:</span><br><span class="line">    register-with-eureka: <span class="keyword">false</span></span><br><span class="line">    service-url:</span><br><span class="line">      defaultZone: http:<span class="comment">//localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/</span></span><br></pre></td></tr></table></figure><h5 id="RestTemplate"><a href="#RestTemplate" class="headerlink" title="RestTemplate"></a>RestTemplate</h5><p>restTemplate与controller同级</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//配置文件 注解</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ConfigBean</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span>   <span class="comment">//需要什么就把什么当做返回值</span></span><br><span class="line">    <span class="meta">@LoadBalanced</span>   <span class="comment">//负载均衡</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> RestTemplate <span class="title">getRestTemplate</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> RestTemplate();<span class="comment">//默认配置</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="controller"><a href="#controller" class="headerlink" title="controller"></a>controller</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeptConsumer_80</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * 消费者，不需要service业务层</span></span><br><span class="line"><span class="comment">     * 自定义RestTemplate，注册到spring中</span></span><br><span class="line"><span class="comment">     * 通过 RestTemplate 来获取提供者的方法</span></span><br><span class="line"><span class="comment">     * 参数 ：String url(路径),  Object request(实体，通常用Map,也可以使用实体), Class&lt;T&gt; responseType(返回的参数类型)</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> RestTemplate restTemplate;  <span class="comment">//提供多种便捷访问远程http服务的方法，简单的Restful服务模板</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    *   单机情况下的配置</span></span><br><span class="line"><span class="comment">    *   private static final String REST_URL_PREFIX = "http://localhost:8081";</span></span><br><span class="line"><span class="comment">    * */</span></span><br><span class="line">    <span class="comment">//当有负载均衡时，访问路径前缀不再是 固定的了，此时应该 用application作为访问路径</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String REST_URL_PREFIX = <span class="string">"http://SPRINGCLOUD-PROVIDER-DEPT"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/consumer/dept/add"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Boolean <span class="title">add</span><span class="params">(Dept dept)</span></span>&#123;</span><br><span class="line">        <span class="comment">//restTemplate之后 用post 还是 get 取决于提供者的controller</span></span><br><span class="line">        <span class="keyword">return</span> restTemplate.postForObject(REST_URL_PREFIX+<span class="string">"/dept/add"</span>,dept,Boolean<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/consumer/dept/get/&#123;id&#125;"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Dept <span class="title">getDept</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id)</span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> restTemplate.getForObject(REST_URL_PREFIX+<span class="string">"/dept/get/"</span>+id,Dept<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/consumer/dept/list"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;Dept&gt; <span class="title">list</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> restTemplate.getForObject(REST_URL_PREFIX+<span class="string">"/dept/list"</span>,List<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="启动类-1"><a href="#启动类-1" class="headerlink" title="启动类"></a>启动类</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Eureka集成了Ribbon后，客户端可直接调用方法</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaClient</span></span><br><span class="line"><span class="meta">@RibbonClient</span>(name = <span class="string">"myRlue"</span>,configuration = myRule<span class="class">.<span class="keyword">class</span>)//自定义的均衡策略，文件在<span class="title">controller</span>的上一级</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">DeptConsumer_80_start</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(DeptConsumer_80_start<span class="class">.<span class="keyword">class</span>,<span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>访问路径，按照==消费者Controller中配置的路径==</p><h4 id="自定义负载均衡算法"><a href="#自定义负载均衡算法" class="headerlink" title="自定义负载均衡算法"></a>自定义负载均衡算法</h4><h5 id="负载均衡种类"><a href="#负载均衡种类" class="headerlink" title="负载均衡种类"></a>负载均衡种类</h5><ul><li><strong>RandomRule</strong>：随机选取负载均衡策略，随机Random对象，在所有服务实例中随机找一个服务的索引号，然后从上线的服务中获取对应的服务。</li><li><strong>RoundRobinRule</strong>：线性轮询负载均衡策略。</li><li><strong>WeightedResponseTimeRule</strong>：响应时间作为选取权重的负载均衡策略，根据平均响应时间计算所有服务的权重，响应时间越短的服务权重越大，被选中的概率越高。刚启动时，如果统计信息不足，则使用线性轮询策略，等信息足够时，再切换到WeightedResponseTimeRule。</li><li><strong>RetryRule</strong>：使用线性轮询策略获取服务，如果获取失败则在指定时间内重试，重新获取可用服务。</li><li><strong>ClientConfigEnabledRoundRobinRule</strong>：默认通过线性轮询策略选取服务。通过继承该类，并且对choose方法进行重写，可以实现更多的策略，继承后保底使用RoundRobinRule策略。</li><li><strong>BestAvailableRule</strong>：继承自ClientConfigEnabledRoundRobinRule。从所有没有断开的服务中，选取到目前为止请求数量最小的服务。</li><li><strong>PredicateBasedRule</strong>：抽象类，提供一个choose方法的模板，通过调用AbstractServerPredicate实现类的过滤方法来过滤出目标的服务，再通过轮询方法选出一个服务。</li><li><strong>AvailabilityFilteringRule</strong>：按可用性进行过滤服务的负载均衡策略，会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务，还有并发的连接数超过阈值的服务，然后对剩余的服务列表进行线性轮询。</li><li><strong>ZoneAvoidanceRule</strong>：本身没有重写choose方法，用的还是抽象父类PredicateBasedRule的choose。</li></ul><h5 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h5><p>自定义的负载均衡策略不能与主启动类位于同一级目录下，这样会被springBoot扫描到，自定义的规则必须在Eureka的规则实例化以后再实例化才会生效 </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Eureka集成了Ribbon后，客户端可直接调用方法</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaClient</span><span class="comment">//加入注解，随意写名字，confinguration的值是自定义策略的类</span></span><br><span class="line"><span class="meta">@RibbonClient</span>(name = <span class="string">"myRlue"</span>,configuration = myRule<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">DeptConsumer_80_start</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(DeptConsumer_80_start<span class="class">.<span class="keyword">class</span>,<span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="Feign负载均衡"><a href="#Feign负载均衡" class="headerlink" title="Feign负载均衡"></a>Feign负载均衡</h1><h4 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h4><p>feign是声明式的web service客户端，它让微服务之间的调用变得更简单，类似于controller调用service接口，springCloud集成了Ribbo和Eureka，可在使用Feign时提供负载均衡的http客户端</p><p>只需创建一个接口，然后添加注解即可</p><p>feign，主要是社区，都习惯面向接口编程，调用微服务访问两种方法:</p><ol><li>微服务名字【ribbon】</li><li>接口和注解【feign】</li></ol><h4 id="Feign作用"><a href="#Feign作用" class="headerlink" title="Feign作用"></a>Feign作用</h4><ul><li>Feign旨在使编写Java Http客户端变得更容易</li><li>前面在使用Ribbon + RestTemplate时， 利用RestTemplate对Http请求的封装处理，形成了-套模板化的调用方法。但是在实际开发中，由于对服务依赖的调用可能不止一处，往往一个接口会被多处调用， 所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以，Feign在此基础上做了进一步封装，由他来帮助我们定义和实现依赖服务接口的定义，==在Feign的实现下， 我们只需要创建一个接口并使用注解的方式来配置它(类似于以前Dao接口上标注Mapper注解，现在是一个微服务接口， 上面标注一个Feign注解即可。)== 即可完成对服务提供方的接口绑定，简化了使用Spring Cloud Ribbon时，自动封装服务调用客户端的开发量。</li></ul><h4 id="feign编码"><a href="#feign编码" class="headerlink" title="feign编码"></a>feign编码</h4><h5 id="1-导入依赖"><a href="#1-导入依赖" class="headerlink" title="1.导入依赖"></a>1.导入依赖</h5><p>在api 模块以及 feign(消费者)模块导入feign jar包</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- spring-cloud-starter-openfeign --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-openfeign&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><h5 id="2-api接口配置service"><a href="#2-api接口配置service" class="headerlink" title="2.api接口配置service"></a>2.api接口配置service</h5><p>在api接口新建一个service，并在类上引入@FeignClient</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@FeignClient</span>(value = <span class="string">"SPRINGCLOUD-PROVIDER-DEPT"</span>) <span class="comment">//服务实例名</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">FeignService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/dept/add"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Boolean <span class="title">add</span><span class="params">(Dept dept)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/consumer/dept/get/&#123;id&#125;"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Dept <span class="title">getDept</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/dept/list"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;Dept&gt; <span class="title">list</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="3-在消费者注册"><a href="#3-在消费者注册" class="headerlink" title="3.在消费者注册"></a>3.在消费者注册</h5><p>在消费者中注册api的service，并调用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeptConsumer_feign</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> FeignService feignService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/consumer/dept/add"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Boolean <span class="title">add</span><span class="params">(Dept dept)</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.feignService.add(dept);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/consumer/dept/get/&#123;id&#125;"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Dept <span class="title">getDept</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id)</span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.feignService.getDept(id);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/consumer/dept/list"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;Dept&gt; <span class="title">list</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">this</span>.feignService.list();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="4-在消费者启动类加注解"><a href="#4-在消费者启动类加注解" class="headerlink" title="4.在消费者启动类加注解"></a>4.在消费者启动类加注解</h5><p>在主启动类上加入@EnableFeignClients(basePackages = {“com.flf.springcloud”}),值为api接口service所在的目录</p><h1 id="Hystrix"><a href="#Hystrix" class="headerlink" title="Hystrix"></a>Hystrix</h1><blockquote><p>分布式面临的问题</p></blockquote><p>复杂分布式体系结构中应用程序有数十个依赖，每个依赖在某些时候会不可避免的失败</p><h4 id="服务雪崩"><a href="#服务雪崩" class="headerlink" title="服务雪崩"></a>服务雪崩</h4><p>​    多个微服务之间调用的时候，假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务，这就是所谓的”扇出”、如果扇出的链路上某个微服务的满用响应时间过长或者不可用，对微服务A的调用就会占用越来越多的系统资源，进而引起系统崩渍，所谓的雪崩效应。</p><p>​    对于高流量的应用来说，单一的后端依赖可能会导致所有服务器上的所有资源都在几秒中内饱和，比失败更糟糕的是，这些应用程序还可能导致服务之间的延迟增加，备份认列，线程和其他系统资源紧张，导致整个系统发生更多的级联故障，这些都表示需要对故障和延识进行隔离和管理，以便单个依赖关系的失败，不能取消整个应用程序或系统。</p><h4 id="什么是Hystrix"><a href="#什么是Hystrix" class="headerlink" title="什么是Hystrix"></a>什么是Hystrix</h4><p>​    Hystrix是一个用于处理分布式系统的延迟和容惜的开源库，在分布式系统里，许多依赖不可避免的会调用失败，比如超时，异常等，Hystrix能够保证在一个依赖出问题的情况下， 不会导致整体服务失败，避免级联故障，以提高分布式系统的弹性。</p><p>​    “断路器”本身是一种开关装置，当某个服务单元发生故障之后，通过断路器的故障监控(类似熔断保险丝)，向调用方返回一个服务预期的，可处理的箭选响应(FallBack) ，而不是长时间的等待或者抛出调用方法无法处理的异常，这样就可以保证了服务调用方的线程不会被长时间，不必要的占用，从而避免了故障在分布式系统中的蔓正，乃至雪崩</p><ul><li><h4 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h4></li><li><p>服务降级</p></li><li><p>服务熔断</p></li><li><p>服务限流</p></li><li><p>接近实时的监控</p></li></ul><h4 id="服务熔断"><a href="#服务熔断" class="headerlink" title="服务熔断"></a>服务熔断</h4><h5 id="1-导入依赖-1"><a href="#1-导入依赖-1" class="headerlink" title="1.导入依赖"></a>1.导入依赖</h5><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-netflix-hystrix&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><h5 id="2-编写备用方法"><a href="#2-编写备用方法" class="headerlink" title="2.编写备用方法"></a>2.编写备用方法</h5><h5 id="3-在方法上加入注解"><a href="#3-在方法上加入注解" class="headerlink" title="3.在方法上加入注解"></a>3.在方法上加入注解</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/dept/get/&#123;id&#125;"</span>)</span><br><span class="line">    <span class="meta">@HystrixCommand</span>(fallbackMethod = <span class="string">"hystrix"</span>)<span class="comment">//hystrix 是备用的方法名字，该方法失败后，会调用备用方法</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Dept <span class="title">queryById</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span> Long id)</span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> deptService.queryById(id);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h5 id="4-在启动类上配置"><a href="#4-在启动类上配置" class="headerlink" title="4.在启动类上配置"></a>4.在启动类上配置</h5><p>在启动类上加入 @EnableCircuitBreaker ，开启熔断机制</p><h4 id="服务降级"><a href="#服务降级" class="headerlink" title="服务降级"></a>服务降级</h4><h5 id="1-在api模块写失败返回工厂"><a href="#1-在api模块写失败返回工厂" class="headerlink" title="1.在api模块写失败返回工厂"></a>1.在api模块写失败返回工厂</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DeptClientServiceFallBack</span> <span class="keyword">implements</span> <span class="title">FallbackFactory</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> FeignService <span class="title">create</span><span class="params">(Throwable throwable)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> FeignService() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> List&lt;Dept&gt; <span class="title">list</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> Dept <span class="title">get</span><span class="params">(Long id)</span> </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> Dept().setDeptNo(id)</span><br><span class="line">                        .setDeptName(<span class="string">"已被关闭"</span>)</span><br><span class="line">                        .setDb_source(<span class="string">"not found in any datasource"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="2-api接口引入返回工厂类"><a href="#2-api接口引入返回工厂类" class="headerlink" title="2.api接口引入返回工厂类"></a>2.api接口引入返回工厂类</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//feign 默认集成了 Hystrix，value是 spring，application.name 关闭服务时，客户再次访问服务时，定义的返回</span></span><br><span class="line"><span class="meta">@FeignClient</span>(value = <span class="string">"SPRINGCLOUD-PROVIDER-DEPT"</span>,fallbackFactory = DeptClientServiceFallBack<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">interface</span> <span class="title">FeignService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/dept/list"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> List&lt;Dept&gt; <span class="title">list</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"dept/get/&#123;id&#125;"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Dept <span class="title">get</span><span class="params">(@PathVariable(<span class="string">"id"</span>)</span>Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="3-配置消费者yaml文件"><a href="#3-配置消费者yaml文件" class="headerlink" title="3.配置消费者yaml文件"></a>3.配置消费者yaml文件</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">#开启降级</span><br><span class="line">feign:</span><br><span class="line">  hystrix:</span><br><span class="line">    enabled: <span class="keyword">true</span></span><br></pre></td></tr></table></figure><blockquote><p>服务关闭后，再次访问的页面信息</p></blockquote><p><img src="/article/def94604/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210403143819172.png" alt="image-20210403143819172"></p><h4 id="Dashboard流监控"><a href="#Dashboard流监控" class="headerlink" title="Dashboard流监控"></a>Dashboard流监控</h4><h5 id="1-新建dashboard模块"><a href="#1-新建dashboard模块" class="headerlink" title="1.新建dashboard模块"></a>1.新建dashboard模块</h5><blockquote><p>导入jar包</p></blockquote><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- 新加入两个jar包 --&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">           &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">           &lt;artifactId&gt;spring-cloud-starter-netflix-hystrix&lt;/artifactId&gt;</span><br><span class="line">           &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">       &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">       &lt;!-- hystrix-dashboard --&gt;</span><br><span class="line">       &lt;dependency&gt;</span><br><span class="line">           &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">           &lt;artifactId&gt;spring-cloud-starter-netflix-hystrix-dashboard&lt;/artifactId&gt;</span><br><span class="line">           &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">       &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">       </span><br><span class="line">       &lt;dependency&gt;</span><br><span class="line">           &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">           &lt;artifactId&gt;spring-cloud-starter-netflix-ribbon&lt;/artifactId&gt;</span><br><span class="line">           &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">       &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">       &lt;dependency&gt;</span><br><span class="line">           &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">           &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-client&lt;/artifactId&gt;</span><br><span class="line">           &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">       &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">       &lt;dependency&gt;</span><br><span class="line">           &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">           &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;</span><br><span class="line">       &lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">       &lt;dependency&gt;</span><br><span class="line">           &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">           &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;</span><br><span class="line">       &lt;/dependency&gt;</span><br></pre></td></tr></table></figure><blockquote><p>配置yml</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: <span class="number">9001</span></span><br><span class="line"></span><br><span class="line">eureka:</span><br><span class="line">  client:</span><br><span class="line">    register-with-eureka: <span class="keyword">false</span></span><br><span class="line"></span><br><span class="line">#如果不配置，http://localhost:9001/hystrix/monitor 可能会出现找不到服务</span><br><span class="line">hystrix:</span><br><span class="line">  dashboard:</span><br><span class="line">    proxy-stream-allow-list: <span class="string">"*"</span></span><br></pre></td></tr></table></figure><p>然后配置主启动类</p><h5 id="2-配置提供者"><a href="#2-配置提供者" class="headerlink" title="2.配置提供者"></a>2.配置提供者</h5><p>==需要在有Hystrix熔断的提供者中配置==,即提供者的主配置类需要有<code>@EnableCircuitBreaker</code>,并且方法需要用到<code>@HystrixCommand</code></p><p>如果提供者没有熔断，访问时会出现404</p><blockquote><p>在主启动类中注册一个servlet</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ServletRegistrationBean <span class="title">hystrixMetricsStreamServlet</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">       ServletRegistrationBean servletRegistrationBean = <span class="keyword">new</span> ServletRegistrationBean(<span class="keyword">new</span> HystrixMetricsStreamServlet());</span><br><span class="line"></span><br><span class="line">       servletRegistrationBean.addUrlMappings(<span class="string">"/actuator/hystrix.stream"</span>);</span><br><span class="line"></span><br><span class="line">       <span class="keyword">return</span> servletRegistrationBean;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>在进入提供者的 <code>/actuator/hystrix.stream</code> 页面时，需先==调用一次提供者里的方法，否则会一直ping,却没有数据==</p><h5 id="3-进入监控页面"><a href="#3-进入监控页面" class="headerlink" title="3.进入监控页面"></a>3.进入监控页面</h5><p>进入<code>http://localhost:9001/hystrix</code>，并在stream中输入提供者的ip</p><h1 id="Zuul"><a href="#Zuul" class="headerlink" title="Zuul"></a>Zuul</h1><h4 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h4><p>Zuul包含了对请求的==路由==和==过滤==两个最主要的功能:</p><p>其中路由功能负责将外部请求转发到具体的微服务实例上，是实现外部访问统一入口的基础， 而过滤器功能则负责对请求的处理过程进行干预，是实现请求校验，服务聚合等功能的基础。Zuul和Eureka进行整合， 将Zuul自身注册为Eureka服务治理下的应用，同时从Eureka中获得其他微服务的消息，也即以后的访问微服务都是通过Zuul跳转后获得。</p><p>注意: Zuul服务最终还是会注册进Eureka提供:代理+路由+过滤三大功能</p><h4 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h4><h5 id="1-添加依赖"><a href="#1-添加依赖" class="headerlink" title="1.添加依赖"></a>1.添加依赖</h5><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-netflix-zuul&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;2.2.7.RELEASE&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure><h5 id="2-配置yml"><a href="#2-配置yml" class="headerlink" title="2.配置yml"></a>2.配置yml</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">eureka:</span><br><span class="line">  client:</span><br><span class="line">    service-url:</span><br><span class="line">      defaultZone: http:<span class="comment">//localhost:7001/eureka,http://localhost:7002/eureka,http://localhost:7003/eureka</span></span><br><span class="line">  instance:</span><br><span class="line">    instance-id: zuul</span><br><span class="line"></span><br><span class="line">spring:</span><br><span class="line">  application:</span><br><span class="line">    name: zuul-<span class="number">9527</span></span><br><span class="line">zuul:</span><br><span class="line">  routes:</span><br><span class="line">    serviceId: springcloud-provider-dept</span><br><span class="line">    path: /Mydept<span class="comment">/**</span></span><br><span class="line"><span class="comment">  ignored-services: springcloud-provider-dept#不允许访问的路径</span></span><br></pre></td></tr></table></figure><h5 id="3-主启动类"><a href="#3-主启动类" class="headerlink" title="3.主启动类"></a>3.主启动类</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableEurekaClient</span></span><br><span class="line"><span class="meta">@EnableZuulProxy</span> <span class="comment">//网关</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Zuul_9527</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(Zuul_9527<span class="class">.<span class="keyword">class</span>,<span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;SpringCloud学习笔记&lt;/p&gt;
    
    </summary>
    
    
    
      <category term="SpringCloud" scheme="https://www.tengfei.eu.org/tags/SpringCloud/"/>
    
  </entry>
  
  <entry>
    <title>Redis学习笔记</title>
    <link href="https://www.tengfei.eu.org/article/8eda3648.html"/>
    <id>https://www.tengfei.eu.org/article/8eda3648.html</id>
    <published>2023-01-15T10:07:51.000Z</published>
    <updated>2023-10-16T04:52:45.830Z</updated>
    
    <content type="html"><![CDATA[<p> Redis学习笔记</p><a id="more"></a><h1 id="redis数据类型"><a href="#redis数据类型" class="headerlink" title="redis数据类型"></a>redis数据类型</h1><h3 id="五种基本类型"><a href="#五种基本类型" class="headerlink" title="五种基本类型"></a>五种基本类型</h3><h4 id="string"><a href="#string" class="headerlink" title="string"></a>string</h4><ul><li>keys * 查看所有 的key</li><li>select + 数字 选择 数据库(redis有16个数据库)</li><li>flushall 删除所有数据</li><li>exists + key 判断某个key 是否存在</li><li>expire + key + time 设置某个key的过期时间 单位是 秒</li><li>setex + key + time + value 设置一个key的过期时间和值setex(expire )</li><li>ttl + key 查看key的过期时间还剩多少 </li><li>del + key 删除 key</li><li>type + key 查看 key的数据类型</li><li>move + key + 数字 把当前库的key转移的 下表为 数字 的库中</li><li>append + key + value 在key后追加 value，如果 key不存在，就添加</li><li>strlen + key 获取key下value的长度</li><li>incr/decr + key  key的value +/-1</li><li>incrby/decrby + key + 数字 key的value+/- 数字长度</li><li>getrange + key + startIndex + endIndex 查看下标为startIndex到end的key的值(查看所有的话为 0  -1)</li><li>setrange + key + index +value 将kye下标为index的值替换成value,如果value有两个，则替换两位</li><li>setnx + key + value 如果key不存在，key值为value，如果存在，则创建失败setnx(set if Not eXists)</li><li>mset + [key,value] 一次性赋多个值 如 mset k1 v1 k2 v2</li><li>mget + key1 +.. 一次性获取多个值 如 mget  k1 k2 ..</li><li>msetnx 原子性操作，设置多个值时，如果有一个值存在，则其他的也不会被设置成功</li><li>getset + key + value 获取值然后设置值，如果不存在，则返回空并设置值,如果存在，则返回值并设置新的值</li></ul><h4 id="list"><a href="#list" class="headerlink" title="list"></a>list</h4><ul><li>lpush + list+ element  将元素放入list中 先进后出</li><li>lrange +list+sindex+eindex 取出list中下表为 sindex到eindex中的值</li><li>rpush 从右边(尾部)插入</li><li>lpop + list  将list左边第一个元素移除 rpop移除右边第一个</li><li>lindex +list+ index 通过下标获取</li><li>llen + list返回数组的长度</li><li>lrem + count + list 移除list中指定个数的list值 如 lrem 2 one 移除2个one（如果count&gt;0，从表头开始删除，如果count=0，删除所有，如果count&lt;0，从尾部开始删）</li><li>ltrim + list+  sIndex + eIndex 获取list中 下表为 sindex到eindex中的元素(删除不在区间的其他值)</li><li>rpoplpush + 原来数组 + 新数组 将原来数组的最后一个元素移到新数组去，返回的值是原来数组的最后一个元素</li><li>lset + list +index+ value 将list中指定下标的值替换成value(如果list不存在会报错)</li><li>linsert + list +before/after + 指定值 + value 在list指定值的前/后插入value，如果指定值有多个，则会添加在第一个指定值的前或者后</li></ul><blockquote><p>应用场景</p></blockquote><ol><li>最新消息排行等功能(比如朋友圈的时间线)</li><li>消息队列</li></ol><h4 id="set"><a href="#set" class="headerlink" title="set"></a>set</h4><ul><li>sadd + set + v1  v2  v3 向set中添加值（可一次性添加多个）</li><li>smembers + set 获取set中的所有值（无序不可重复）</li><li>sismember +set + value 判断set中是否包含指定的value值 </li><li>scard + set 获取set中的元素个数</li><li>srem + set + key 删除set中的指定key，可一次性删除多个</li><li>srandmember +set (+ count) 在set中随机挑选一个(count 个)元素</li><li>spop +set (+count) 从set中随机弹出(删除)一个(count个)元素</li><li>smove +原来set + 新set + key 将指定的元素剪贴到新的set里</li><li>sdiff + set1 + set2 两个集合的差集(相比set1来说，如set1 有 abc，set2有bcd，返回结果为ab)</li><li>sinter +set1+set2 两个集合的交集</li><li>sunion + set1 +set2 两个集合的并集 </li></ul><blockquote><p>应用场景</p></blockquote><ol><li>共同好友 </li><li>利用唯一性,统计访问网站的所有独立ip </li><li>好友推荐时,根据tag求交集,大于某个阈值就可以推荐</li></ol><h4 id="hash"><a href="#hash" class="headerlink" title="hash"></a>hash</h4><p>与string类似，hash更适合对象的存储</p><ul><li>hset + hash + field + value 在一个hash中存入一个key-value</li><li>hget  + hast + field 获取hash中某个field的值</li><li>hset + hash+ field + value 在hash中一次性插入多个field 和值 如 hset hash1 f1 v1 f2 v2(如果field存在，则会覆盖之前的值)</li><li>mgetall + hash 获取hash中的所有键值对</li><li>mdel + hash + field1 + field2.. 从hash中删除一个或多个key-value</li><li>hlen + hash 查看hash中有个几个键值对(长度) </li><li>hexists + hash + field 判断hash中的filed是否存在</li><li>hkeys + hash 获取hash中所有的key</li><li>hvals + hash 获取hash中所有的value</li></ul><h4 id="zset"><a href="#zset" class="headerlink" title="zset"></a>zset</h4><ul><li>zadd + set + number +  key + number + key 往set中添加值(需要加序号，可以一次添加多个)</li><li>zrangebyscore + set + 范围(如 -inf  +inf,负无穷到正无穷) 给一个set按照序号从小到大排序(只有key没有值)</li><li>zrangebyscore  set  -inf +inf withscores 从小到大排序(显示key和value) </li><li>zrevrangebyscore + set + +inf + -inf 从大到小给set排序(ps：范围需要从大到小,与正序相反 如：正无穷~负无穷)</li><li>zrevrang + set + 0 + -1 给set反向排序</li><li>zrem + set + key 移除set中key值</li><li>zcard + set 显示set的长度(有几个值)</li><li>zcount + set + num1 + num2 获取指定区间值的个数</li></ul><blockquote><p>应用场景</p></blockquote><ol><li>排行榜 </li><li>带权重的消息队列</li></ol><h3 id="三大特殊类型"><a href="#三大特殊类型" class="headerlink" title="三大特殊类型"></a>三大特殊类型</h3><h4 id="geospatial"><a href="#geospatial" class="headerlink" title="geospatial"></a>geospatial</h4><p>地理位置, 底层是zset，所以可以用zset操作geo</p><ul><li>geoadd + key +经度 + 维度 + 城市名 (可一次性添加多个) egg: geoadd china:city 116.40 39.90 beijing</li><li>geopos + key + city 获取城市经纬度 egg: geopos china:city beijing</li><li>geodist + key + city1 + city2  + 单位  两地之间的直线距离</li><li>georadius + key + 经度 + 维度 + 数值 + 单位  以经纬度为中心数值半径内的城市 egg:georadius china:city 110 30 100 km </li><li>georadius + key + 经度 + 维度 + 数值 + 单位 withcoord  withdist withhash count 2     withcoord:带距离  withcoord:带经纬度   count:限制返回值的个数  egg:georadius china:city 110 30 10000 km withcoord  withdist withhash count 2</li><li>georadiusbymember +key + 城市 + 数值 + 单位   查询以城市(对象)为中心,数值半径内的对象 egg：georadiusbymember china:city beijing 10000 km</li><li>geohash + key + 城市 返回 11位字符的GeoHash字符串，将二维的经纬度转换为字符串 egg:geohash china:city beijing</li><li>zrange china:city 0 -1 查询所有城市(zset的用法)</li><li>zrem china:city beijing 删除北京(zset的用法)</li></ul><h4 id="Hyperloglog"><a href="#Hyperloglog" class="headerlink" title="Hyperloglog"></a>Hyperloglog</h4><p>统计基数</p><ul><li>pfadd + set + 元素1 + 元素2 + .. 往数据组添加元素，可一次性添加多个(不重复)</li><li>pfcount + set 返回数据组里的元素</li><li>pfmerge + newSet + set1 + set2 将set1和set2数据组合并成newSet</li></ul><h4 id="BitMaps"><a href="#BitMaps" class="headerlink" title="BitMaps"></a>BitMaps</h4><ul><li>setbit  + key + 位 +0/1</li><li>getbit + key + 位 返回key中该位的值 0/1</li><li>bitcount + key +(start + end)key中的值为1的个数</li></ul><h3 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h3><ul><li><p>Redis事务的本质:一组命令的集合。一个事务中的所有命令都会被序列化，在事务执行过程中，会按照顺序执行</p></li><li><p>==Redies事务没有隔离级别的概念==</p></li><li><p>==Redies单挑命令保证原子性，但是Redis事务不保证原子性==</p></li></ul><h4 id="Redies事务"><a href="#Redies事务" class="headerlink" title="Redies事务"></a>Redies事务</h4><ul><li>开启事务(multi)</li><li>命令入队(…)</li><li>终止事务(discard,事务终止后，入队的命令也不会被执行)</li><li>执行事务(exec)</li></ul><blockquote><p>事务执行</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; multi<span class="comment">#开启事务</span></span><br><span class="line">OK</span><br><span class="line"><span class="comment">#命令入队</span></span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> k1 v1</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> k2 v2</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; get k2</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> k3 v3</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">exec</span><span class="comment">#执行事务</span></span><br><span class="line">1) OK</span><br><span class="line">2) OK</span><br><span class="line">3) <span class="string">"v2"</span></span><br><span class="line">4) OK</span><br></pre></td></tr></table></figure><h4 id="问题类型"><a href="#问题类型" class="headerlink" title="问题类型"></a>问题类型</h4><blockquote><p>编译型错误(代码/命令有问题)，事务中所有命令都不会执行</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; multi<span class="comment">#开启事务</span></span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> k1 v1</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; getset k2<span class="comment">#错误的命令</span></span><br><span class="line">(error) ERR wrong number of arguments <span class="keyword">for</span> <span class="string">'getset'</span> <span class="built_in">command</span></span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> k3 v3</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">exec</span><span class="comment">#执行命令报错</span></span><br><span class="line">(error) EXECABORT Transaction discarded because of previous errors.</span><br><span class="line">127.0.0.1:6379&gt; get k2  <span class="comment">#所有的命令都不会被执行</span></span><br><span class="line">(nil)</span><br></pre></td></tr></table></figure><blockquote><p>运行时异常(如 1/0)，其他正常命令可以执行，错误的命令抛出异常</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> k1 <span class="string">"v1"</span></span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; multi</span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; incr k1</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> k2 v2</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; get k2</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">exec</span><span class="comment">#错误命令抛出异常，其他正常执行</span></span><br><span class="line">1) (error) ERR value is not an <span class="built_in">integer</span> or out of range</span><br><span class="line">2) OK</span><br><span class="line">3) <span class="string">"v2"</span></span><br></pre></td></tr></table></figure><blockquote><p>Redies监视器测试(可充当乐观锁)</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> money 100   <span class="comment">#初始</span></span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> out 0<span class="comment">#支出</span></span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; watch money<span class="comment">#监视(开启事务后，另一个线程先修改了值，事务会执行失败，watch可以充当Redis的的乐观锁)</span></span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; multi</span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; decrby money 20</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; incrby out 20</span><br><span class="line">QUEUED</span><br><span class="line">127.0.0.1:6379&gt; <span class="built_in">exec</span></span><br><span class="line">1) (<span class="built_in">integer</span>) 80</span><br><span class="line">2) (<span class="built_in">integer</span>) 20</span><br></pre></td></tr></table></figure><h1 id="Jedis"><a href="#Jedis" class="headerlink" title="Jedis"></a>Jedis</h1><h4 id="1-导入对应的依赖"><a href="#1-导入对应的依赖" class="headerlink" title="1.导入对应的依赖"></a>1.导入对应的依赖</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span>     </span><br><span class="line">    <span class="comment">&lt;!--导入jedis的包--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>redis.clients<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jedis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.5.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2-编码测试"><a href="#2-编码测试" class="headerlink" title="2.编码测试"></a>2.编码测试</h4><h5 id="连接数据库"><a href="#连接数据库" class="headerlink" title="连接数据库"></a>连接数据库</h5><blockquote><p>前提</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">连接外网首先有在redis.conf改几个设置</span><br><span class="line">1.daemonize yes</span><br><span class="line">2.注释 <span class="built_in">bind</span> 127.0.0.1</span><br><span class="line">3.protected-mode no</span><br><span class="line">其次看一下自己防火墙对端口号开没开放</span><br><span class="line">firewall-cmd --query-port=6379/tcp</span><br><span class="line">如果是yes就是开放的,如果没有开放,则需要手动添加</span><br><span class="line">firewall-cmd --zone=public --add-port=6379/tcp --permanent</span><br><span class="line">然后重启防火墙</span><br><span class="line">firewall-cmd --reload</span><br><span class="line">然后 redis-server fconfig/redis.conf</span><br><span class="line">redis-cli -h 自己外网端口号 -p 6379</span><br><span class="line">最后ping一下如果pong就成功了​</span><br></pre></td></tr></table></figure><blockquote><p>连接</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">       Jedis jedis = <span class="keyword">new</span> Jedis(<span class="string">"114.55.107.193"</span>,<span class="number">6379</span>);</span><br><span class="line"></span><br><span class="line">       System.out.println(jedis.ping());  #返回的结果是 pong 就代表连接成功了</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure><p>操作命令</p><blockquote><p>java操作事务</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        Jedis jedis = <span class="keyword">new</span> Jedis(<span class="string">"114.55.107.193"</span>, <span class="number">6379</span>);</span><br><span class="line"></span><br><span class="line">        jedis.flushDB();</span><br><span class="line">        <span class="comment">//开启事务</span></span><br><span class="line">        Transaction multi = jedis.multi();</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            multi.lpush(<span class="string">"list1"</span>,<span class="string">"ele1"</span>,<span class="string">"ele2"</span>);</span><br><span class="line">            multi.lpush(<span class="string">"list1"</span>,<span class="string">"ele3"</span>);</span><br><span class="line">            multi.lpop(<span class="string">"list1"</span>);</span><br><span class="line">           <span class="comment">//执行事务</span></span><br><span class="line">            multi.exec();</span><br><span class="line">        &#125;<span class="keyword">catch</span> (Exception e)&#123;</span><br><span class="line">            <span class="comment">//如果报错，终止事务</span></span><br><span class="line">            multi.discard();</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;<span class="keyword">finally</span> &#123;</span><br><span class="line">            System.out.println(jedis.lrange(<span class="string">"list1"</span>, <span class="number">0</span>, -<span class="number">1</span>));</span><br><span class="line">            <span class="comment">//关闭连接</span></span><br><span class="line">            jedis.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>断开连接</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jedis.close();</span><br></pre></td></tr></table></figure><h1 id="SpringBoot整合"><a href="#SpringBoot整合" class="headerlink" title="SpringBoot整合"></a>SpringBoot整合</h1><h5 id="下载jar包"><a href="#下载jar包" class="headerlink" title="下载jar包"></a>下载jar包</h5><h5 id="配置application文件"><a href="#配置application文件" class="headerlink" title="配置application文件"></a>配置application文件</h5><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">spring:</span><br><span class="line">  redis:</span><br><span class="line">    port: 6379     #端口</span><br><span class="line">    host: 114.55.107.193#ip</span><br></pre></td></tr></table></figure><h5 id="连接测试"><a href="#连接测试" class="headerlink" title="连接测试"></a>连接测试</h5><p>实际应用中，一般用 ObjectMapper 将对象序列化然后存入</p><p><img src="/article/8eda3648/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210220212253669.png" alt="image-20210220212253669"></p><p>如果 pojo（entity）实体类没有 序列化（implements Serializable），直接将对象存入，则会报错</p><p><img src="/article/8eda3648/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210220212605299.png" alt="image-20210220212605299"></p><h5 id="自定义redisTemplate"><a href="#自定义redisTemplate" class="headerlink" title="自定义redisTemplate"></a>自定义redisTemplate</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//自定义redisTemplate</span></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> RedisTemplate&lt;String,Object&gt; <span class="title">redisTemplate</span><span class="params">(RedisConnectionFactory redisConnectionFactory)</span></span></span><br><span class="line"><span class="function">            <span class="keyword">throws</span> UnknownHostException </span>&#123;</span><br><span class="line"></span><br><span class="line">        RedisTemplate&lt;String,Object&gt; template = <span class="keyword">new</span> RedisTemplate&lt;&gt;();</span><br><span class="line">        template.setConnectionFactory(redisConnectionFactory);</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">        * 配置具体的 序列化方法(只需要new一个序列化的方法对象，然后放入setDefaultSerializer中)</span></span><br><span class="line"><span class="comment">        * 序列化配置</span></span><br><span class="line"><span class="comment">        * */</span></span><br><span class="line">        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = <span class="keyword">new</span> Jackson2JsonRedisSerializer(Object<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">        template.setDefaultSerializer(jackson2JsonRedisSerializer);</span><br><span class="line">        ObjectMapper om = <span class="keyword">new</span> ObjectMapper();</span><br><span class="line">        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);</span><br><span class="line">        <span class="comment">//om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);</span></span><br><span class="line">        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL)</span><br><span class="line">        jackson2JsonRedisSerializer.setObjectMapper(om);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//String序列化</span></span><br><span class="line">        StringRedisSerializer stringRedisSerializer = <span class="keyword">new</span> StringRedisSerializer();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//key采用String的序列化方式</span></span><br><span class="line">        template.setKeySerializer(stringRedisSerializer);</span><br><span class="line">        <span class="comment">//hash的key也采用String的序列化方式</span></span><br><span class="line">        template.setHashKeySerializer(stringRedisSerializer);</span><br><span class="line">        <span class="comment">//value采用Jackson</span></span><br><span class="line">        template.setValueSerializer(jackson2JsonRedisSerializer);</span><br><span class="line">        <span class="comment">//hash的value序列化方式采用Jackson</span></span><br><span class="line">        template.setHashValueSerializer(jackson2JsonRedisSerializer);</span><br><span class="line">        template.afterPropertiesSet();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> template;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在使用自定义的redisTemplate时，需指定</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">@Autowired</span><br><span class="line">@Qualifier(&quot;redisTemplate&quot;)</span><br><span class="line">private RedisTemplate redisTemplate;</span><br></pre></td></tr></table></figure><h5 id="自定义-redis-util工具类"><a href="#自定义-redis-util工具类" class="headerlink" title="自定义 redis util工具类"></a>自定义 redis util工具类</h5><p>类似 jdbc的 Helper，方便操作，不用在使用 redisTemplate.ops 方法操作</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br><span class="line">479</span><br><span class="line">480</span><br><span class="line">481</span><br><span class="line">482</span><br><span class="line">483</span><br><span class="line">484</span><br><span class="line">485</span><br><span class="line">486</span><br><span class="line">487</span><br><span class="line">488</span><br><span class="line">489</span><br><span class="line">490</span><br><span class="line">491</span><br><span class="line">492</span><br><span class="line">493</span><br><span class="line">494</span><br><span class="line">495</span><br><span class="line">496</span><br><span class="line">497</span><br><span class="line">498</span><br><span class="line">499</span><br><span class="line">500</span><br><span class="line">501</span><br><span class="line">502</span><br><span class="line">503</span><br><span class="line">504</span><br><span class="line">505</span><br><span class="line">506</span><br><span class="line">507</span><br><span class="line">508</span><br><span class="line">509</span><br><span class="line">510</span><br><span class="line">511</span><br><span class="line">512</span><br><span class="line">513</span><br><span class="line">514</span><br><span class="line">515</span><br><span class="line">516</span><br></pre></td><td class="code"><pre><span class="line">public class RedisUtils &#123;  </span><br><span class="line">  </span><br><span class="line">  </span><br><span class="line">    private RedisTemplate&lt;String, Object&gt; redisTemplate;  </span><br><span class="line">      </span><br><span class="line">    public void setRedisTemplate(RedisTemplate&lt;String, Object&gt; redisTemplate) &#123;  </span><br><span class="line">        this.redisTemplate = redisTemplate;  </span><br><span class="line">    &#125;  </span><br><span class="line">    //=============================common============================  </span><br><span class="line">    /** </span><br><span class="line">     * 指定缓存失效时间 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param time 时间(秒) </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public boolean expire(String key,long time)&#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            <span class="keyword">if</span>(time&gt;0)&#123;  </span><br><span class="line">                redisTemplate.expire(key, time, TimeUnit.SECONDS);  </span><br><span class="line">            &#125;  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 根据key 获取过期时间 </span><br><span class="line">     * @param key 键 不能为null </span><br><span class="line">     * @<span class="built_in">return</span> 时间(秒) 返回0代表为永久有效 </span><br><span class="line">     */  </span><br><span class="line">    public long getExpire(String key)&#123;  </span><br><span class="line">        <span class="built_in">return</span> redisTemplate.getExpire(key,TimeUnit.SECONDS);  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 判断key是否存在 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @<span class="built_in">return</span> <span class="literal">true</span> 存在 <span class="literal">false</span>不存在 </span><br><span class="line">     */  </span><br><span class="line">    public boolean hasKey(String key)&#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            <span class="built_in">return</span> redisTemplate.hasKey(key);  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 删除缓存 </span><br><span class="line">     * @param key 可以传一个值 或多个 </span><br><span class="line">     */  </span><br><span class="line">    @SuppressWarnings(<span class="string">"unchecked"</span>)  </span><br><span class="line">    public void del(String ... key)&#123;  </span><br><span class="line">        <span class="keyword">if</span>(key!=null&amp;&amp;key.length&gt;0)&#123;  </span><br><span class="line">            <span class="keyword">if</span>(key.length==1)&#123;  </span><br><span class="line">                redisTemplate.delete(key[0]);  </span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;  </span><br><span class="line">                redisTemplate.delete(CollectionUtils.arrayToList(key));  </span><br><span class="line">            &#125;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    //============================String=============================  </span><br><span class="line">    /** </span><br><span class="line">     * 普通缓存获取 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @<span class="built_in">return</span> 值 </span><br><span class="line">     */  </span><br><span class="line">    public Object get(String key)&#123;  </span><br><span class="line">        <span class="built_in">return</span> key==null?null:redisTemplate.opsForValue().get(key);  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 普通缓存放入 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @<span class="built_in">return</span> <span class="literal">true</span>成功 <span class="literal">false</span>失败 </span><br><span class="line">     */  </span><br><span class="line">    public boolean <span class="built_in">set</span>(String key,Object value) &#123;  </span><br><span class="line">         try &#123;  </span><br><span class="line">            redisTemplate.opsForValue().<span class="built_in">set</span>(key, value);  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">          </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 普通缓存放入并设置时间 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 </span><br><span class="line">     * @<span class="built_in">return</span> <span class="literal">true</span>成功 <span class="literal">false</span> 失败 </span><br><span class="line">     */  </span><br><span class="line">    public boolean <span class="built_in">set</span>(String key,Object value,long time)&#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            <span class="keyword">if</span>(time&gt;0)&#123;  </span><br><span class="line">                redisTemplate.opsForValue().<span class="built_in">set</span>(key, value, time, TimeUnit.SECONDS);  </span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;  </span><br><span class="line">                <span class="built_in">set</span>(key, value);  </span><br><span class="line">            &#125;  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 递增 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param by 要增加几(大于0) </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public long incr(String key, long delta)&#123;    </span><br><span class="line">        <span class="keyword">if</span>(delta&lt;0)&#123;  </span><br><span class="line">            throw new RuntimeException(<span class="string">"递增因子必须大于0"</span>);  </span><br><span class="line">        &#125;  </span><br><span class="line">        <span class="built_in">return</span> redisTemplate.opsForValue().increment(key, delta);  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 递减 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param by 要减少几(小于0) </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public long decr(String key, long delta)&#123;    </span><br><span class="line">        <span class="keyword">if</span>(delta&lt;0)&#123;  </span><br><span class="line">            throw new RuntimeException(<span class="string">"递减因子必须大于0"</span>);  </span><br><span class="line">        &#125;  </span><br><span class="line">        <span class="built_in">return</span> redisTemplate.opsForValue().increment(key, -delta);    </span><br><span class="line">    &#125;    </span><br><span class="line">      </span><br><span class="line">    //================================Map=================================  </span><br><span class="line">    /** </span><br><span class="line">     * HashGet </span><br><span class="line">     * @param key 键 不能为null </span><br><span class="line">     * @param item 项 不能为null </span><br><span class="line">     * @<span class="built_in">return</span> 值 </span><br><span class="line">     */  </span><br><span class="line">    public Object hget(String key,String item)&#123;  </span><br><span class="line">        <span class="built_in">return</span> redisTemplate.opsForHash().get(key, item);  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 获取hashKey对应的所有键值 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @<span class="built_in">return</span> 对应的多个键值 </span><br><span class="line">     */  </span><br><span class="line">    public Map&lt;Object,Object&gt; hmget(String key)&#123;  </span><br><span class="line">        <span class="built_in">return</span> redisTemplate.opsForHash().entries(key);  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * HashSet </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param map 对应多个键值 </span><br><span class="line">     * @<span class="built_in">return</span> <span class="literal">true</span> 成功 <span class="literal">false</span> 失败 </span><br><span class="line">     */  </span><br><span class="line">    public boolean hmset(String key, Map&lt;String,Object&gt; map)&#123;    </span><br><span class="line">        try &#123;  </span><br><span class="line">            redisTemplate.opsForHash().putAll(key, map);  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * HashSet 并设置时间 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param map 对应多个键值 </span><br><span class="line">     * @param time 时间(秒) </span><br><span class="line">     * @<span class="built_in">return</span> <span class="literal">true</span>成功 <span class="literal">false</span>失败 </span><br><span class="line">     */  </span><br><span class="line">    public boolean hmset(String key, Map&lt;String,Object&gt; map, long time)&#123;    </span><br><span class="line">        try &#123;  </span><br><span class="line">            redisTemplate.opsForHash().putAll(key, map);  </span><br><span class="line">            <span class="keyword">if</span>(time&gt;0)&#123;  </span><br><span class="line">                expire(key, time);  </span><br><span class="line">            &#125;  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 向一张<span class="built_in">hash</span>表中放入数据,如果不存在将创建 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param item 项 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @<span class="built_in">return</span> <span class="literal">true</span> 成功 <span class="literal">false</span>失败 </span><br><span class="line">     */  </span><br><span class="line">    public boolean hset(String key,String item,Object value) &#123;  </span><br><span class="line">         try &#123;  </span><br><span class="line">            redisTemplate.opsForHash().put(key, item, value);  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 向一张<span class="built_in">hash</span>表中放入数据,如果不存在将创建 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param item 项 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @param time 时间(秒)  注意:如果已存在的<span class="built_in">hash</span>表有时间,这里将会替换原有的时间 </span><br><span class="line">     * @<span class="built_in">return</span> <span class="literal">true</span> 成功 <span class="literal">false</span>失败 </span><br><span class="line">     */  </span><br><span class="line">    public boolean hset(String key,String item,Object value,long time) &#123;  </span><br><span class="line">         try &#123;  </span><br><span class="line">            redisTemplate.opsForHash().put(key, item, value);  </span><br><span class="line">            <span class="keyword">if</span>(time&gt;0)&#123;  </span><br><span class="line">                expire(key, time);  </span><br><span class="line">            &#125;  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 删除<span class="built_in">hash</span>表中的值 </span><br><span class="line">     * @param key 键 不能为null </span><br><span class="line">     * @param item 项 可以使多个 不能为null </span><br><span class="line">     */  </span><br><span class="line">    public void hdel(String key, Object... item)&#123;    </span><br><span class="line">        redisTemplate.opsForHash().delete(key,item);  </span><br><span class="line">    &#125;   </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 判断<span class="built_in">hash</span>表中是否有该项的值 </span><br><span class="line">     * @param key 键 不能为null </span><br><span class="line">     * @param item 项 不能为null </span><br><span class="line">     * @<span class="built_in">return</span> <span class="literal">true</span> 存在 <span class="literal">false</span>不存在 </span><br><span class="line">     */  </span><br><span class="line">    public boolean hHasKey(String key, String item)&#123;  </span><br><span class="line">        <span class="built_in">return</span> redisTemplate.opsForHash().hasKey(key, item);  </span><br><span class="line">    &#125;   </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * <span class="built_in">hash</span>递增 如果不存在,就会创建一个 并把新增后的值返回 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param item 项 </span><br><span class="line">     * @param by 要增加几(大于0) </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public double hincr(String key, String item,double by)&#123;    </span><br><span class="line">        <span class="built_in">return</span> redisTemplate.opsForHash().increment(key, item, by);  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * <span class="built_in">hash</span>递减 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param item 项 </span><br><span class="line">     * @param by 要减少记(小于0) </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public double hdecr(String key, String item,double by)&#123;    </span><br><span class="line">        <span class="built_in">return</span> redisTemplate.opsForHash().increment(key, item,-by);    </span><br><span class="line">    &#125;    </span><br><span class="line">      </span><br><span class="line">    //============================<span class="built_in">set</span>=============================  </span><br><span class="line">    /** </span><br><span class="line">     * 根据key获取Set中的所有值 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public Set&lt;Object&gt; sGet(String key)&#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            <span class="built_in">return</span> redisTemplate.opsForSet().members(key);  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> null;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 根据value从一个<span class="built_in">set</span>中查询,是否存在 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @<span class="built_in">return</span> <span class="literal">true</span> 存在 <span class="literal">false</span>不存在 </span><br><span class="line">     */  </span><br><span class="line">    public boolean sHasKey(String key,Object value)&#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            <span class="built_in">return</span> redisTemplate.opsForSet().isMember(key, value);  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 将数据放入<span class="built_in">set</span>缓存 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param values 值 可以是多个 </span><br><span class="line">     * @<span class="built_in">return</span> 成功个数 </span><br><span class="line">     */  </span><br><span class="line">    public long sSet(String key, Object...values) &#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            <span class="built_in">return</span> redisTemplate.opsForSet().add(key, values);  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> 0;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 将<span class="built_in">set</span>数据放入缓存 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param time 时间(秒) </span><br><span class="line">     * @param values 值 可以是多个 </span><br><span class="line">     * @<span class="built_in">return</span> 成功个数 </span><br><span class="line">     */  </span><br><span class="line">    public long sSetAndTime(String key,long time,Object...values) &#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            Long count = redisTemplate.opsForSet().add(key, values);  </span><br><span class="line">            <span class="keyword">if</span>(time&gt;0) expire(key, time);  </span><br><span class="line">            <span class="built_in">return</span> count;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> 0;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 获取<span class="built_in">set</span>缓存的长度 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public long sGetSetSize(String key)&#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            <span class="built_in">return</span> redisTemplate.opsForSet().size(key);  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> 0;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 移除值为value的 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param values 值 可以是多个 </span><br><span class="line">     * @<span class="built_in">return</span> 移除的个数 </span><br><span class="line">     */  </span><br><span class="line">    public long setRemove(String key, Object ...values) &#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            Long count = redisTemplate.opsForSet().remove(key, values);  </span><br><span class="line">            <span class="built_in">return</span> count;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> 0;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">    //===============================list=================================  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 获取list缓存的内容 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param start 开始 </span><br><span class="line">     * @param end 结束  0 到 -1代表所有值 </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public List&lt;Object&gt; lGet(String key,long start, long end)&#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            <span class="built_in">return</span> redisTemplate.opsForList().range(key, start, end);  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> null;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 获取list缓存的长度 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public long lGetListSize(String key)&#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            <span class="built_in">return</span> redisTemplate.opsForList().size(key);  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> 0;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 通过索引 获取list中的值 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param index 索引  index&gt;=0时， 0 表头，1 第二个元素，依次类推；index&lt;0时，-1，表尾，-2倒数第二个元素，依次类推 </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public Object lGetIndex(String key,long index)&#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            <span class="built_in">return</span> redisTemplate.opsForList().index(key, index);  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> null;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 将list放入缓存 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @param time 时间(秒) </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public boolean lSet(String key, Object value) &#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            redisTemplate.opsForList().rightPush(key, value);  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 将list放入缓存 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @param time 时间(秒) </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public boolean lSet(String key, Object value, long time) &#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            redisTemplate.opsForList().rightPush(key, value);  </span><br><span class="line">            <span class="keyword">if</span> (time &gt; 0) expire(key, time);  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 将list放入缓存 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @param time 时间(秒) </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public boolean lSet(String key, List&lt;Object&gt; value) &#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            redisTemplate.opsForList().rightPushAll(key, value);  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 将list放入缓存 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @param time 时间(秒) </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public boolean lSet(String key, List&lt;Object&gt; value, long time) &#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            redisTemplate.opsForList().rightPushAll(key, value);  </span><br><span class="line">            <span class="keyword">if</span> (time &gt; 0) expire(key, time);  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 根据索引修改list中的某条数据 </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param index 索引 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @<span class="built_in">return</span> </span><br><span class="line">     */  </span><br><span class="line">    public boolean lUpdateIndex(String key, long index,Object value) &#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            redisTemplate.opsForList().<span class="built_in">set</span>(key, index, value);  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">true</span>;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> <span class="literal">false</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;   </span><br><span class="line">      </span><br><span class="line">    /** </span><br><span class="line">     * 移除N个值为value  </span><br><span class="line">     * @param key 键 </span><br><span class="line">     * @param count 移除多少个 </span><br><span class="line">     * @param value 值 </span><br><span class="line">     * @<span class="built_in">return</span> 移除的个数 </span><br><span class="line">     */  </span><br><span class="line">    public long lRemove(String key,long count,Object value) &#123;  </span><br><span class="line">        try &#123;  </span><br><span class="line">            Long remove = redisTemplate.opsForList().remove(key, count, value);  </span><br><span class="line">            <span class="built_in">return</span> remove;  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">            <span class="built_in">return</span> 0;  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h1 id="redis-conf详解"><a href="#redis-conf详解" class="headerlink" title="redis.conf详解"></a>redis.conf详解</h1><h5 id="单位"><a href="#单位" class="headerlink" title="单位"></a>单位</h5><p>配置文件units单位对大小写不敏感</p><p><img src="/article/8eda3648/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210221162820452.png" alt="image-20210221162820452"></p><h5 id="包含"><a href="#包含" class="headerlink" title="包含"></a>包含</h5><p>类似于 import,include</p><p><img src="/article/8eda3648/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210221163003584.png" alt="image-20210221163003584"></p><h5 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">bind</span> 127.0.0.1    <span class="comment">#绑定的ip</span></span><br><span class="line">protected-mode no <span class="comment">#保护模式</span></span><br><span class="line">port 6379   <span class="comment">#端口号</span></span><br></pre></td></tr></table></figure><p>GENERAL 通用</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">daemonize yes   <span class="comment">#以守护线程的方式执行，默认是 no，需要手动更改为yes</span></span><br><span class="line">pidfile /var/run/redis_6379.pid  <span class="comment">#如果以后台的方式(守护线程方式)运行，就需要指定一个 pid 文件</span></span><br><span class="line"><span class="comment">#日志</span></span><br><span class="line"><span class="comment"># Specify the server verbosity level.</span></span><br><span class="line"><span class="comment"># This can be one of:</span></span><br><span class="line"><span class="comment"># debug (a lot of information, useful for development/testing)</span></span><br><span class="line"><span class="comment"># verbose (many rarely useful info, but not a mess like the debug level)</span></span><br><span class="line"><span class="comment"># notice (moderately verbose, what you want in production probably)   生产环境 </span></span><br><span class="line"><span class="comment"># warning (only very important / critical messages are logged)</span></span><br><span class="line">loglevel notice</span><br><span class="line">logfile <span class="string">""</span><span class="comment">#日志的文件位置名，如果为空 就是输出</span></span><br><span class="line">databases 16<span class="comment">#数据库的数量</span></span><br><span class="line">always-show-logo yes<span class="comment">#是否总是显示logo</span></span><br></pre></td></tr></table></figure><h5 id="SNAPSHOTTING-快照"><a href="#SNAPSHOTTING-快照" class="headerlink" title="SNAPSHOTTING 快照"></a>SNAPSHOTTING 快照</h5><p>持久化，在规定的时间内，达到规定的操作次数，则会持久化到文件  .rdb, .aof</p><p>redis 是内存数据库，如果没有持久化，断电即失</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># 900s内，至少有 1 个key进行了修改，就进行持久化操作</span><br><span class="line">save 900 1</span><br><span class="line"># 300s内，至少有 10 个key进行了修改，就进行持久化操作</span><br><span class="line">save 300 10</span><br><span class="line"># 60s内，至少有 10000 个key进行了修改，就进行持久化操作</span><br><span class="line">save 60 10000</span><br><span class="line"></span><br><span class="line">stop-writes-on-bgsave-error yes  #如果持久化出错，是否继续工作</span><br><span class="line">rdbcompression yes #是否压缩rdb文件(需要消耗一些cpu资源)</span><br><span class="line">rdbchecksum yes#保存rdb文件时，进行错误的检查校验</span><br><span class="line">dir ./#rdb保存的目录</span><br></pre></td></tr></table></figure><h5 id="SECURITY-安全"><a href="#SECURITY-安全" class="headerlink" title="SECURITY 安全"></a>SECURITY 安全</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">config get requirepass  <span class="comment">#获取数据库的密码</span></span><br><span class="line">config <span class="built_in">set</span> requirepass  123  <span class="comment">#设置数据库密码 为 123(设置完之后，所有的操作会提示没有权限，需要登录才可)</span></span><br><span class="line">auth 123 <span class="comment">#使用密码 123 进行登录</span></span><br></pre></td></tr></table></figure><h5 id="CLIENTS-客户端"><a href="#CLIENTS-客户端" class="headerlink" title="CLIENTS 客户端"></a>CLIENTS 客户端</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">maxclients 10000  <span class="comment">#最大客户端连接数</span></span><br><span class="line"></span><br><span class="line">maxmemory &lt;bytes&gt; <span class="comment">#redis配置最大内存容量</span></span><br><span class="line"></span><br><span class="line">maxmemory-policy noeviction<span class="comment">#内存到达上限后的处理策略</span></span><br><span class="line">- noeviction：当内存使用达到阈值的时候，所有引起申请内存的命令会报错。</span><br><span class="line">- allkeys-lru：在所有键中采用lru算法删除键，直到腾出足够内存为止。</span><br><span class="line">- volatile-lru：在设置了过期时间的键中采用lru算法删除键，直到腾出足够内存为止。</span><br><span class="line">- allkeys-random：在所有键中采用随机删除键，直到腾出足够内存为止。</span><br><span class="line">- volatile-random：在设置了过期时间的键中随机删除键，直到腾出足够内存为止。</span><br><span class="line">- volatile-ttl：在设置了过期时间的键空间中，具有更早过期时间的key优先移除。</span><br><span class="line"><span class="comment">#LRU是Least Recently Used的缩写，即最近最少使用，是一种常用的页面置换算法，选择最近最久未使用的页面予以淘汰</span></span><br></pre></td></tr></table></figure><h5 id="APPEND-ONLY-MODE-aof配置"><a href="#APPEND-ONLY-MODE-aof配置" class="headerlink" title="APPEND ONLY MODE aof配置"></a>APPEND ONLY MODE aof配置</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">appendonly no<span class="comment">#默认不开启(rdb持久化方式可以适应的大部分场景，一般用不到 aof)</span></span><br><span class="line">appendfilename <span class="string">"appendonly.aof"</span>  <span class="comment">#持久化的文件名字</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># appendfsync always#每次修改都会执行 sync，消耗资源</span></span><br><span class="line">appendfsync everysec<span class="comment">#每秒执行一次 sync，可能会丢失 1s 的数据</span></span><br><span class="line"><span class="comment"># appendfsync no#不执行 sync 操作，操作系统自己同步数据</span></span><br></pre></td></tr></table></figure><h1 id="redis持久化"><a href="#redis持久化" class="headerlink" title="redis持久化"></a>redis持久化</h1><p>Redis是内存数据库，如果不将内存中的数据库状态保存到磁盘，一但服务器进程退出，服务器中的数据库状态也会丢失，Redis提供了持久化功能</p><h2 id="RDB-Redis-DataBase"><a href="#RDB-Redis-DataBase" class="headerlink" title="RDB(Redis DataBase)"></a>RDB(Redis DataBase)</h2><p>在指定的时间间隔内内存中的数据集快照写入磁盘(Snapshot)，它恢复时是将快照文件直接读到内存里</p><p>Redis会单独创建( fork ) 一个子进程来进行持久化,会先将数据写入到一个临时文件中，待持久化过程都结束了，再用这个临时文件替换上次持久化好的文件。整个过程中, 主进程是不进行任何I0操作的。确保了极高的性能。如果需要进行大规模数据的恢复，且对于数据恢复的完整性不是非常敏感，那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一 次持久化后的数据可能丢失。默认的就是RDB，一般情况下不需要修改这个配置(最后一次持久化如果宕机了，数据可能丢失)</p><p>==rdb保存的文件是 dump.rdb==    在配置文件中  SNAPSHOTTING 快照 中设置</p><p><img src="/article/8eda3648/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210221171739453.png" alt="image-20210221171739453"></p><blockquote><p>dump.rdb 触发机制</p></blockquote><ul><li>save 条件满足的情况下，会自动触发 rdb规则</li><li>执行flushall 命令 也会触发 rdb规则</li><li>退出也会产生rdb文件（kill 结束进程的话，redis会来不及保存）</li></ul><blockquote><p>恢复rdb文件</p></blockquote><ol><li>只需要将rdb文件放在redis启动目录即可，redis启动时会自动检测dump.rdb，恢复其中的数据</li><li>查看需要存在的位置</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; config get dir</span><br><span class="line">1) <span class="string">"dir"</span></span><br><span class="line">2) <span class="string">"/usr/local/bin"</span><span class="comment">#如果该目录下存在 dump.rdb文件，启动数据库就会自动恢复其中的数据</span></span><br></pre></td></tr></table></figure><h5 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h5><ul><li>适合大规模的数据恢复</li></ul><h5 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h5><ol><li>需要一定的时间间隔进行操作(可以自定义为每次修改都同步，但是会消耗资源)，如果redis意外宕机，可能会丢失数据</li><li>fork进程时，会占用一定的内存空间</li></ol><h2 id="AOF-Append-Only-File"><a href="#AOF-Append-Only-File" class="headerlink" title="AOF(Append Only File)"></a>AOF(Append Only File)</h2><p>以日志的形式记录每一个操作，将Redis执行过的所有指令记录下来（读操作不记录），只许追加文件但不可以改写文件，reids再启动时，会读取该文件重新构建数据</p><p>==Aof 保存的是 appendonly.aop 文件==</p><blockquote><p>优点</p></blockquote><p>每次修改都同步，保证了数据的完整性</p><blockquote><p>缺点</p></blockquote><p>相对于数据文件来说，aof远远大于rdb，修复速度慢与rdb</p><p>运行效率比rdb慢</p><h5 id="Rewrite"><a href="#Rewrite" class="headerlink" title="Rewrite"></a>Rewrite</h5><blockquote><p>是什么</p></blockquote><p>Aof采用的是文件追加方式，文件会越来越大，为避免这种情况，新增了重写机制，当AOF文件大小超过规定的阈值时，Redis会启动AOF文件的内容压缩</p><blockquote><p>重写原理</p></blockquote><p>AOF文件持续增长过大时，会fork出新进程将文件重写（也就是先写零时文件最后再rename），遍历新进程的内存中数据，每条记录有一条set语句。重写aof文件的操作，没有读取旧的aof文件</p><blockquote><p>触发机制</p></blockquote><p>Redis会记录上一次重写时的AOF大小，默认配置是aof大小是上次rewrite后得大小且文件大于64M  </p><h1 id="redis发布订阅"><a href="#redis发布订阅" class="headerlink" title="redis发布订阅"></a>redis发布订阅</h1><p>Redis发布/订阅(pub/sub)是一种==消息通信模式==：发送者(pub)发送信息，订阅者(sub)接收消息。(微信，微博，哔哩哔哩有关注系统的)</p><p>Redis客户端可订阅任意数量的频道</p><h5 id="发布订阅命令"><a href="#发布订阅命令" class="headerlink" title="发布订阅命令"></a>发布订阅命令</h5><p><img src="/article/8eda3648/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210223215210796.png" alt="image-20210223215210796"></p><h5 id="订阅频道"><a href="#订阅频道" class="headerlink" title="订阅频道"></a>订阅频道</h5><blockquote><p>subscribe + 频道名，订阅一个或多个频道，如果频道不存在也可订阅</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; subscribe kuanshen</span><br><span class="line">Reading messages... (press Ctrl-C to quit)</span><br><span class="line">1) <span class="string">"subscribe"</span></span><br><span class="line">2) <span class="string">"kuanshen"</span></span><br><span class="line">3) (<span class="built_in">integer</span>) 1</span><br></pre></td></tr></table></figure><h5 id="频道操作者-向频道发送内容"><a href="#频道操作者-向频道发送内容" class="headerlink" title="(频道操作者)向频道发送内容"></a>(频道操作者)向频道发送内容</h5><blockquote><p>publish + 频道名 + 内容</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; publish kuanshen aaaaa</span><br><span class="line">(<span class="built_in">integer</span>) 1</span><br></pre></td></tr></table></figure><p>发送内容后，已订阅该频道的人都会接收到消息</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; subscribe kuanshen</span><br><span class="line">Reading messages... (press Ctrl-C to quit)</span><br><span class="line">1) <span class="string">"subscribe"</span></span><br><span class="line">2) <span class="string">"kuanshen"</span></span><br><span class="line">3) (<span class="built_in">integer</span>) 1</span><br><span class="line"><span class="comment">#接收到的消息</span></span><br><span class="line">1) <span class="string">"message"</span><span class="comment">#消息</span></span><br><span class="line">2) <span class="string">"kuanshen"</span><span class="comment">#发送消息的频道</span></span><br><span class="line">3) <span class="string">"aaaaa"</span><span class="comment">#接收到的消息内容</span></span><br></pre></td></tr></table></figure><h5 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h5><p>Redis是使用C实现的，通过分析Redis源码里的pubsub.c文件，了解发布和订阅机制的底层实现，籍此加深对Redis的理解。Redis通过PUBLISH、SUBSCRIBE 和PSUBSCRIBE等命令实现发布和订阅功能。</p><p>通过SUBSCRIBE命令订阅某频道后，redis-server 里维护了一个字典，字典的键就是一个个 channel , 而字典的值则是一个链表，链表中保存了所有订阅这个channel的客户端。SUBSCRIBE 命令的关键，就是将客户端添加到给定channel的订阅链表中。通过PUBLISH命令向订阅者发送消息，redis-server 会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表，将消息发布给所有订阅者。</p><p>Pub/Sub从字面上理解就是发布( Publish )与订阅( Subscribe )，在Redis中 ，你可以设定对某一个key值进行消息发布及消息订阅，当一个key值上进行了消息发布后，所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统，比如普通的即时聊天，群聊等功能。</p><h1 id="redis主从复制"><a href="#redis主从复制" class="headerlink" title="redis主从复制"></a>redis主从复制</h1><h4 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h4><p>主从复制，是指将一台Redis服务器的数据 ，复制到其他的Redis服务器。前者称为主节点master/leader)，后者称为从节点(slavefollower) ;==数据的复制是单向的，只能由主节点到从节点==。Master以写为主 ，Slave 以读为主。(一主多仆，只有允许有一个主机)</p><p>默认情况下，每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点) ,但一个从节点只能有一 个主节点主从复制的作用主要包括:</p><p>1、数据冗余:主从复制实现了数据的热备份，是持久化之外的一种数据冗余方式。</p><p>2、故障恢复:当主节点出现问题时，可以由从节点提供服务，实现快速的故障恢复;实际上是一种服务的冗余。</p><p>3、负载均衡:在主从复制的基础上，配合读写分离，可以由主节点提供写服务，由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点) , 分担服务器负载;尤其是在写少读多的场景下，通过多个从节点分担读负载，可以大大提高Redis服务器的并发量。</p><p>4、高可用基石:除了上述作用外，主从复制还是哨兵和集群能够实施的基础，因此主从复制是Redis高可用的基础</p><p>==主从复制，读写分离，主机用来写，从机只能读==</p><h4 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h4><p>只配置从库，主库不做配置(每连接一个reids，会默认为是一个主机Master)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; info replication<span class="comment">#查看当前库的状况</span></span><br><span class="line"><span class="comment"># Replication</span></span><br><span class="line">role:master<span class="comment">#Master主机</span></span><br><span class="line">connected_slaves:0<span class="comment">#从机个数 0</span></span><br><span class="line">master_replid:63c194a47b34e70c626c20779f7a958dd4c98b97</span><br><span class="line">master_replid2:0000000000000000000000000000000000000000</span><br><span class="line">master_repl_offset:616</span><br><span class="line">second_repl_offset:-1</span><br><span class="line">repl_backlog_active:0</span><br><span class="line">repl_backlog_size:1048576</span><br><span class="line">repl_backlog_first_byte_offset:1</span><br><span class="line">repl_backlog_histlen:616</span><br></pre></td></tr></table></figure><p>复制三个配置文件，修改对应的信息(将信息改为自己的),避免会造成文件覆盖</p><ol><li>端口</li><li>pid名字</li><li>log文件名字</li><li>dump.rdb名字</li></ol><p>修改完毕之后，启动三个redis服务器</p><p><img src="/article/8eda3648/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210226001212516.png" alt="image-20210226001212516"></p><h4 id="配置为子节点"><a href="#配置为子节点" class="headerlink" title="配置为子节点"></a>配置为子节点</h4><h5 id="命令配置"><a href="#命令配置" class="headerlink" title="命令配置"></a>命令配置</h5><p>暂时的，断开连接，再重连就会自动变回主机(默认)，==与主机共享的数据也会获取不到==</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6380&gt; slaveof 127.0.0.1 6370<span class="comment">#将当前redis配置为 子节点 salveof + ip + 端口号</span></span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6380&gt; info replication</span><br><span class="line"><span class="comment"># Replication</span></span><br><span class="line">role:slave<span class="comment">#slave  从机</span></span><br><span class="line">master_host:127.0.0.1<span class="comment">#主机ip</span></span><br><span class="line">master_port:6379<span class="comment">#主机端口</span></span><br><span class="line">master_link_status:down</span><br><span class="line">master_last_io_seconds_ago:-1</span><br><span class="line">master_sync_in_progress:0</span><br><span class="line">slave_repl_offset:0</span><br><span class="line">master_link_down_since_seconds:1614416493</span><br><span class="line">slave_priority:100</span><br><span class="line">slave_read_only:1</span><br><span class="line">connected_slaves:0</span><br><span class="line">master_replid:9316f9c9ee435be60ef27462979e60f1d44e778b</span><br><span class="line">master_replid2:0000000000000000000000000000000000000000</span><br><span class="line">master_repl_offset:0</span><br><span class="line">second_repl_offset:-1</span><br><span class="line">repl_backlog_active:0</span><br><span class="line">repl_backlog_size:1048576</span><br><span class="line">repl_backlog_first_byte_offset:0</span><br><span class="line">repl_backlog_histlen:0</span><br></pre></td></tr></table></figure><h5 id="配置文件配置"><a href="#配置文件配置" class="headerlink" title="配置文件配置"></a>配置文件配置</h5><p>删除注释，replicaof + 主机ip + 主机端口，重连之后，==与主机共享的数据不会丢失，依旧可以获取==</p><p><img src="/article/8eda3648/Users\tt\Documents\Tencent Files\2413846128\FileRecv\范大师笔记\分享笔记截图\image-20210227171635558.png" alt="image-20210227171635558"></p><blockquote><p>重新登录后，依然是从机</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">[root@xiaoxi bin]<span class="comment"># redis-server fconfig/redis-1.conf </span></span><br><span class="line">[root@xiaoxi bin]<span class="comment"># redis-cli -p 6380</span></span><br><span class="line">127.0.0.1:6380&gt; info replication</span><br><span class="line"><span class="comment"># Replication</span></span><br><span class="line">role:slave</span><br><span class="line">master_host:127.0.0.1</span><br><span class="line">master_port:6379</span><br><span class="line">master_link_status:down</span><br><span class="line">master_last_io_seconds_ago:-1</span><br><span class="line">master_sync_in_progress:0</span><br><span class="line">slave_repl_offset:1</span><br><span class="line">master_link_down_since_seconds:1614417299</span><br><span class="line">slave_priority:100</span><br><span class="line">slave_read_only:1</span><br><span class="line">connected_slaves:0</span><br><span class="line">master_replid:8c539f4d1b5cbd315c9ce54c57802ac0277c7d98</span><br><span class="line">master_replid2:0000000000000000000000000000000000000000</span><br><span class="line">master_repl_offset:0</span><br><span class="line">second_repl_offset:-1</span><br><span class="line">repl_backlog_active:0</span><br><span class="line">repl_backlog_size:1048576</span><br><span class="line">repl_backlog_first_byte_offset:0</span><br><span class="line">repl_backlog_histlen:0</span><br></pre></td></tr></table></figure><h4 id="主机写，从机读"><a href="#主机写，从机读" class="headerlink" title="主机写，从机读"></a>主机写，从机读</h4><blockquote><p>主机可以写</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; <span class="built_in">set</span> k1 v1</span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; get k1</span><br><span class="line"><span class="string">"v1"</span></span><br></pre></td></tr></table></figure><blockquote><p>从机只能读，不能写</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6380&gt; get k1</span><br><span class="line"><span class="string">"v1"</span></span><br><span class="line">127.0.0.1:6380&gt; <span class="built_in">set</span> k2 v2</span><br><span class="line">(error) READONLY You can<span class="string">'t write against a read only replica.</span></span><br></pre></td></tr></table></figure><p>主要redis被配置为从机，则会实时共享主机的所有数据（成为该主机的从机前，主机存的数据也可以获取到）</p><h4 id="复制原理"><a href="#复制原理" class="headerlink" title="复制原理"></a>复制原理</h4><p>Slave启动成功连接到master后会发送一个synct同步命令</p><p>Master接到命令， 启动后台的存盘进程，同时收集所有接收到的用于修改数据集命令，在后台进程执行完毕之后，==master将传送整个数据文件到slave，并完成一次完全同步==。</p><p>==全量复制==:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。</p><p>==增量复制==: Master继续将新的所有收集到的修改命令依次传给slave，完成同步</p><p>但是只要是重新连接master，一次完全同步(全量复制)将被自动执行</p><blockquote><p>谋朝篡位(没有配置哨兵模式)</p></blockquote><p>如果主机失去连接，从机可以使用<code>slaveof no one</code>把自己作为主机，其他节点可以连接到该最新的主节点(手动)，如果之前主节点修复了，也只能重新配置</p><h1 id="哨兵模式"><a href="#哨兵模式" class="headerlink" title="哨兵模式"></a>哨兵模式</h1><p>自动选老大（主节点）的模式</p><blockquote><p>概述</p></blockquote><p>主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器，就需要人工干预，还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候，优先考虑哨兵模式。</p><p>谋朝算位的自动版，能够后台监控主机是否故障，如果故障 了根据投票数自动将从库转换为主库。</p><p>哨兵模式是一种特殊的模式 ，首先Redis提供了哨兵的命令， 哨兵是一个独立的进程 ，作为进程，它会独立运行。其原理是哨兵通过发送命令，等待Redis服务器响应，从而监控运行的多个Redis实例。</p><h5 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h5><ol><li><p>建一个 sentinel.conf 的哨兵配置文件</p></li><li><p>在配置文件写入 核心配置语句</p><blockquote><h1 id="命令格式-sentinel-monitor-名字-可自定义-需要监视的redis的-ip-端口号-n-数字指的是主机挂了之后，从机需要得到哨兵-‘n’-张票之后，才可以自动设为主机"><a href="#命令格式-sentinel-monitor-名字-可自定义-需要监视的redis的-ip-端口号-n-数字指的是主机挂了之后，从机需要得到哨兵-‘n’-张票之后，才可以自动设为主机" class="headerlink" title="命令格式  sentinel monitor + 名字(可自定义) + 需要监视的redis的 ip + 端口号 + n(数字指的是主机挂了之后，从机需要得到哨兵 ‘n’ 张票之后，才可以自动设为主机)"></a>命令格式  sentinel monitor + 名字(可自定义) + 需要监视的redis的 ip + 端口号 + n(数字指的是主机挂了之后，从机需要得到哨兵 ‘n’ 张票之后，才可以自动设为主机)</h1><p>sentinel monitor myredis 127.0.0.1 6379 1</p></blockquote></li><li><p>启动哨兵</p><blockquote><p>redis-sentinel fconfig/sentinel.conf </p></blockquote></li></ol><p>启动成功:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#监视的主机</span></span><br><span class="line">21689:X 28 Feb 2021 00:25:59.227 <span class="comment"># +monitor master myredis 127.0.0.1 6379 quorum 1</span></span><br><span class="line"><span class="comment">#监视的主机的从机</span></span><br><span class="line">21689:X 28 Feb 2021 00:25:59.228 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379</span><br><span class="line">21689:X 28 Feb 2021 00:25:59.273 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379</span><br></pre></td></tr></table></figure><h5 id="主机宕机后"><a href="#主机宕机后" class="headerlink" title="主机宕机后"></a>主机宕机后</h5><p>哨兵的日志</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">21689:X 28 Feb 2021 00:34:09.347 <span class="comment"># +sdown master myredis 127.0.0.1 6379</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:09.347 <span class="comment"># +odown master myredis 127.0.0.1 6379 #quorum 1/1</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:09.347 <span class="comment"># +new-epoch 1</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:09.347 <span class="comment"># +try-failover master myredis 127.0.0.1 6379</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:09.383 <span class="comment"># +vote-for-leader a4c980cd2c769e10dc758af3df9f84c349573e50 1</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:09.383 <span class="comment"># +elected-leader master myredis 127.0.0.1 6379</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:09.383 <span class="comment"># +failover-state-select-slave master myredis 127.0.0.1 6379</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:09.440 <span class="comment"># +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:09.441 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379</span><br><span class="line">21689:X 28 Feb 2021 00:34:09.498 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379</span><br><span class="line">21689:X 28 Feb 2021 00:34:09.604 <span class="comment"># +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:09.604 <span class="comment"># +failover-state-reconf-slaves master myredis 127.0.0.1 6379</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:09.650 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379</span><br><span class="line">21689:X 28 Feb 2021 00:34:10.641 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379</span><br><span class="line">21689:X 28 Feb 2021 00:34:10.641 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379</span><br><span class="line">21689:X 28 Feb 2021 00:34:10.731 <span class="comment"># +failover-end master myredis 127.0.0.1 6379</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:10.732 <span class="comment"># +switch-master myredis 127.0.0.1 6379 127.0.0.1 6381 #投票6381 为主机</span></span><br><span class="line">21689:X 28 Feb 2021 00:34:10.732 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381</span><br><span class="line">21689:X 28 Feb 2021 00:34:10.732 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381</span><br><span class="line">21689:X 28 Feb 2021 00:34:40.775 <span class="comment"># +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381</span></span><br></pre></td></tr></table></figure><p>6381的replication信息</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6381&gt; info replication</span><br><span class="line"><span class="comment"># Replication</span></span><br><span class="line">role:master<span class="comment">#由从机变为主机</span></span><br><span class="line">connected_slaves:1</span><br><span class="line">slave0:ip=127.0.0.1,port=6380,state=online,offset=32964,lag=1</span><br><span class="line">master_replid:84176ff5d3adcfbf8ff0ded5a51f850fa43be575</span><br><span class="line">master_replid2:ebaaf65cfdf89c6da8548a36e41a0dd393231559</span><br><span class="line">master_repl_offset:32964</span><br><span class="line">second_repl_offset:30788</span><br><span class="line">repl_backlog_active:1</span><br><span class="line">repl_backlog_size:1048576</span><br><span class="line">repl_backlog_first_byte_offset:57</span><br><span class="line">repl_backlog_histlen:32908</span><br></pre></td></tr></table></figure><p>当之前的主机(6379)重连后，会自动变为当前主机（6381）的从机</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379&gt; info replication</span><br><span class="line"><span class="comment"># Replication</span></span><br><span class="line">role:slave</span><br><span class="line">master_host:127.0.0.1</span><br><span class="line">master_port:6381</span><br><span class="line">master_link_status:up</span><br><span class="line">master_last_io_seconds_ago:1</span><br><span class="line">master_sync_in_progress:0</span><br><span class="line">slave_repl_offset:52123</span><br><span class="line">slave_priority:100</span><br><span class="line">slave_read_only:1</span><br><span class="line">connected_slaves:0</span><br><span class="line">master_replid:84176ff5d3adcfbf8ff0ded5a51f850fa43be575</span><br><span class="line">master_replid2:0000000000000000000000000000000000000000</span><br><span class="line">master_repl_offset:52123</span><br><span class="line">second_repl_offset:-1</span><br><span class="line">repl_backlog_active:1</span><br><span class="line">repl_backlog_size:1048576</span><br><span class="line">repl_backlog_first_byte_offset:49947</span><br><span class="line">repl_backlog_histlen:2177</span><br></pre></td></tr></table></figure><h5 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h5><blockquote><p>优点</p></blockquote><ol><li>哨兵集群，基于主从复制模式，包含所有主从配置的优点</li><li>主从一切换，故障可以转移，系统的可用性会更好</li><li>哨兵模式是主从复制的升级，手动到自动，更加健壮</li></ol><blockquote><p>缺点</p></blockquote><ol><li>集群容量一旦达到上限，Redis扩容会很困难</li><li>实现哨兵模式的配置麻烦，里面有很多选择</li></ol><h5 id="哨兵模式全部配置"><a href="#哨兵模式全部配置" class="headerlink" title="哨兵模式全部配置"></a>哨兵模式全部配置</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 哨兵sentinel实例运行的端口   默认是26379，如果有哨兵集群，我们还需要配置每个哨兵端口</span></span><br><span class="line">port 26379</span><br><span class="line"></span><br><span class="line"><span class="comment">#哨兵sentinel的工作目录</span></span><br><span class="line">dir /tmp</span><br><span class="line"></span><br><span class="line"><span class="comment">#哨兵 sentine1 监控的redis主节点的 ip port   </span></span><br><span class="line"><span class="comment"># master-name  ，可以自己命名的主节点名字 只能由字母A-Z、数字0-9、这三个字符"  .   -  _ "组成。</span></span><br><span class="line"><span class="comment"># quorum配置多少个sentine1哨兵统- -认为master主节点失联那么这时客观上认为主节点失联了</span></span><br><span class="line"><span class="comment"># sentine1 monitor &lt;master-name&gt; &lt;ip&gt; &lt;redis-port&gt; &lt;quorum&gt;</span></span><br><span class="line">sentinel monitor mymaster   127.0.0.1   6379   2</span><br><span class="line"></span><br><span class="line"><span class="comment">#当在Redis实例中开启了requirepass foobared 授权密码这样所有连接kedis实例的客户端都要提供密码</span></span><br><span class="line"><span class="comment">#设置哨兵sentinel连接主从的密码注意必须为主从设置- - 样的验证密码</span></span><br><span class="line"><span class="comment"># sentine1 auth-pass &lt;master-name&gt; &lt;password&gt;</span></span><br><span class="line">sentine1 auth-pass mymaster MySUPER--secret-0123passwOrd</span><br><span class="line"></span><br><span class="line"><span class="comment">#指定多少毫秒之后主节点没有应答哨兵sentine1 此时哨兵主观上认为主节点下线默认30秒</span></span><br><span class="line"><span class="comment"># sentinel down-after-mi 11i seconds &lt;master-name&gt; &lt;mi 11iseconds&gt;</span></span><br><span class="line">sentine1 down-after-mi 11iseconds mymaster 30000</span><br><span class="line"></span><br><span class="line"><span class="comment">#这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步，这个数字越小，完成fai lover所需的时间就越长，但是如果这个数字越大，就意味着越多的slave因为replication而 不可用。可以通过将这个值设为1来保证每次只有一个slave处于不能处理命令请求的状态。</span></span><br><span class="line"><span class="comment"># sentine1 paralle1-syncs &lt;master-name&gt; &lt;numslaves&gt;</span></span><br><span class="line">sentine1 paralle1-syncs mymaster 1</span><br><span class="line"></span><br><span class="line"><span class="comment">#故障转移的超时时间failover-timeout 可以用在以下这些方面:</span></span><br><span class="line"><span class="comment">#1.同一个sentine1对同一 个master两次fai lover之间的间隔时间。</span></span><br><span class="line"><span class="comment">#2.当一个slave从一 个错误的master那里同步数据开始计算时间。直到s1ave被纠正为向正确的master那里同步数据时。</span></span><br><span class="line"><span class="comment">#3.当想要取消一个正在进行的failover所需要的时间。</span></span><br><span class="line"><span class="comment">#4.当进行failover时，配置所有s1aves指向新的master所需的最大时间。不过，即使过了这个超时，slaves 依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了</span></span><br><span class="line"><span class="comment">#默认三分钟</span></span><br><span class="line"><span class="comment"># sentine1 failover-timeout &lt;master-name&gt; &lt;milliseconds&gt;</span></span><br><span class="line">sentine1 fai lover-ti meout mymaster 180000</span><br><span class="line"></span><br><span class="line"><span class="comment"># SCRIPTS EXECUTION</span></span><br><span class="line"><span class="comment">#配置当某一事件发生时所需要执行的脚本，可以通过脚本来通知管理员，例如当系统运行不正常时发邮件通知相关人员。</span></span><br><span class="line"><span class="comment">#对于脚本的运行结果有以下规则:</span></span><br><span class="line"><span class="comment">#若脚本执行后返回1，那么该脚本稍后将会被再次执行，重复次数目前默认为10</span></span><br><span class="line"><span class="comment">#若脚本执行后返回2，或者比2更高的一个返回值，脚本将不会重复执行。</span></span><br><span class="line"><span class="comment">#如果脚本在执行过程中由于收到系统中断信号被终止了，则同返回值为1时的行为相同。</span></span><br><span class="line"><span class="comment">#一个脚本的最大执行时间为60s，如果超过这个时间，脚本将会被-一个SIGKILL信号终止，之后重新执行。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#通知型脚本:当sentine1有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等)，将会去调用这个脚本，这时这个脚本应该通过邮件，SMS等 方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时，将传给脚本两个参数，一 个是事件的类型，一个是事件的描述。如果sentine1. conf配置文件中配置了这个脚本路径，那么必须保证这个脚本存在于这个路径，并且是可执行的，否则sentine1无法正常启动成功。</span></span><br><span class="line"><span class="comment">#通知脚本</span></span><br><span class="line"><span class="comment"># she11编程</span></span><br><span class="line"><span class="comment"># sentine1 notification-script &lt;master-name&gt; &lt;script-path&gt;</span></span><br><span class="line">sentine1 notificati on-script mymaster /var/redis/notify. sh</span><br><span class="line"></span><br><span class="line"><span class="comment">#客户端重新配置主节点参数脚本</span></span><br><span class="line"><span class="comment">#当一个master由于failover而发生改变时，这个脚本将会被调用，通知相关的客户端关于master地址已经发生改变的信息。</span></span><br><span class="line"><span class="comment">#以下参数将会在调用脚本时传给脚本: </span></span><br><span class="line"><span class="comment"># &lt;master-name&gt; &lt;role&gt; &lt;state&gt; &lt;from-ip&gt; &lt;from-port&gt; &lt;to-ip&gt; &lt;to-port&gt;</span></span><br><span class="line"><span class="comment">#目前&lt;state&gt;总是“failover",</span></span><br><span class="line"><span class="comment"># &lt;role&gt;是“Teader"或者"observer"中的-一个。</span></span><br><span class="line"><span class="comment">#参数from-ip， from-port， to-ip，to-port是用来和旧的master和新的master(即旧的s lave)通信的</span></span><br><span class="line"><span class="comment">#这个脚本应该是通用的，能被多次调用，不是针对性的。</span></span><br><span class="line"><span class="comment"># sentine1 client-reconfig-script &lt;master-name&gt; &lt;script-path&gt;</span></span><br><span class="line">sentine1 client-reconfig-script mymaster /var/redis/reconfig.sh <span class="comment">#一般都是由运维来配置!</span></span><br></pre></td></tr></table></figure><h1 id="缓存穿透和雪崩"><a href="#缓存穿透和雪崩" class="headerlink" title="缓存穿透和雪崩"></a>缓存穿透和雪崩</h1><h3 id="缓存穿透"><a href="#缓存穿透" class="headerlink" title="缓存穿透"></a>缓存穿透</h3><h4 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h4><p>用户想要查询某个数据，但是redis内存数据库没有（也就是缓存没有命中），于是向持久层数据库进行查询，发现也没有，查询失败。当用户很多，缓存都没有命中（如：秒杀），都去请求了持久层数据库， 给持久层数据库带来很带压力，这时候相当于出现了缓存穿透。</p><h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><h5 id="布隆过滤器"><a href="#布隆过滤器" class="headerlink" title="布隆过滤器"></a>布隆过滤器</h5><p>布隆过滤器是一种数据结构，对所有可能查询的参数以hash形式储存，在控制层先进行校验，不符合则丢弃，从而避免了对底层存储系统的查询压力（人话：在用户和缓存之间加入布隆过滤器，布隆过滤器会对用户查询的参数进行判断，判断缓存是否有这个参数）</p><h5 id="缓存空对象"><a href="#缓存空对象" class="headerlink" title="缓存空对象"></a>缓存空对象</h5><p>当存储层未命中后，及时返回的空对象也将其缓存起来，同时会设置一个过期时间，之后再访问这个数据会从缓存中获取，保护了后端数据源（人话：用户查询数据（如：user1），缓存中没有这个数据，然后会查询数据库（如：mysql），数据库也没有这个数据，这时候就在缓存中加入该数据（user1），但是该数据的值是空）</p><h5 id="缓存空对象存在的问题"><a href="#缓存空对象存在的问题" class="headerlink" title="缓存空对象存在的问题"></a>缓存空对象存在的问题</h5><ol><li>如果空值被缓存起来，意味着需要更多的空间缓存更多的键（因为这当中有很多的空值键）</li><li>即使对空值设置了过期时间，还是会存在 缓存层和数据层会有一段时间窗口的不一致，对于需要保持一致性的业务有影响</li></ol><h3 id="缓存击穿"><a href="#缓存击穿" class="headerlink" title="缓存击穿"></a>缓存击穿</h3><h4 id="概述-1"><a href="#概述-1" class="headerlink" title="概述"></a>概述</h4><p>缓存击穿，指的是一个key非常热点，大并发集中对这一个点访问，当该key过期失效的瞬间，持续的大并发就穿破缓存，直接请求数据库</p><h4 id="解决方案-1"><a href="#解决方案-1" class="headerlink" title="解决方案"></a>解决方案</h4><h5 id="设置热点数据永不过期"><a href="#设置热点数据永不过期" class="headerlink" title="设置热点数据永不过期"></a>设置热点数据永不过期</h5><p>从缓存层面上讲，没有设置过期时间，就不会出现key失效后产生的问题</p><h5 id="加互斥锁"><a href="#加互斥锁" class="headerlink" title="加互斥锁"></a>加互斥锁</h5><p>分布式锁：使用分布式锁，保证每个key同一时间只有一个线程去查询后端服务，其他线程没有获取分布式锁的的权限（人话：在缓存和持久层数据库之间加一把锁，只有一个线程可以获取锁，保证同一时间只有一个线程可以访问数据库）</p><h3 id="缓存雪崩"><a href="#缓存雪崩" class="headerlink" title="缓存雪崩"></a>缓存雪崩</h3><h4 id="概述-2"><a href="#概述-2" class="headerlink" title="概述"></a>概述</h4><p>缓存雪崩是指在某一个时间段内，缓存集中过期（如：redis宕机/设置过期时间过于集中，如：双11)，或缓存服务器某个节点宕机或断网</p><p>缓存集中过期是自然形成的雪崩，数据库一般可以顶住压力。缓存服务器某个节点宕机或断网形成的雪崩可能会瞬间把数据库冲垮</p><h4 id="解决方案-2"><a href="#解决方案-2" class="headerlink" title="解决方案"></a>解决方案</h4><h5 id="redis高可用性（搭建集群）"><a href="#redis高可用性（搭建集群）" class="headerlink" title="redis高可用性（搭建集群）"></a>redis高可用性（搭建集群）</h5><p>redis可能挂掉，那可以多增加几台服务器，搭建集群</p><h5 id="限流降级"><a href="#限流降级" class="headerlink" title="限流降级"></a>限流降级</h5><p>缓存失效后，通过加锁或者队列来控制读数据库写缓存的线程数量。</p><h5 id="数据预热"><a href="#数据预热" class="headerlink" title="数据预热"></a>数据预热</h5><p>在正式部署前，把可能的数据先访问一次，部分可能大量访问的数据就加载到缓存中，手动触发加载缓存不同的key，让缓存失效的时间尽量均匀</p><h1 id="缓存和持久层数据库数据一致性"><a href="#缓存和持久层数据库数据一致性" class="headerlink" title="缓存和持久层数据库数据一致性"></a>缓存和持久层数据库数据一致性</h1>]]></content>
    
    <summary type="html">
    
      &lt;p&gt; Redis学习笔记&lt;/p&gt;
    
    </summary>
    
    
    
      <category term="Redis" scheme="https://www.tengfei.eu.org/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>SpringSecurity+JWT权限认证</title>
    <link href="https://www.tengfei.eu.org/article/5253b791.html"/>
    <id>https://www.tengfei.eu.org/article/5253b791.html</id>
    <published>2022-12-28T05:18:29.000Z</published>
    <updated>2023-01-31T13:22:19.600Z</updated>
    
    <content type="html"><![CDATA[<p>🏮🏮🏮</p><p>使用token实现单点登录</p><a id="more"></a><h2 id="SpringSecurity"><a href="#SpringSecurity" class="headerlink" title="SpringSecurity"></a>SpringSecurity</h2><h3 id="什么是安全框架？"><a href="#什么是安全框架？" class="headerlink" title="什么是安全框架？"></a>什么是安全框架？</h3><p>解决系统安全问题的框架。如果没有安全框架，我们需要手动处理每个资源的访问控制，非常麻烦。使用安全框架，我们可以通过配置的方式实现对资源的访问限制。 </p><h3 id="常用安全框架"><a href="#常用安全框架" class="headerlink" title="常用安全框架"></a>常用安全框架</h3><p><strong>Spring Security</strong>：Spring家族一员。是一个能够为基于Spring的企业应用系统提供声明式的安全访 问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean，充分利用了 Spring IoC ， DI（控制反转Inversion of Control,DI:Dependency Injection 依赖注入） 和 AOP（面向切面编程） 功能，为应用系统提供声明式的安全访问控制功能，减少了为企业系统安 全控制编写大量重复代码的工作。 </p><p><strong>Apache Shiro</strong>：一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理。 </p><h3 id="Spring-Security简介"><a href="#Spring-Security简介" class="headerlink" title="Spring Security简介"></a>Spring Security简介</h3><p>概述 Spring Security是一个高度自定义的安全框架。利用 Spring IoC/DI和AOP功能，为系统提供了声明 式安全访问控制功能，减少了为系统安全而编写大量重复代码的工作。使用 Spring Secruity 的原因有 很多，但大部分都是发现了 javaEE的 Servlet 规范或 EJB 规范中的安全功能缺乏典型企业应用场景。同 时认识到他们在 WAR 或 EAR 级别无法移植。因此如果你更换服务器环境，还有大量工作去重新配置你 的应用程序。使用 Spring Security解决了这些问题，也为你提供许多其他有用的、可定制的安全功能。 正如你可能知道的两个应用程序的两个主要区域是“认证”和“授权”（或者访问控制）。这两点也是 Spring Security 重要核心功能。“认证”，是建立一个他声明的主体的过程（一个“主体”一般是指用户， 设备或一些可以在你的应用程序中执行动作的其他系统），通俗点说就是系统认为用户是否能登录。 “授权”指确定一个主体是否允许在你的应用程序执行一个动作的过程。通俗点讲就是系统判断用户是否 有权限去做某些事情。</p><h3 id="SpringSecurity项目搭建"><a href="#SpringSecurity项目搭建" class="headerlink" title="SpringSecurity项目搭建"></a>SpringSecurity项目搭建</h3><h4 id="1、配置pom文件，引入jar包"><a href="#1、配置pom文件，引入jar包" class="headerlink" title="1、配置pom文件，引入jar包"></a>1、配置pom文件，引入jar包</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--spring security 组件--&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-security<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">   <span class="comment">&lt;!--web 组件--&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">   <span class="comment">&lt;!-- test 组件--&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">           <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.junit.vintage<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>junit-vintage-engine<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">           <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.security<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-security-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2、登陆界面"><a href="#2、登陆界面" class="headerlink" title="2、登陆界面"></a>2、登陆界面</h4><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Title<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">action</span>=<span class="string">"/login"</span> <span class="attr">method</span>=<span class="string">"post"</span>&gt;</span></span><br><span class="line">   用户名：<span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">name</span>=<span class="string">"username"</span> /&gt;</span><span class="tag">&lt;<span class="name">br</span>/&gt;</span></span><br><span class="line">   密码：<span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"password"</span> <span class="attr">name</span>=<span class="string">"password"</span> /&gt;</span><span class="tag">&lt;<span class="name">br</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"submit"</span> <span class="attr">value</span>=<span class="string">"登录"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p>访问页面 导入spring-boot-starter-security 启动器后，Spring Security 已经生效，默认拦截全部请求，如果用户没有登录，跳转到内置登录页面。</p><h3 id="UserDetailsService"><a href="#UserDetailsService" class="headerlink" title="UserDetailsService"></a>UserDetailsService</h3><p>当什么也没有配置的时候，账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密 码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时，只需要实现 UserDetailsService 接口即可。</p><p><img src="/article/5253b791/SpringSecurity_01.png" alt="img"></p><h4 id="1、创建user实体类实现UserDetails"><a href="#1、创建user实体类实现UserDetails" class="headerlink" title="1、创建user实体类实现UserDetails"></a>1、创建user实体类实现UserDetails</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> <span class="keyword">implements</span> <span class="title">UserDetails</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="keyword">private</span> List&lt;GrantedAuthority&gt; authorities;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">User</span><span class="params">(String username, String password, List&lt;GrantedAuthority&gt; authorities)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.username = username;</span><br><span class="line">        <span class="keyword">this</span>.password = password;</span><br><span class="line">        <span class="keyword">this</span>.authorities = authorities;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Collection&lt;? extends GrantedAuthority&gt; getAuthorities() &#123;</span><br><span class="line">        <span class="keyword">return</span> authorities;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getPassword</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> password;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getUsername</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> username;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isAccountNonExpired</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isAccountNonLocked</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isCredentialsNonExpired</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isEnabled</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Oauth2"><a href="#Oauth2" class="headerlink" title="Oauth2"></a>Oauth2</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>第三方认证技术方案最主要是解决认证协议的通用标准问题，因为要实现跨系统认证，各系统之间要 遵循一定的接口协议。</p><p>OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时，任何第三方都可以 使用OAUTH认证服务，任何服务提供商都可以实现自身的OAUTH认证服务，因而OAUTH是开放的。业 界提供了OAUTH的多种实现如PHP、JavaScript，Java，Ruby等各种语言开发包，大大节约了程序员的 时间，因而OAUTH是简易的。互联网很多服务如Open API，很多大公司如Google，Yahoo， Microsoft等都提供了OAUTH认证服务，这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。</p><p>Oauth协议目前发展到2.0版本，1.0版本过于复杂，2.0版本已得到广泛应用。<br>参考：<a href="https://baike.baidu.com/item/oAuth/7153134?fr=aladdin" target="_blank" rel="noopener">https://baike.baidu.com/item/oAuth/7153134?fr=aladdin</a><br>Oauth 协议：<a href="https://tools.ietf.org/html/rfc6749" target="_blank" rel="noopener">https://tools.ietf.org/html/rfc6749</a></p><h3 id="Oauth2认证的例子"><a href="#Oauth2认证的例子" class="headerlink" title="Oauth2认证的例子"></a>Oauth2认证的例子</h3><p><img src="/article/5253b791/Oauth2_01.png" alt="image-20201228142917738"></p><p>1、用户进入网站的登录页面，点击微信的图标以微信账号登录系统，用户是自己在微信里信息的资源 拥有者。<br>2、 资源拥有者同意给客户端授权 资源拥有者扫描二维码表示资源拥有者同意给客户端授权，微信会对资源拥有者的身份进行验证，验 证通过后，微信会询问用户是否给授权网站访问自己的微信数据，用户点击“确认登录”表示同意授权， 微信认证服务器会颁发一个授权码，并重定向到网站。<br>3、客户端获取到授权码，请求认证服务器申请令牌 此过程用户看不到，客户端应用程序请求认证服务器，请求携带授权码。<br>4、认证服务器向客户端响应令牌 认证服务器验证了客户端请求的授权码，如果合法则给客户端颁发令牌，令牌是客户端访问资源的通 行证。此交互过程用户看不到，当客户端拿到令牌后，用户在网站看到已经登录成功。<br>5、客户端请求资源服务器的资源 客户端携带令牌访问资源服务器的资源。网站携带令牌请求访问微信服务器获取用户的基本信息。<br>6、资源服务器返回受保护资源 资源服务器校验令牌的合法性，如果合法则向用户响应资源信息内容。</p><p>注意：资源服务器和认证服务器可以是一个服务也可以分开的服务，如果是分开的服务资源服务器通常 要请求认证服务器来校验令牌的合法性。</p><p>Oauth2.0认证流程如下： </p><p>引自Oauth2.0协议rfc6749 <a href="https://tools.ietf.org/html/rfc6749" target="_blank" rel="noopener">https://tools.ietf.org/html/rfc6749</a><br><img src="/article/5253b791/Oauth2_02.png" alt="image-20201228144310059"></p><h3 id="角色"><a href="#角色" class="headerlink" title="角色"></a>角色</h3><h4 id="客户端"><a href="#客户端" class="headerlink" title="客户端"></a>客户端</h4><p>本身不存储资源，需要通过资源拥有者的授权去请求资源服务器的资源，比如：Android客户端、Web 客户端（浏览器端）、微信客户端等。 </p><h4 id="资源拥有者"><a href="#资源拥有者" class="headerlink" title="资源拥有者"></a>资源拥有者</h4><p>通常为用户，也可以是应用程序，即该资源的拥有者。 </p><h4 id="授权服务器（也称认证服务器）"><a href="#授权服务器（也称认证服务器）" class="headerlink" title="授权服务器（也称认证服务器）"></a>授权服务器（也称认证服务器）</h4><p>用来对资源拥有的身份进行认证、对访问资源进行授权。客户端要想访问资源需要通过认证服务器由资 源拥有者授权后方可访问。</p><h4 id="资源服务器"><a href="#资源服务器" class="headerlink" title="资源服务器"></a>资源服务器</h4><p>存储资源的服务器，比如，网站用户管理服务器存储了网站用户信息，网站相册服务器存储了用户的相 册信息，微信的资源服务存储了微信的用户信息等。客户端最终访问资源服务器获取资源信息。</p><h4 id="常用术语"><a href="#常用术语" class="headerlink" title="常用术语"></a>常用术语</h4><p><strong>客户凭证(client Credentials)</strong> ：客户端的clientId和密码用于认证客户；<br><strong>令牌(tokens)</strong> ：授权服务器在接收到客户请求后，颁发的访问令牌；<br><strong>作用域(scopes)</strong> ：客户请求访问令牌时，由资源拥有者额外指定的细分权限(permission)；</p><h4 id="令牌类型"><a href="#令牌类型" class="headerlink" title="令牌类型"></a>令牌类型</h4><p><strong>授权码</strong> ：仅用于授权码授权类型，用于交换获取访问令牌和刷新令牌；<br><strong>访问令牌</strong> ：用于代表一个用户或服务直接去访问受保护的资源；<br><strong>刷新令牌</strong> ：用于去授权服务器获取一个刷新访问令牌；<br><strong>BearerToken</strong> ：不管谁拿到Token都可以访问资源，类似现金；<br><strong>Proof of Possession(PoP) Token</strong> ：可以校验client是否对Token有明确的拥有权；</p><h4 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h4><p>1、更安全，客户端不接触用户密码，服务器端更易集中保护；<br>2、广泛传播并被持续采用；<br>3、短寿命和封装的token；<br>4、资源服务器和授权服务器解耦；<br>5、集中式授权，简化客户端；<br>6、HTTP/JSON友好，易于请求和传递token；<br>7、考虑多种客户端架构场景 客户可以具有不同的信任级别；</p><h4 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h4><p>协议框架太宽泛，造成各种实现的<strong>兼容性和互操作性差</strong>，<strong>不是一个认证协议</strong>，本身并不能告诉你任何用户信息。</p><h3 id="Oauth2授权模式"><a href="#Oauth2授权模式" class="headerlink" title="Oauth2授权模式"></a>Oauth2授权模式</h3><h4 id="授权码模式（Authorization-Code）"><a href="#授权码模式（Authorization-Code）" class="headerlink" title="授权码模式（Authorization Code）"></a>授权码模式（Authorization Code）</h4><ul><li>第一步：用户访问页面</li><li>第二步：访问的页面将请求重定向到认证服务器</li><li>第三步：认证服务器向用户展示授权页面，等待用户授权</li><li>第四步：用户授权，认证服务器生成一个code和带上client_id发送给应用服务器； 然后，应用服务器拿到code，并用client_id去后台查询对应的client_secret</li><li>第五步：将code、client_id、client_secret传给认证服务器换取access_token和  refresh_token</li><li>第六步：将access_token和refresh_token传给应用服务器</li><li>第七步：验证token，访问真正的资源页面</li></ul><p><strong>优点：安全性、token可以刷新，避免多次登录、安全信息存在应用服务器暴露风险小，安全性高</strong><br><strong>缺点：多次请求</strong><br><strong>应用场景：目前市面上主流的第三方认证都是这个模式</strong></p><p><img src="/article/5253b791/Oauth2_04.png" alt="img"></p><h4 id="简化授权模式（Implicit）"><a href="#简化授权模式（Implicit）" class="headerlink" title="简化授权模式（Implicit）"></a>简化授权模式（Implicit）</h4><ul><li>第一步：用户访问页面时，重定向到认证服务器。</li><li>第二步：认证服务器给用户一个认证页面，等待用户授权。</li><li>第三步：用户授权，认证服务器想应用页面返回Token</li><li>第四步：验证Token，访问真正的资源页面</li></ul><p><strong>优点：简单</strong><br><strong>缺点：应用没有后台、不能存储refresh_token的必要信息、Token暴露风险</strong><br><strong>应用场景：应用只有页面没有后台管理，只能使用第三方认证后直接访问，例如：调查问卷，评论</strong></p><p><img src="/article/5253b791/Oauth2_05.png" alt="img"></p><h4 id="密码模式（Resource-Owner-PasswordCredentials）"><a href="#密码模式（Resource-Owner-PasswordCredentials）" class="headerlink" title="密码模式（Resource Owner PasswordCredentials）"></a>密码模式（Resource Owner PasswordCredentials）</h4><ul><li>第一步：用户访问用页面时，输入第三方认证所需要的信息(QQ/微信账号密码)</li><li>第二步：应用页面那种这个信息去认证服务器授权</li><li>第三步：认证服务器授权通过，拿到token，访问真正的资源页面</li></ul><p><strong>优点：不需要多次请求转发，额外开销，同时可以获取更多的用户信息。(都拿到账号密码了)</strong><br><strong>缺点：局限性，认证服务器和应用方必须有超高的信赖。(比如亲兄弟？)</strong><br><strong>应用场景：自家公司搭建的认证服务器</strong></p><p><img src="/article/5253b791/Oauth2_06.png" alt="img"></p><h4 id="客户端模式（Client-Credentials）"><a href="#客户端模式（Client-Credentials）" class="headerlink" title="客户端模式（Client Credentials）"></a>客户端模式（Client Credentials）</h4><ul><li>第一步：用户访问应用客户端</li><li>第二步：通过客户端定义的验证方法，拿到token，无需授权</li><li>第三步：访问资源服务器A</li><li>第四步：拿到一次token就可以畅通无阻的访问其他的资源页面。</li></ul><p><strong>这是一种最简单的模式，只要client请求，我们就将AccessToken发送给它。这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任，而client本身也是安全的。</strong></p><p><strong>因此这种模式一般用来提供给我们完全信任的服务器端服务。在这个过程中不需要用户的参与。</strong></p><p><img src="/article/5253b791/Oauth2_07.png" alt="img"></p><h3 id="Spring-Security-Oauth2架构"><a href="#Spring-Security-Oauth2架构" class="headerlink" title="Spring Security Oauth2架构"></a>Spring Security Oauth2架构</h3><p><img src="/article/5253b791/Oauth2_03.png" alt="image-20201228150603638"></p><h4 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h4><p>1、用户访问,此时没有Token。Oauth2RestTemplate会报错，这个报错信息会被 Oauth2ClientContextFilter捕获并重定向到认证服务器<br>2、认证服务器通过Authorization Endpoint进行授权，并通过AuthorizationServerTokenServices生 成授权码并返回给客户端<br>3、客户端拿到授权码去认证服务器通过Token Endpoint调用AuthorizationServerTokenServices生 成Token并返回给客户端<br>4、客户端拿到Token去资源服务器访问资源，一般会通过Oauth2AuthenticationManager调用 ResourceServerTokenServices进行校验。校验通过可以获取资源。</p><h3 id="Spring-Security-Oauth2授权码模式"><a href="#Spring-Security-Oauth2授权码模式" class="headerlink" title="Spring Security Oauth2授权码模式"></a>Spring Security Oauth2授权码模式</h3><h4 id="1、添加依赖"><a href="#1、添加依赖" class="headerlink" title="1、添加依赖"></a>1、添加依赖</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.teng<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>demo<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>demo<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">description</span>&gt;</span>Demo project for Spring Boot<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">java.version</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">java.version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">project.build.sourceEncoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">project.build.sourceEncoding</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">project.reporting.outputEncoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">project.reporting.outputEncoding</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">spring-boot.version</span>&gt;</span>2.3.4.RELEASE<span class="tag">&lt;/<span class="name">spring-boot.version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">spring-cloud.version</span>&gt;</span>Hoxton.SR8<span class="tag">&lt;/<span class="name">spring-cloud.version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-oauth2<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-security<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.junit.vintage<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>junit-vintage-engine<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencyManagement</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-dependencies<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;spring-cloud.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">type</span>&gt;</span>pom<span class="tag">&lt;/<span class="name">type</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">scope</span>&gt;</span>import<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-dependencies<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;spring-boot.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">type</span>&gt;</span>pom<span class="tag">&lt;/<span class="name">type</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">scope</span>&gt;</span>import<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencyManagement</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.8.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">source</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">source</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">target</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">target</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">encoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">encoding</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.3.4.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">mainClass</span>&gt;</span>com.teng.demo.DemoApplication<span class="tag">&lt;/<span class="name">mainClass</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">id</span>&gt;</span>repackage<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">goal</span>&gt;</span>repackage<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2、实体类"><a href="#2、实体类" class="headerlink" title="2、实体类"></a>2、实体类</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">User</span> <span class="keyword">implements</span> <span class="title">UserDetails</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String username;</span><br><span class="line">    <span class="keyword">private</span> String password;</span><br><span class="line">    <span class="keyword">private</span> List&lt;GrantedAuthority&gt; authorities;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">User</span><span class="params">(String username, String password, List&lt;GrantedAuthority&gt; authorities)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.username = username;</span><br><span class="line">        <span class="keyword">this</span>.password = password;</span><br><span class="line">        <span class="keyword">this</span>.authorities = authorities;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Collection&lt;? extends GrantedAuthority&gt; getAuthorities() &#123;</span><br><span class="line">        <span class="keyword">return</span> authorities;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getPassword</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> password;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getUsername</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> username;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isAccountNonExpired</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isAccountNonLocked</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isCredentialsNonExpired</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isEnabled</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3、编写service"><a href="#3、编写service" class="headerlink" title="3、编写service"></a>3、编写service</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserService</span> <span class="keyword">implements</span> <span class="title">UserDetailsService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> PasswordEncoder passwordEncoder;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> UserDetails <span class="title">loadUserByUsername</span><span class="params">(String username)</span> <span class="keyword">throws</span> UsernameNotFoundException </span>&#123;</span><br><span class="line"></span><br><span class="line">        String pwd = passwordEncoder.encode(<span class="string">"123456"</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> User(username,pwd, AuthorityUtils.commaSeparatedStringToAuthorityList(<span class="string">"admin"</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="4、编写控制层"><a href="#4、编写控制层" class="headerlink" title="4、编写控制层"></a>4、编写控制层</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/getCurrentUser"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">getCurrentUser</span><span class="params">(Authentication authentication)</span></span>&#123;</span><br><span class="line"><span class="comment">//        返回自定义的登陆类</span></span><br><span class="line">        <span class="keyword">return</span> authentication.getPrincipal();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="5、编写配置类"><a href="#5、编写配置类" class="headerlink" title="5、编写配置类"></a>5、编写配置类</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableWebSecurity</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SecurityConfig</span> <span class="keyword">extends</span> <span class="title">WebSecurityConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception</span>&#123;</span><br><span class="line">    http.csrf()</span><br><span class="line">            .disable()</span><br><span class="line">            .authorizeRequests()</span><br><span class="line">            .antMatchers(<span class="string">"/oauth/**"</span>,<span class="string">"/login/**"</span>,<span class="string">"/logout/**"</span>)</span><br><span class="line">            .permitAll()</span><br><span class="line">            .anyRequest()</span><br><span class="line">            .authenticated()</span><br><span class="line">            .and()</span><br><span class="line">            .formLogin()</span><br><span class="line">            .permitAll();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> PasswordEncoder <span class="title">passwordEncoder</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> BCryptPasswordEncoder();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="授权服务器"><a href="#授权服务器" class="headerlink" title="授权服务器"></a>授权服务器</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAuthorizationServer</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AuthorizationServiceConfig</span> <span class="keyword">extends</span> <span class="title">AuthorizationServerConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> PasswordEncoder passwordEncoder;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    授权服务器</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(ClientDetailsServiceConfigurer clients)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        clients.inMemory()</span><br><span class="line">                .withClient(<span class="string">"client"</span>)<span class="comment">//客户端id</span></span><br><span class="line">                .secret(passwordEncoder.encode(<span class="string">"112233"</span>))<span class="comment">//密钥</span></span><br><span class="line">                .accessTokenValiditySeconds(<span class="number">60</span>*<span class="number">60</span>)<span class="comment">//token的有效时间</span></span><br><span class="line">                .refreshTokenValiditySeconds(<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>*<span class="number">100</span>)<span class="comment">//刷新token有效时间</span></span><br><span class="line">                .redirectUris(<span class="string">"http://www.baidu.com"</span>)<span class="comment">//重定向地址</span></span><br><span class="line">                .scopes(<span class="string">"all"</span>)<span class="comment">//授权范围</span></span><br><span class="line">                <span class="comment">//授权类型</span></span><br><span class="line">                .authorizedGrantTypes(<span class="string">"authorization_code"</span>);<span class="comment">//授权码模式</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="资源服务器-1"><a href="#资源服务器-1" class="headerlink" title="资源服务器"></a>资源服务器</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableResourceServer</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ResourceServerConfig</span> <span class="keyword">extends</span> <span class="title">ResourceServerConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    配置资源服务器</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        http.csrf().disable()</span><br><span class="line">                .authorizeRequests()</span><br><span class="line">                .anyRequest()</span><br><span class="line">                .authenticated()</span><br><span class="line">                .and()</span><br><span class="line">                .requestMatchers()</span><br><span class="line">                .antMatchers(<span class="string">"/user/**"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>获取授权码</strong>：<a href="http://localhost:8080/oauth/authorize?response_type=code&amp;client_id=client&amp;redirect_uri=http://www.baidu.com&amp;scope=all" target="_blank" rel="noopener">http://localhost:8080/oauth/authorize?response_type=code&amp;client_id=client&amp;redirect_uri=http://www.baidu.com&amp;scope=all</a></p><h4 id="6、根据授权码获取令牌（POST请求）"><a href="#6、根据授权码获取令牌（POST请求）" class="headerlink" title="6、根据授权码获取令牌（POST请求）"></a>6、根据授权码获取令牌（POST请求）</h4><p><img src="/article/5253b791/Oauth2_08.png" alt="img"></p><p><img src="/article/5253b791/Oauth2_09.png" alt="img"></p><h4 id="7、根据token去资源服务器拿资源"><a href="#7、根据token去资源服务器拿资源" class="headerlink" title="7、根据token去资源服务器拿资源"></a>7、根据token去资源服务器拿资源</h4><p><img src="/article/5253b791/Oauth2_10.png" alt="img"></p><h3 id="Spring-Security-Oauth2-密码模式"><a href="#Spring-Security-Oauth2-密码模式" class="headerlink" title="Spring Security Oauth2 密码模式"></a>Spring Security Oauth2 密码模式</h3><h4 id="1、修改配置类"><a href="#1、修改配置类" class="headerlink" title="1、修改配置类"></a>1、修改配置类</h4><h5 id="SecurityConfig"><a href="#SecurityConfig" class="headerlink" title="SecurityConfig"></a>SecurityConfig</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableWebSecurity</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SecurityConfig</span> <span class="keyword">extends</span> <span class="title">WebSecurityConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception</span>&#123;</span><br><span class="line">    http.csrf()</span><br><span class="line">            .disable()</span><br><span class="line">            .authorizeRequests()</span><br><span class="line">            .antMatchers(<span class="string">"/oauth/**"</span>,<span class="string">"/login/**"</span>,<span class="string">"/logout/**"</span>)</span><br><span class="line">            .permitAll()</span><br><span class="line">            .anyRequest()</span><br><span class="line">            .authenticated()</span><br><span class="line">            .and()</span><br><span class="line">            .formLogin()</span><br><span class="line">            .permitAll();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> AuthenticationManager <span class="title">authenticationanager</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">         <span class="keyword">return</span>  <span class="keyword">super</span>.authenticationManager();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> PasswordEncoder <span class="title">passwordEncoder</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> BCryptPasswordEncoder();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="授权服务器-1"><a href="#授权服务器-1" class="headerlink" title="授权服务器"></a>授权服务器</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAuthorizationServer</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AuthorizationServiceConfig</span> <span class="keyword">extends</span> <span class="title">AuthorizationServerConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> PasswordEncoder passwordEncoder;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AuthenticationManager authenicationManager;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//使用密码模式配置    </span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(AuthorizationServerEndpointsConfigurer endpoints)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        endpoints.authenticationManager(authenicationManager)</span><br><span class="line">                .userDetailsService(userService);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    授权服务器</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(ClientDetailsServiceConfigurer clients)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        clients.inMemory()</span><br><span class="line">                .withClient(<span class="string">"client"</span>)<span class="comment">//客户端id</span></span><br><span class="line">                .secret(passwordEncoder.encode(<span class="string">"112233"</span>))<span class="comment">//密钥</span></span><br><span class="line">                .accessTokenValiditySeconds(<span class="number">60</span>*<span class="number">60</span>)<span class="comment">//token的有效时间</span></span><br><span class="line">                .refreshTokenValiditySeconds(<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>*<span class="number">100</span>)<span class="comment">//刷新token有效时间</span></span><br><span class="line">                .redirectUris(<span class="string">"http://www.baidu.com"</span>)<span class="comment">//重定向地址</span></span><br><span class="line">                .scopes(<span class="string">"all"</span>)<span class="comment">//授权范围</span></span><br><span class="line">                <span class="comment">//授权类型</span></span><br><span class="line">                .authorizedGrantTypes(<span class="string">"authorization_code"</span>,<span class="string">"password"</span>);<span class="comment">//授权码模式</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2、测试"><a href="#2、测试" class="headerlink" title="2、测试"></a>2、测试</h4><p><img src="/article/5253b791/Oauth2_11.png" alt="img"></p><p><img src="/article/5253b791/Oauth2_12.png" alt="img"></p><h2 id="JWT"><a href="#JWT" class="headerlink" title="JWT"></a>JWT</h2><h3 id="常见的认证机制"><a href="#常见的认证机制" class="headerlink" title="常见的认证机制"></a>常见的认证机制</h3><p><strong>HTTP Basic Auth</strong><br>HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password，简言之， Basic Auth是配合RESTful API 使用的最简单的认证方式，只需提供用户名密码即可，但由于有把用户 名密码暴露给第三方客户端的风险，在生产环境下被使用的越来越少。因此，在开发对外开放的 RESTful API时，尽量避免采用HTTP Basic Auth。</p><p><strong>Cookie Auth</strong><br>Cookie认证机制就是为一次请求认证在服务端创建一个Session对象，同时在客户端的浏览器端创建 了一个Cookie对象；通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理 的。默认的，当我们关闭浏览器的时候，cookie会被删除。但可以通过修改cookie 的expire time使 cookie在一定时间内有效。</p><p><img src="/article/5253b791/JWT_02.png" alt="image-20201228163655908"></p><p><strong>OAuth</strong><br>OAuth（开放授权,Open Authorization）是一个开放的授权标准，允许用户让第三方应用访问该用 户在某一web服务上存储的私密的资源（如照片，视频，联系人列表），而无需将用户名和密码提供给 第三方应用。如网站通过微信、微博登录等，主要用于第三方登录。<br>OAuth允许用户提供一个令牌，而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一 个令牌授权一个特定的第三方系统（例如，视频编辑网站)在特定的时段（例如，接下来的2小时内）内 访问特定的资源（例如仅仅是某一相册中的视频）。这样，OAuth让用户可以授权第三方网站访问他们 存储在另外服务提供者的某些特定信息，而非所有内容。</p><p><img src="/article/5253b791/JWT_01.png" alt="img"></p><p>这种基于OAuth的认证机制适用于个人消费者类的互联网产品，如社交类APP等应用，但是不太适合 拥有自有认证权限管理的企业应用。<br><strong>缺点</strong>：过重。</p><p><strong>Token Auth</strong><br>使用基于 Token 的身份验证方法，在服务端不需要存储用户的登录记录。大概的流程是这样的：</p><p>1、客户端使用用户名跟密码请求登录<br>2、服务端收到请求，去验证用户名与密码<br>3、验证成功后，服务端会签发一个 Token，再把这个 Token 发送给客户端<br>4、客户端收到 Token 以后可以把它存储起来，比如放在 Cookie 里<br>5、客户端每次向服务端请求资源的时候需要带着服务端签发的 Token<br>6、服务端收到请求，然后去验证客户端请求里面带着的 Token，如果验证成功，就向客户端返回请 求的数据</p><p><strong>Token Auth的优点</strong>（Token机制相对于Cookie机制又有什么好处呢？）： </p><p>1、支持跨域访问: Cookie是不允许垮域访问的，这一点对Token机制是不存在的，前提是传输的用户 认证信息通过HTTP头传输.<br>2、无状态(也称：服务端可扩展行):Token机制在服务端不需要存储session信息，因为Token 自身包 含了所有登录用户的信息，只需要在客户端的cookie或本地介质存储状态信息.<br>3、更适用CDN: 可以通过内容分发网络请求你服务端的所有资料（如：javascript，HTML,图片 等），而你的服务端只要提供API即可.<br>4、去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成，只要在你的API被调用 的时候，你可以进行Token生成调用即可.<br>5、更适用于移动应用: 当你的客户端是一个原生平台（iOS, Android，Windows 10等）时，Cookie 是不被支持的（你需要通过Cookie容器进行处理），这时采用Token认证机制就会简单得多。<br>6、CSRF:因为不再依赖于Cookie，所以你就不需要考虑对CSRF（跨站请求伪造）的防范。<br>7、性能: 一次网络往返时间（通过数据库查询session信息）总比做一次HMACSHA256计算的Token 验证和解析要费时得多.<br>8、不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候，不再需要为登录页面做 特殊处理.<br>9、基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库 （.NET, Ruby, Java,Python, PHP）和多家公司的支持（如：Firebase,Google, Microsoft）.</p><h3 id="JWT简介"><a href="#JWT简介" class="headerlink" title="JWT简介"></a>JWT简介</h3><p>JSON Web Token（JWT）是一个开放的行业标准（RFC 7519），它定义了一种简介的、自包含的协 议格式，用于在通信双方传递json对象，传递的信息经过数字签名可以被验证和信任。JWT可以使用 HMAC算法或使用RSA的公钥/私钥对来签名，防止被篡改。</p><p>官网： <a href="https://jwt.io/" target="_blank" rel="noopener">https://jwt.io/</a> 标准： <a href="https://tools.ietf.org/html/rfc7519" target="_blank" rel="noopener">https://tools.ietf.org/html/rfc7519</a></p><p><strong>JWT令牌的优点</strong>：</p><p>1、jwt基于json，非常方便解析。<br>2、可以在令牌中自定义丰富的内容，易扩展。<br>3、通过非对称加密算法及数字签名技术，JWT防止篡改，安全性高。<br>4、资源服务使用JWT可不依赖认证服务即可完成授权。 </p><p><strong>缺点</strong>：<br>1、 JWT令牌较长，占存储空间比较大。</p><h3 id="JWT组成："><a href="#JWT组成：" class="headerlink" title="JWT组成："></a>JWT组成：</h3><p>一个JWT实际上就是一个字符串，它由三部分组成，头部、载荷与签名。</p><h4 id="头部-Header"><a href="#头部-Header" class="headerlink" title="头部(Header)"></a>头部(Header)</h4><p>头部用于描述关于该JWT的最基本的信息，例如其类型（即JWT）以及签名所用的算法（如HMAC SHA256或RSA）等。这也可以被表示成一个JSON对象。</p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">typ</span>: <span class="string">是类型。</span></span><br><span class="line"><span class="attr">alg</span>: <span class="string">签名的算法，这里使用的算法是HS256算法</span></span><br></pre></td></tr></table></figure><p><strong>Base64</strong> 是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64，所以每6 个比特为一个单元，对应某个可打印字符。三个字节有24个比特，对应于4个Base64单元，即3个字节 需要用4个可打印字符来表示。JDK 中提供了非常方便的 BASE64Encoder 和 BASE64Decoder ，用它们 可以非常方便的完成基于 BASE64 的编码和解码。</p><h4 id="负载-Payload"><a href="#负载-Payload" class="headerlink" title="负载(Payload)"></a>负载(Payload)</h4><p>第二部分是负载，就是存放有效信息的地方。这个名字像是特指飞机上承载的货品，这些有效信息包含 三个部分： </p><p><strong>标准中注册的声明（建议但不强制使用）</strong></p><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">iss</span>: <span class="string">jwt签发者</span></span><br><span class="line"><span class="attr">sub</span>: <span class="string">jwt所面向的用户</span></span><br><span class="line"><span class="attr">aud</span>: <span class="string">接收jwt的一方</span></span><br><span class="line"><span class="attr">exp</span>: <span class="string">jwt的过期时间，这个过期时间必须要大于签发时间</span></span><br><span class="line"><span class="attr">nbf</span>: <span class="string">定义在什么时间之前，该jwt都是不可用的.</span></span><br><span class="line"><span class="attr">iat</span>: <span class="string">jwt的签发时间</span></span><br><span class="line"><span class="attr">jti</span>: <span class="string">jwt的唯一身份标识，主要用来作为一次性token,从而回避重放攻击。</span></span><br></pre></td></tr></table></figure><p><strong>公共的声明</strong><br>公共的声明可以添加任何的信息，一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加 敏感信息，因为该部分在客户端可解密. </p><p><strong>私有的声明</strong><br>私有声明是提供者和消费者所共同定义的声明，一般不建议存放敏感信息，因为base64是对称解密 的，意味着该部分信息可以归类为明文信息。</p><h4 id="签证、签名（signature）"><a href="#签证、签名（signature）" class="headerlink" title="签证、签名（signature）"></a>签证、签名（signature）</h4><p>jwt的第三部分是一个签证信息，这个签证信息由三部分组成：<br>1、header (base64后的)<br>2、payload (base64后的)<br>3、secret（盐，一定要保密）</p><p>这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串，然后通过 header中声明的加密方式进行加盐secret组合加密。</p><p><strong>注意</strong>： secret 是保存在服务器端的， jwt 的签发生成也是在服务器端的， secret 就是用来进行 jwt 的签发和 jwt 的验证，所以，它就是你服务端的私钥，在任何场景都不应该流露出去。一旦客户端得知 这个 secret , 那就意味着客户端是可以自我签发 jwt 了。</p><h3 id="JJWT简介"><a href="#JJWT简介" class="headerlink" title="JJWT简介"></a>JJWT简介</h3><p>JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License，版本2.0)，JJW 很容易使用和理解。它被设计成一个以建筑为中心的流畅界面，隐藏了它的大部分复杂性。<br>规范官网：<a href="https://jwt.io/" target="_blank" rel="noopener">https://jwt.io/</a></p><h3 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h3><h4 id="1、添加依赖-1"><a href="#1、添加依赖-1" class="headerlink" title="1、添加依赖"></a>1、添加依赖</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.teng<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>springboot_jwt<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>springboot_jwt<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">description</span>&gt;</span>Demo project for Spring Boot<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">java.version</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">java.version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">project.build.sourceEncoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">project.build.sourceEncoding</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">project.reporting.outputEncoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">project.reporting.outputEncoding</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">spring-boot.version</span>&gt;</span>2.3.7.RELEASE<span class="tag">&lt;/<span class="name">spring-boot.version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">spring-cloud.version</span>&gt;</span>Hoxton.SR8<span class="tag">&lt;/<span class="name">spring-cloud.version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-oauth2<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-security<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!--JWT依赖--&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.9.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.security<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-security-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.junit.vintage<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>junit-vintage-engine<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencyManagement</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-dependencies<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;spring-cloud.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">type</span>&gt;</span>pom<span class="tag">&lt;/<span class="name">type</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">scope</span>&gt;</span>import<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-dependencies<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;spring-boot.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">type</span>&gt;</span>pom<span class="tag">&lt;/<span class="name">type</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">scope</span>&gt;</span>import<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencyManagement</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.8.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">source</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">source</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">target</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">target</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">encoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">encoding</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.3.7.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">mainClass</span>&gt;</span>com.teng.springboot_jwt.SpringbootJwtApplication<span class="tag">&lt;/<span class="name">mainClass</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">id</span>&gt;</span>repackage<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">goal</span>&gt;</span>repackage<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2、测试Token"><a href="#2、测试Token" class="headerlink" title="2、测试Token"></a>2、测试Token</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootTest</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SpringbootJwtApplicationTests</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    获取token</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">testJWT</span><span class="params">()</span></span>&#123;</span><br><span class="line">        JwtBuilder jwtBuilder = Jwts.builder()</span><br><span class="line"><span class="comment">//                唯一ID</span></span><br><span class="line">                .setId(<span class="string">"123"</span>)</span><br><span class="line"><span class="comment">//                接受用户</span></span><br><span class="line">                .setSubject(<span class="string">"Rose"</span>)</span><br><span class="line"><span class="comment">//                签发时间</span></span><br><span class="line">                .setIssuedAt(<span class="keyword">new</span> Date())</span><br><span class="line"><span class="comment">//                签名算法，密钥(盐)，key最短四个字符！！！</span></span><br><span class="line">                .signWith(SignatureAlgorithm.HS256,<span class="string">"xxxx"</span>);</span><br><span class="line"><span class="comment">//        签发token</span></span><br><span class="line">        String token = jwtBuilder.compact();</span><br><span class="line">        System.out.println(token);</span><br><span class="line"></span><br><span class="line"><span class="comment">//        使用base64解密</span></span><br><span class="line">        String[] split = token.split(<span class="string">"\\."</span>);</span><br><span class="line">        System.out.println(Base64Codec.BASE64.decodeToString(split[<span class="number">0</span>]));</span><br><span class="line">        System.out.println(Base64Codec.BASE64.decodeToString(split[<span class="number">1</span>]));</span><br><span class="line"><span class="comment">//        解密乱码</span></span><br><span class="line">        System.out.println(Base64Codec.BASE64.decodeToString(split[<span class="number">2</span>]));</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//    解析token</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">testParseToKen</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        String token = <span class="string">"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjMiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjA4OTU2MjM5fQ.TPVSJPUL1mVNRKzMtly9BnYmZqjXS9LUN-xVUyz4RMA"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//        解析token,获取Claims对象(jwt中负载申明的对象)</span></span><br><span class="line">        Claims claims = (Claims)Jwts.parser()</span><br><span class="line">                <span class="comment">//密钥必须一致</span></span><br><span class="line">                .setSigningKey(<span class="string">"xxxx"</span>)</span><br><span class="line">                .parse(token)</span><br><span class="line">                .getBody();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//打印声明的属性</span></span><br><span class="line">        System.out.println(<span class="string">"id:"</span>+claims.getId());</span><br><span class="line">        System.out.println(<span class="string">"subject:"</span>+claims.getSubject());</span><br><span class="line">        System.out.println(<span class="string">"issuedAt:"</span>+claims.getIssuedAt());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//    生成JWT(设置失效时间)</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">testJWTHasExp</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">//当前系统时间的长整型</span></span><br><span class="line">        <span class="keyword">long</span> now = System.currentTimeMillis();</span><br><span class="line">        <span class="comment">//过期时间，这里是3分钟后的时间长整型</span></span><br><span class="line">        <span class="keyword">long</span> exp = now + <span class="number">60</span> * <span class="number">1000</span> * <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">        JwtBuilder jwtBuilder = Jwts.builder()</span><br><span class="line"><span class="comment">//                唯一ID</span></span><br><span class="line">                .setId(<span class="string">"123"</span>)</span><br><span class="line"><span class="comment">//                接受用户</span></span><br><span class="line">                .setSubject(<span class="string">"Rose"</span>)</span><br><span class="line"><span class="comment">//                签发时间</span></span><br><span class="line">                .setIssuedAt(<span class="keyword">new</span> Date())</span><br><span class="line"><span class="comment">//                签名算法，密钥(盐)，key最短四个字符！！！</span></span><br><span class="line">                .signWith(SignatureAlgorithm.HS256,<span class="string">"xxxx"</span>)</span><br><span class="line">                .setExpiration(<span class="keyword">new</span> Date(exp));</span><br><span class="line"></span><br><span class="line"><span class="comment">//        签发token</span></span><br><span class="line">        String token = jwtBuilder.compact();</span><br><span class="line">        System.out.println(token);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">//    解析token(设置失效时间)</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">testParseToKenHasExp</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        String token = <span class="string">"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjMiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjA4OTY0MDE1LCJleHAiOjE2MDg5NjQxOTV9.6apIX0KiSFxTexJMvpG2aq2N5lnAXg13hd-ls-GZpWg"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//        解析token,获取Claims对象(jwt中负载申明的对象)</span></span><br><span class="line">        Claims claims = (Claims)Jwts.parser()</span><br><span class="line">                <span class="comment">//密钥必须一致</span></span><br><span class="line">                .setSigningKey(<span class="string">"xxxx"</span>)</span><br><span class="line">                .parse(token)</span><br><span class="line">                .getBody();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//打印声明的属性</span></span><br><span class="line">        System.out.println(<span class="string">"id:"</span>+claims.getId());</span><br><span class="line">        System.out.println(<span class="string">"subject:"</span>+claims.getSubject());</span><br><span class="line">        System.out.println(<span class="string">"issuedAt:"</span>+claims.getIssuedAt());</span><br><span class="line">        DateFormat sf =<span class="keyword">new</span> SimpleDateFormat(<span class="string">"yyyy-MM-dd HH:mm:ss"</span>);</span><br><span class="line">        System.out.println(<span class="string">"签发时间:"</span>+sf.format(claims.getIssuedAt()));</span><br><span class="line">        System.out.println(<span class="string">"过期时间:"</span>+sf.format(claims.getExpiration()));</span><br><span class="line">        System.out.println(<span class="string">"当前时间:"</span>+sf.format(<span class="keyword">new</span> Date()));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">//    获取token(自定义信息)</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">testJWTByClaims</span><span class="params">()</span></span>&#123;</span><br><span class="line">        JwtBuilder jwtBuilder = Jwts.builder()</span><br><span class="line"><span class="comment">//                唯一ID</span></span><br><span class="line">                .setId(<span class="string">"123"</span>)</span><br><span class="line"><span class="comment">//                接受用户</span></span><br><span class="line">                .setSubject(<span class="string">"Rose"</span>)</span><br><span class="line"><span class="comment">//                创建时间</span></span><br><span class="line">                .setIssuedAt(<span class="keyword">new</span> Date())</span><br><span class="line"><span class="comment">//                签名算法，密钥(盐)，key最短四个字符！！！</span></span><br><span class="line">                .signWith(SignatureAlgorithm.HS256,<span class="string">"xxxx"</span>)</span><br><span class="line"><span class="comment">//                传入map</span></span><br><span class="line">                .claim(<span class="string">"roles"</span>,<span class="string">"admin"</span>)</span><br><span class="line">                .claim(<span class="string">"logo"</span>,<span class="string">"shsxt.jpg"</span>);</span><br><span class="line"><span class="comment">//        签发token</span></span><br><span class="line">        String token = jwtBuilder.compact();</span><br><span class="line">        System.out.println(token);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">//    解析token(自定义信息)</span></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">testParseToKenByClaims</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        String token = <span class="string">"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMjMiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjA4OTY0ODAyLCJyb2xlcyI6ImFkbWluIiwibG9nbyI6InNoc3h0LmpwZyJ9.3gKqFbfyYYZN6YUSmhU0McztMW6RFbLofxkkvdjL4iw"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//        解析token,获取Claims对象(jwt中负载申明的对象)</span></span><br><span class="line">        Claims claims = Jwts.parser()</span><br><span class="line">                <span class="comment">//密钥必须一致</span></span><br><span class="line">                .setSigningKey(<span class="string">"xxxx"</span>)</span><br><span class="line">                .parseClaimsJws(token)</span><br><span class="line">                .getBody();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//打印声明的属性</span></span><br><span class="line">        System.out.println(<span class="string">"id:"</span>+claims.getId());</span><br><span class="line">        System.out.println(<span class="string">"subject:"</span>+claims.getSubject());</span><br><span class="line">        System.out.println(<span class="string">"issuedAt:"</span>+claims.getIssuedAt());</span><br><span class="line">        System.out.println(<span class="string">"roles:"</span>+claims.get(<span class="string">"roles"</span>));</span><br><span class="line">        System.out.println(<span class="string">"logo:"</span>+claims.get(<span class="string">"logo"</span>));</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Spring-Security-Oauth2-整合JWT"><a href="#Spring-Security-Oauth2-整合JWT" class="headerlink" title="Spring Security Oauth2 整合JWT"></a>Spring Security Oauth2 整合JWT</h3><h4 id="1、添加配置文件"><a href="#1、添加配置文件" class="headerlink" title="1、添加配置文件"></a>1、添加配置文件</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JwtTokenStoreConfig</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> TokenStore <span class="title">jwtTokenStore</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> JwtTokenStore(jwtAccessTokenConverter());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> JwtAccessTokenConverter <span class="title">jwtAccessTokenConverter</span><span class="params">()</span></span>&#123;</span><br><span class="line">        JwtAccessTokenConverter accessTokenConverter = <span class="keyword">new</span></span><br><span class="line">                JwtAccessTokenConverter();</span><br><span class="line">        <span class="comment">//配置JWT使用的秘钥</span></span><br><span class="line">        accessTokenConverter.setSigningKey(<span class="string">"test_key"</span>);</span><br><span class="line">        <span class="keyword">return</span> accessTokenConverter;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2、授权服务器"><a href="#2、授权服务器" class="headerlink" title="2、授权服务器"></a>2、授权服务器</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAuthorizationServer</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AuthorizationServiceConfig</span> <span class="keyword">extends</span> <span class="title">AuthorizationServerConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> PasswordEncoder passwordEncoder;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AuthenticationManager authenicationManager;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"jwtTokenStore"</span>)<span class="comment">//指定</span></span><br><span class="line">    <span class="keyword">private</span> TokenStore tokenStore;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JwtAccessTokenConverter jwtAccessTokenConverter;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     *  密码模式</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> endpoints</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(AuthorizationServerEndpointsConfigurer endpoints)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        endpoints.authenticationManager(authenicationManager)</span><br><span class="line">                .userDetailsService(userService)</span><br><span class="line">                <span class="comment">//accessToken转换为JWTtoken</span></span><br><span class="line">                .tokenStore(tokenStore)</span><br><span class="line">                .accessTokenConverter(jwtAccessTokenConverter);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    授权服务器</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(ClientDetailsServiceConfigurer clients)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        clients.inMemory()</span><br><span class="line">                .withClient(<span class="string">"client"</span>)<span class="comment">//客户端id</span></span><br><span class="line">                .secret(passwordEncoder.encode(<span class="string">"112233"</span>))<span class="comment">//密钥</span></span><br><span class="line">                .accessTokenValiditySeconds(<span class="number">60</span>)<span class="comment">//token的有效时间(一分钟)</span></span><br><span class="line">                .refreshTokenValiditySeconds(<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>)<span class="comment">//刷新token有效时间（一天）</span></span><br><span class="line">                .redirectUris(<span class="string">"http://www.baidu.com"</span>)<span class="comment">//重定向地址</span></span><br><span class="line">                .scopes(<span class="string">"all"</span>)<span class="comment">//授权范围</span></span><br><span class="line">                <span class="comment">//授权类型</span></span><br><span class="line">                .authorizedGrantTypes(<span class="string">"authorization_code"</span>,<span class="string">"password"</span>);<span class="comment">//授权码模式,密码模式</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>发现获取到的令牌已经变成了JWT令牌，将access_token拿到<a href="https://jwt.io/" target="_blank" rel="noopener">https://jwt.io/</a> 网站上去解析</p><h3 id="扩展JWT中存储的内容"><a href="#扩展JWT中存储的内容" class="headerlink" title="扩展JWT中存储的内容"></a>扩展JWT中存储的内容</h3><p>有时候我们需要扩展JWT中存储的内容，这里我们在JWT中扩展一个 key为enhance，value为 enhance info 的数据。</p><h4 id="1、添加扩展配置文件"><a href="#1、添加扩展配置文件" class="headerlink" title="1、添加扩展配置文件"></a>1、添加扩展配置文件</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JwtTokenEnhancer</span> <span class="keyword">implements</span> <span class="title">TokenEnhancer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * JWT拓展内容</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> oAuth2AccessToken</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> oAuth2Authentication</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> OAuth2AccessToken <span class="title">enhance</span><span class="params">(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication)</span> </span>&#123;</span><br><span class="line">        Map&lt;String,Object&gt; map= <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        map.put(<span class="string">"enhance"</span>,<span class="string">"enhance info"</span>);</span><br><span class="line">        ((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(map);</span><br><span class="line">        <span class="keyword">return</span> oAuth2AccessToken;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2、创建一个JwtTokenEnhancer实例"><a href="#2、创建一个JwtTokenEnhancer实例" class="headerlink" title="2、创建一个JwtTokenEnhancer实例"></a>2、创建一个JwtTokenEnhancer实例</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JwtTokenStoreConfig</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> TokenStore <span class="title">jwtTokenStore</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> JwtTokenStore(jwtAccessTokenConverter());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> JwtAccessTokenConverter <span class="title">jwtAccessTokenConverter</span><span class="params">()</span></span>&#123;</span><br><span class="line">        JwtAccessTokenConverter accessTokenConverter = <span class="keyword">new</span></span><br><span class="line">                JwtAccessTokenConverter();</span><br><span class="line">        <span class="comment">//配置JWT使用的秘钥</span></span><br><span class="line">        accessTokenConverter.setSigningKey(<span class="string">"test_key"</span>);</span><br><span class="line">        <span class="keyword">return</span> accessTokenConverter;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    JWT存储扩展内容</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> JwtTokenEnhancer <span class="title">jwtTokenEnhancer</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> JwtTokenEnhancer();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3、授权服务器配置中配置JWT的内容增强器"><a href="#3、授权服务器配置中配置JWT的内容增强器" class="headerlink" title="3、授权服务器配置中配置JWT的内容增强器"></a>3、授权服务器配置中配置JWT的内容增强器</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAuthorizationServer</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AuthorizationServiceConfig</span> <span class="keyword">extends</span> <span class="title">AuthorizationServerConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> PasswordEncoder passwordEncoder;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AuthenticationManager authenicationManager;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"jwtTokenStore"</span>)<span class="comment">//指定</span></span><br><span class="line">    <span class="keyword">private</span> TokenStore tokenStore;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JwtAccessTokenConverter jwtAccessTokenConverter;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JwtTokenEnhancer jwtTokenEnhancer;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     *  密码模式</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> endpoints</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(AuthorizationServerEndpointsConfigurer endpoints)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line"><span class="comment">//        设置JWT增强内容</span></span><br><span class="line">        TokenEnhancerChain chain = <span class="keyword">new</span> TokenEnhancerChain();</span><br><span class="line">        List&lt;TokenEnhancer&gt; delegates = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">        delegates.add(jwtTokenEnhancer);</span><br><span class="line">        delegates.add(jwtAccessTokenConverter);</span><br><span class="line">        chain.setTokenEnhancers(delegates);</span><br><span class="line"></span><br><span class="line">        endpoints.authenticationManager(authenicationManager)</span><br><span class="line">                .userDetailsService(userService)</span><br><span class="line">                <span class="comment">//accessToken转换为JWTtoken</span></span><br><span class="line">                .tokenStore(tokenStore)</span><br><span class="line">                .accessTokenConverter(jwtAccessTokenConverter)</span><br><span class="line">                <span class="comment">//添加增强内容</span></span><br><span class="line">                .tokenEnhancer(chain);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    授权服务器</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(ClientDetailsServiceConfigurer clients)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        clients.inMemory()</span><br><span class="line">                .withClient(<span class="string">"client"</span>)<span class="comment">//客户端id</span></span><br><span class="line">                .secret(passwordEncoder.encode(<span class="string">"112233"</span>))<span class="comment">//密钥</span></span><br><span class="line">                .accessTokenValiditySeconds(<span class="number">60</span>)<span class="comment">//token的有效时间(一分钟)</span></span><br><span class="line">                .refreshTokenValiditySeconds(<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>)<span class="comment">//刷新token有效时间（一天）</span></span><br><span class="line">                .redirectUris(<span class="string">"http://www.baidu.com"</span>)<span class="comment">//重定向地址</span></span><br><span class="line">                .scopes(<span class="string">"all"</span>)<span class="comment">//授权范围</span></span><br><span class="line">                <span class="comment">//授权类型</span></span><br><span class="line">                .authorizedGrantTypes(<span class="string">"authorization_code"</span>,<span class="string">"password"</span>);<span class="comment">//授权码模式,密码模式</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Java中解析JWT中的内容"><a href="#Java中解析JWT中的内容" class="headerlink" title="Java中解析JWT中的内容"></a>Java中解析JWT中的内容</h3><h4 id="1、添加依赖-2"><a href="#1、添加依赖-2" class="headerlink" title="1、添加依赖"></a>1、添加依赖</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--jwt 依赖--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.9.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2、修改控制文件"><a href="#2、修改控制文件" class="headerlink" title="2、修改控制文件"></a>2、修改控制文件</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/getCurrentUser"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">getCurrentUser</span><span class="params">(Authentication authentication, HttpServletRequest request)</span></span>&#123;</span><br><span class="line"><span class="comment">//        JJWT客户端解密</span></span><br><span class="line">        String header = request.getHeader(<span class="string">"Authorization"</span>);</span><br><span class="line">        String token = header.substring(header.lastIndexOf(<span class="string">"bearer"</span>) + <span class="number">7</span>);</span><br><span class="line">        <span class="keyword">return</span> Jwts.parser()</span><br><span class="line">                .setSigningKey(<span class="string">"test_key"</span>.getBytes(StandardCharsets.UTF_8))<span class="comment">//中文转换</span></span><br><span class="line">                .parseClaimsJws(token)</span><br><span class="line">                .getBody();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将令牌放入Authorization头中，访问如下地址获取信息： <a href="http://localhost:8080/user/getCurrentUser" target="_blank" rel="noopener">http://localhost:8080/user/getCurrentUser</a></p><p><img src="/article/5253b791/Oauth2_13.png" alt="img"></p><h3 id="刷新令牌"><a href="#刷新令牌" class="headerlink" title="刷新令牌"></a>刷新令牌</h3><p>在Spring Cloud Security 中使用oauth2时，如果令牌失效了，可以使用刷新令牌通过refresh_token 的授权模式再次获取access_token。<br>只需修改认证服务器的配置，添加refresh_token的授权模式即可。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//    授权服务器</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(ClientDetailsServiceConfigurer clients)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        clients.inMemory()</span><br><span class="line">                .withClient(<span class="string">"client"</span>)<span class="comment">//客户端id</span></span><br><span class="line">                .secret(passwordEncoder.encode(<span class="string">"112233"</span>))<span class="comment">//密钥</span></span><br><span class="line">                .accessTokenValiditySeconds(<span class="number">60</span>)<span class="comment">//token的有效时间(一分钟)</span></span><br><span class="line">                .refreshTokenValiditySeconds(<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>)<span class="comment">//刷新token有效时间（一天）</span></span><br><span class="line">                .redirectUris(<span class="string">"http://www.baidu.com"</span>)<span class="comment">//重定向地址</span></span><br><span class="line">                .scopes(<span class="string">"all"</span>)<span class="comment">//授权范围</span></span><br><span class="line">                <span class="comment">//授权类型</span></span><br><span class="line">                .authorizedGrantTypes(<span class="string">"authorization_code"</span>,<span class="string">"password"</span>,<span class="string">"refresh_token"</span>);<span class="comment">//授权码模式,密码模式,刷新令牌</span></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p><img src="/article/5253b791/Oauth2_14.png" alt="img"></p><h3 id="Spring-Security-Oauth2-整合单点登录（SSO）"><a href="#Spring-Security-Oauth2-整合单点登录（SSO）" class="headerlink" title="Spring Security Oauth2 整合单点登录（SSO）"></a>Spring Security Oauth2 整合单点登录（SSO）</h3><h4 id="创建客户端"><a href="#创建客户端" class="headerlink" title="创建客户端"></a>创建客户端</h4><h4 id="1、添加依赖-3"><a href="#1、添加依赖-3" class="headerlink" title="1、添加依赖"></a>1、添加依赖</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.teng<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>demo<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">name</span>&gt;</span>demo<span class="tag">&lt;/<span class="name">name</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">description</span>&gt;</span>Demo project for Spring Boot<span class="tag">&lt;/<span class="name">description</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">properties</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">java.version</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">java.version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">project.build.sourceEncoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">project.build.sourceEncoding</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">project.reporting.outputEncoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">project.reporting.outputEncoding</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">spring-boot.version</span>&gt;</span>2.3.7.RELEASE<span class="tag">&lt;/<span class="name">spring-boot.version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">spring-cloud.version</span>&gt;</span>Hoxton.SR8<span class="tag">&lt;/<span class="name">spring-cloud.version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">properties</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-oauth2<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-security<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.9.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">exclusions</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">exclusion</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.junit.vintage<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>junit-vintage-engine<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">exclusion</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">exclusions</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.security<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-security-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencyManagement</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-dependencies<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;spring-cloud.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">type</span>&gt;</span>pom<span class="tag">&lt;/<span class="name">type</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">scope</span>&gt;</span>import<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-dependencies<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;spring-boot.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">type</span>&gt;</span>pom<span class="tag">&lt;/<span class="name">type</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">scope</span>&gt;</span>import<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencyManagement</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">build</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">plugins</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.maven.plugins<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>maven-compiler-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.8.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">source</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">source</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">target</span>&gt;</span>1.8<span class="tag">&lt;/<span class="name">target</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">encoding</span>&gt;</span>UTF-8<span class="tag">&lt;/<span class="name">encoding</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">plugin</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.3.7.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">configuration</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">mainClass</span>&gt;</span>com.teng.demo.DemoApplication<span class="tag">&lt;/<span class="name">mainClass</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">configuration</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">executions</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;<span class="name">execution</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">id</span>&gt;</span>repackage<span class="tag">&lt;/<span class="name">id</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">goals</span>&gt;</span></span><br><span class="line">                            <span class="tag">&lt;<span class="name">goal</span>&gt;</span>repackage<span class="tag">&lt;/<span class="name">goal</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;/<span class="name">goals</span>&gt;</span></span><br><span class="line">                    <span class="tag">&lt;/<span class="name">execution</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">executions</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">plugin</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">plugins</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">build</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="2、修改配置文件"><a href="#2、修改配置文件" class="headerlink" title="2、修改配置文件"></a>2、修改配置文件</h4><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 应用服务 WEB 访问端口</span></span><br><span class="line"><span class="meta">server.port</span>=<span class="string">8081</span></span><br><span class="line"><span class="comment">#防止Cookie冲突，冲突会导致登录验证不通过</span></span><br><span class="line"><span class="meta">server.servlet.session.cookie.name</span>=<span class="string">OAUTH2-CLIENT-SESSIONID01</span></span><br><span class="line"><span class="comment">#授权服务器地址</span></span><br><span class="line"><span class="meta">oauth2-server-url</span>: <span class="string">http://localhost:8080</span></span><br><span class="line"><span class="comment">#与授权服务器对应的配置</span></span><br><span class="line"><span class="meta">security.oauth2.client.client-id</span>=<span class="string">client</span></span><br><span class="line"><span class="meta">security.oauth2.client.client-secret</span>=<span class="string">112233</span></span><br><span class="line"><span class="comment"># 获取授权码</span></span><br><span class="line"><span class="meta">security.oauth2.client.user-authorization-uri</span>=<span class="string">$&#123;oauth2-serverurl&#125;/oauth/authorize</span></span><br><span class="line"><span class="comment"># 获取token</span></span><br><span class="line"><span class="meta">security.oauth2.client.access-token-uri</span>=<span class="string">$&#123;oauth2-server-url&#125;/oauth/token</span></span><br><span class="line"><span class="comment"># 获取JWT</span></span><br><span class="line"><span class="meta">security.oauth2.resource.jwt.key-uri</span>=<span class="string">$&#123;oauth2-server-url&#125;/oauth/token_key</span></span><br></pre></td></tr></table></figure><h4 id="3、启动类上添加单点登录"><a href="#3、启动类上添加单点登录" class="headerlink" title="3、启动类上添加单点登录"></a>3、启动类上添加单点登录</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="comment">//开启单点登录</span></span><br><span class="line"><span class="meta">@EnableOAuth</span>2Sso</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoApplication</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(DemoApplication<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="4、添加控制层获取用户登录信息"><a href="#4、添加控制层获取用户登录信息" class="headerlink" title="4、添加控制层获取用户登录信息"></a>4、添加控制层获取用户登录信息</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>&#123;</span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/getCurrentUser"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">getCurrentUser</span><span class="params">(Authentication authentication)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> authentication;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="5、修改认证服务器配置"><a href="#5、修改认证服务器配置" class="headerlink" title="5、修改认证服务器配置"></a>5、修改认证服务器配置</h4><p>将绑定的跳转路径修改为 <a href="http://localhost:8081/login，并添加获取秘钥时的身份认证；" target="_blank" rel="noopener">http://localhost:8081/login，并添加获取秘钥时的身份认证；</a></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAuthorizationServer</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AuthorizationServiceConfig</span> <span class="keyword">extends</span> <span class="title">AuthorizationServerConfigurerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> PasswordEncoder passwordEncoder;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> AuthenticationManager authenicationManager;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> UserService userService;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="meta">@Qualifier</span>(<span class="string">"jwtTokenStore"</span>)<span class="comment">//指定</span></span><br><span class="line">    <span class="keyword">private</span> TokenStore tokenStore;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JwtAccessTokenConverter jwtAccessTokenConverter;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> JwtTokenEnhancer jwtTokenEnhancer;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     *  密码模式</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> endpoints</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(AuthorizationServerEndpointsConfigurer endpoints)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line"><span class="comment">//        设置JWT增强内容</span></span><br><span class="line">        TokenEnhancerChain chain = <span class="keyword">new</span> TokenEnhancerChain();</span><br><span class="line">        List&lt;TokenEnhancer&gt; delegates = <span class="keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">        delegates.add(jwtTokenEnhancer);</span><br><span class="line">        delegates.add(jwtAccessTokenConverter);</span><br><span class="line">        chain.setTokenEnhancers(delegates);</span><br><span class="line"></span><br><span class="line">        endpoints.authenticationManager(authenicationManager)</span><br><span class="line">                .userDetailsService(userService)</span><br><span class="line">                <span class="comment">//accessToken转换为JWTtoken</span></span><br><span class="line">                .tokenStore(tokenStore)</span><br><span class="line">                .accessTokenConverter(jwtAccessTokenConverter)</span><br><span class="line">                <span class="comment">//添加增强内容</span></span><br><span class="line">                .tokenEnhancer(chain);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//    授权服务器</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(ClientDetailsServiceConfigurer clients)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        clients.inMemory()</span><br><span class="line">                .withClient(<span class="string">"client"</span>)<span class="comment">//客户端id</span></span><br><span class="line">                .secret(passwordEncoder.encode(<span class="string">"112233"</span>))<span class="comment">//密钥</span></span><br><span class="line">                .accessTokenValiditySeconds(<span class="number">60</span>)<span class="comment">//token的有效时间(一分钟)</span></span><br><span class="line">                .refreshTokenValiditySeconds(<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>)<span class="comment">//刷新token有效时间（一天）</span></span><br><span class="line"><span class="comment">//                .redirectUris("http://www.baidu.com")//重定向地址</span></span><br><span class="line">                .redirectUris(<span class="string">"http://localhost:8081/login"</span>)<span class="comment">//单点登录时配置</span></span><br><span class="line">                .scopes(<span class="string">"all"</span>)<span class="comment">//授权范围</span></span><br><span class="line">                .autoApprove(<span class="keyword">true</span>)<span class="comment">//开启自动授权</span></span><br><span class="line">                <span class="comment">//授权类型</span></span><br><span class="line">                .authorizedGrantTypes(<span class="string">"authorization_code"</span>,<span class="string">"password"</span>,<span class="string">"refresh_token"</span>);<span class="comment">//授权码模式,密码模式,刷新令牌</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configure</span><span class="params">(AuthorizationServerSecurityConfigurer security)</span> <span class="keyword">throws</span> Exception</span>&#123;</span><br><span class="line">        <span class="comment">// 获取密钥需要身份认证，使用单点登录时必须配置</span></span><br><span class="line">        security.tokenKeyAccess(<span class="string">"isAuthenticated()"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;🏮🏮🏮&lt;/p&gt;
&lt;p&gt;使用token实现单点登录&lt;/p&gt;
    
    </summary>
    
    
    
      <category term="SpringSecurity" scheme="https://www.tengfei.eu.org/tags/SpringSecurity/"/>
    
      <category term="JWT" scheme="https://www.tengfei.eu.org/tags/JWT/"/>
    
  </entry>
  
  <entry>
    <title>随笔感悟</title>
    <link href="https://www.tengfei.eu.org/article/9f5f8fb5.html"/>
    <id>https://www.tengfei.eu.org/article/9f5f8fb5.html</id>
    <published>2022-12-25T14:11:34.000Z</published>
    <updated>2025-08-13T09:00:37.161Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><strong>活着是一种过程，生命只是活着，无论幸福或者不幸，都需要我们去经历，去面对。活着，生命才有意义。</strong></p><p>​                                                                                                                                                                                                ——余华《活着》</p></blockquote><p>最近推出的防疫优化，使疫情防控风向转变的很是仓促，但也不能否认之前一直的医护人员一直兢兢业业的守护着大家所作出的奉献，“为众人抱薪者，不可使其冻毙于风雪”，不能让那些进行防疫援助的工作人员心寒。</p><p>以后防疫主要就是自我防护了，尽量不使疫情恶性扩散，找到最佳的平衡点，也算是与自然共存的最好认证了。</p><p>养精蓄锐，做好和病毒打持久战的准备，愿旗开得胜，马到成功 。</p><p><img src="/article/9f5f8fb5/shark_dolphin_sea_130036_1280x720.jpg" alt="shark_dolphin_sea_130036_1280x720"></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;活着是一种过程，生命只是活着，无论幸福或者不幸，都需要我们去经历，去面对。活着，生命才有意义。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;​                                                   
      
    
    </summary>
    
    
    
      <category term="感慨" scheme="https://www.tengfei.eu.org/tags/%E6%84%9F%E6%85%A8/"/>
    
      <category term="随笔" scheme="https://www.tengfei.eu.org/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
</feed>
